Context
Branch recast-gate adds a module-level bit-exactness harness driven by RecastEngine (see test/recast/README.md). Running it on one routine produced a three-way result worth a human look:
| comparison |
result |
| Codon port vs native Fortran |
bit_exact — 43200/43200 points, 0 ULP |
| engine's NumPy translation vs native Fortran |
bit_exact — 43200/43200 points, 0 ULP |
| engine's NumPy translation vs Codon port |
bit-identical, 43200 points across 120 output arrays |
So for this routine there are now two independent modernizations that agree with the original to the last bit — one written by the LLM-agent pipeline in this repository, one produced mechanically by RecastEngine's rule-driven translator. They are very different code. What the machine cannot judge is which properties matter for CAM going forward, and that is the ask below.
The ask
@cnitlrt — could you read the two generated implementations side by side and give an opinion? Not a correctness review (the gate covers that) but a judgement call: what does each path give up, and where would you want which?
Pair 1 — the routine already gated
- native Fortran:
src/physics/cam/vertical_diffusion.F90, vertical_diffusion_ptend_core_native, lines 2125–2159
- Codon port:
src/physics/cam/vertical_diffusion_codon.py, vertical_diffusion_ptend_core_codon, from line 588 (body from line 613)
- engine's NumPy: not committed (it is generated). To produce it:
pip install -e ../RecastEngine'[fortran,translate,verify]' -e ../recast-cesm
clang -c -fPIC -O1 -o test/recast/build/vd_callback_stubs.o test/recast/vd_callback_stubs.c
codon build --relocation-model=pic -release --lib \
--linker-flags="$PWD/test/recast/build/vd_callback_stubs.o" \
-o test/recast/build/libvertical_diffusion_codon.dylib \
src/physics/cam/vertical_diffusion_codon.py
python test/recast/run_threeway.py
# then read:
# test/recast/work-threeway/engine/candidate/vd_ptend_core_numpy.py
The visible differences on this routine:
|
Codon port |
engine's NumPy |
| loops |
explicit for k / for i / for m, 1-based, range(1, pver + 1) |
vectorized [:ncol, :] slices |
| addressing |
hand-linearized _idx2(i, k, pcols) / _idx3(...) (definition near line 326), column-major arithmetic written out |
NumPy multi-dimensional indexing, 0-based |
| out arguments |
written through pointers in place, no return value |
intent(out/inout) become a returned tuple |
| dimensions |
passed explicitly (pcols, pver, pcnst, psetcols) |
read from a generated constants module; shapes carry the rest |
| provenance |
none |
every statement carries # B00N <- L45-L45 back to the Fortran line |
Both produce the same bits here. That is not automatic — it holds because this routine is elementwise with no reduction, so no summation order can differ. Which brings the interesting question:
Pair 2 — where the two paths would be expected to diverge
dadadj is the opposite shape: iterative, converging, numerically fragile.
- native Fortran:
src/physics/cam/dadadj.F90, dadadj_native, from line 122
- Codon port:
src/physics/cam/dadadj_codon.py, dadadj_codon, from line 29
- engine's NumPy: RecastEngine currently refuses this routine —
test/recast/run_gate.py gates the Codon port against Fortran (9600/9600 bit-exact), but the translator defers one block, reporting:
dadadj_native/B004: goto 80 is not a loop-exit pattern
The two F77 gotos in dadadj_native — go to 80 jumping to the terminator of the labelled do 80 i=1,ncol (a cycle) and the backward go to 50 (a retry with doubled zeps) — have no mechanical rewrite in the translator yet. Per the plan discussed, those rules will be added upstream in CESM-language-translator and retrieved from there rather than invented here.
Questions worth your judgement
- Readability / maintainability. If someone has to debug a wrong number in three years, which of the two would you rather be holding? Does the engine's block-marker provenance (
# B004 <- L48-L48) earn its keep, or is _idx2(i, k, pcols) clearer about what the memory is doing?
- Where vectorization is a trap. The NumPy form is bit-identical for elementwise work. For which CAM routines would you expect it to stop being — reductions, iterative solvers, anything where FMA contraction or SIMD reassociation can bite? Two more pure candidates to look at if useful:
macrop_driver.F90 / macrop_driver_detrain_init_shell_native (line 1704, Codon at macrop_driver_codon.py:3931) and physpkg.F90 / tphysbc_init_fields_native (line 7220).
- Whether a second path is worth having at all. The Codon path is the one that ships — it links into CAM and keeps the calling interface. The NumPy path is standalone and cannot. Is a mechanically-generated, provenance-carrying NumPy version useful to you as a cross-check on the Codon port, as documentation, or not at all?
- The goto rules. When you add the cycle / backward-retry rules to CESM-language-translator, do the shapes in
dadadj_native generalize, or is that routine's control flow unusual enough to be worth its own note?
Notes
- A survey of this repository's 32 Codon-ported files found 49 routines that are pure (no module state read or written, no calls) and therefore gateable with no setup, so this harness extends by pointing it at another name.
- The gate's inputs are physically ranged but synthetic, not sampled from a model run. Both paths agreeing here means they agree on the sampled region, not that the region CAM actually visits was covered — captured dumps are the honest next step.
- Nothing under
src/ was modified on recast-gate; the branch adds only test/recast/.
Context
Branch
recast-gateadds a module-level bit-exactness harness driven by RecastEngine (seetest/recast/README.md). Running it on one routine produced a three-way result worth a human look:bit_exact— 43200/43200 points, 0 ULPbit_exact— 43200/43200 points, 0 ULPSo for this routine there are now two independent modernizations that agree with the original to the last bit — one written by the LLM-agent pipeline in this repository, one produced mechanically by RecastEngine's rule-driven translator. They are very different code. What the machine cannot judge is which properties matter for CAM going forward, and that is the ask below.
The ask
@cnitlrt — could you read the two generated implementations side by side and give an opinion? Not a correctness review (the gate covers that) but a judgement call: what does each path give up, and where would you want which?
Pair 1 — the routine already gated
src/physics/cam/vertical_diffusion.F90,vertical_diffusion_ptend_core_native, lines 2125–2159src/physics/cam/vertical_diffusion_codon.py,vertical_diffusion_ptend_core_codon, from line 588 (body from line 613)The visible differences on this routine:
for k / for i / for m, 1-based,range(1, pver + 1)[:ncol, :]slices_idx2(i, k, pcols)/_idx3(...)(definition near line 326), column-major arithmetic written outintent(out/inout)become a returned tuplepcols,pver,pcnst,psetcols)# B00N <- L45-L45back to the Fortran lineBoth produce the same bits here. That is not automatic — it holds because this routine is elementwise with no reduction, so no summation order can differ. Which brings the interesting question:
Pair 2 — where the two paths would be expected to diverge
dadadjis the opposite shape: iterative, converging, numerically fragile.src/physics/cam/dadadj.F90,dadadj_native, from line 122src/physics/cam/dadadj_codon.py,dadadj_codon, from line 29test/recast/run_gate.pygates the Codon port against Fortran (9600/9600 bit-exact), but the translator defers one block, reporting:The two F77 gotos in
dadadj_native—go to 80jumping to the terminator of the labelleddo 80 i=1,ncol(a cycle) and the backwardgo to 50(a retry with doubledzeps) — have no mechanical rewrite in the translator yet. Per the plan discussed, those rules will be added upstream in CESM-language-translator and retrieved from there rather than invented here.Questions worth your judgement
# B004 <- L48-L48) earn its keep, or is_idx2(i, k, pcols)clearer about what the memory is doing?macrop_driver.F90/macrop_driver_detrain_init_shell_native(line 1704, Codon atmacrop_driver_codon.py:3931) andphyspkg.F90/tphysbc_init_fields_native(line 7220).dadadj_nativegeneralize, or is that routine's control flow unusual enough to be worth its own note?Notes
src/was modified onrecast-gate; the branch adds onlytest/recast/.