Derive the EAMxx source vertical grid file at run time - #864
chengzhuzhang wants to merge 2 commits into
Conversation
zppy hardcoded an L128v1 vertical coordinate file as ncremap --vrt_in whenever prc_typ == 'eamxx'. E3SM PR #8692 makes L128v4 the default EAMxx grid, and because the level count is unchanged at 128, ncremap accepts the v1 file against v4 input and silently interpolates with the wrong coefficients. Derive the file from the run's own output instead: ncks the hybrid coefficients out, then append P0 with ncap2. This is grid-agnostic, so L72, L128v1, L128v4 and any future EAMXX_VGRID work with no staged file. [ts] derives from the raw history file, so it does not depend on extra_vars; [e3sm_to_cmip] derives lazily from the file it is about to remap, since its loop usually no-ops for EAMxx. An explicit vrt_in_file still takes precedence and skips the derivation. Fixes #863 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGCrzkffuiQ1Y5PR9FGv7C
EAMxx does not write P0 today, which is why the staged vert_L128.nc had one appended by hand and why the derivation synthesized the reference pressure. Later EAMxx versions will write it, so take P0 from the source when it is there and fall back to appending 100000.0 only when the extraction fails. Verified against real files: a source without P0 yields the synthesized 100000, a source carrying P0=99999 keeps 99999, and a source missing the hybrid coefficients still fails loudly with the NCO error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGCrzkffuiQ1Y5PR9FGv7C
End-to-end test: L128v4 EAMxx,
|
| Job | Status | Notes |
|---|---|---|
ts_atm_monthly_180x360_aave_2010-2012-0003 |
OK (2m04s) |
All 43 variables written with 36 time steps each. The 8 vrt_remap_vars were remapped to plev19 in ts_vrt_remap/. |
e3sm_to_cmip_atm_monthly_180x360_aave_2010-2012-0003 |
OK (44s) |
28/28 handlers converted with 0 failures. The 13 variables marked "non-derivable" are expected, because their EAMxx inputs weren't in vars. |
- Derivation used: neither generated script refers to
vert_L128. The[ts]job derivedvrt_in.ncand remapped the 8vrt_remap_vars. The[e3sm_to_cmip]job made noncremapcalls, because all of its 3D inputs were symlinks to files[ts]had already remapped (ts_vrt_remap/), so its derivation block wasn't run in this test. That block is covered by the unit tests and was checked by hand on a split ts file. - plev19 output is physically reasonable (
cmip_ts, shape (36, 19, 180, 360)):taranges from 160 to 312 K, with a mean of 257 K at 1 hPa. 1 hPa is below the L128v4 top (0.593 hPa), so the top level holds real data. With the old L128v1 file, the 1 hPa level was all missing.zggoes up to about 50 km, with a mean of 47.4 km at 1 hPa.uaranges from −96 to 184 m/s.- About 48% of the 1000 hPa level is masked where the surface pressure is below 1000 hPa. That's the expected below-ground masking.
Output: /pscratch/sd/c/chengzhu/tests/eamxx_L128v4_vrtin_09292026/post/
Unrelated issue found
The CMORized hur is a 0–1 fraction but is labeled %, because the EAMxx hur handler in e3sm_to_cmip has no unit conversion. This comes from e3sm_to_cmip, not from this PR. It's tracked in E3SM-Project/e3sm_to_cmip#350, with a fix in E3SM-Project/e3sm_to_cmip#351.
| {%- if prc_typ == 'eamxx' %} | ||
| {%- if vrt_in_file == '' %} | ||
| # ncremap needs an explicit source vertical grid file for EAMxx. Derive it from | ||
| # this run's own output rather than a staged file, so any vertical grid (L72, | ||
| # L128v1, L128v4, ...) works. Take P0 from the output too when it is there; | ||
| # EAMxx does not write one yet, so fall back to the reference pressure. | ||
| raw_file=`head -n 1 input.txt` | ||
| run_nco ncks -O -v hyai,hybi,hyam,hybm,P0 ${raw_file} vrt_in.nc 2> /dev/null || \ | ||
| { run_nco ncks -O -v hyai,hybi,hyam,hybm ${raw_file} vrt_in.nc && \ | ||
| run_nco ncap2 -A -s 'P0=100000.0' vrt_in.nc ; } | ||
| if [ $? != 0 ]; then | ||
| cd {{ scriptDir }} | ||
| echo 'Failed to derive source vertical grid file from '${raw_file} | ||
| echo 'Set vrt_in_file explicitly to work around this.' | ||
| echo 'ERROR (7)' > {{ prefix }}.status | ||
| exit 7 | ||
| fi | ||
| vrt_in_file=vrt_in.nc | ||
| {%- else %} | ||
| vrt_in_file='{{ vrt_in_file }}' | ||
| {%- endif %} | ||
| {%- endif %} |
There was a problem hiding this comment.
@czender hey Charlie, this PR is made to support generating vrt_in file on the fly in zppy to replace the use of a static file, so that when eamxx vertical grid gets changed, zppy workflow remain works. Could you help review this PR, especially this block that derives vrt_in? Thank you.
Fixes #863.
Problem
zppy hardcoded an L128v1 vertical coordinate file (
e3sm_to_cmip_data/grids/vert_L128.nc) asncremap --vrt_inwheneverprc_typ == 'eamxx'. E3SM#8692 makes L128v4 the default EAMxx grid, and because the level count is still 128,ncremapdoes not error on v4 input — it interpolates with v1 coefficients and emits silently wrong pressure-level data intots_vrt_remap/andcmip_ts/.Setting
vrt_in_fileexplicitly already worked as a user override, so this is a bad default rather than a missing capability. The problem is that a user who does not know to set it gets no error.Change
Derive the source vertical grid from the run's own output rather than shipping a grid-version-specific data file:
(the fallback is the operation recorded in
vert_L128.nc's ownhistoryattribute). This is grid-agnostic — L72, L128v1, L128v4 and any futureEAMXX_VGRIDwork with no staged file and no zppy release.ts.bashderives from the raw history file (head -n 1 input.txt), which always carrieshy*, so the[ts]path does not depend onextra_vars. Onlypsremains load-bearing there, for--ps_nm. Failure gets its own status code 7.e3sm_to_cmip.bashderives lazily, inside the loop, from the file it is about to remap, guarded so it runs once. Eager derivation would have been a regression:interp_varsdefaults to EAM names, so that loop usually remaps nothing for EAMxx, and a job that never callsncremapmust not fail on a missing derivation. Status code 5, with a message naming[ts] extra_varsas the fix.vrt_in_file, so an explicitvrt_in_filestill takes precedence and short-circuits the derivation entirely.vrt_remap_file/cmip_plevdata(the--vrt_outplev19 target) are untouched — the target grid is unaffected by this.Docs,
default.inicomments, and theexamples/post.v3.eamxx.cfgheader no longer claim avert_L128.ncdefault. No references to that file remain anywhere inzppy/,docs/, orexamples/.Tests
New
tests/test_vertical_remap.pyrenders both templates and asserts three cases: derived (novert_L128, derivation emitted), user-suppliedvrt_in_file(path wins, no derivation), andprc_typ = eam(no--vrt_in/--ps_nmat all). 96 unit tests pass; pre-commit clean.Verification
Checked against the L128v1 EAMxx case from
examples/post.v3.eamxx.cfg(ne256pg2 ... F20TR-SCREAMv1, ne30pg2 monthly output, 1995-1999), whosets/andts_vrt_remap/output from the old staged-file code is still on scratch.vert_L128.ncbit-for-bit.hyai,hyam,hybi,hybmandP0(double, 100000.0) are bit-identical, whether derived from the raw history file (the[ts]path) or from the split per-variable ts file (the[e3sm_to_cmip]path).levtop/bottom match at 2.5802608 / 998.49646 hPa. The derived file additionally carriesilev, whichnckspulls in as a coordinate;ncremapdoes not use it.ncremapoutput is bit-identical. Samencremapline on a two-month subset ofT_mid_199501_199912.nc, derived vs staged--vrt_in:maxabsdiff = 0.0over all (2, 19, 180, 360) values, with no change in missing-value count. The same holds against thets_vrt_remap/T_mid_199501_199912.ncthat the old code wrote in July, so this is a real before/after comparison and not just self-consistency within one session.P0is genuinely required, not assumed by NCO. WithP0stripped from an otherwise identicalvrt_infile,ncremapfails:ERROR Failed to vertically interpolate. cmd_rgr[0] failed. Thencap2step is load-bearing, hence the status check around it.P0handling is future-proof. EAMxx writes noP0today, but later versions will, so the derivation asks for it first and synthesizes 100000.0 only when that extraction fails. Checked against real files: a source withoutP0gives the synthesized 100000; a source carryingP0 = 99999keeps 99999 rather than being overwritten; a source missing the hybrid coefficients still fails loudly with the NCO error and trips the status code.ne256pg2_ne256pg2.F2010xx-ZM-CICE.260306.splitform_TMSoff_Recipe2_updated,1ma_ne30pg2monthly output, 2010-01). The derivation succeeds on the raw file. That file has noP0, so the fallback supplies 100000.0. All four hybrid coefficients differ fromvert_L128.nc(max |Δhybm| = 0.29), and the derivedlevtop is 0.593 hPa vs v1's 2.58 hPa (ilevtop 0.499 vs 2.26 hPa).ncremapline onT_midto plev19, derived vs staged--vrt_in: the staged v1 file puts T wrong by up to 25.6 K (mean −21 K at 300–400 hPa), gives errors of several K throughout the stratosphere, and returns all-missing at 1 hPa, which is above v1's top.🤖 Generated with Claude Code