Skip to content

feat(utils): port the interp and boost kernels to rust - #61

Merged
LoganAMorrison merged 2 commits into
masterfrom
claude/cython-to-rust/task-3.4-interpolation-boost-kernels
Aug 11, 2026
Merged

feat(utils): port the interp and boost kernels to rust#61
LoganAMorrison merged 2 commits into
masterfrom
claude/cython-to-rust/task-3.4-interpolation-boost-kernels

Conversation

@LoganAMorrison

Copy link
Copy Markdown
Owner

Summary

  • Ports np.interp and the four live routines of hazma/_utils/boost.pyxboost_beta, boost_gamma, boost_delta_function, boost_integrate_linear_interp — to PyO3-free Rust (rust/src/{interp,boost}.rs), with hazma._core.{interp,boost} registration-only probes so the tests can reach them. No kernel is swapped: nothing under hazma/ imports either module, the only change under hazma/ is a comment in hazma/_core.pyi, and the parity corpus still runs in bit-equality mode (rtol = 0, provenance → exact=True).
  • The port reproduces the shipped Cython's fused multiply-adds, and that is load-bearing rather than pedantic. Clang defaults to -ffp-contract=on; the corpus's capturing platform (macOS/arm64) contracts eight distinct expressions across boost.pyx, and NumPy's arr_interp contracts too. Written the obvious unfused way the port misses the corpus by up to 3.6e-12 relative on the corpus's own grids — past the 1e-12 TABULATED budget in test/parity/tolerances.py — so the Phase 04 swap would have failed its own gate. With f64::mul_add at those sites it is bit-equal at every one of those points. The converse is the trap: boost_beta is deliberately not fused, because none of its ten inlining call sites contract 1 - (m/E)**2. Each site was established twice, by disassembling the shipped .so and by bisecting all 16 on/off combinations against the live kernel.
  • No values move in this PR, but one drift is now known and lands with the Phase 04 swap: the Rust returns the contracted (arm64) numbers on every platform, so on a target whose C compiler does not contract — baseline x86-64, which is what the Linux wheels are built for — today's Cython returns values up to 3.6e-12 relative away from these. Past rules.md rule 3's 1e-12 threshold, so it is recorded in the project's "Numerical impact so far" for the Phase 04 PR to declare. The alternative, plain arithmetic, misses the corpus by that same amount on every platform.
  • Found while porting: boost_integrate_linear_interp is wrong near threshold, in released hazma. Its interior sum stops one cell short of the upper partial cell, and when both integration bounds fall inside one cell the two partial-cell terms overlap instead — an over-count of cell width / window width, which diverges as beta → 0. All seven tabulated photon spectra therefore blow up instead of converging to their own rest-frame spectrum as the parent slows: dnde_photon_eta(54.79, m_eta) is 0.02313 MeV⁻¹ at rest against 767.2 one part in 1e12 above it, a factor of 33,000; the other six run 6,500×–9,800×. This PR reproduces the behavior rather than repairing it (rules.md rule 1 — the corpus pins these values, so a fix here would fail the gate), pins it in both languages, and files the repair, blocked until after Phase 06 Task 6.4 because it needs a declared corpus regeneration. Worth a look independently of this project's schedule.
  • Two canonical patches, in the same PR as the code: the phase file's Task 3.4 block gains five "criteria added during execution" bullets (the oracle it named does not exist — Phase 01 captures top-level defs and these are all cdef, so the tests call the live Cython through __pyx_capi__ with ctypes instead), and references/numerics-replacements.md gains the measured block, since its own np.interp and boost prose is what a next reader would port from and both are incomplete in ways that change the numbers.

Project

projects/cython-to-rust/ — Task 3.4: Interpolation + boost kernels.

See projects/cython-to-rust/task-notes/phase-03/task-3.4-interp-boost.md
for implementation detail, decisions, the mutation tables, and verification.

Test plan

  • scripts/agents/preflight.sh --paths "…" --md "…"RESULT: PASS (ten rows PASS, one expected SKIP for the version bump)
PASS   black --check / isort --check-only / ruff check
PASS   cargo fmt --check / cargo clippy / cargo test        rust/
PASS   pytest                  1314 passed, 13 skipped, 5 warnings in 566.77s (0:09:26)
PASS   import hazma            version 2.1.0
PASS   markdownlint            (8 changed .md)
SKIP   version bump            not a closing PR (pass --closing)
PASS   forbidden tokens        none added
RESULT: PASS
  • +102 on Task 3.3's 1212 passed, 13 skipped, all of them this task's tests. The skip count is unchanged at 13, which is how the parity suite reports it is still in bit-equality mode — confirmed directly rather than inferred:
$ python -c "...tolerances.provenance(manifest)..."
Provenance(exact=True, detail='')
$ python -c "...cases.rust_core_kernels()..."
[]
  • pytest test/test_core_interp.py -q33 passed in 0.46s (6 classes); pytest test/test_core_boost.py -q69 passed in 0.91s (9 classes)
  • cargo test --manifest-path rust/Cargo.toml --no-default-features67 passed (24 new)
  • Bit-equality against the oracles, not a tolerance: zero mismatches on all seven live tables across six boost regimes × 400 energies, zero across 40,000 boost_delta_function draws at both live product masses, and zero on 20,204 np.interp abscissae per table.
  • Test validity — 21 mutations, run sequentially behind a lock with a green baseline asserted before and after. 17 of the first 20 caught; all 21 after two tests were added. The three survivors shared one shape — each moved a branch boundary by a single double without touching any returned value, which no grid sample can see. The tests written for them bisect on the bit pattern. Tables in the task note.
  • No public value changes: git diff origin/master -- hazma is one file, hazma/_core.pyi, and every line of the hunk is comment text.

LoganAMorrison and others added 2 commits August 10, 2026 22:21
cython-to-rust Task 3.4. Adds `hazma_core::interp` (np.interp) and
`hazma_core::boost` (the four live routines of hazma/_utils/boost.pyx),
both PyO3-free, plus registration-only `hazma._core.{interp,boost}`
probes so the tests can reach them. No kernel is swapped: nothing under
hazma/ imports either, and the parity corpus still runs at rtol = 0.

Three things the port had to establish by measurement rather than by
reading the spec.

The oracle the phase file named does not exist. It asked for
"micro-fixtures captured in Phase 01", but the corpus enumerates
top-level defs and every routine here is cdef. boost.pxd declares them,
so Cython exports them through __pyx_capi__ and ctypes can call the live
kernel at arbitrary arguments — strictly stronger than a frozen sample.
The shim needs PYFUNCTYPE, not CFUNCTYPE: the latter drops the GIL and
boost_integrate_linear_interp calls back into NumPy.

The port must reproduce the compiler's fused multiply-adds. Clang
defaults to -ffp-contract=on and the corpus's capturing platform
contracts eight distinct expressions here, as does NumPy's arr_interp.
Written unfused the port misses the corpus by up to 3.6e-12 relative on
the corpus's own grids — past the 1e-12 TABULATED budget — so the Phase
04 swap would have failed its own gate. With f64::mul_add at those sites
it is bit-equal at every one of those points. The converse matters as
much: boost_beta is deliberately *not* fused, because none of its ten
inlining call sites contract `1 - (m/E)**2`.

boost_integrate_linear_interp mis-covers its window at both ends, and is
reproduced that way under rules.md rule 1. The interior sum stops one
cell short of the upper partial cell, and when both bounds fall in one
cell the two partial-cell terms overlap instead — an over-count that
diverges as beta goes to zero. All seven tabulated photon spectra
therefore blow up rather than converging to their rest-frame values near
threshold. That is a live defect in 2.1.0 which the corpus pins by
construction; the repair is filed and blocked until after Phase 06.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught what the local run could not: 19 of this task's assertions
compare the Rust against a *locally compiled* oracle — the Cython twin
and NumPy — and assert bit-equality. That holds on macOS/arm64, where
the C compiler contracts `a*b + c` into an FMA and whose numbers the
parity corpus pins, and fails on Linux/x86-64, where neither reference
contracts. The port is right; the tests asserted a property of the
platform's instruction selection as though it were a property of the
port.

`CYTHON_CONTRACTS` and `NUMPY_CONTRACTS` are now measured at import and
the cross-implementation comparisons skip where false — the same scoping
`test/parity` already has (CI runs `pytest --ignore=test/parity` off
macOS for exactly this reason). Loosening to a tolerance was rejected:
the worst *relative* gap between the two forms sits at a catastrophic
cancellation point (the eta tail, interpolant 2.4e-26 against a table of
scale 0.2, an absolute gap of 1.4e-30), so any tolerance admitting it
would be wide enough to hide a real defect.

The per-branch tests keep their platform-independent halves — the closed
forms, the sensitivity checks, the dropped-cell pins — running
everywhere, and route only the bit-equality claim through
`assert_matches_cython`, which skips mid-test after those have already
run. Verified by forcing both detectors false: 74 passed, 28 skipped, no
failures; unforced, 102 passed.

Also addresses PR #61 review round 1, which found three derived counts
in the task note contradicting their sources: `interp.rs` has 11 unit
tests and `boost.rs` 13 (the verification table said 10 and 14), and the
change inventory is re-derived after every edit rather than during.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LoganAMorrison
LoganAMorrison merged commit f3feddb into master Aug 11, 2026
8 checks passed
@LoganAMorrison
LoganAMorrison deleted the claude/cython-to-rust/task-3.4-interpolation-boost-kernels branch August 11, 2026 21:51
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