feat(spectra): port the tabulated photon spectra to rust - #66
Open
LoganAMorrison wants to merge 1 commit into
Open
Conversation
The five tabulated photon extensions -- _kaon, _eta, _eta_prime, _omega
and _phi, seven public entry points between them -- become one Rust
module, kernels::photon_tables: a single `dnde` over seven `Spectrum`
values, each holding an include_str!-embedded CSV parsed lazily on first
use, a parent mass, and a list of (line energy, weight) pairs. The five
.pyx differed only in that data. Import-time file I/O is gone: the Cython
paid seven np.loadtxt calls on `import hazma.spectra` whether or not a
spectrum was evaluated.
No public value moves. Before the twins were deleted the port was
compared against them over 336,000 points (7 entry points x 6 parent
energies x 8,000 energies) with zero bitwise mismatches; against a built
origin/master the whole public surface is 97 arrays / 18,694 values
bit-for-bit identical. One declared behavior change, at NaN inputs only:
a NaN photon energy returned IndexError and now returns NaN -- the
Cython reached np.flatnonzero(lb <= x)[0] on an empty match, and
reproducing that inside an element-wise map would mean a PanicException
rather than a catchable error -- and a NaN parent energy moves
AssertionError to ValueError under rules.md rule 9.
Two things had to be reproduced rather than written correctly, because
the parity corpus pins them (rules.md rule 1). Both are live defects in
hazma 2.1.0 that writing five near-copies as one parameterised
implementation is what exposed, and both are filed and blocked behind
Phase 06 Task 6.4:
* _eta_prime.pyx:107 weighted its eta' -> gamma gamma line with BR
where its four two-photon siblings use 2*BR, so the mode carries
0.02307 photons per decay instead of 0.04614 -- 0.63% of the eta'
yield, missing;
* _phi.pyx:111,113 placed both photon lines at the daughter meson's
energy: 656.94 MeV where 362.52 is right, and 959.65 where 59.82 is
right, a factor of 16.
Also repairs two things the deletion stranded: test_core_{boost,interp}
built their table fixtures from the deleted modules' globals and failed
at collection, and two parity meta-tests monkeypatched hazma._core.photon
with a fake that now replaced seven real kernels instead of adding one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_kaon,_eta,_eta_prime,_omega,_phi, seven public entry points between them — to one Rust module,kernels::photon_tables: a singledndeover sevenSpectrumvalues, each holding aninclude_str!-embedded CSV parsed lazily on first use, a parent mass, and a list of(line energy, weight)pairs. The five.pyxdiffered only in that data. The Cython and its headers/stubs are deleted (16 files, 1,037 lines, of which 204 were commented-out dead code), along with the now-callerlesshazma/spectra/_photon/path.py. Import-time file I/O is gone — the Cython paid sevennp.loadtxtcalls onimport hazma.spectrawhether or not a spectrum was ever evaluated. The CSVs stay in-repo as the source of truth; retiring thehazma.spectra._photon.datapackage-data glob is Phase 07's, and only half-true until then, since the sdist must still carry them forinclude_str!to find at build time.origin/master, the whole public surface (12dnde_photon_*, 2dnde_positron_*, 2dnde_neutrino_*, plus both models'spectra(),positron_spectra(),annihilation_cross_sections()andthermal_cross_section()) — 97 arrays / 18,694 values, bit-for-bit identical. One declared behavior change, atNaNinputs only: aNaNphoton energy returnedIndexErrorand now returnsNaN(the Cython reachednp.flatnonzero(lb <= x)[0]on an empty match; reproducing that insidedispatch::map_unary, which has no per-element error channel, would mean aPanicExceptionwhere Python can catch an error today), and aNaNparent energy movesAssertionError→ValueErrorunderrules.mdrule 9. No finite input moves and the corpus samples noNaNabscissa.rules.mdrule 1 — the corpus pins the shipped values, so a fix here would fail the gate governing every remaining swap). Writing five near-copies as one parameterised implementation is what exposed them, by putting the five line-weight expressions in one column._eta_prime.pyx:107weights itsη′ → γγline withBRwhere its four two-photon siblings use2·BR, so the mode carries 0.02307 photons per decay instead of 0.04614 — 0.63% of the η′ yield, missing, all atM_η′/2 = 478.89MeV._phi.pyx:111,113place both photon lines at the daughter meson's energy rather than the photon's: 656.94 MeV where 362.52 is right (φ → ηγ, ×1.81) and 959.65 where 59.82 is right (φ → η′γ, ×16.0 — 94% of the φ's rest mass in one photon). Filed aseta-prime-two-photon-line-missing-factor-two.mdandphi-photon-lines-use-the-daughter-meson-energy.md, both blocked behind Phase 06 Task 6.4 — four defects now share one eventual corpus regeneration.test/test_core_{boost,interp}.pybuilt their seven-table fixtures from the deleted modules' import-time globals and failed at collection; twotest/parity/test_parity.pymeta-tests monkeypatchedhazma._core.photonwith a one-kernel fake that now replaced seven real kernels instead of adding one. The wrapper's 11 pre-existingruffD205/D412/D400findings are fixed (same 11 onorigin/master— zero delta, but preflight fails on the count), three docstrings named aphoton_energiesargument the signature spellsphoton_energy, the short- and long-kaon blocks both called their argument the "Charged kaon energy", and all seven now state their units.boost::boost_integrate_linear_interpis now total — aNaNwindow returnsNaNinstead of panicking at an.expect(reachable through thehazma._core.boostprobe since Task 3.4) — andboost::pairwise_sumispub(crate), reused for the CSV column sums becausenumpy.sum(axis=0)is pairwise above eight terms and the φ table has ten.Project
projects/cython-to-rust/— Task 4.2: Photon table family (_kaon,_eta,_omega,_eta_prime,_phi).See
projects/cython-to-rust/task-notes/phase-04/task-4.2-photon-table-family.mdfor implementation detail, decisions, the numerical-impact measurements, and the stale-state sweep. The phase README asked this task to resolve or waiveparity-corpus-pins-ill-conditioned-points.mdfirst: waived on evidence — the port is bit-equal at every sampled point, sospectra.photon.eta[boosted_strong]had nothing for a conditioning budget to absorb. That refutes the follow-up's prediction for this family; its remaining five blocks are all scalar cross sections, so it now gates Phase 05 rather than the rest of Phase 04.Test plan
scripts/agents/preflight.sh --paths <9 changed Python files> --md <8 changed markdown files>—RESULT: PASS, every gate green (black, isort, ruff, the three cargo gates, pytest, import smoke, markdownlint, forbidden tokens).pytest -q—1628 passed, 15 skipped, 5 warnings in 604.26s (0:10:04). Collection goes 1458 → 1643 againstorigin/master(pytest --collect-only -qon both trees) — +185, every one of themtest/test_core_photon_tables.py, so no existing module gained or lost a test.pytest -q test/parity—629 passed, 1 skipped, all seven tabulated cases green at theirTABULATED(1e-12) budget.python test/parity/generate.py --check—corpus OK: 41 cases / 1580 arrays match the manifest. The stored corpus is untouched.cargo test --manifest-path rust/Cargo.toml --no-default-features—test result: ok. 96 passed; 0 failed, 15 of themkernels::photon_tables::tests(folded constants against the disassembled immediates, parsed tables against NumPy's bit patterns, row counts, the three branch boundaries, theNaNguards, the tails, each line's boosted plateau, and the two reproduced defects).pytest -q test/test_core_photon_tables.py—184 passed, 1 skipped(the skip is the charged kaon in the per-line photon-count test; it has no monochromatic line).pytest -q test/test_theory_aggregation.py— the identity gate Task 1.4 built for exactly this risk, that a swap repoints a kernel correctly but drops a branching-fraction weight.boost::pairwise_sumwith a sequential fold fails 6 tests, every one of them φ and none on the other six tables — the predicted signature, since only the φ table has enough mode columns to reach NumPy's pairwise path; replacingdelta.mul_add(weight, result)with the unfused form fails 15 tests across all six spectra that carry a line.🤖 Generated with Claude Code