Skip to content

feat(topology): register CAM_MAM4 and CAM_MAM5 — MAM5 now has data, not just a shape - #73

Open
aliakherati wants to merge 1 commit into
mainfrom
feat/cam-mam5-topology
Open

feat(topology): register CAM_MAM4 and CAM_MAM5 — MAM5 now has data, not just a shape#73
aliakherati wants to merge 1 commit into
mainfrom
feat/cam-mam5-topology

Conversation

@aliakherati

Copy link
Copy Markdown
Contributor

Topology already accepted nmodes=5, but nothing was registered — so "MAM5-JAX" was a validated shape with no data behind it. This lands the data.

registered: ('cam_mam4', 'cam_mam5', 'e3sm_mam4_mom')
cam_mam5:   nmodes=5  variant=cesm  is_mam5=True  pcnst=108
mode 5 =    coarse_strat, so4 only, σ_g 1.2, dgnum 9e-7

Suite: 176 passed (was 164). E3SM derived quantities verified bit-identical.

Where the data comes from

mam4_jax/core/cam_topologies.py is generated in the sibling repo mam-box-fortran and vendored here. The indirection isn't laziness: numptr_amode, lmassptr_amode and the per-slot species properties are not tabulated in any source filemodal_aero_data.F90 builds them at init from the chemistry preprocessor's species list. They can only be observed, not transcribed. The header carries the regeneration commands and a sha256 of each input dump.

The design question, settled with data

CAM has no lspectype_amode. It resolves each (mode, slot) straight to specdens_amode(l,m) / spechygro(l,m), while Topology keeps per-type tables plus an index. Two options were on the table: give Topology per-slot arrays as a second shape, or synthesise a type list for CAM.

Synthesis wins, but only because it's lossless — checked, not assumed. Across both topologies each of the 6 prefixes (so4, pom, soa, bc, dst, ncl) carries exactly one (density, hygroscopicity) pair in every mode it appears in. The generator reconstructs CAM's per-slot arrays and exits non-zero on mismatch, and tests/test_cam_topology.py re-checks it from the committed data — a generator-time assertion says nothing about what actually landed in the repo.

Full precision mattered

pom and bc both read 1.0e-10 hygroscopicity at one decimal place, but are 1.000000082740371e-10 and 1.000000013351432e-10 — float32 round-trips of 1e-10 differing in the last bits. Had the dump stayed at es12.5, the two types would have collapsed into one and the synthesis would have been silently wrong. There's a test for it.

One assumption worth killing

MAM5 is not simply MAM4 plus a mode. The strat size variant also changes accumulation's width, 1.8 → 1.6. Asserted explicitly, because "MAM4 + 1" is the natural intuition and it's wrong.

Bonus

The numbers the upstream stale-dumfac bug report quotes are now derived from these tables in a test rather than living only in markdown: the last mode's dumfac gives +32.5 % under MAM5 (last mode is coarse_strat, σ_g 1.2) against +20.6 % under MAM4 (whose last mode happens to share aitken's width).

Registration deliberately does not activate — E3SM stays the default, so importing the CAM tables can't silently repoint existing code.

Not in this PR

The topologies exist and validate; no physics has been run under cam_mam5 yet. That needs the CAM driver (plan 024 PR G) plus reference capture against mam-box-fortran. This is the data layer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A3zu6nmLHsAUzTkDJFokjv

…ot just a shape

Topology already accepted nmodes=5, but nothing was registered, so "MAM5-JAX"
was a validated shape with no data behind it. This lands the data.

  registered: ('cam_mam4', 'cam_mam5', 'e3sm_mam4_mom')
  cam_mam5: nmodes=5 variant=cesm is_mam5=True pcnst=108
  mode 5 = coarse_strat, so4 only, sigma_g 1.2, dgnum 9e-7

mam4_jax/core/cam_topologies.py is GENERATED in the sibling repo
mam-box-fortran and vendored here. That indirection is not laziness:
numptr_amode, lmassptr_amode and the per-slot species properties are not
tabulated in any source file -- modal_aero_data.F90 builds them at init from
the chemistry preprocessor's species list -- so they can only be observed,
not transcribed. The header carries the regeneration commands and a sha256
of each input dump.

THE DESIGN QUESTION, settled with data. CAM has no lspectype_amode: it
resolves each (mode, slot) straight to specdens_amode(l,m) and
spechygro(l,m), while Topology keeps per-TYPE tables plus an index. Rather
than give Topology two shapes, the generator synthesises the type list from
species-name prefixes -- legitimate only if lossless, so it was checked
rather than assumed. Across both topologies each of the 6 prefixes (so4,
pom, soa, bc, dst, ncl) carries exactly one (density, hygroscopicity) pair
in every mode it appears in. The generator reconstructs CAM's per-slot
arrays and exits non-zero on mismatch, and tests/test_cam_topology.py
re-checks it from the COMMITTED data, since a generator-time assertion says
nothing about what landed in the repo.

Full precision mattered. pom and bc both read 1.0e-10 hygroscopicity at one
decimal place but are 1.000000082740371e-10 and 1.000000013351432e-10 --
float32 round-trips of 1e-10 differing in the last bits. Had the dump stayed
at es12.5 the two types would have collapsed into one and the synthesis
would have been silently wrong. There is a test for it.

One assumption worth killing: MAM5 is NOT simply MAM4 plus a mode. The strat
size variant also changes accum's width, 1.8 -> 1.6. Asserted, because
"MAM4 + 1" is the natural intuition and it is wrong.

Also pins the numbers the upstream stale-dumfac bug report quotes, straight
from these tables: the last mode's dumfac gives +32.5% under MAM5 (last mode
is coarse_strat, sigma_g 1.2) against +20.6% under MAM4 (whose last mode
happens to share aitken's width). Those figures now have a test rather than
living only in a markdown file.

Registration deliberately does not activate: E3SM stays the default, so
importing the CAM tables cannot silently repoint existing code. Verified the
E3SM derived quantities stay bit-identical.

Suite: 176 passed (was 164; +12).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3zu6nmLHsAUzTkDJFokjv
aliakherati added a commit that referenced this pull request Aug 27, 2026
…it consumes

A6 (sub-stepping ON by default) approved by owner 2026-08-26; substep
count to be picked empirically in G5. Records the source-read findings
that reshape the reuse table: CAM's gas_aer_uptkrates is a third variant
(fixed beta=2, ac=0.65 literals, truncated constants — port, don't
reuse); the legacy 8.0-monolayer aging runs inside BOTH CAM's gasaerexch
and coag_sub (closing the #75 attribution question — CAM genuinely ages
at 8.0, amicphys at 3.0); DGNUM pbuf-inits to 0.0 so end-to-end parity
needs topology-threaded calcsize+wateruptake while the microphysics
sequence validates against isolated captures first; MAM5 references pin
nl_acc_crs=0 (rename-A2 stays deferred); merges feat/cam-mam5-topology
(PR #73) into this branch as the driver's data layer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant