diff --git a/examples/post.v3.eamxx.cfg b/examples/post.v3.eamxx.cfg new file mode 100644 index 00000000..541d9b7c --- /dev/null +++ b/examples/post.v3.eamxx.cfg @@ -0,0 +1,190 @@ +# Example: EAMxx → CMIP and e3sm_diags, initially contributed by Shixuan Zhang. +# Reproduces the cfg from issue #820 plus e3sm_diags-related tasks. +# (https://github.com/E3SM-Project/zppy/issues/820) +# +# Demonstrates: +# - EAMxx CMORization end-to-end (issue #817) +# - The fix to the fragile component-detection gate (now uses prc_typ), +# so input_component="eamxx" with input_files="AVERAGE.nmonths_x1" works +# - EAMxx-specific ncremap flags (--ps_nm=${file}/ps, --vrt_in=vert_L128.nc) +# auto-emitted when prc_typ == 'eamxx' +# - climo, ts, e3sm_to_cmip, and e3sm_diags wiring for an EAMxx run +# +# Run with: zppy -c examples/post.v3.eamxx.cfg +[default] +input = /pscratch/sd/z/zhan391/e3smv4_project/ne256pg2_ne256pg2.F20TR-SCREAMv1.July-1.spanc800.2xauto.acc150.n0032.test2.1 +output = /pscratch/sd/c/chengzhu/tests/ne256pg2_ne256pg2.F20TR-SCREAMv1.July-1.spanc800.2xauto.acc150.n0032.test2.1_05182026 +case = ne256pg2_ne256pg2.F20TR-SCREAMv1.July-1.spanc800.2xauto.acc150.n0032.test2.1 +www = /global/cfs/cdirs/e3sm/www/chengzhu/tests/zppy_for_eamxx_05182026 +partition = "debug" +account = "e3sm" +campaign = "water_cycle" +debug = False +#environment_commands = "source /global/common/software/e3sm/anaconda_envs/test_e3sm_unified_1.13.0rc10_pm-cpu.sh" + +[climo] +active = True +walltime = "2:00:00" +years = "1995:2004:10", + + [[ atm_monthly_180x360_aave ]] + # The following e3sm_diags sets require it: + # "lat_lon", "zonal_mean_xy", "zonal_mean_2d", "polar", "cosp_histogram", + # "meridional_mean_2d", "annual_cycle_zonal_mean", "zonal_mean_2d_stratosphere", + # "aerosol_aeronet", "aerosol_budget" + input_component = "eamxx" + case = "1ma_ne30pg2" + input_files = "AVERAGE.nmonths_x1" + frequency = "monthly" + input_subdir = "run/" + mapping_file = map_ne30pg2_to_cmip6_180x360_aave.20200201.nc + + [[ atm_monthly_diurnal_8xdaily_180x360_aave ]] + # The following e3sm_diags sets require it: + # "diurnal_cycle" + input_component = "eamxx" + case = "3ha_ne30pg2" + input_files = "AVERAGE.nhours_x3" + input_subdir = "run/" + frequency = "diurnal_8xdaily" + mapping_file = map_ne30pg2_to_cmip6_180x360_aave.20200201.nc + vars = "precip_liq_surf_mass_flux,precip_ice_surf_mass_flux" + # Bump walltime for a potential performance issue in ncclimo for processing eamxx high freq data. + walltime = "4:00:00" + + [[ land_monthly_climo ]] + active = True + # This subtask is a dependency for the e3sm_diags task's lnd_monthly_mvm_lnd subtask. + # The following e3sm_diags sets require it: + # "lat_lon_land" + input_component = "elm" + frequency = "monthly" + input_files = "elm.h0" + input_subdir = "run/" + vars = "" # Setting this as "" will tell zppy to use ALL variables + + [[ land_monthly_180x360_traave ]] + active = True + input_component = "elm" + frequency = "monthly" + input_files = "elm.h0" + input_subdir = "run/" + mapping_file = "map_ne256pg2_to_cmip6_180x360_traave.20250301.nc" + vars = "" + +[ts] +active = True +walltime = "00:30:00" +years = "1995:2004:5" +ts_num_years=5 +#qos = "debug" + + [[atm_monthly_180x360_aave]] + # Merged 2D+3D atm timeseries: vrt_remap_vars selectively vert-remaps the + # 3D vars into a sibling ts_vrt_remap/ directory, so downstream e3sm_to_cmip + # sees a single clean ts subsection. + input_component = "eamxx" + input_subdir = "run/" + case = "1ma_ne30pg2" + input_files = "AVERAGE.nmonths_x1" + frequency = "monthly" + mapping_file = map_ne30pg2_to_cmip6_180x360_aave.20200201.nc + vars="ps,surf_radiative_T,SeaLevelPressure,IceWaterPath,qv_2m,precip_liq_surf_mass_flux,precip_ice_surf_mass_flux,omega_at_500hPa,omega_at_700hPa,omega_at_850hPa,T_mid_at_700hPa,T_2m,surface_upward_latent_heat_flux,surf_sens_flux,z_mid_at_700hPa,wind_speed_10m,surf_evap,U_at_10m_above_surface,V_at_10m_above_surface,LW_clrsky_flux_dn_at_model_bot,LW_clrsky_flux_up_at_model_top,LW_flux_dn_at_model_bot,LW_flux_up_at_model_bot,LW_flux_up_at_model_top,SW_clrsky_flux_dn_at_model_bot,SW_clrsky_flux_dn_at_model_top,SW_clrsky_flux_up_at_model_bot,SW_clrsky_flux_up_at_model_top,SW_flux_dn_at_model_bot,SW_flux_dn_at_model_top,SW_flux_up_at_model_bot,SW_flux_up_at_model_top,ShortwaveCloudForcing,LongwaveCloudForcing,isccp_cldtot,U,V,T_mid,z_mid,omega,RelativeHumidity,p_mid,qv" + # NOTE: ncclimo --prc_typ=eamxx does NOT auto-extract ps/hyai/hyam/hybi/hybm + # for 3D vars the way --prc_typ=cam/eam does due to a meta data issue, so we must list them explicitly + # in extra_vars. ps is load-bearing for the vrt_remap step (ts.bash:138 + # passes --ps_nm=${file}/ps to ncremap). + # If ncclimo and eamxx is fixed upstream to mirror cam/eam behavior, drop + # ps + hybrid coeffs here. + extra_vars= "ps,hyai,hyam,hybi,hybm,area,landfrac,ocnfrac" + vrt_remap_vars = "U,V,T_mid,z_mid,omega,RelativeHumidity,p_mid,qv" + + [[ atm_daily_180x360_aave ]] + active = True + # This subtask is a dependency for the e3sm_diags task's atm_monthly_180x360 subtask. + # The following e3sm_diags sets require it: + # "tropical_subseasonal", "precip_pdf" + input_component = "eamxx" + case = "1da_ne30pg2" + input_files = "AVERAGE.ndays_x1" + frequency = "daily" + input_subdir = "run/" + mapping_file = map_ne30pg2_to_cmip6_180x360_aave.20200201.nc + # Needed for Wheeler Kiladis + vars = "LW_flux_up_at_model_top,precip_liq_surf_mass_flux,precip_ice_surf_mass_flux,U_at_850hPa" + # For this simulation's "1da_ne30pg2.AVERAGE.ndays_x1" stream, each file + # holds one daily average (1 day, 1 timestep). Override the zppy defaults + # (dpf=30, tpd=1) which assume E3SM EAM monthly h0 files; otherwise ncclimo + # treats each daily file as 30 days and silently produces ts files whose + # contents (~2 months) don't match their filenames. + dpf = 1 + tpd = 1 + + [[ atm_monthly_glb ]] + active = True + # This subtask is a dependency for the global_time_series task. + input_component = "eam" + input_subdir = "run/" + case = "1ma_ne30pg2" + input_files = "AVERAGE.nmonths_x1" + frequency = "monthly" + mapping_file = "glb" + vars="ps,surf_radiative_T,SeaLevelPressure,IceWaterPath,qv_2m,precip_liq_surf_mass_flux,precip_ice_surf_mass_flux" + + + [[ rof_monthly ]] + active = True + # The following e3sm_diags sets require it: + # "streamflow" + input_component = "mosart" + frequency = "monthly" + input_files = "mosart.h0" + input_subdir = "run/" + mapping_file = "" + vars = "RIVER_DISCHARGE_OVER_LAND_LIQ" + extra_vars = 'areatotal2' + +[e3sm_to_cmip] +active = True +frequency = "monthly" +ts_grid = "180x360_aave" +ts_num_years=5 +walltime = "00:10:00" +years = "1995:2004:5" +qos = "debug" + + [[atm_monthly_180x360_aave]] + # Merged subsection matching the [ts] atm_monthly_180x360_aave output. + # Without cmip_vars, e3sm_to_cmip uses its built-in default list (~41 vars). + # Uncomment to override the default ~41-var list. + #cmip_vars = "pr, cltisccp, evspsbl, hfls, hfss, huss, ps, psl, rlds, rldscs, rlus, rlut, rlutcs, rsds, rsdscs, rsdt, rsus, rsuscs, rtmt, uas, vas, sfcWind, tas, ts, ta, ua, va, zg" + input_component = "eamxx" + case = "1ma_ne30pg2" + input_files = "AVERAGE.nmonths_x1" + + +[e3sm_diags] +active = True +multiprocessing = True +num_workers = 8 +ref_start_yr = 1995 +ref_final_yr = 2004 +ts_num_years = 5 +walltime = "2:00:00" +years = "1995:2004:10", + +# case inherits the top-level [default] case; climo output naming includes +# the stream id automatically via fml_nm, so this subsection can consume +# both monthly and diurnal climo without per-frequency overrides. + + [[ atm_monthly_180x360_aave ]] + # `e3sm_diags` is largely driven by which e3sm_diags sets are requested: + sets="lat_lon","zonal_mean_xy","zonal_mean_2d","polar","cosp_histogram","meridional_mean_2d","annual_cycle_zonal_mean","enso_diags","qbo","zonal_mean_2d_stratosphere","aerosol_aeronet","mp_partition","tropical_subseasonal","precip_pdf","streamflow","diurnal_cycle", +# sets = #"tc_analysis," # not yet supported for eamxx! + climo_diurnal_frequency = "diurnal_8xdaily" + climo_diurnal_subsection = "atm_monthly_diurnal_8xdaily_180x360_aave" + ts_daily_subsection = "atm_daily_180x360_aave" + # ts_subsection defaults to "atm_monthly_180x360_aave" — matches the merged + # [ts] subsection above, so no explicit override needed. + grid = '180x360_aave' + short_name = 'e3sm.amip.EAMXX.test2_1' diff --git a/tests/test_sections.py b/tests/test_sections.py index fe21e62b..8b34fd42 100644 --- a/tests/test_sections.py +++ b/tests/test_sections.py @@ -120,6 +120,9 @@ def test_sections(): "mapping_file": "MAPPING_FILE_TS", "tpd": 1, "vars": "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT", + "vrt_remap_vars": "", + "vrt_in_file": "", + "vrt_remap_file": "", "years": ["0001:0020:5"], } compare(actual_section, expected_section) @@ -135,6 +138,7 @@ def test_sections(): "case": "CASE", "constraint": "", "debug": False, + "default_case": "CASE", "dpf": 30, "dry_run": False, "environment_commands": "", @@ -171,6 +175,9 @@ def test_sections(): "ts_num_years": 5, "ts_subsection": "", "vars": "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT", + "vrt_remap_vars": "", + "vrt_in_file": "", + "vrt_remap_file": "", "walltime": "02:00:00", "www": "WWWW", "years": ["0001:0020:5"], @@ -204,6 +211,7 @@ def test_sections(): "climo_jobs": 0, "constraint": "", "debug": False, + "default_case": "CASE", "dry_run": False, "environment_commands": "", "environment_commands_secondary": "", @@ -326,6 +334,9 @@ def test_subsections(): "job_nbr": None, "mapping_file": "MAPPING_FILE_TS_GRID1", "tpd": None, + "vrt_remap_vars": None, + "vrt_in_file": None, + "vrt_remap_file": None, "years": ["0001:0020:5"], }, "ts_grid2": { @@ -336,9 +347,15 @@ def test_subsections(): "job_nbr": None, "mapping_file": "MAPPING_FILE_TS_GRID2", "tpd": None, + "vrt_remap_vars": None, + "vrt_in_file": None, + "vrt_remap_file": None, "years": ["0001:0020:10"], }, "vars": "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT", + "vrt_remap_vars": "", + "vrt_in_file": "", + "vrt_remap_file": "", } compare(actual_section, expected_section) actual_tasks = get_tasks(config, section_name) @@ -353,6 +370,7 @@ def test_subsections(): "case": "CASE", "constraint": "", "debug": False, + "default_case": "CASE", "dpf": 30, "dry_run": False, "environment_commands": "", @@ -389,6 +407,9 @@ def test_subsections(): "ts_num_years": 5, "ts_subsection": "", "vars": "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT", + "vrt_remap_vars": "", + "vrt_in_file": "", + "vrt_remap_file": "", "walltime": "02:00:00", "www": "WWWW", "years": ["0001:0020:5"], @@ -404,6 +425,7 @@ def test_subsections(): "case": "CASE", "constraint": "", "debug": False, + "default_case": "CASE", "dpf": 30, "dry_run": False, "environment_commands": "", @@ -440,6 +462,9 @@ def test_subsections(): "ts_num_years": 5, "ts_subsection": "", "vars": "FSNTOA,FLUT,FSNT,FLNT,FSNS,FLNS,SHFLX,QFLX,PRECC,PRECL,PRECSC,PRECSL,TS,TREFHT", + "vrt_remap_vars": "", + "vrt_in_file": "", + "vrt_remap_file": "", "walltime": "02:00:00", "www": "WWWW", "years": ["0001:0020:10"], @@ -491,6 +516,7 @@ def test_subsections(): "climo_jobs": 0, "constraint": "", "debug": False, + "default_case": "CASE", "dry_run": False, "environment_commands": "", "environment_commands_secondary": "", @@ -539,6 +565,7 @@ def test_subsections(): "climo_jobs": 0, "constraint": "", "debug": False, + "default_case": "CASE", "dry_run": False, "environment_commands": "", "environment_commands_secondary": "", diff --git a/zppy/climo.py b/zppy/climo.py index 1a4e768b..711b76dc 100644 --- a/zppy/climo.py +++ b/zppy/climo.py @@ -35,6 +35,15 @@ def climo(config: ConfigObj, script_dir: str, existing_bundles, job_ids_file): set_mapping_file(c) set_grid(c) set_component_and_prc_typ(c) + # Compose the climo output filename prefix. For EAMxx, subsection-level + # `case` (e.g., "1ma_ne30pg2") is the stream id ncclimo needs to find + # input files; prefixing with the top-level case keeps output names + # unified across streams so a single e3sm_diags subsection can consume + # both monthly and diurnal climo via one `case` setting. + if c["prc_typ"] == "eamxx" and c["case"] != c["default_case"]: + c["fml_nm"] = f"{c['default_case']}.{c['case']}" + else: + c["fml_nm"] = c["case"] year_sets: List[Tuple[int, int]] = get_years(c["years"]) # Loop over year sets for s in year_sets: diff --git a/zppy/defaults/default.ini b/zppy/defaults/default.ini index d8621747..9fcb02d2 100755 --- a/zppy/defaults/default.ini +++ b/zppy/defaults/default.ini @@ -151,6 +151,18 @@ job_nbr = integer(default=0) tpd = integer(default=1) # Model component having generated input files (eam, eamxx, elm, mosart, ...) input_component = string(default="") +# Vertical regrid (model levels → pressure levels) — applied after ncclimo splits +# files into per-var time-series. Vars listed here will additionally be regridded +# and written to a sibling directory `ts_vrt_remap/` parallel to `ts/`, so users +# can use native-level and pressure-level data at the same time. +# If empty, no vertical regrid is performed. +vrt_remap_vars = string(default="") +# Target vertical grid file (output pressure levels) for ncremap. +# If empty, zppy defaults to a vrt_remap_plev19.nc under diagnostics_base_path. +vrt_remap_file = string(default="") +# Source vertical coordinate file for ncremap. Required for EAMxx (L128 grid). +# If empty, zppy defaults to a vert_L128.nc under diagnostics_base_path when prc_typ=='eamxx'. +vrt_in_file = string(default="") [[__many__]] area_nm = string(default=None) @@ -159,6 +171,9 @@ input_component = string(default="") job_nbr = integer(default=None) tpd = integer(default=None) input_component = string(default=None) + vrt_remap_vars = string(default=None) + vrt_remap_file = string(default=None) + vrt_in_file = string(default=None) [e3sm_to_cmip] # Metadata json file @@ -172,6 +187,9 @@ cmip_vars = string(default="") input_component = string(default="") # TBD: description here interp_vars = string(default="U,V,T,Q,RELHUM,OMEGA,Z3") +# Source vertical coordinate file for ncremap. Required for EAMxx (L128 grid). +# Defaults to a vert_L128.nc under diagnostics_base_path when prc_typ=='eamxx'. +vrt_in_file = string(default="") [[__many__]] cmip_metadata = string(default=None) @@ -179,6 +197,7 @@ interp_vars = string(default="U,V,T,Q,RELHUM,OMEGA,Z3") cmip_vars = string(default=None) input_component = string(default=None) interp_vars = string(default=None) + vrt_in_file = string(default=None) [tc_analysis] # NOTE: always overrides value in [default] diff --git a/zppy/e3sm_diags.py b/zppy/e3sm_diags.py index a70e51bb..63e18ef6 100644 --- a/zppy/e3sm_diags.py +++ b/zppy/e3sm_diags.py @@ -38,6 +38,7 @@ def e3sm_diags(config: ConfigObj, script_dir: str, existing_bundles, job_ids_fil dependencies: List[str] = [] check_parameters_for_bash(c) c["scriptDir"] = script_dir + c["default_case"] = config["default"]["case"] if "ts_num_years" in c.keys(): c["ts_num_years"] = int(c["ts_num_years"]) # Loop over year sets diff --git a/zppy/e3sm_to_cmip.py b/zppy/e3sm_to_cmip.py index dea48260..1af2b4ab 100644 --- a/zppy/e3sm_to_cmip.py +++ b/zppy/e3sm_to_cmip.py @@ -46,6 +46,18 @@ def e3sm_to_cmip(config: ConfigObj, script_dir: str, existing_bundles, job_ids_f default_cmip_plevdata, ParameterInferenceType.PATH_INFERENCE, ) + # EAMxx needs an explicit source vertical grid file (L128); + # EAM/CAM have vertical coord info embedded in the file already. + if c["prc_typ"] == "eamxx": + default_vrt_in_file = ( + f"{c['diagnostics_base_path']}/e3sm_to_cmip_data/grids/vert_L128.nc" + ) + set_value_of_parameter_if_undefined( + c, + "vrt_in_file", + default_vrt_in_file, + ParameterInferenceType.PATH_INFERENCE, + ) year_sets: List[Tuple[int, int]] = get_years(c["years"]) # Loop over year sets for s in year_sets: diff --git a/zppy/templates/climo.bash b/zppy/templates/climo.bash index 9b0e8e25..a51c256d 100644 --- a/zppy/templates/climo.bash +++ b/zppy/templates/climo.bash @@ -20,7 +20,7 @@ cd ${workdir} run_nco ncclimo \ {% if prc_typ == 'eamxx' -%} --case={{ case }}.{{ input_files }}.0001-01-01-00000.nc \ ---fml_nm={{ case }} \ +--fml_nm={{ fml_nm }} \ {%- else -%} --case={{ case }} \ {%- endif %} @@ -97,6 +97,9 @@ fi # Now, call ncclimo cat input.txt | run_nco ncclimo \ --case={{ case }}.{{ input_files }} \ +{%- if prc_typ == 'eamxx' %} +--fml_nm={{ fml_nm }} \ +{%- endif %} --jobs=${SLURM_NNODES} \ --thr=1 \ {%- if exclude %} diff --git a/zppy/templates/e3sm_diags.bash b/zppy/templates/e3sm_diags.bash index e45681bb..76ff49e2 100644 --- a/zppy/templates/e3sm_diags.bash +++ b/zppy/templates/e3sm_diags.bash @@ -56,7 +56,8 @@ create_links_climo() error_num=$6 mkdir -p ${climo_dir_destination} cd ${climo_dir_destination} - cp -s ${climo_dir_source}/${nc_prefix}_*_${begin_year}??_${end_year}??_climo.nc . + # Glob covers both EAM monthly (${case}_VAR_*) and EAMxx monthly (${case}.{stream}_VAR_*). + cp -s ${climo_dir_source}/${nc_prefix}*_${begin_year}??_${end_year}??_climo.nc . if [ $? != 0 ]; then cd {{ scriptDir }} echo "ERROR (${error_num})" > {{ prefix }}.status @@ -556,7 +557,7 @@ echo ===== COPY FILES TO WEB SERVER ===== echo # Create top-level directory -web_dir=${www}/${case}/e3sm_diags/{{ sub }} +web_dir=${www}/{{ default_case }}/e3sm_diags/{{ sub }} mkdir -p ${web_dir} if [ $? != 0 ]; then cd {{ scriptDir }} diff --git a/zppy/templates/e3sm_to_cmip.bash b/zppy/templates/e3sm_to_cmip.bash index 092f7273..9bf29421 100644 --- a/zppy/templates/e3sm_to_cmip.bash +++ b/zppy/templates/e3sm_to_cmip.bash @@ -33,7 +33,18 @@ EOF dest_cmip={{ output }}/post/{{ component }}/{{ ts_grid }}/cmip_ts/{{ frequency }} mkdir -p ${dest_cmip} - {% if input_files.split(".")[0] == 'cam' or input_files.split(".")[0] == 'eam' -%} + # If the [ts] task vert-remapped some vars (sibling directory ts_vrt_remap/), + # replace the corresponding ts/ symlinks with ts_vrt_remap/ ones. The inline + # regrid loop below will detect these (via readlink) and skip them. + dest_plev={{ output }}/post/{{ component }}/{{ ts_grid }}/ts_vrt_remap/{{ frequency }}/{{ '%dyr' % (ypf) }} + if [ -d "${dest_plev}" ]; then + cp -sf ${dest_plev}/*_{{ '%04d' % (yr_start) }}??_{{ '%04d' % (yr_end) }}??.nc $input_dir 2>/dev/null || true + fi + + # Gate on prc_typ (atm processing type) rather than fragile input_files + # prefix parsing — fixes #820 where input_component="eamxx" with non-eamxx + # input_files (e.g., "AVERAGE.nmonths_x1") caused this block to be skipped. + {% if prc_typ in ('cam', 'eam', 'eamxx') -%} #add code to do vertical interpolation variables at model levels before e3sm_to_cmip IFS=',' read -ra mlvars <<< "{{ interp_vars }}" for var in "${mlvars[@]}" @@ -41,8 +52,18 @@ EOF for file in ${input_dir}/${var}_{{ '%04d' % (yr_start) }}??_{{ '%04d' % (yr_end) }}??.nc do if [ -f ${file} ]; then + # Skip if file is no longer a symlink — a prior run of this script already + # vert-remapped it in place (mv ${file}.plev ${file} replaced the symlink). + # Re-running ncremap on the resulting plev file would fail (no lev/ilev dim). + if [ ! -L "${file}" ]; then + continue + fi + # Skip if [ts] already vert-remapped this var (symlink points into ts_vrt_remap/) + if [[ "$(readlink ${file})" == *ts_vrt_remap* ]]; then + continue + fi #run_nco ncks --rgr xtr_mth=mss_val --vrt_fl='{{cmip_plevdata}}' ${file} ${file}.plev - run_nco ncremap {% if machine == 'dane' %}--mpi_pfx='srun -n {{ nodes }}'{% else %}-p mpi{% endif %} --vrt_ntp=log --vrt_xtr=mss_val --vrt_out='{{cmip_plevdata}}' ${file} ${file}.plev + run_nco ncremap {% if machine == 'dane' %}--mpi_pfx='srun -n {{ nodes }}'{% else %}-p mpi{% endif %} --vrt_ntp=log --vrt_xtr=mss_val{% if prc_typ == 'eamxx' %} --ps_nm=${file}/ps --vrt_in='{{ vrt_in_file }}'{% endif %} --vrt_out='{{cmip_plevdata}}' ${file} ${file}.plev if [ $? != 0 ]; then cd {{ scriptDir }} echo 'ERROR (1)' > {{ prefix }}.status diff --git a/zppy/templates/ts.bash b/zppy/templates/ts.bash index aa517081..e49b9a99 100644 --- a/zppy/templates/ts.bash +++ b/zppy/templates/ts.bash @@ -112,6 +112,42 @@ if [ $? != 0 ]; then exit 2 fi +{%- if vrt_remap_vars != '' %} + +# Vertical regrid (model levels → pressure levels) +# When vrt_remap_vars is set, regrid the listed vars and write to a sibling +# directory ts_vrt_remap/ alongside the standard ts/ output. +mkdir output_plev +IFS=',' read -ra vremap_vars <<< "{{ vrt_remap_vars }}" +for var in "${vremap_vars[@]}" +do + for file in output/${var}_{{ '%04d' % (yr_start) }}??_{{ '%04d' % (yr_end) }}??.nc + do + if [ ! -f "${file}" ]; then continue; fi + out_file=output_plev/$(basename "${file}") + run_nco ncremap \ + {%- if machine == 'dane' %} + --mpi_pfx='srun -n {{ nodes }}' \ + {%- else %} + -p mpi \ + {%- endif %} + --vrt_ntp=log \ + --vrt_xtr=mss_val \ + --vrt_out='{{ vrt_remap_file }}' \ + {%- if prc_typ == 'eamxx' %} + --ps_nm=${file}/ps \ + --vrt_in='{{ vrt_in_file }}' \ + {%- endif %} + ${file} ${out_file} + if [ $? != 0 ]; then + cd {{ scriptDir }} + echo 'ERROR (6)' > {{ prefix }}.status + exit 6 + fi + done +done +{%- endif %} + # Move output ts files to final destination { dest={{ output }}/post/{{ component }}/{{ grid }}/ts/{{ frequency }}/{{ '%dyr' % (ypf) }} @@ -124,6 +160,20 @@ if [ $? != 0 ]; then exit 3 fi +{%- if vrt_remap_vars != '' %} +# Move vertically regridded ts files to sibling destination +{ + dest_plev={{ output }}/post/{{ component }}/{{ grid }}/ts_vrt_remap/{{ frequency }}/{{ '%dyr' % (ypf) }} + mkdir -p ${dest_plev} + mv output_plev/*.nc ${dest_plev} +} +if [ $? != 0 ]; then + cd {{ scriptDir }} + echo 'ERROR (5)' > {{ prefix }}.status + exit 5 +fi +{%- endif %} + # Delete temporary workdir cd .. if [[ "${debug,,}" != "true" ]]; then diff --git a/zppy/ts.py b/zppy/ts.py index 249de9dd..28ebec23 100644 --- a/zppy/ts.py +++ b/zppy/ts.py @@ -15,6 +15,7 @@ set_component_and_prc_typ, set_grid, set_mapping_file, + set_value_of_parameter_if_undefined, submit_script, write_settings_file, ) @@ -35,6 +36,25 @@ def ts(config: ConfigObj, script_dir: str, existing_bundles, job_ids_file): set_mapping_file(c) set_grid(c) set_component_and_prc_typ(c) + # Defaults for vertical regrid params (only used when vrt_remap_vars is set) + if c["vrt_remap_vars"] != "": + default_vrt_remap_file = f"{c['diagnostics_base_path']}/e3sm_to_cmip_data/maps/vrt_remap_plev19.nc" + set_value_of_parameter_if_undefined( + c, + "vrt_remap_file", + default_vrt_remap_file, + ParameterInferenceType.PATH_INFERENCE, + ) + if c["prc_typ"] == "eamxx": + default_vrt_in_file = ( + f"{c['diagnostics_base_path']}/e3sm_to_cmip_data/grids/vert_L128.nc" + ) + set_value_of_parameter_if_undefined( + c, + "vrt_in_file", + default_vrt_in_file, + ParameterInferenceType.PATH_INFERENCE, + ) year_sets: List[Tuple[int, int]] = get_years(c["years"]) # Loop over year sets for s in year_sets: diff --git a/zppy/utils.py b/zppy/utils.py index da822763..4afff338 100644 --- a/zppy/utils.py +++ b/zppy/utils.py @@ -123,6 +123,8 @@ def get_tasks(config: ConfigObj, section_name: str) -> List[Dict[str, Any]]: # Merge current section with default. Work with copies to avoid contamination task = config["default"].copy() task.update(config[section_name].copy()) + # Preserve top-level case (see comment in the sub-section branch below). + task["default_case"] = config["default"]["case"] # Set 'subsection' in dictionary to None task["subsection"] = None # Add to list of tasks if it is active @@ -141,6 +143,10 @@ def get_tasks(config: ConfigObj, section_name: str) -> List[Dict[str, Any]]: sub: Dict[str, Any] = {k: v for k, v in tmp.items() if v is not None} # Merge content of sub-secton into section task.update(sub) + # Preserve top-level case so subsections can still reference it + # after their own `case` override has shadowed it (used by climo's + # fml_nm composition for EAMxx). + task["default_case"] = config["default"]["case"] # At this point, task will still include dictionary entries for # all sub-sections. Remove them to clean up. for s in sub_section_names: