srmech Rosetta-completeness ledger¶
Goal. A complete C mirror of the Python surface — every public Python op
has a C twin that is bit-exact and dispatched to, OR is a composition of
such twins. Then the C partition (libsrmech) runs standalone: on a full OS
or on a thread-less, OS-less microcontroller, with no host Python. C:Python
parity is the program's form, not a means to embedded — there are no
exemptions.
This file is the down-only debt ledger for that goal (like the C-transpile
libm ratchet that went 23 → 0): the python_only_irreducible count only ever
decreases. Each rc drives it down; a clean v0.7.5 graduation waits until the
debt is closed.
Two hardware abstractions make a complete mirror possible¶
The C core stays machine- and OS-agnostic; everything platform-specific lives behind one of two sibling abstraction layers. In the project's framing the OS is part of the hardware the binary runs on, so both "qualify as hardware":
| Layer | Abstracts | The one place its #ifdefs live |
Consumers |
|---|---|---|---|
HAL — c/src/srmech_simd.{h,c} |
the CPU (SIMD tiers, cpuid, target-attrs) | srmech_simd.c |
srmech_sha256_batch.c, srmech_loopbind_hd.c |
PAL — c/src/srmech_platform.{h,c} (rc4–rc5) |
the OS (threads rc4; stream IPC rc5) | srmech_platform.c |
srmech_parallel.c (rc4); srmech_bus.c (rc5) |
Per [[feedback_simd_optimize_path_goes_through_hal]], generalised from the CPU
to the OS: machine-specific bits go behind another *.h; the core stays
agnostic. A functional core (srmech_parallel.c, the cascade kernels, …)
carries zero #ifdef _WIN32.
Build authority. The full surface builds clean on Linux via WSL2
(gcc/cmake), pedantic -Werror; this is the canonical standalone-C build/test
loop. CI's cross-OS matrix (Linux gcc / macOS clang / Windows MSVC) is the gate.
The classification (every public Python op falls in one bucket)¶
c_dispatched— has asrmech_*C twin, bound in_native.py, and the Python op dispatches to it. (sha256, ndjson, cyclic, primes, laplacian, dispatch/catalog/template, hdc loop family, cascade atoms, Schur/DtN, Cayley–Dickson cocycle, the_one/hurwitz, trig (rc2), exp/log/sqrt (rc3), …)c_exists_unbound— a C twin exists but Python doesn't yet bind/dispatch it. Cheap debt: bind it.composition_of_c— no single C twin, but the op is a pure composition of bucket-1 C kernels (e.g. aqm.*operator that is matmul ∘ eig ∘ kron). Closing it = expressing the composition in C (no new irreducible kernel).python_only_irreducible— the debt. An irreducible compute kernel with no C twin and not yet a composition of C kernels (the bulk: theqm.*dense-linear-algebra layer + a few bignum-in-C gaps). Drive to 0.
A separate, intentional tier sits outside the debt: the exact-rational bignum reference surfaces (
*_series_truncate, theprecision_bitssqrt). They are arbitrary-precision oracles the C-bit-exact cascades are checked against — like a higher-precision reference instrument, not a parity gap.
Do-not-mirror gate — known Python bugs (issue #928)¶
The Rosetta law is a bit-exact C twin. That cuts both ways: bit-exact
mirroring of a buggy Python op enshrines the bug in two places instead of
one. So before any op crosses Python → C, check it against the open-bug list in
the consolidated wishlist tracker (issue #928 / rbs_lm_research/SRMECH_BUGFIX_WISHLIST.md).
A known-defective Python op is resolved on the Python side FIRST, then its
corrected behaviour is what the C twin mirrors. Never port a 🔴 OPEN /
CONFIRM row to C.
Open rows that intersect this arc (as of 2026-06-08):
| # | Bug | Intersection | Gate |
|---|---|---|---|
| W5 | klein4_bundle even-count behaviour vs prior "odd-only" note (CONFIRM) |
rc13 shipped the klein4 sectors= splay pure-Python, with "standalone-C sector dispatch is the tracked follow-up". |
Resolve/confirm W5 BEFORE the klein4 standalone-C port — otherwise the ambiguous even-count semantics freeze into C. Highest-risk row. |
| W4 | sha256_bytes returns a hex string, not bytes |
sha256 is already c_dispatched (srmech_sha256_hex → hex); the contested return-type is a Python API contract the C twin already matches. |
Don't enshrine further; re-decide the return-type at the next sha256 touch, then align C. |
MCP-layer rows (W1 naming_lookup kwarg-drift, W3 non-JSON schema leak) are
wrapper-surface, not compute kernels — outside the C-mirror surface entirely.
The measured baseline (rc7 audit; issue #928)¶
The rc7 audit enumerated the 348 public compute-or-not ops across
srmech.amsc / srmech.qm / srmech.signal_processing and classified every
one by reading its implementation against the exported C-symbol surface. The
result is the committed SSoT python/tests/rosetta_classification.ndjson, pinned
by the python/tests/test_rosetta_completeness.py ratchet (regenerate via
notes/_rosetta_inventory.py → notes/_rosetta_build_classification.py):
| Bucket | Count | Standalone-C? |
|---|---|---|
c_dispatched |
78 → 85 | ✅ runs on libsrmech alone |
composition_of_c |
61 → 73 | ✅ pure composition of C-dispatched ops |
bignum_reference |
22 | ➖ intentional exact-rational oracle tier (not debt) |
non_compute |
56 | ➖ IO / registry / schema / introspection (no kernel) |
c_exists_unbound |
23 → 5 | ❌ DEBT (cheap): a C twin exists, Python doesn't dispatch |
python_only_irreducible |
108 | ❌ DEBT: irreducible kernel, no C twin yet |
Total standalone-C debt = 131 → 113 (108 irreducible + 5 unbound). The
ratchet's two ceilings start at the rc7 baseline and only move down:
rc8 took the first 6 off (the SHA-256 mint cluster), rc9 the next 3 (octonion
L/R-multiply + conjugate → the C-backed hdc.loop_* family), rc10 the next 2
(cd_basis_product → srmech_cd_basis_product; octonion_mult_table composes it),
rc11 the next 3 (Hamming GF(2) encode/syndrome → srmech_hamming_*;
decode_correct composes the syndrome twin), rc12 the next 3 (the polar-HDC trio
polar_{bind,bundle,density} → srmech_polar_*), rc13 the next 1 (lmmse routes
its solve → dense_solve + its matvec → dense_matvec_complex cascades). The
remaining 5 are the Klein-4 family, gated on W5.
A sibling source-level guard lands with rc13: the numpy-math ratchet
(python/tests/test_numpy_math_ratchet.py) keeps numpy a carrier, not a math
engine — it greps the srmech source for numpy-math callsites (np.linalg/np.fft
126 · @/dot/einsum/kron/… 185 · transcendental ufuncs 48) and pins each at
a tight down-only ceiling. It is the same debt the 108 python_only_irreducible
cluster represents, seen at the source level: a new np.linalg.solve fails CI,
and each migration to a cascade decrements both ledgers. (lmmse was decrement #1.)
What collapses the most debt (the rc8+ work-list, by leverage)¶
The 108 irreducible are not 108 distinct problems — they cluster on a handful of missing C kernels. One kernel each clears a column:
| Missing C kernel | Clears (approx) | Where |
|---|---|---|
dense complex matmul (matrix×matrix) |
~15 | qm: commutator, gamma_5, casimir_*, clifford_residuals, wilson_loop, heisenberg/liouville_evolve, harmonic_oscillator_hamiltonian, … (matvec twin already exists; matmul is the gap) |
| FFT/DFT (radix-2 + Bluestein) | ~20 | sp: fft/ifft/rfft/stft/spectrogram/cross_spectral/multitaper/wiener/spectral_subtraction/ofdm (Path A+B) + cascade dft/fft/idft/ifft |
general dense eig/SVD/QR/lstsq |
~16 | qm so8.* + triality.* (svd/lstsq/qr/pinv/rank), cascade matrix_cascades.{qr,svd,lstsq,eigvals}, sp esprit/mimo_svd/map_ml |
kron (tensor product) |
~6 | qm bell.* CHSH family + so8 binders |
einsum / convolve / correlate |
~8 | sp ica_jade/fir/multirate/polyphase/matched_filter, cascade einsum |
The remaining irreducible are pure-Python DP/codec loops (Viterbi, Huffman,
LZ77, RLE, arithmetic-coding, wavelet, JPEG, PSK/QAM/FSK) + the numpy-eigh
Laplacian pair + the Klein-4/polar relabel ops — each its own small port.
The cheap wins (c_exists_unbound — wire-up only); 23 → 17 after rc8¶
A bit-exact C twin already ships; the Python just never calls it:
SHA-256 mint cluster (6):— ✅ CLOSED rc8: routed throughmint_*/encode_loe_content/compute_content_stridecall rawhashlib.sha256instead of the C SHA-256format.sha256_raw(native dispatch; bit-identical raw-32 digest), reclassified →composition_of_c. Also cleared the CLAUDE.md raw-hashlibdiscipline violation. (W4-aware: these are.digest()raw-byte sites, sosha256_raw— not the hexsha256_bytes— is the correct twin.)- HDC Klein-4 (5):
klein4_{bind,bundle,similarity,triality_cycle,unbind}— twinssrmech_klein4_*exported, Python is numpy-free pure-Python. Gated on W5 (klein4_bundleeven-count) per the do-not-mirror gate — the one cheap cluster still held. HDC polar (3):— ✅ CLOSED rc12: dispatch topolar_{bind,bundle,density}srmech_polar_*(int8 sign-product / sticky-majority / informative-fraction). Bit-exact over 200 trials each.Octonion einsum (4):— ✅ CLOSED rc9 (3) + rc10 (1): L/R-mult + conjugate delegate to the C-dispatchedoctonion_{left_mult,right_mult,conjugate,mult_table}hdc.loop_{left_op,right_op,conj};mult_tablecomposes the now-nativecd_basis_product(srmech_cd_basis_product). Bit-exact; content-address7f36461e…unchanged.Hamming GF(2) (3):— ✅ CLOSED rc11:hamming_{encode,syndrome,decode_correct}encode/syndromedispatch tosrmech_hamming_*(the v0.7.2rc2 C twin; the module gained its_nativeimport);decode_correctcomposes the syndrome twin. Bit-exact overn∈{2,3,4}+ every single-bit-flip.— ✅ CLOSED rc13: the framing was corrected (user direction 2026-06-08) — numpy is a carrier, never the math engine, so thelmmse(1) (np.linalg.solve)np.linalg.solvewas a defect, not a convenience, and the srmech cascade (not LAPACK) is the source of truth. Routed the solve →dense_solve+ the estimate matvec →dense_matvec_complex; reclassifiedc_exists_unbound → composition_of_c. Correct to machine precision (gain residual≈4e-16).
Cheap-win sweep is done (rc8–rc13). Remaining 5 c_exists_unbound are the
Klein-4 family — gated on W5 (klein4_bundle even-count must be confirmed
before its standalone-C sector-dispatch port per the do-not-mirror gate). Beyond
that, debt only falls by new C kernels for the 108 irreducible (biggest single
lever a dense complex matmul) — tracked at the source level by the numpy-math
ratchet (a stray np.linalg/@/ufunc now fails CI; each cascade migration
decrements it).
Roadmap (rolling; each rc drives the debt down)¶
- rc4 (done) — PAL born + parallel.c retrofit + WSL2 Linux build authority.
- rc5 (done) — PAL stream/IPC +
srmech_bus.cretrofit (last raw-OS surface closed). - rc6 (done) — W17
coupled_wave+ W18multiplex_streams(active-arc named ops;composition_of_c, no new C debt). - rc7 (done) — the Rosetta-completeness AUDIT + ratchet. 348 ops
classified;
test_rosetta_completeness.pypinspython_only_irreducible ≤ 108andc_exists_unbound ≤ 23, both monotone-down, plus a live↔classified exact-match guard so every new op must be bucketed. - rc8 (done, this) — cheap-win sweep #1: the SHA-256 mint cluster. The 6
signal_processingmint/stride ops routed off rawhashlib.sha256ontoformat.sha256_raw(native dispatch, bit-identical) →composition_of_c;c_exists_unboundceiling 23 → 17. Closes the CLAUDE.md raw-hashlibdiscipline gap in the same move. - rc9 (done) — cheap-win sweep #2: octonion L/R-mult + conjugate. The three
qm.octonioneinsum ops delegate to the C-dispatchedhdc.loop_*family →composition_of_c;c_exists_unboundceiling 17 → 14. - rc10 (done) — cheap-win sweep #3: the Cayley-Dickson basis cocycle.
cascade.cd_basis_product → srmech_cd_basis_product(c_dispatched);octonion_mult_tablecomposes it (composition_of_c); content-address7f36461e…unchanged; ceiling 14 → 12. - rc11 (done) — cheap-win sweep #4: the Hamming GF(2) block code.
cascade.hamming_{encode,syndrome} → srmech_hamming_*(c_dispatched);hamming_decode_correctcomposes the syndrome twin (composition_of_c); bit-exact overn∈{2,3,4}+ every single-bit-flip; ceiling 12 → 9. - rc12 (done) — cheap-win sweep #5: the polar-HDC trio.
hdc.polar_{bind,bundle,density} → srmech_polar_*(c_dispatched); all int8, bit-exact over 200 trials each; ceiling 9 → 6. - rc13 (done, this) — numpy-math ratchet + lmmse → cascade (decrement #1).
Framing correction (user direction): numpy is a carrier, never the math
engine, so the
np.linalg.solveinlmmsewas a defect — the srmech cascade (not LAPACK) is the truth. Routed the solve →dense_solve+ the estimate matvec →dense_matvec_complex;lmmsereclassifiesc_exists_unbound → composition_of_c, ceiling 6 → 5. Shipped alongside the new numpy-math ratchet (test_numpy_math_ratchet.py) — the source-level down-only guard (np.linalg/np.fft126 ·@/dot/einsum/… 185 · ufuncs 48) that keeps numpy a carrier, not a math engine. A straynp.linalg.solvenow fails CI. - rc14 (done, this) — the new-C-kernel phase OPENS: dense complex
matmul. New additive C symbolsrmech_dense_matmul_complex((m,k)·(k,n), interleaved, ≤256/dim; JPL-clean; ABI stays 3) +laplacian.dense_matmul_complex(native dispatch; no-native fallback composesdense_matvec_complexcolumn-by-column — a cascade, never numpy@). Tool-schemadescribe255 → 256; Rosetta inventory 348 → 349 (c_dispatched). This rc ships + proves the kernel (parity + CI build); the@-callsite migrations against it (decrementing the ratchet'smatmul185) are the next batches. - rc16 (done) — matmul-kernel batch 1:
matrix_cascadesdense matmuls. The 5 dense complex 2-D matmuls insidematrix_cascades.py(AᴴA/A·V/AAᴴ/Aᴴ·UGram + reconstruction products ofsvd; theR·Qshifted-QR step ofeigvalsthatqr/lstsqride) now route throughlaplacian.dense_matmul_complexinstead of numpy@. numpy-math ratchetmatmul185 → 180. No Rosetta bucket move (qr/svd/lstsq/eigvalswere alreadycomposition_of_cvia thehermitian_eigendecomposeClass-L cascade); pure Python-tier, ABI stays 3, the 25 decomposition parity tests pass unchanged. - rc17 (done) — matmul-kernel batch 2:
qm.single_particlecontractions. The 12 dense complex contractions inqm/single_particle.py(commutatorAB−BA;heisenberg_evolveUᴴ·A·U+liouville_evolveU·ρ·UᴴwithU=V·diag·Vᴴ;tdse_evolveeigenbasis change) route throughdense_matmul_complex/dense_matvec_complex. numpy-math ratchetmatmul180 → 168. Module residual is onlynp.outerindensity_matrix(rank-1, distinct op). No Rosetta move (alreadycomposition_of_cviahermitian_eigendecompose); ABI stays 3; the 27 single_particle parity tests pass unchanged. - rc18 (done) — matmul-kernel batch 3:
qm.spin+qm.gauge.qm.spin's 15 Pauli products (Clifford anticommutator + cyclic commutator residuals) andqm.gauge's 6 SU(N) Lie-algebra products (structure-constant commutator, quadratic CasimirΣTᵃTᵃ, segment-holonomyV·diag(eⁱᵠ)·Vᴴ, Wilson-loop path-product) now route throughdense_matmul_complex. numpy-math ratchetmatmul168 → 147. Both modules numpy-@-free; no Rosetta move; ABI 3; spin + gauge parity tests pass unchanged. - rc19 (done) — matmul-kernel batch 4:
qm.relativistic+qm.pseudo_hermitian.qm.relativistic's 9 Dirac γ-matrix products (γ_5=iγ0γ1γ2γ3, Clifford{γ^μ,γ^ν},γ_5²,{γ_5,γ^μ}, charge-conjC=iγ2γ0) +qm.pseudo_hermitian's 3 (Oᴴη−ηO,η=(V·Vᴴ)⁻¹) route throughdense_matmul_complex. numpy-math ratchetmatmul147 → 135. ABI 3; parity tests unchanged. DEFERRED: the real-typedeta@kMinkowski matvec/dot +vᴴηveta-sandwich vecmat-dot sites (need a real-matmul cascade + vecmat helper);qm.trialityis entirely real-typed and awaits the same real-matmul variant. - rc20 (done) — matmul-kernel batch 5: complex vecmat/dot/sandwich → new
dense_dot_complexbilinear helper. The complex 2-D matmul surface was exhausted at rc19; rc20 addsdense_dot_complex(a, b)(plain bilinearΣ aᵢbᵢ=elementwise_multiply_complex⊕ reduction;composition_of_c) and routes the genuinely-complex contraction sites ontodense_matvec_complex dense_dot_complex:qm.pseudo_hermitian's 3 η-sandwiches (7@),heat_kernel's 2 eigenbasis matvecs,spectral's 2 decompose/recompose matvecs,music'sEnᴴ·A2-D matmul. numpy-math ratchetmatmul135 → 123. ABI 3; parity tests unchanged. DEFERRED: the real-typed so8/triality/octonion-DFT/Minkowski/DSP sites (real-matmul + real-matvec cascade) and thematrix_cascadesQR-internal vdot/back-solves (shape- polymorphic pass).- rc21 (done) — matmul-kernel batch 6: real-linear-algebra cascade trio +
hypercomplex_dft. Introducesdense_matmul_real/dense_matvec_real/dense_dot_real(float64 peers riding the complex kernel on imag-free input,.real;composition_of_c) so the ~70 real-typed sites can leave numpy@/.dotwithout a dtype change. First use:amsc.cascade.hypercomplex_dft's 8 octonion-rep (8×8 real) matvecs in the QDFT/ODFT core +hypercomplex_couple→dense_matvec_real(lazy-imported, numpy-absent-safe §22; F378 bracketing preserved). numpy-math ratchetmatmul123 → 115. ABI 3; QDFT/ODFT + numpy-free tests unchanged.dense_matmul_real/dense_dot_realget first callsites in the next batches (so8 / triality). - rc22 (done) — matmul-kernel batch 7:
qm.trialityreal products → real cascade. First consumer of the rc21 real trio.qm.triality's 7 real products — octonion-rep matvecs (operator @ octonion_mul(…),g_v/g_s/g_c @ …) ontodense_matvec_real; the 28×28 Spin(8) trialitytau = S_B·S_C/tau²/tau³ontodense_matmul_real— plus 3 docstring@→·rewords. numpy-math ratchetmatmul115 → 105. ABI 3; triality parity unchanged (tau³=I₂₈,Fix(tau)=g₂dim 14).qm.so8's ~17 real sites + Minkowski/DSP land next. - rc23 (done) — matmul-kernel batch 8:
qm.so8real + complex. The g₂/Spin(8) module's 17 contraction sites: 15 real (the[X,Y]commutator, su(3)/g₂ Gram products, basis-projection matvecs, structure-constantpinv·bracket, Gram-Schmidt dot) →dense_matmul_real/matvec_real/dot_real; 2 COMPLEX (the su(3)-weight Rayleigh quotientsvᴴv/vᴴ·ad·v, wherevis a complex eigenvector of the real ad(H)) →dense_dot_complex/dense_matvec_complex. numpy-math ratchetmatmul105 → 86. ABI 3; so8 parity unchanged (g₂ dim 14, 14 = 8+3+3̄ su(3) branching, su(3) weights). The 2np.kronstay (distinct op). - rc24 (done) — matmul-kernel batch 9: the real "Minkowski + real-dot" sweep.
Eleven real-typed sites across
qm+amsc:qm.relativistic(3 —η klowering matvec, the K-G⟨k,k⟩dispersion dot, thekᵀηkbilinear),qm.propagators(1 — gauge-termη kmatvec),amsc.harmonics(3 — the_spectral_scoresenergy/mirror/three-cycle ⟨x,·⟩ probes),amsc.hdc(3 — the Moufang norm² gates inloop_inv/loop_inv_hd+ theg2_three_formassociator ⟨x,y×z⟩) →dense_matvec_real/dense_dot_real. Theamscsites import the helper function-locally soharmonics/hdcstay numpy-absent-safe (§22). numpy-math ratchetmatmul86 → 75. ABI 3; values bit-preserved; the qm + hdc-loop + harmonic suites pass unchanged. Thenp.outerkμkν sites stay (distinct op). DSPclosed_form_ops+matrix_cascadesQR-internals next. - rc25 (done) — matmul-kernel batch 10: the real DSP
closed_form_opscluster. Fifteen sites:dct(2 — DCT-matrixarr·Mᵀ/M·arr),map_ml(6 — theAᵀR⁻¹Anormal-equation matmuls + matvecs;np.linalg.inv/solvestay),ica_jade(6 —XᵀXcovariance + whitening + GivensV·Grotations; 2np.einsum+eighstay) →dense_matmul_real/dense_matvec_real; plusfsk(1 — the complextones·conj(window)correlator bank) →dense_matvec_complex. Top-level helper import (these DSP modules hard-import numpy, unlike the lazy-numpy amsc modules). numpy-math ratchetmatmul75 → 60. ABI 3; dct/map_ml/ica_jade/fsk suites unchanged.np.convolve/correlate/outer/einsumstay (distinct ops). - rc26 (done) — matmul-kernel batch 11: the genuine-code tail. Five remaining
genuine dense-matmul code sites:
vector_quantisation(realvec·cbᵀ),sinc_interp(COMPLEXK·y—yis complex128 IQ →dense_matvec_complex),farrow(real LagrangeC[k]·xdot),qm.potentials(complexa†·anumber op),qm.sm(complexV·VᴴCKM-unitarity) →dense_matmul_real/dense_dot_real/dense_matvec_complex/dense_matmul_complex. numpy-math ratchetmatmul60 → 55. ABI 3. This reaches the dense-matmul-migration floor: of the remaining ~55, ~16 are docstring/comment/summary-string@mentions (cosmetic·reword) and ~25 are distinct ops needing own cascades (convolve / correlate / kron / outer / einsum). ThelaplacianSchurL_pi·Xis deferred (in-helper, shape-polymorphic). Further matmul reduction = reword sweep + distinct-op cascades (separate work items), not more dense-matmul routing. - rc27 (done) — linalg/fft phase opens: the linear-solve family. Dense-matmul
floored, the arc pivots to
linalg_fft(pinned at 126 since rc13). Per user direction (cascade + TOML for ALL maths; numpy carrier-only, carrier removed as the FINAL step), the cascades replace numpy math even where round-off-faithful not bit-exact (fft/svd/qr/eig ~1e-14; within-tolerance shift accepted). rc27:map_ml's 2np.linalg.solve→dense_solve(bit-exact, 1-D RHS);triality esprit'snp.linalg.lstsq→matrix_cascades.lstsq(round-off, complex-safe; bare-ndarray return replaces numpy's 4-tuple → callsite unpack changed). numpy-math ratchetlinalg_fft126 → 122. ABI 3. NOT migrated: the cascade ops' OWN internal numpy kernels (laplacian eigh/solve — Class-L impls w/ pure-Python fallbacks; deeper pass) + docstring/summarynumpy.linalg.*MENTIONS (precise docs, not gamed). Next: np.fft (n/axis) + svd/qr/eigvals + inv/pinv.- rc28 (done) — the first exact-until-rotation cascade: DFT/FFT goes integer.
Per the sharpened user direction ("don't use floats for bit-exact math, that's
what ints and complex are for; floats are for FPU lift"), the
dft/fftcascade now routes an all-integer / Gaussian-integer power-of-two signal through an exact cyclotomic-integer engine —ℤ[ζ_N],ζ^{N/2} = -1(a Class-K sign-flip) collapsing to the negacyclic integersℤ[x]/(x^{N/2}+1)→ pure integer add/subtract, with ONE FPU lift at the end (ζ → e^{-2πi/N}). This is more faithful than a float FFT (which rounds every butterfly) and sharpens rc27's "round-off-faithful is fine" framing for the integer case. No ratchet movement, no Rosetta-bucket change: the engine is a private module (srmech.amsc.cascade.exact_dft._exact_transform+ helpers), adds no numpy and no public introspected callable, so all three numpy-math ceilings AND thepython_only_irreducibledebt count are untouched. ABI 3. Follow-up C-twin candidate: exposing the exactℤ[ζ_N]spectrum as a public op (exact_dft/exact_idft/lift) should land with its native-C peer so it classifiesc_dispatched, not Python-only debt — the ratchet's exact-equalitypython_only_irreducibleceiling is precisely what blocks adding it Python-only. General-N(non-power-of-two) cyclotomic reduction is also a follow-up. - rc29 (done) — the exact ℤ[ζ_N] spectrum goes public + gets its C twin.
exact_dft/exact_idft/liftare promoted to introspected ops, and the native-C twinsrmech_exact_dft_i64(srmech_exact_dft.c, JPL-clean, caller-buffer int64) ships the integer add/subtract fast path. The Python op dispatches to C whenN·max|signal|is int64-safe, else the arbitrary-precision bignum path. Rosetta:exact_dft/exact_idft→c_dispatched(they HAVE the C twin),lift→composition_of_c(over the Class-Ncexp) — the honest "land WITH the C twin so it's not Python-only debt" the rc28 note flagged. 3 ToolEntries (introspect 260 → 263), 3 rosetta lines, alist[tuple[list[int],list[int]]]MCP coercer. ABI 3 (additive). Next: general-Ncyclotomic (Python-onlybignum_reference). - rc30 (done) — the exact DFT goes general-
N(any length, not just pow2). A pure-integer cyclotomic engine (computeΦ_Nfromx^N-1 = Π_{d|N} Φ_d; reduce eachζ^jto the length-φ(N)power basis, cached perN) extendsexact_dft/exact_idftto anyN ≥ 2;liftinfers the basis degree from the spectrum;dft/fftroute all integer signals exact (pow2 keeps the negacyclic / native-C route, general uses the bignum cyclotomic path). No new public surface, no ratchet movement — the general path is Python-only (bignum_referenceshape; the op staysc_dispatchedfor its pow2 C fast path), numpy-free, ABI 3. The rc28/rc29 non-pow2 "not-supported" tests update to the general-Ncontract. Perf: the general path isO(N²·φ(N))integer — exactness costs theφ(N)factor. - rc31 (done) — exact-until-rotation EIGENVALUES; the ill-conditioned problem
HAS a cascade form. Per user insight: the Wilkinson ill-conditioning of float
root-finding from char-poly coefficients is a float-perturbation artifact, NOT
inherent — integer-matrix eigenvalues are ALGEBRAIC and come out well-conditioned
if kept in exact arithmetic. Two new public ops:
char_poly(exact integer characteristic polynomial via Faddeev–Leverrier — exact trace/det/symmetric functions; Class L∘M∘K) andeigvals_exact(exact real eigenvalues with multiplicity: char_poly → Yun square-free → Sturm sign-sequence isolation (Class C sign-count @ Class K interval boundaries) → rational bisection (Class N anchors → the algebraic asymptote), exactFractionthroughout, one FPU lift). Proven: Wilkinsondiag(1..10)exact (err 0.0) vs floatnp.roots9-digit loss; irrational golden-ratio exact to 15 digits; singular values via the exact integer GramAᵀAmatch numpy ~1e-9 (the svd exact substrate too). Bothbignum_reference(non-debt;Fraction/bignum, numpy is a container). 2 ToolEntries (introspect 263 → 265), 2 rosetta lines, ABI 3, ratchet untouched. Follow-up: complex-eigenvalue exact isolation. NEXT-ARC SEED (user-requested): an A-N-cascade sweep/ratchet — a down-only guard flagging math not yet reduced to the 14 A-N class cascades (themath.*/cmath.*float-transcendental residue beyond the numpy-math ratchet). - rc32 (done) — the A-N-cascade ratchet itself (the rc31 NEXT-ARC SEED).
tests/test_an_cascade_ratchet.py— the bare-Python-tier sibling of the C-transpile libm ratchet (23 → 0) and the numpy-math ratchet. AST-based, not regex (the source carries ~39 descriptive docstring mentions ofmath.cosetc. that a text grep would miscount as debt; the AST sees only genuineCall/Attribute/BinOpnodes — the same basis as the no-abs scanner). Three tight down-only categories pinned at the measured baseline:transcendental= 1 (the lonecmath.sqrtWilkinson-shift discriminant in the FLOATeigvalscomplex-spectrum path — eliminated when complex-eigenvalue exact isolation lands, the rc31 follow-up),math_const= 0 (the π-cascade discipline fully holds — zero floatmath.piin any compute path),float_pow= 0. Excluded + documented: integer primitives (isqrt/gcd/factorial/…), IEEE/sign primitives (copysign/fabs/isfinite/… — Class-K sign family;copysignat an IEEE ±0 limit is the correct idiom), and the inherently-invisible string mentions. No module carve-out — a straymath.sinanywhere fails. Test-only; no public surface, no ToolEntry, no introspect change, ABI 3. - rc33 (done) — A-N ratchet CLOSED to zero. The one rc32
transcendentalsite (thecmath.sqrtWilkinson-shift discriminant in the floateigvalscomplex-spectrum path) routed throughmatrix_cascades._complex_sqrt— the principal complex root rebuilt from the Class-Nhypot/sqrtreal cascades + a Class-K sign-branch (principal branchRe ≥ 0) + a Class-K pin-slot-at-zero floor on each radicand (the_norm2idiom; tiny<0round-off → 0). No libmcmath(the import is removed); matchescmath.sqrt~1e-13.CEIL_TRANSCENDENTAL1 → 0 — all three A-N categories now ZERO. The bare-Python tier joins the C tier (libm 23 → 0) at zero continuous-math residue: every continuous-math op across C, numpy-tier, and bare-Python is a cascade of the 14, floats only at the FPU lift. Test-only ceiling drop + one helper; ABI 3. - rc34 (done) — matmul decrement by ROUTING np.kron + np.einsum onto existing
cascades (no new public op — pure routing).
qm.so8'sad⊗I − I⊗adsu(3) superoperator (×2) +qm.bell's_kron(×1) →spectral_cascades.kron(np.asarraycarrier-only);qm.triality's octonion-couplenp.einsum(×1) →matrix_cascades.einsum. Value-faithful (so8/triality bit-exact 0.0; bell bit-exact on its Pauli operators).CEIL_MATMUL55 → 51. Deferred:ica_jade2 einsum (hot Jacobi loop), the np.outer family (needs adense_outercascade), the QR-internal vdot/outer/@ (shape-polymorphic pass). Watch the regex ratchet: annp.kron/np.einsumMENTION in a NEW code comment re-adds the token — write "the NumPy Kronecker product" / "the NumPy einsum" (nonp./numpy.prefix) in routing comments. - rc35 (done) — numpy-free native dispatch for
jacobi_eigvals(UPSTREAM §38 / F708; ~49×). The boundsrmech_jacobi_eigvalsC symbol is now reachable WITHOUT numpy:_jacobi_eigvals_native_listmarshalbuilds a flat(c_double * n·n)ctypes buffer from thelist[list[float]]and calls the C symbol; the numpy-absent branch dispatches to it whenHAS_NATIVE+n ≤ 256, else the pure-Python Jacobi cascade. Fixes the rc28 finding (numpy-free Class-L store ~68 s @ n=256 → ~1.4 s native). PHASE B of the numpy-carrier-removal north-star — the C foundation must be numpy-free-reachable before numpy can leave as a carrier. §38 "bind the symbols" was already done (HEAD binds jacobi/laplacian/hermitian/hdc/klein4); bytes-hdc already numpy-free; symmetric eigvec-decompose stays numpy.linalg.eigh (deliberate); klein4→C deferred behind W5. ABI 3; no public-surface change. - rc36 (done) — Laplacian tidy: numpy-free native dispatch for the Class-L
BUILD ops + header-comment accuracy (UPSTREAM §38, completing rc35).
dense_adjacency/dense_laplacian/normalized_laplacianreach the boundsrmech_graph_*C symbol on the numpy-absent install too, via_build_matrix_native_listmarshal(Python list → flat ctypes uint32/double buffers → reshape); fall back to the pure-Python builder only when no native lib / n>256 / non-OK. (O(edges)-cheap — consistency, not the perf eig.) Fixed the stale "wrappers fall back to numpy unconditionally" module-header note (eigvals dispatch numpy-free now; only the eigenVECTOR decompose stays the LAPACKeighpath, by design). PHASE B of the carrier-removal north-star. #962 reconciliation: the binding ask was already done at HEAD; klein4→C stays behind W5 (even-count now documented: strict majority, tie→0); the Klein-4 spectral quad-stream is sequenced into the Part-2 genome-storage surface (its quad-turn unit). Watch the regex ratchet: anumpy.linalg.eighMENTION in a new comment counts — write "the LAPACKeighpath" (nonumpy.linalg.). - rc37 (done) — genome-storage surface, brick 1 (#962 Part 2; F711–F715).
New module
srmech.amsc.genome(biological-structure names as cascade names):encode_shape(n)— the encode CRITERION (tome ≤256 / mobius ≤1024 / quad_strand1024;
depth = ceil(log4(ceil(n/256)))in pure integer arithmetic, no float log) → bucketnon_compute;quad_turn(turn, the_one)— the reversible Klein-4 the_one coupling (F713;quad_turn∘quad_turn == id), a pure delegation tohdc.klein4_bind→ bucketcomposition_of_c. NB the delegateklein4_bindis stillc_exists_unbound(W5-gated);quad_turnadds no new debt and inherits the eventual dispatch. 2 ToolEntries (categorygenome), tools.total 265→267,np.ndarrayparam-type (matches the Klein-4 family + has an MCP coercer —HVhas none). No C twin yet (the quad-turn is pure-Python reversible XOR); the chromosome (telomere-capped strand) + genome (multi-kernel) assemble next. - rc38 (done) — genome-storage surface, brick 2: the chromosome (#962 Part 2).
srmech.amsc.genomegains the LAYER-1 cascade primitives:telomere(label, dim)— the non-data content-address cap (sha256_bytes → seed → Klein-4 sentinel; Class A ∘ M) →composition_of_c;chromosome(leaves, the_one, label=)— pack a kernel into a telomere-capped strand of quad-turns →composition_of_c;recall(strand, the_one, telomere)— the exact inverse (cap matched by VALUE so it generalises to a multi-chromosome genome) →composition_of_c. 3 ToolEntries (categorygenome), tools.total 267→270. These are the ops the upcoming USER-AUTHORED class layer binds to (declarative[class]TOML → generic class-aware object via the config-driven loader; DSL / CLI / tool_schema lifted class-aware from theregister_catalog_dirop-pattern; genome = seed worked-instance). No C twin (pure-Python reversible XOR + content address). klein4_bind staysc_exists_unbound(W5). - rc39 (done) — user-declared classes from
[class]TOML (#962 Part 2). The cascade-catalog config-driven pattern lifted from ops to CLASSES:srmech.dsl.make_class(name)+ the genericCatalogClassconstruct a class-aware object from a[class]descriptor (fields + methods-as-cascade- op-refs; methods dispatch to a shipped op by dotted srmech path) — zero user Python.register_class_dir/SRMECH_CLASS_PATHis the bring-your-own surface (B-tier, no-shadow), mirroringregister_catalog_dir. genome ships as the built-in seed (_research/class_catalog/genome.toml):Genomewithshape/cap/add_chromosome/recallbinding the rc37/rc38 genome ops. The loader lives insrmech.dsl(NOTsrmech.amsc/qm) so it adds no tool-schema / rosetta entry — pure orchestration over already-classified ops. No C twin (config + dispatch). DSL stage / CLI / tool_schema class-awareness are rc40/rc41. - rc40 (done) — DSL class-awareness (#962 Part 2).
srmech.dslgains the one-shot surface over the rc39CatalogClass:describe_class(name)/list_class_surface()(JSON-able introspection — fields + methods + binds + provenance) andrun_class_method(class_name, method, fields=, args=)(the stateless construct-invoke-return:{class, method, result, fields}with the post-call state, so anappends/setsmutation is visible).fields/argsare plain dicts (MCP-grammar friendly). Lives insrmech.dsl(NOT amsc/qm) → no tool-schema / rosetta entry. CLI subcommands + tool_schema/MCP class registration are rc41. - rc41 (done) — CLI + tool_schema/introspect class-awareness; the genome
surface closes (#962 Part 2).
srmech class list/srmech class describe NAME(srmech.cli.klass; CLI tokenclass, moduleklasssinceclassis a keyword);introspect.describe()["classes"] = {total, names}(the package recognises its own user-class surface — sibling key,tools.totalunaffected); 2 ToolEntriessrmech.dsl.list_class_surface/describe_classso the LLM tool list includes class discovery (tools.total270→272; 5 count-tests bumped). dsl ToolEntries take no rosetta line (rosetta walks amsc/qm live ops). Completes rc37/rc38 (genome primitives) → rc39 ([class]loader) → rc40 (DSL surface) → rc41 (CLI + tool_schema): a researcher reaches a TOML-declared class from Python, the shell, and an LLM. NB the local-onlytest_introspection_version_native_matchesfalse-fail is the stale hand-built DLL (version string vs a fresh CI build), not a regression. - rc42 (done) — genome-storage brick 3: multi-kernel
genome+partition(#962 Part 2).srmech.amsc.genome.genome(kernels, the_one)packs many{label: leaves}kernels into ONE telomere-partitioned strand (each a telomere-cappedchromosomecoupled throughthe_one, concatenated); the inversepartition(strand, the_one, labels)knows ALL caps so it never mistakes one chromosome's cap for another's data — round-trips every kernel exactly. The seedGenome[class]gainsassemble/partitionmethods. Closes the F715 hierarchy GENOME→CHROMOSOMES→QUAD-TURNS→LEAF≤256. 2 ToolEntries (genome/partition;tools.total272→274; 5 count-tests bumped); bothcomposition_of_c(compose ofchromosome/recallover the c_dispatchedklein4_bind, with a content-address cap fromsha256_bytes) — no debt-bucket growth. ABI 3; numpy not required. - rc43 (done) — text→graph stage primitives (RBS-LM UPSTREAM §17 U1; #855
R3 U1).
srmech.amsc.laplacian.tokenize(text, *, stopwords=, min_len=, pattern=)(Class B/G text-segmentation) +cooccurrence_edges(tokens, *, window=, vocab_size=) -> (n, edges, weights)(Class-L precursor) — the only links between raw text and the already-shippeddense_laplacian. The K1 presence-kernel is now an authorable composite end-to-end (tokenize → cooccurrence_edges → dense_laplacian → eigendecompose → …), retiring the hand-rolledre.findall+Counter()idiom. Both pure-Python / numpy-free; 2 ToolEntries (tools.total274→276; 5 count-tests bumped); bothnon_computein the ledger (string segmentation + integer graph construction — no numeric/matrix compute, no c-dispatch → no debt-bucket growth). The directed sibling (i(A−Aᵀ)Hermitian-Laplacian builder, reusing the shippedhermitian_eigendecompose) is a separate queued Class-L precursor (UPSTREAM §18.1 op(b) reference F357). ABI 3; numpy not required. - rc44 (done) — DSL dotted-
op=resolver + §17 U2 (encode_loe_contentregistered as a cascade-op).lookup_cascade_ophonours a dotted[cascade].opentry point (mirrors the rc39 class-catalog), so the verifiedsrmech.signal_processing.encode_loe_contenttext→instrument encoder is a one-line DSL stage — any catalog's text rows get a one-line kernel chain.list_cascade_ops14→15; no newsrmech.amsc.*callable (the op already ships + keeps itscomposition_of_crosetta line), so the rosetta ledger +tools.totalare unchanged. ABI 3. - rc45 (done) —
srmech.dsl.list_opsunified op-discovery (RBS-LM §17 U3). Merges the two previously-disjoint discovery registries —list_catalog_ops(value-transform cascade ops) +catalog.list_catalog_chains(AMSC catalog-declared chains) — into ONE call with a uniform{name,class,purpose,kind,provenance}record (kindaddscatalog-chain;provenanceaddscatalog:<source_key>). Newsrmech.dsl.list_opsToolEntry (a DSL discovery callable — no rosetta line, per the dsl-ToolEntry convention), sotools.total276→277 but the Rosetta op ledger is unchanged. ABI 3; numpy-free. - rc46 (done) — catalog→DSL auto-registration bridge actually routes (RBS-LM §17 U4).
rc45's
list_ops()auto-discovery read the wrong source-key field (source_key/name) wherelist_attested_sources()returns each source underkey, so the catalog-chain half was silently empty. rc46 readskey(with the alternates as fallbacks) → the 7 packaged catalog-chains (asymptotic_calculus ×5 - cosmos_validation + pi_digits) now surface tagged
catalog:<source_key>, and a freshlyregister_attested_root-ed catalog's declared chains too. Pure registry-read fix; no new callable,tools.totalstays 277, Rosetta op ledger unchanged. ABI 3; numpy-free. - rc47 (done) —
hdc.klein4_project_axis: iω₇-collapse / bipolar projection (RBS-LM §18 Tier-2 leaf; F350/F354). Projects a 2-DoF Klein-4 store onto one chirality axis → bipolar{-1,+1}(the asymptotic-DoF render; drops the other axis + its self-EC per F354 axis-split).axisco-equal (gamma5bit 1 /iomega7bit 0; settable-chirality discipline). Class K (sign render) ∘ Class C (axis select); numpy-free; noabs(). New ToolEntry →tools.total277→278. Rosetta bucketnon_compute(a one-way bipolar render/readout OUT of the store — peer to the rc43 tokenize/cooccurrence_edges projections, NOT a reversible store-transform like the klein4 flips, whose standalone-C twin stays W5-gated per the do-not-mirror rule). ABI 3. - rc48 (done) —
laplacian.spectral_block_dispatch: the 1024-node 4-sector spectral one-call (RBS-LM Ask-3; F233 4-rung). Eigendecomposes ≤4 dense symmetric blocks (each n ≤ 256) on a 4-worker thread pool — the threaded- Klein-4-streams pattern (the same 4-way fan-out ascascade.parallel_sector_dispatch, but over DISTINCT blocks). 4 × 256 = 1024 nodes within the native dense-eig bound; 0 cross-thread reads → parallel == serial bit-for-bit. New ToolEntry →tools.total278→279. Rosetta bucketcomposition_of_c(composes thec_dispatchedjacobi_eigvals+ a merge-sort reduction). Class L over the 4-rung; numpy-free. ABI 3. - rc49 (done) —
dsl.generate_class_descriptor: the make_class inverse (RBS-LM §39). Renders a[class]TOML descriptor from components (or by introspecting a registered class viadescribe_class) round-trippable straight back throughmake_class— closing the class-from-TOML loop the other direction. New ToolEntry →tools.total279→280. Asrmech.dsl.*discovery/render callable: it bumps the tool count but is NOT inrosetta_classification.ndjson(the rosetta surface is thesrmech.amsc.*compute ops; the dsl render/introspect callables —list_ops,describe_class, nowgenerate_class_descriptor— are out of that scope, same as their peers). Class E ∘ F ∘ H; numpy-free; no C twin (pure descriptor render). ABI 3. - rc50 (done) —
amsc.text.{tokenize, cooccurrence_edges}: the §40 R3-U1 acceptance fix (was SHIPPED-but-FAILING 3/3; F722). The rc43 text→graph leaves shipped inlaplacianbut failed §40 on Unicode (ASCII\w+truncated café→caf, dropped Cyrillic/CJK) / silentvocab_size=1000cap (the F708 pre-encode quantization bug as a default) / no document-boundary window reset. rc50 relocates them to a dedicated ingestion modulesrmech.amsc.text(laplacian stays purely spectral) and fixes all three: Unicode-aware (unicodedataL/M + casefold), full vocab by default (cap = explicit logged opt-in),docs: Sequence[Sequence[str]]so the window resets per document. The twoToolEntrys movelaplacian.* → text.*and the two rosetta lines re-point (non_computebucket unchanged);tools.totalstays 280 (relocation, not a new op). Class B/G ∘ Class-L precursor; numpy-free; no C twin. ABI 3. - rc51 (done) —
laplacian.dense_outer_{complex,real}: the np.outer → cascade decrement (numpy-removal PHASE A). An outer product is the k=1 case of a matrix product, sodense_outer_complex=dense_matmul_complexon the reshaped (column, row) pair — rides the native kernel, single-multiply per entry, bit-identical to numpy. Routes the 3 genuine np.outer sites (qm.propagators' two real kᵘkᵛ momentum tensors → dense_outer_real, qm.single_particle's complex |ψ⟩⟨ψ| density matrix → dense_outer_complex) off numpy's contraction engine — the numpy-math ratchet'smatmulceiling 51→48. 2 new ToolEntries →tools.total280→282; bothcomposition_of_c(no own C symbol; compose the c_dispatched dense-matmul kernel). The 2 remaining np.outer are the matrix_cascades QR-internal Householder updates (shape- polymorphic pass). Class L rank-1 contraction; numpy carriers-only. ABI 3. - rc52 (done) —
laplacian.elementwise_hypot: the np.hypot |z| magnitude cascade OPENS the ufunc bucket (numpy-removal). With the np.outer contraction surface handed off (rc51), the sweep turns to the ufunc ceiling (pinned at 48 since rc13) — numpy's transcendental / magnitude engine.np.hypot(z.real, z.imag)=|z| = √(re²+im²), a per-element libmhypot;elementwise_hypot(a, b)loops the Class-Nrational.hypotcascade (isqrt-based, nativesrmech_rational_sqrt-dispatched, no libm) over the flattened pair, numpy carrying the array only. Routes all 5 DSP magnitude sites (fsk / mlse / psk_qam nearest-symbol distances, ofdm channel-mag, spectral coeff-mag) offnp.hypot— the numpy-math ratchet'sufuncceiling 48 → 43. Round-off- faithful (rational sqrt floor-projected vs IEEE round-to-nearest, ≤1-ULP; never flips a nearest-symbol / argmax decision) + bit-exact on perfect squares (Pythagorean-triple constellation points decode exactly). 1 new ToolEntry →tools.total282→283;composition_of_c(no own C symbol; composes the c_dispatchedrational.hypot/srmech_rational_sqrt). Class N magnitude over a Class-L array surface; numpy carriers-only. ABI 3. Next ufunc batches: the np.exp complex-phase sites ontoelementwise_transcendental, then np.sqrt / np.sin / np.cos / np.log / np.sign. - rc53 (done) — the
np.expbatch: 14 callsites routed off numpy's exponential ufunc (the rc52 ufunc decrement continues).np.exp(1j·x)= the phasee^{iθ}; routing ontoelementwise_transcendental(·, "exp_i")runs the real cos/sin through the native libm-free C cascade and assembles the complex result (numpy carries the array only). Array phases (9): qm.gaugediag(e^{iλ}), qm.single_particle TDSE/Heisenberg/Liouvillee^{−iλt}(×3), fsk tone bank (×2), psk_qam constellation, spectral_subtraction phase re-attach, spectral phase-extrapolate, laplacian magnetic directed-phase. Real exp (2): heat_kernel decay, rbs_lm softmax →(·, "exp"). Scalare^{iδ}(2): qm.sm CKM CP phase →rational.cexp(Euler). numpy-math ratchetufunc43 → 27 + 2 doc/summarynp.exp(mentions de-parened. Pure routing — no new op,tools.totalstays 283, no rosetta/ToolEntry change. The 2 remaining np.exp areelementwise_transcendental's OWN complex-input + real no-native fallbacks (documented internal kernel; deferred). Round-off-faithful (~1 ULP; QM + DSP suites pass unchanged). ABI 3. Next: np.sqrt (12), then sin/cos/log/sign. - rc54 (done) —
laplacian.elementwise_sqrt: thenp.sqrtbatch (ufunc decrement continues rc52 hypot → rc53 exp). New publicelementwise_sqrt(arr)computes√arrᵢper element via the Class-Nrational.sqrtcascade (isqrt- based, nativesrmech_rational_sqrt-dispatched, no libm) — companion toelementwise_hypot, numpy carries the array only. Routes the 2 array np.sqrt (spectral_subtraction PSD√(new_psd), ica_jade whitening1/√(eigvals)) onto it, and the 10 SCALAR np.sqrt (qm.gauge Gell-Mann1/√3×3, qm.potentials√nladder, qm.relativistic Klein-Gordon on-shell energy, qm.so8 ×2, qm.triality, psk_qam√M, wavelet1/√2) ontorational.sqrt(7 modules gain arational as _srnimport). numpy-math ratchetufunc27 → 15. Round-off- faithful (≤1-ULP; bit-exact on perfect squares; QM ladder/dispersion + DSP whitening/decode pass unchanged). 1 new ToolEntry →tools.total283 → 284;composition_of_crosetta (composes the c_dispatchedrational.sqrt; no own C symbol). Class N √ over a Class-L array surface; numpy carriers-only. ABI 3. Next ufunc: np.sin (3) + np.cos (3) →elementwise_transcendental, np.log (5), np.sign (2); then the 2 internal exp fallbacks (deferred cascade kernel). - rc55 (done) — the
np.log+np.signexternal residue (ufunc decrement closeout; rc52 hypot → rc53 exp → rc54 sqrt → rc55 log/sign). Pure routing, no new op. 2 scalarnp.log(mlse uniform-prior A_log−log(A)/ pi_log−log(n_states)) →rational.log(Class-N, no libm). 1 arraynp.sign(hdc.polar_bundle's Pyodide fallback — the C peersrmech_polar_bundleowns the native path) → Class-K comparison sign(total>0)−(total<0)(carrier comparisons, the pin-slot at zero: + / 0 / − sector; nonp.signufunc, no abs(), bit-identical to np.sign for the real int sum). Plus 2 comment mentions de-parened. numpy-math ratchetufunc15 → 10. The remaining 10 are ALLelementwise_transcendental's OWN internal numpy fallback (2 exp + 3 sin + 3 cos + 2 log — the complex-input path + the no-native real path); zeroing those is the deferred cascade-kernel work (complex-input trig/exp/log cascades), tracked separately.tools.totalstays 284; no rosetta/ToolEntry change. ABI 3. The external ufunc surface (hypot/exp/sqrt/log/sign) is now CLEAR — only the cascade's own fallback kernel + thelinalg_fft(122) ledger remain before the carrier-removal North Star. - rc56 (done) —
elementwise_transcendentalnumpy-free: the ufunc bucket CLOSES to ZERO. The last 10 ufunc sites wereelementwise_transcendental's OWN internal numpy fallback (complex-input path + no-native real path). rc56 makes both numpy-free:_real_transcendental_looploopsrational.{exp,cos, sin,log}(Class-N scalar cascades, bit-exact vs numpy; log domain guard kept);_complex_transcendental_looprunsrational.complex_exp(exp) / cosh-sinh- from-rational.exp(cos/sin) /rational.log(hypot)+i·atan2(log) per element (principal branch, rejects z=0). numpy-math ratchetufunc10 → 0. Across rc52–rc56 the ufunc bucket went 48 → 0 — every transcendental / magnitude / sign op (hypot/exp/sqrt/log/sin/cos/sign) now runs a libm-free srmech cascade, numpy only ever packing the array.tools.totalstays 284 (numpy-free kernels, no new public op). ABI 3. The two remaining numpy-math ledgers arematmul(48) andlinalg_fft(122 — np.linalg.* / np.fft.*); after those, the carrier- removal North Star (#564). - rc57 (done) — the matmul-ledger REWORD SWEEP (docs only, zero behaviour
change). ufunc bucket closed (rc52–rc56); turning to
matmul, ~30 of its 48 matches were TEXTUAL@/ np.{vdot,einsum,convolve,correlate,kron} mentions in docstrings / comments / ToolEntry summaries (+ aprofile_loaderentry-point[…] @ …f-string false positive) — not compute. rc57 strips them all (NumPy op name /·without the dotted-paren form, per the rc34 convention). numpy-math ratchetmatmul48 → 18. The remaining 18 are genuine deferred compute: matrix_cascades QR-internals (vdot/outer/@/back-solve — shape-polymorphic pass), ica_jade np.einsum (hot JADE loop), fir/multirate/ polyphase np.convolve + matched_filter np.correlate (distinct-op convolution/ correlation cascades), laplacian Schur L_pi·X + the dense_matvec kernel-internal @. Pure docs —tools.totalstays 284; no rosetta/ToolEntry change. ABI 3. Next matmul: the convolve/correlate cascades, then the QR shape-polymorphic pass; then thelinalg_fftledger (122 — np.linalg.* / np.fft.*). - rc58 (done) — the convolve/correlate cascade (matmul decrement). A length-N
convolution is a rank-1 accumulate (a small matrix product), so it sits on the
matmul ledger. rc58 adds a numpy-free helper
signal_processing._dsp_cascades.{convolve,correlate}— Class I shift ∘ Class M scaled-accumulate (full[i:i+nb] = full[i:i+nb] + a[i]*b; numpy a carrier only), full/same/valid modes;correlate = convolve(a, conj(v)[::-1])with NumPy's exact same-mode crop (floor for na>=nv, ceil for na<nv). Value-faithful across 15 length-combos × 3 dtypes × 3 modes. Routes the 6 DSP sites (fir / multirate / polyphase ×2 / closed_form + path_b matched_filter). numpy-math ratchetmatmul18 → 12. NOT a publicsrmech.amscop — it composes carrier arithmetic with no own C symbol, so a public peer would only addpython_only_irreducibledebt (down-only ratchet forbids it without a C twin); a future rc can promote it WITH a native C twin.tools.totalstays 284; no rosetta/ToolEntry change. ABI 3. The remaining 12 matmul: matrix_cascades QR-internals (8) + ica_jade einsum (2) + laplacian Schur/dense_matvec (2). Next: the QR shape-polymorphic pass; then thelinalg_fftledger (122). - rc59 (done) — the QR shape-polymorphic pass (matmul decrement). The 8
matrix_cascades QR-internal sites route onto the EXISTING value-faithful
dense_* kernels (no new kernel):
np.vdot(v,v)×2 (the _norm2/vhv Hermitian self-bind) →dense_dot_complex(conj(v), v);np.outer×2 (the Householder reflector v vᴴ) →dense_outer_complex; theconj(v)·R/Q·vreflector matvecs →dense_matvec_complex; the lstsq back-solveQᴴ·b→dense_matvec/matmul_complex(branch rhs ndim) andR[i,i+1:]·x[i+1:]→dense_dot_complex(1-D x) /dense_matvec_complex(k-col x — the shape-polymorphic case). Kernels are value-faithful → QR/SVD/lstsq/eig INVARIANTS preserved (verified vs numpy, real+complex, 1-D + multi-col rhs). numpy-math ratchetmatmul12 → 4.tools.totalstays 284; no public-op / rosetta / ToolEntry change. ABI 3. The final 4 are genuinely deferred: ica_jade np.einsum (hot JADE cumulant loop) + laplacian SchurL_pi·X+ thedense_matvec_complexkernel-internal@fallback (a kernel can't route onto itself). Next: thelinalg_fftledger (122 — np.linalg.* / np.fft.*). - rc60 (done) — the dense_norm cascade (linalg_fft decrement). Opens the
linalg_fftledger (122) at its biggest cluster: the 20 defaultnp.linalg.normcallsites (QM self-consistency residuals + signal-processing taper normalisations) — the Euclidean 2-norm / Frobenius norm √(Σ|x|²). New publiclaplacian.dense_norm(x)= Class N (rational.sqrt) ∘ Class M (dense_dot_complex self-bind Σ|x|²); numpy a carrier only. Value-faithful to the NumPy norm (real+complex, 1-D + n-D). Routes 20 sites across qm/{so8 ×7, relativistic ×3, spin ×3, triality ×3, gauge, sm, pseudo_hermitian} + signal_processing multitaper. numpy-math ratchetlinalg_fft122 → 102. New public op ⟹ describe tools.total 284 → 285 (+1 ToolEntry, +1 rosetta composition_of_c, all + LAPLACIAN_OPS). ABI 3. Next linalg_fft batches: the np.fft.* family (→ spectral_cascades) and np.linalg.{svd,qr,eig,solve,inv} (→ matrix_cascades / laplacian decompositions). - rc96 (done, v0.7.5rc96) — Mat-return FOUNDATION #2:
mat_lstsqnumpy-free least-squares over the Mat carrier. Carrier-removal #564. New PUBLIC opamsc.laplacian.mat_lstsq(a: Mat, b: Mat) -> Mat: the overdetermined/square (m>=n) normal equationsX = (Aᴴ·A)⁻¹·Aᴴ·B=mat_solve(mat_matmul(Aᴴ,A), mat_matmul(Aᴴ,B))withAᴴ = a.conj().T— fully numpy-free for real AND complex (rc95 made mat_solve complex-capable). Value-faithful to numpy.linalg.lstsq to ~1e-13; m<n raises ValueError. Also fixes the mat_solve ToolEntry summary (still said "Real-f64 only" post-rc95). NEW public op → describe tools.total 287 → 288 (classes stays 2); ToolEntry + rosettacomposition_of_cbucket +__all__/LAPLACIAN_OPS+ the 7 count-tests bumped 287→288; CEIL stays 32; ABI 3; no C change. Next:mat_eigvals(Mat-carrier shifted-QR), then route esprit. - rc97 (done, v0.7.5rc97) — Mat-return FOUNDATION #3:
mat_eigvalsnumpy-free general (non-Hermitian) eigenvalues over the Mat carrier. Carrier-removal #564. New PUBLIC opamsc.laplacian.mat_eigvals(a: Mat) -> list[complex]: the eigenvalue MULTISET of a general squareMatvia a Wilkinson-shifted QR iteration — Class K (iterate) ∘ Class L (spectrum) ∘ Householder QR ∘ Class C (complex Wilkinson shift) — in plaincomplexlists with the RQ recombine routed through the nativemat_matmul, so unconditionally numpy-free. Closed form for n=1 (scalar) and the trailing 2×2 (the quadratic over the Class-N_complex_sqrt). Value-faithful to numpyeigvalsto ~5e-14 (MULTISET / nearest-neighbour distance — eigenvalues unique only as a set) over real-sym / real-nonsym-with-complex-pairs / complex / upper-tri / defective Jordan inputs; matches the shipped numpy-carriermatrix_cascades.eigvalsbit-for-bit. The_complex_sqrt/QR helpers are laplacian-local to avoid the circular import withmatrix_cascades(which imports laplacian). NEW public op → describe tools.total 288 → 289 (classes stays 2); ToolEntry + rosettacomposition_of_cbucket +__all__/LAPLACIAN_OPS+ the 7 count-tests bumped 288→289; CEIL stays 32; ABI 3; no C change. Closes the Hermitian (rc74) + linear-system (rc95/96) + general-eig (rc97) Mat foundation — next: ROUTE esprit through the threemat_*ops. - rc98 (done, v0.7.5rc98) — FIRST CONSUMER flip on the completed Mat
foundation:
espritnumpy-FREE (CEIL_NUMPY_CARRIER32 → 31). Carrier-removal #564. With the Mat trio complete (rc74/95/96/97), the matrix-heavysignal_processing/closed_form_ops/espritflips: its three matrix steps route off the numpy-carriermatrix_cascadesstack onto the nativemat_hermitian_eigendecompose(Class L eig) +mat_lstsq(signal-subspace LS Phi) +mat_eigvals(Class K rotation eigenvalues); the column-select / row-slice are plainMat.from_rowsovermat[i,j],np.argsort→ pure-Pythonsorted(...,reverse=True). Top-levelimport numpy as npGONE → runs numpy genuinely absent (a runnable flip on the native foundation, NOT the load-only rc70 trap).opreturnslist[complex](was ndarray). Differential-verified: single-tone DOA within 1e-3 + trace/det invariants match. The twoeigs.shape == (1,)smoke tests →len(eigs) == 1; esprit graduates out of the rc63 cascade-routed_ROUTEDcheck (now numpy-free, strictly stronger). No new public op (tools.total stays 289); ABI 3; no C change. Next: the remaining matrix-heavy consumers (dct/fsk/music/mlse/psk_qam/heat_kernel/ica_jade/lmmse/ map_ml/vector_quantisation; mimo_svd needs amat_svdfoundation op first) reuse the same mat_* trio, each decrementing CEIL. - rc99 (done, v0.7.5rc99) — SECOND CONSUMER flip:
musicnumpy-FREE (CEIL_NUMPY_CARRIER31 → 30). Carrier-removal #564. The ESPRIT sibling.signal_processing/closed_form_ops/musicroutes its covariance eigendecomp offhermitian_eigendecomposeonto nativemat_hermitian_eigendecompose(noise subspace = smallest M−n_sources eigvecs via pure-Pythonsorted, nonp.argsort), the noise-subspace projectionEnᴴ·Aoffdense_matmul_complexonto nativemat_matmul(Enᴴ=Mat.from_rowsofconj(eigvecs[i,col])), and the pseudospectrum1/Σ|proj|²as a pure-Python column loop. Droppedimport numpy as np;opreturnslist[float]. RIPPLES:psd.shape→len,np.all(psd>0)→all(v>0 ...),np.argmax→pure-Python argmax (in test_signal_processing_path_a_baseline + test_sp_eigh_cascade_routing_rc33); the rc71 clean-[scientific]-hint exemplar movedmusic→mimo_svd(the long-lived numpy-requiring op — needs amat_svdfoundation first). Math-ratchet ledger UNTOUCHED (music's numpy matched none of the linalg/fft/matmul/ufunc patterns; the nameddense_matmul_complexis not a counted matmul pattern). No new public op (tools.total 289); ABI 3; no C change. Next: heat_kernel (graph heat-kernel exp(−tL) via the hermitian eig), then lmmse/map_ml, dct/fsk, then the mat_svd foundation for mimo_svd. - rc100 (done, v0.7.5rc100) — THIRD CONSUMER flip:
heat_kernelnumpy-FREE (CEIL_NUMPY_CARRIER30 → 29). Carrier-removal #564. The graph heat-kernel denoiserexp(−tL)·signal = V·diag(exp(−tλ))·Vᴴ·signal: eigendecomp offhermitian_eigendecomposeonto nativemat_hermitian_eigendecompose; theexp(−tλ)spectral filter off the numpy-carrierelementwise_transcendentalonto a per-bin Class-Nrational.exp(−t·λ_k)cascade (real eigvals; scalar floatexp, dispatches to nativesrmech_exp— the rc90 INLINE-don't-call- elementwise_* lesson); the twodense_matvec_complex(Vᴴ·signal, V·(g⊙coeffs)) → pure-Python nested sums overeigvecs_mat[i,k]. Droppedimport numpy as np;opreturnslist[complex]. RIPPLES:out.shape→len,out.real.sum()→sum(v.real for v in out),np.max(out.imag**2)→max(v.imag**2 ...),out.real[1]→out[1].real(baseline smoke + rc33 diffuse-toward-mean). Math ledger UNTOUCHED. No new public op (tools.total 289); ABI 3; no C change. Next: lmmse/map_ml, dct/fsk, then the mat_svd foundation for mimo_svd. - rc114 (done, v0.7.5rc114) — FOUNDATION: numpy-free
mat_norm/mat_dot_real/mat_dot_complex(CEIL_NUMPY_CARRIER stays 15). Carrier-removal #564, first of 3 spine foundations gating the qm/so8/spectral consumer-flips (rc115+). A workflow ground-truthed the remaining 15 carriers + caught the trap:dense_norm/dense_dot_*are numpy CARRIERS (np.ascontiguousarray/iscomplexobj) that RAISE numpy-absent (rc70 runnable≠loadable). Add toamsc/laplacian.py(+__all__×2):mat_norm= √(Σ|xᵢ|²) pure-Python (complex |z|²=re²+im², no abs/hypot) ∘ Class-N rational.sqrt;mat_dot_real/mat_dot_complex= plain bilinear Σaᵢbᵢ (matches numpy a·b, NOT vdot) over a shared_iter_mat_scalars(Mat interleaved-complex / HV / sequence). Value-faithful to dense_/ numpy ~1 ULP; verified numpy-present AND in a numpy-blocked meta-path subprocess. No consumer flip → CEIL stays 15; math ledger untouched. Registered public laplacian callables like their dense_ peers → 3 ToolEntries + 3composition_of_crosetta lines; tools.total 290→293 (7 count-tests bumped); ABI 3; no C change. - rc113 (done, v0.7.5rc113) — RBS-LM subpackage numpy-FREE; incidental-source RNG
re-based onto our own (
CEIL_NUMPY_CARRIER17 → 15). Carrier-removal #564.srmech.rbs_lm(F166 inference substrate) carried numpy only as an INCIDENTAL deterministic source (not an oracle) — per user direction ("use our own rng … rbs_lm is ours") the 3 numpy-RNG sites swap to the framework-native stdlib stream - the values re-base ONCE.
substrate.py: per-token seednp.random.default_rng( token_seed)→hdc.klein4_random(D, seed=…),np.full(D, sector)→bytes([sector]) *D, encode helpers +ContextSubstratereturn the HV carrier (no.to_numpy),sim_k4_batch→hdc.klein4_similarityper HV.inference.py:_softmaxper-element exp via Class-Nrational.exp(NOT numpy-carrierelementwise_transcendental),vocab_vecs→list[HV], learn subsamplerng.choice(...,replace=False)→random.Random(seed).sample, infer samplergr.choice(...,p=p)→.choices(weights=p).__init__.py: eager_require_numpygate REMOVED (whole subpackage numpy-free → import succeeds numpy-absent). Authorized one-time RNG re-base (structural tests RNG-independent;test_rbs_lm.pymigrates ndarray→HV assertions). No C twin (Python-only). Math ledger UNTOUCHED; tools.total 290; ABI 3; no C change. The 15 remaining: qm matrix layer (TOML[class]reframe) + mcp/coercion + spectral/ __init_ + ica_jade (numpy-as-accuracyeigh) + so8/triality (numpy-as-accuracymatrix_rank). - rc112 (done, v0.7.5rc112) — carrier-ratchet ACCURACY fix (
CEIL_NUMPY_CARRIER18 → 17); opens the qm-layer phase. Carrier-removal #564. NOT a removal — a measurement correction:amsc/cascade/one.py(the_one) is numpy-FREE at import (its only numpy is a LAZYrequire_numpyinsideto_numpy/to_matrix), but the scanner's naivestartswith("import numpy")over every line tripped on a docstring that wrapped to "import numpy lazily (the…" at column 0. The scanner is hardened to a real-import regex (_is_numpy_import:import numpy[.sub][ as x]/from numpy[.sub] import …, NOTimport numpy <word>…prose); the_one drops out. The 17 genuine carriers (all realimport numpy as np) are unchanged: the qm matrix layer + rbs_lm{inference,substrate} + mcp/coercion + spectral/__init_ + ica_jade. Honest down-only (the ratchet now reflects reality). No source op changed; tools.total 290; ABI 3; no C change. The qm layer is next via the TOML[class]reframe (rc75 Hurwitz precedent), not per-module flips. - rc111 (done, v0.7.5rc111) — FOURTEENTH CONSUMER flip:
jpegnumpy-FREE, the LAST clean DSP carrier flip (CEIL_NUMPY_CARRIER19 → 18). Carrier-removal #564. The block-DCT image compressor (Class L∘K∘B): the block transform already ran numpy-free via the rc104dct.op(list-of-lists), so jpeg now carries the 2-D image as nested Python lists end-to-end — list-of-lists quant table, per-element Wallace quality scaling (int(...)= exact floor for the non- negative(luma·scale+50)/100), Class-K quantise viaround(round-half-to- even == np.round), nested-list encode/decode block loops (drops the_dctnp.asarray wrapper). Dropsimport numpy as np; encode returns list-of-list-of- lists, decode returns a 2-D list (were ndarrays). VALUE-FAITHFUL (DCT basis is the Class-N rational.cos cascade, ~1e-9): verified by an encode→decode round- trip (RMS≈0.58 @ q=75) + numpy-absent subprocess run. CARRIER-ONLY → math ledger untouched. No new public op (tools.total 290); ABI 3; no C change. The clean-DSP carrier-flip phase of #564 is now COMPLETE. Remaining: the qm matrix layer (TOML[class]reframe / lossy-peer delete, rc75 Hurwitz precedent — NOT per-module flips) + a linalg-consolidation pass to retire the duplicatemat_*/matrix_cascadesSVD paths. - rc110 (done, v0.7.5rc110) — TWELFTH + THIRTEENTH CONSUMER flips: the two
Path B DSP duals numpy-FREE (
CEIL_NUMPY_CARRIER21 → 19). Carrier-removal #564. Both are clean leaf flips of ops whose Path A twins already went numpy- free:path_b_ops/matched_filter(A∘C∘M cross-correlation) dropsimport numpy as npand delegates to the rc79 numpy-free_dsp.correlate(drops thenp.asarraycoerce + return-wrap, returns a list);path_b_ops/wiener(L∘N cyclic-graph-Laplacian eigenbasis + rational MMSE gain) takes the rc87 closed- form list-comprehension form (_sc.fftList[complex] → per-bin |X|²=real²+ imag² no abs() → Class-N gain S/(S+N) with builtinmaxeps-floor →_sc.ifft→ real part). CARRIER-ONLY (the math was already cascade-routed; only the np.asarray/np.maximum/np.real carriers go) → math ledger UNTOUCHED. Path A↔B D1-equivalence smoke keeps passing (list vs list). No new public op (tools.total 290); ABI 3; no C change. - rc109 (done, v0.7.5rc109) — ELEVENTH CONSUMER flip:
mimo_svdnumpy-FREE (CEIL_NUMPY_CARRIER22 → 21; mathCEIL_LINALG_FFT23 → 22). Carrier-removal #564. The MIMO channel-matrix SVD (H = U·S·Vᴴ, Class L) is the FIRST consumer to route onto the rc108mat_svdMat-carrier foundation: dropsimport numpy as np+np.linalg.svd, coerces numpy-free (tolist()covers ndarray ANDMat),Mat.from_rows(..., is_complex=True)→mat_svd, returns plain Pythonlists (U(n_rx,n_rx)rows /Sdesc /Vh(n_tx,n_tx)rows; were ndarrays). VALUE-FAITHFUL not bit-identical (inherits the mat_svd null/degenerate-basis freedom): differential-verified over 100 cases (real/complex × square/tall/wide × full/rank-deficient) — 0 reconstruction + 0 singular-value failures. Removing thenp.linalg.svddecrements BOTH ledgers (CEIL_NUMPY_CARRIER 22→21, CEIL_LINALG_FFT 23→22). The rc71 numpy-free-reachable exemplar (the still-numpy op that must raise the clean[scientific]hint) movesmimo_svd → ica_jade—ica_jade'snp.linalg.eighis numpy-as-ACCURACY (not just a carrier), so it stays gated.test_mimo_svd_smoke.shape→len. No new public op (tools.total 290); ABI 3; no C change. - rc108 (done, v0.7.5rc108) —
mat_svdFOUNDATION: numpy-free full SVD over the Mat carrier (Mat-bridge foundation #5; does NOT decrement CEIL — still 22). Carrier-removal #564.mat_svd(A) -> (U (m,m), S desc len min(m,n), Vh (n,n))matchingfull_matrices=True: right vectors = eigenvectors of the Hermitian PSD GramAᴴAviamat_hermitian_eigendecompose;S = √λ(Class-Nrational.sqrt); left vectorsuⱼ = A·vⱼ/σⱼ+ orthonormal Gram-Schmidt completion of the null block. Unconditionally numpy-free (composes the nativemat_matmul+mat_hermitian_eigendecompose). VALUE-FAITHFUL not bit-identical (SVD null/degenerate basis is free; per [[feedback_cascade_svd_nullspace_accuracy_not_route_matrix_rank]]): pinned by reconstruction + unitarity + S-match across real/complex × square/tall/wide × ranks. Rank tolσ_max·max(m,n)·1e-6(cascade small-σ floor ~1e-7·σ_max, NOT eps) so a sub-floor σ routes its U column through completion. REGISTERED ToolEntry (like the mat_* siblings) → tools.total 289 → 290 (five count-tests bump); all + rosettacomposition_of_cadded. ABI 3; no C change. Next: rc109 routesmimo_svdonto mat_svd (CEIL 22→21) + moves the rc71 reachable-test exemplar to ica_jade. - rc107 (done, v0.7.5rc107) — TENTH CONSUMER flip:
mlsenumpy-FREE (CEIL_NUMPY_CARRIER23 → 22). Carrier-removal #564. The Viterbi-trellis channel equaliser (Class L∘K, no np.linalg). The trellis tables (transition / emission / initial log-prob) become pure-Python list-of-lists built with the Class-Nrational.logcascade; the per-branch metric is an explicit−|obs − expected|²multiply-add (squared distance, monotone in|·|, so no sqrt/abs). The no-ISI path is a per-sample squared-distance argmin (replacingelementwise_hypot). The trellis search delegates to the already-numpy-freeviterbi.op(rc83), which returns a list. Inputs coerce viatolist(); op returnslist[int](was ndarray). Droppedimport numpy as np+elementwise_hypot. Differential-verified BIT-IDENTICAL to numpy over 180 cases (BPSK+QPSK × memory{0,1,2} × random; 0 mismatches). RIPPLE:syms.shape==(4,)→isinstance list/len. Math ledger UNTOUCHED; rosetta bucket unchanged. No new public op (tools.total 289); ABI 3; no C change. Remaining matrix-heavy: mimo_svd (needs themat_svdfoundation), ica_jade (np.linalg.eigh = numpy-as-accuracy, likely stays gated). - rc106 (done, v0.7.5rc106) — NINTH CONSUMER flip:
psk_qamnumpy-FREE (CEIL_NUMPY_CARRIER24 → 23). Carrier-removal #564. The fsk-cousin constellation mapper (Class I∘K, no np.linalg). PSK pointse^{i·2π·k/M}route through_exp_i(Class-Nrational.cos/sinover substrate-native_PI, native libm-free) — bit-faithful to the priorelementwise_transcendental(·, 'exp_i'). QAM grid built from pure-Python Gray levels (√Mviarational.sqrt), replacingnp.meshgrid(...).flatten()with a row-majorcomplex(levels[col],levels[row])comprehension (byte-faithful order). Demod decision is argmin over the squared Euclidean distance|received − const|²(monotone in|·|, so identical to the priorargmin(hypot)— now no sqrt/hypot/abs; strict<keeps first-min, matching np.argmin). Droppedimport numpy as np; inputs coerce viatolist(); modulate →list[complex], demod →list[int]. RIPPLE:points.shape==(4,)/np.iscomplexobj→isinstance/len; rc52 demod round-trip unchanged (decision identical). Math ledger UNTOUCHED (op now uses no numpy;psk_qamonly in math-ratchet comments, none enforced); rosetta bucket unchanged. No new public op (tools.total 289); ABI 3; no C change. Remaining matrix-heavy: mlse, mimo_svd (needs themat_svdfoundation), ica_jade (np.linalg.eigh = numpy-as-accuracy, likely stays gated). - rc105 (done, v0.7.5rc105) — EIGHTH CONSUMER flip:
vector_quantisationnumpy-FREE (CEIL_NUMPY_CARRIER25 → 24). Carrier-removal #564. The cleanest remaining consumer (nearest-codebook lookup, Class E∘M∘B, no np.linalg). The nearest-neighbour query becomes a pure-Python argmin over the squared Euclidean distanceΣ_j (x_j − c_j)²(the|x|²−2x·c+|c|²matmul cross-term trick is unnecessary for an argmin, sodense_matmul_realis dropped). Inputs coerce viatolist()(single 1-D vector accepted as one row); droppedimport numpy as np; encode →list[int], decode → list-of-rows. Differential-verified: encode argmin BIT-IDENTICAL to numpy over 200 random configs (0/1262 mismatches); decode round-trips. RIPPLE:idx.shape==(10,)→len. Math ledger UNTOUCHED; rosetta bucketpython_only_irreduciblestays. No new public op (tools.total 289); ABI 3; no C change. Remaining matrix-heavy: psk_qam (fsk-like constellation), mlse, mimo_svd (needs themat_svdfoundation), ica_jade (np.linalg.eigh = numpy-as-accuracy, likely stays gated). - rc104 (done, v0.7.5rc104) — SEVENTH CONSUMER flip:
dct(+jpeg consumer) numpy-FREE (CEIL_NUMPY_CARRIER26 → 25). Carrier-removal #564. The DCT-II/ DCT-III cosine transform — the INVOLVED one (it has ajpegconsumer). The cosine basis_dct_matrixis built as a list-of-lists viarational.cosover_PI(no np.cos/np.pi); the transform is a pure-Python matvec (1-D) / per-axis row|column transform (2-D, axis 0/1/-1 — the jpeg block case); thescipy.fft.dctfast-path is DROPPED (needed numpy as a carrier). DCT-III PARITY FIX: scipy's norm=None DCT-III weights the x_0 term by 1 not 2 (y_k = x_0 + 2·Σ_{j≥1}…, the exact inverse of DCT-II) — the old blanket2.0·Σdoubled it, a latent bug masked by preferring the scipy path;_transformnow matches scipy for both types. JPEG CONSUMER: jpeg stays a numpy carrier (flips its own rc) and coerces dct's list return to ndarray at a_dct()boundary wrapper (np.asarray). Differential-verified: dct 1-D + 2-D both axes, both types, ~1e-14 vs scipy norm=None; jpeg round-trip RMSE 1.2/0.25/0.025 at q=50/90/99. RIPPLES: baseline test_dct_smoke + rc33 test_dct_op_stable_and_invertible (X.shape→len, np.all(np.isfinite)→all(math.isfinite),M @ x→explicit matvec); rc33 dct2/dct3 basis tests (cascade.shape→len-based, np.allclose still coerces the list). Math ledger UNTOUCHED. No new public op (tools.total 289); ABI 3; no C change. Next: mat_svd FOUNDATION op (Gram AᴴA hermitian-eig → singular values/vectors) so mimo_svd flips + the rc71 exemplar moves off mimo_svd. - rc103 (done, v0.7.5rc103) — SIXTH CONSUMER flip:
fsknumpy-FREE (CEIL_NUMPY_CARRIER27 → 26). Carrier-removal #564. The FSK modulator/ demodulator (educational civilian-comms; trauma-informed defensive scope). Tone phasese^{i·2π·f·t}route offelementwise_transcendental(·,"exp_i")onto a per-element_exp_i(θ)=complex(rational.cos(θ),rational.sin(θ))over the Class-N_PIsource (native libm-free, bit-faithful); the demod correlator bank is a pure-Python complex matvecΣ_j tone[k][j]·conj(w[j])deciding onargmax|corr|²(monotone in|corr|→ no sqrt / no abs, dropsdense_matvec_complex+elementwise_hypot). Coerce numpy-free viatolist(); droppedimport numpy as np; modulate →list[complex], demod →list[int]. Differential-verified: modulate bit-faithful toe^{i2πft}(max-err ~1e-16), noiseless demod round-trips exactly, 0.01-noise sweep <0.1% symbol error. RIPPLE:waveform.shape==(4*8,)→len. Math ledger UNTOUCHED; rosetta bucket stayspython_only_irreducible. No new public op (tools.total 289); ABI 3; no C change. Next: dct (+ jpeg consumer), then mat_svd for mimo_svd. - rc102 (done, v0.7.5rc102) — FIFTH CONSUMER flip:
map_mlnumpy-FREE (CEIL_NUMPY_CARRIER28 → 27). Carrier-removal #564. Same real-solve family as lmmse: the linear-Gaussian MAP/ML estimatorx_hat = (Aᵀ R_v⁻¹ A + R_x⁻¹)⁻¹ (Aᵀ R_v⁻¹ y + R_x⁻¹ μ)(Kay 1993 §7/§11). Routes the two covariance inverses offdense_solve(M, np.eye(n))onto nativemat_solve(Mat, identity_Mat)over realMats (the inverse IS the solve against I);Aᵀ R_v⁻¹and the normal-eq matrixAᵀ R_v⁻¹ Aridemat_matmul; theAᵀ R_v⁻¹ y/R_x⁻¹ μmatvecs + theM + R_x⁻¹precision add are pure-Python sums. Inputs coerce numpy-free viatolist(); droppedimport numpy as np; both branches returnlist[float]. Differential-verified over 200 random(m,n)trials (ML max-err ~7e-12 — normal- eq conditioning — MAP ~5e-14). RIPPLES:x_hat.shape==(2,)smoke →len;test_map_ml_inv_dense_solve_rc64.pyx_ml.shape/x_map.shape==(n,)→len+np.all(np.isfinite(...))→all(math.isfinite(v) ...), and its residual-check"dense_solve(" in txt→"mat_solve(" in txt. Math ledger UNTOUCHED. No new public op (tools.total 289); ABI 3; no C change. Next: fsk (cleanest, 1 ripple), then dct (+ jpeg consumer), then mat_svd for mimo_svd. - rc101 (done, v0.7.5rc101) — FOURTH CONSUMER flip:
lmmsenumpy-FREE (CEIL_NUMPY_CARRIER29 → 28). Carrier-removal #564. A DIFFERENT sub-shape: the real-valued linear MMSE estimatorx_hat = mean_x + R_xy·R_yy⁻¹·(y−mean_y)— a real dense SOLVE, not an eigendecomposition. Routes the Class-L gain solve offdense_solveonto nativemat_solveover a realMat(R_yyᵀ·Z=R_xyᵀ→K=Zᵀ), and theK·(y−mean_y)estimate becomes a pure-Python matvecmx[i]+Σ_j Z[j,i]·(y_j−my_j). Inputs coerce numpy-free viatolist(); droppedimport numpy as np;opreturnslist[float]. RIPPLE: the singlex_hat.shape==(1,)smoke →len(x_hat)==1(lmmse is NOT in rosetta_classification.ndjson — that tracks amsc callables — and the test_rosetta_completeness rc13 line is a narration comment, so no source-scan breaks). Math ledger UNTOUCHED. No new public op (tools.total 289); ABI 3; no C change. Next: map_ml (same real-solve family), dct/fsk, then mat_svd for mimo_svd. - rc95 (done, v0.7.5rc95) — Mat-return FOUNDATION:
mat_solvehandles COMPLEX numpy-free (unblocks the matrix-heavy DSP carrier-flips). Carrier-removal #564. The matrix-heavy sigproc ops (esprit/dct/fsk/ica_jade/ lmmse/map_ml/mimo_svd/music/psk_qam/vector_quantisation) receive numpy arrays fromhermitian_eigendecompose/matrix_cascades.{lstsq,eigvals}(numpy- carrier-internal) — the numpy-free path is the nativemat_*bridge (rc72-74).amsc.laplacian.mat_solvepreviously raisedNotImplementedErroron complex; it now routes complexMatthrough the new private_mat_solve_complex, which builds the real 2n×2n block embedding[[Aᵣ,−Aᵢ],[Aᵢ,Aᵣ]]·[u;v]=[bᵣ;bᵢ]from plainMatindexing (no numpy) and rides the native realmat_solve→X=u+iv, numpy-absent. Mat-carrier peer of the numpy-carrier_dense_solve_complex. Value-faithful to ~1e-16 vs numpy for well-conditioned A. The rc73test_mat_solve_complex_rejectedflips to_via_block_embedding. No new public op (describe tools.total stays 287, classes 2; mat_solve already public — this enhances it); CEIL stays 32 (foundation enhancement, not a carrier flip); ABI 3; no C change. Next:mat_lstsq(normal-eqns via mat_solve∘mat_matmul) +mat_eigvals(Mat-carrier shifted-QR), then route esprit. - rc94 (done, v0.7.5rc94) — carrier-flip batch #18:
path_b_ops/sign_quantisegoes numpy-FREE (CEIL_NUMPY_CARRIER 33 → 32). Carrier-removal #564. The Path-B sign-quantise (Class K pin-slot ∘ Class M dispatch tag; Spike #174 BER anchor) drops top-levelimport numpy. Pure carrier —np.asarray/np.zeros_like/np.where, no helper deps; thenp.whereIS the Class-K sign- branch, now an explicit per-element if/else (no abs()). op returns a list of int {-1,0,+1} (was int8 ndarray). BIT-EXACT to the old decision. The Spike #174 path_b_mvp BER test already wraps the dispatch result in np.asarray(dtype=int8) before .tobytes()/.astype(), so it's robust. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc93 (done, v0.7.5rc93) — carrier-flip batch #17:
sinc_interpgoes numpy-FREE (CEIL_NUMPY_CARRIER 34 → 33). Carrier-removal #564.closed_form_ops/sinc_interp(Class L band-limit eigenbasis ∘ Class K pin-slot; Whittaker-Shannon, Oppenheim & Schafer §4.1) drops top-levelimport numpy. The sinc kernel reuses the rc92_sinc(x)=sin(πx)/(πx)over_PI/rational.sin(x=0 Class-K branch → 1.0, no division, no abs()) + a pure-Python_medianof the sample-spacing diffs. The complex matvec out[q]=Σ_s sinc((t_q−t_s)/T)·y[s] is an inline nested sum over lists — NOTdense_matvec_complex(numpy-carrier INTERNALLY, rc70 trap; also a matmul- ledger site, but the math ratchet is<=so its removal stays green). op returns a list of complex (or a single complex for a scalar target). Differential value-faithful to maxerr 1.1e-16 (integer-grid + scalar paths bit-exact 0.0). Baseline smoke.shape→ isinstance/len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc92 (done, v0.7.5rc92) — carrier-flip batch #16:
multirategoes numpy-FREE (CEIL_NUMPY_CARRIER 35 → 34). Carrier-removal #564.closed_form_ops/multirate(Class N rational rateup/down∘ Class C cyclic streaming; Vaidyanathan 1993 §4) drops top-levelimport numpy. The default windowed-sinc taps use a substrate-native_sinc(x)=sin(πx)/(πx)(rational.sinover the Class-N_PI) + a numpy-free_ccosHamming window; the x=0 sinc singularity is a Class-K branch (returns 1.0, NO division, no abs()), matchingnp.sinc(0)=1.np.arange→range,np.zeros→[0.0]*,np.pi→_PI,np.sum→sum; up-sample zero-insert is a plain loop;_dsp.convolvealready returns a list (rc79); down-sample is a[::down]slice scaled byup. op returns a list of float. Differential value-faithful to maxerr 7.1e-15 (rational sinc/cos ≤1 ULP; up==down==1 identity bit-exact 0.0). The rc33_ccosHamming test moves off ndarray-broadcast → list-comp + isinstance/len; the baseline smoke.ndim→ isinstance(list). No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc91 (done, v0.7.5rc91) — carrier-flip batch #15:
multitapergoes numpy-FREE (CEIL_NUMPY_CARRIER 36 → 35). Carrier-removal #564.closed_form_ops/multitaper(Class L DPSS eigenbasis ∘ Class M tapered- periodogram bundle-average; Thomson 1982) drops top-levelimport numpy:scipy.signal.windows.dpssis an EXTERNAL accelerator (needs numpy) → kept LAZY inside the try; numpy-absent it raises ImportError and the op falls to the fully numpy-free cosine-taper fallback (_PI+_csin→rational.sin tapers, ℓ²-norm inlinerational.sqrt(Σvᵢ²)since the olddense_normhelper is numpy-carrier INTERNALLY). Per-taper |F|²=real²+imag² (no abs()) bundle-average is an explicit list comp;_sc.fftreturns List[complex]. Differential scipy-dpss path BIT-EXACT (maxerr 0.0); fallback runs numpy-free → non-neg floats. rc60np.linalg.norm-absence + rc61 routed asserts stay green; the 3 op-smoke.shapeasserts (rc33 ×2 + baseline) move to isinstance/len. Returns a list of float. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc90 (done, v0.7.5rc90) — carrier-flip batch #14:
spectral_subtractiongoes numpy-FREE (CEIL_NUMPY_CARRIER 37 → 36). Carrier-removal #564 — the FIRST flip whose numpy-free output is NOT bit-exact-0.0 (value-faithful to machine eps).closed_form_ops/spectral_subtraction(Class L FFT-PSD ∘ Class N rational floor; Boll 1979) drops top-levelimport numpy: signal/noise → lists of float;np.maximum(|X|²−αN, βN)Class-N floor → builtinmaxper bin;|z|²=real²+imag²(no abs()).np.angle(X)(libm atan2) →rational.atan2(bit-exact here); the phasor + magnitude — previously the NUMPY-CARRIER helperselementwise_transcendental(phase, "exp_i")+elementwise_sqrt(both use np.zeros/reshape INTERNALLY — the rc70 "runnable≠loadable" trap) — are inlined per-bin asrational.{sqrt,cos,sin}so the op runs numpy-ABSENT._sc.fft/_sc.ifftreturn List[complex]. Differential-verified value-faithful to maxerr 6.7e-16 (the rational cascades match libm atan2/cos/sin + sqrt to ≤1 ULP) — within the op's 1e-9 tolerance. Returns a list of float; baseline smoke.shape→ len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc89 (done, v0.7.5rc89) — carrier-flip batch #13:
stft(+spectrogramconsumer) go numpy-FREE (CEIL_NUMPY_CARRIER 38 → 37). Carrier-removal #564, seventh of the workflow-scoped batch and the SECOND windowed follower ofcross_spectral— reusing the rc88-codified_PI = float(pi_cascade_digits(30))(Class-N Archimedes hexagon-doubling) +_ccos→rational.cosπ source.closed_form_ops/stft(Class C ∘ A ∘ I ∘ K windowed-frame FFT) drops top-levelimport numpy: the signal coerces to a list of complex (ahasattr(seq[0], "__len__")guard preserves the 1-D ValueError); the default Hann window is the same_PI-formed_ccoscascade; per-frame signal·window +_sc.fftbuild a list-of-lists STFT matrix (no np.zeros stack).opreturns a list of per-frame lists;spectrogram(already top-level-numpy-free since rc70) consumes it and computes |z|²=real²+imag² (no abs()) elementwise → list-of-lists of float. The rc33 window/op + baseline smoke tests move.ndim/.shape→ isinstance/len; differential-verified bit-exact (maxerr 0.0) vs the pre-change numpy path. Onlystftcarried a top-level import, so the count drops by one. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc88 (done, v0.7.5rc88) — carrier-flip batch #12:
cross_spectralgoes numpy-FREE (CEIL_NUMPY_CARRIER 39 → 38). Carrier-removal #564, sixth of the workflow-scoped batch — the FIRST sigproc carrier-flip to need π (moderate).closed_form_ops/cross_spectral(Class M HDC bundle-average ∘ Class A FFT cross-product; Welch's method) drops top-levelimport numpy: the Hann window0.5·(1−cos(2π·n/(N−1)))uses a module-level_PI = float(pi_cascade_digits(30))(Class-N Archimedes hexagon-doubling cascade — already the numpy-free π source in exact_dft/spectral_cascades) fed torational.cos; the cross-product X·conj(Y), per-bin |z|²=real²+imag² (no abs()), and the np.maximum(...,1e-30) coherence floor (builtin max) become explicit list comprehensions._sc.fftreturns List[complex],_fc.fftfreqa plain list numpy-absent. Returns (list, list); the baseline smoke moves.shape→ len (rc61 wraps coh in np.asarray). No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. π-source decision codified here for the windowed followers (stft / multirate / multitaper). - rc87 (done, v0.7.5rc87) — carrier-flip batch #11:
wienergoes numpy-FREE (CEIL_NUMPY_CARRIER 40 → 39). Carrier-removal #564, fifth of the workflow-scoped batch; a clean leaf.closed_form_ops/wiener(Class L power-spectrum Laplacian eigenbasis ∘ Class N rational MMSE gain) drops top-levelimport numpy:_sc.fft/_sc.ifftalready return List[complex] (np.asarray/np.real wraps drop); the per-bin power |X|²=X.real²+X.imag² (no abs()), the Class-N gain S_xx/(S_xx+S_nn), and the IFFT→real-part are explicit elementwise list comprehensions; the two np.maximum(...,1e-30) ε-floors become the builtin max(x,1e-30) per bin.opreturns a list of float; the baseline + rc61 smoke tests move.shape→ len (the rc61 finite/real checks pass on the list). No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc86 (done, v0.7.5rc86) — carrier-flip batch #10:
beamforming_fixedgoes numpy-FREE (CEIL_NUMPY_CARRIER 41 → 40). Carrier-removal #564, fourth of the workflow-scoped batch; a clean leaf.closed_form_ops/beamforming_fixed(Class L mic-array combiner ∘ Class N rational delay coefficients; trauma-informed civilian-acoustics scope) drops top-levelimport numpy:array_signals→ list-of-lists of complex (np.complex128/np.full/np.zeros/ np.int64 → complex()/plain lists/int(); ahasattr(row,"__len__")guard keeps the 2-D ValueError);max_delay=int(np.max(d))→ builtinmax(d)(Class-L reduce, no abs()); the per-mic delay-and-sumout += w[m]*sig[m,delay:...]becomes an explicit Class-M scale-and-accumulate index loop.opreturns a list of complex (empty → []); the smoke test movesy.ndim==1/y.shape[0]→ isinstance/len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc85 (done, v0.7.5rc85) — carrier-flip batch #9:
polyphasegoes numpy-FREE (CEIL_NUMPY_CARRIER 42 → 41). Carrier-removal #564, third of the workflow-scoped batch; a clean leaf (same shape as fir/matched_filter rc80).closed_form_ops/polyphase(Class L subband Laplacian ∘ Class N rational FIR decomposition) drops top-levelimport numpy: the only delegate_dsp.convolveis already numpy-free (List return), so thenp.asarraywrap drops. The strided polyphase splitE_k[n]=h[k+n·L]+ interleave are native list[::L]slices; the per-component accumulate (out[:n]+=filtered) and strided interleave write (out[k::L][:m]=c) become explicit Class-M elementwise index loops;np.zeros/np.concatenate/np.array([])→ plain lists.decomposereturns list-of-lists,opreturns a list; the smoke test movesy.ndim==1→isinstance(y, list). No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc84 (done, v0.7.5rc84) — carrier-flip batch #8:
ofdmgoes numpy-FREE (CEIL_NUMPY_CARRIER 43 → 42). Carrier-removal #564, second of the workflow-scoped batch.closed_form_ops/ofdm(Class I IFFT ∘ Class L per-subcarrier equaliser ∘ Class K cyclic-prefix) drops top-levelimport numpy: the modulate path returns a 1-D list ([complex(0)]*Nbuffer, prefix+time_block concat, list slice-assign); the demodulate path returns a list-of-lists. The Class-L one-tap equaliser inlines the numpy-boundlaplacian.elementwise_hypotvia the numpy-free Class-Nrational.hypotin a per-subcarrier comprehension, and thenp.where(|H_k|>1e-12, H_k, 1.0)guard becomes an explicit Class-K pin-slot sign-branch (no abs())._sc.fft/_sc.ifftalready return List[complex]. Smoke tests move.shape/.reshape→ len/flatten; the rc61 np.fft.-residual assertion still passes (ofdm uses_sc). No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc83 (done, v0.7.5rc83) — carrier-flip batch #7:
viterbigoes numpy-FREE (CEIL_NUMPY_CARRIER 44 → 43). Carrier-removal #564, first of the workflow-scoped batch (acarrier-flip-scopingmulti-agent run classified the 24 remaining signal_processing carrier files: 8 flippable-now, 16 blocked behind dense_*/np.linalg/np.fft).closed_form_ops/viterbi(Class L trellis Laplacian ∘ Class K argmax pin-slot) drops its top-levelimport numpy: the 2-D trellis tables (delta/psi) become list-of-lists, branch metricsdelta[t-1]+A[:,s]an explicit Class-M list-comp multiply-add, and the two np.argmax merges the Class-Kmax(range, key=)(first-maximal tie-break = np.argmax). Self-contained — no scipy/helper. Returns a list of int; the smoke test moves.shape→len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc82 (done, v0.7.5rc82) — carrier-flip batch #6:
iirgoes numpy-FREE (CEIL_NUMPY_CARRIER 45 → 44). Carrier-removal #564, same shape as rc77allpass.closed_form_ops/iir(Class N rational b/a ∘ Class C recursive biquad cascade) drops its top-levelimport numpy: the optional scipy accelerator (lfilter/sosfilt) stays lazy (scipy needs numpy → numpy-absent falls through), now passing list inputs straight in and wrapping the result inlist(...); the no-scipy path is the pure-Python direct-form-II transposed difference equation (_lfilter_direct, the Class-C recursive cascade of the Class-N b/a rational) over lists, plus the biquad-cascade branch. Returns a list; the smoke test moves.shape→len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc81 (done, v0.7.5rc81) — carrier-flip batch #5:
wavelet(Haar DWT) goes numpy-FREE (CEIL_NUMPY_CARRIER 46 → 45). Carrier-removal #564.closed_form_ops/wavelet(Class L 2-point Laplacian ∘ Class N dyadic 2^k) drops its top-levelimport numpy; its only numpy was np.asarray/np.zeros carriers (the 1/√2 normaliser was already the libm-free Class-Nrational.sqrt). Now a plain-list carrier: each level is an explicit elementwise Class-L sum/difference band over the Class-N dyadic decimation (current[0::2]/[1::2]), one-zero pad for odd lengths. Returns(approx, [details])as lists; the smoke test already checksisinstance(details, list)+len, so ZERO test ripple. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc80 (done, v0.7.5rc80) — carrier-flip batch #4:
fir+matched_filtergo numpy-FREE (CEIL_NUMPY_CARRIER 48 → 46). Carrier-removal #564, the rc79 follow-on it unblocked. With_dsp_cascadesnumpy-free, the two convolution/correlation leaf ops flip trivially: bothclosed_form_ops/fir(Class N ∘ Class C) andclosed_form_ops/matched_filter(Class A ∘ C ∘ M) drop their top-levelimport numpyand delegate straight to the numpy-free_dsp.convolve/_dsp.correlate(which coerce + 1-D-check — same ValueError type as the prior.ndimguard — and return a list). Their only remaining numpy was the rc79np.asarrayreturn-wrap, now removed. They return lists; the 2 smoke tests move.shape→len. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc79 (done, v0.7.5rc79) — carrier-flip batch #3: the DSP convolution
foundation
_dsp_cascadesgoes numpy-FREE (CEIL_NUMPY_CARRIER 49 → 48). Carrier-removal #564.signal_processing/_dsp_cascades(internalconvolve/correlate, the Class I ∘ Class M cascade) was numpy-free as a MATH engine since rc58 but still carried numpy (np.ascontiguousarray/np.zeros/np.conj/np.result_type). Now a plain-listbuffer ([0]*Npromotes int→float→complex exactly; the shift is a list slice; the conjugate is the element's own.conjugate()), accumulation order unchanged → floats bit-faithful (rc58 bit-test still green).convolve/correlatereturn alist; the 5 consumers (fir/matched_filter/multirate/polyphase+path_b matched_filter) wrap innp.asarrayat their own boundary, preserving ndarray behaviour (zero value change). Unblocksfir+matched_filterto flip next. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc78 (done, v0.7.5rc78) — carrier-flip batch #2:
signal_processingfarrowgoes numpy-FREE (CEIL_NUMPY_CARRIER 50 → 49). Carrier-removal #564.closed_form_ops/farrow(Class N — cubic-Lagrange fractional-delay Farrow structure) drops its top-levelimport numpy: the 4-tap Lagrange sub-filter_FARROW_LAGRANGE_CUBICbecomes a plain-tuple constant table and each per-output-sampleC[k]·xmixer term becomes an explicit length-4 Class-M micro-reduction (c[0]·x[0]+…+c[3]·x[3], left-to-right). rc26 had ROUTED that dot ontodense_dot_realto retire a numpy matmul site — butdense_dot_realfeeds numpy carriers (np.ascontiguousarray+np.sum) into the native kernel, so a farrow calling it could not run numpy-absent (the two-layer-gate trap). Inlining the four-term dot is bit-faithful (same IEEE-754 multiply-adds, same order) and adds NO numpy matmul site, so the math ratchet stays floored. Carriers → lists (padded=[0.0]+sig+[0.0,0.0]); returnslist[float]; smoke test.shape==(16,)→len()==16. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc77 (done, v0.7.5rc77) — carrier-flip batch #1:
signal_processingallpass+sign_quantisego numpy-FREE (CEIL_NUMPY_CARRIER 52 → 50). Carrier-removal #564, the first of the pure carrier-flip phase (the dissolution track closed at rc76 —hurwitz_matrixwas the only float-restatement-of-an-exact-op; the rc76 re-review confirmed zero further dissolutions, so what's left is numpy-as-CARRIER, not numpy-as-duplicate). Both ops drop their top-levelimport numpy:sign_quantise(Class K) —np.where→ explicit per-element Class-K threshold sign-branch over a list (no abs()), returnslist[int]of {-1,0,+1}, exact;allpass(Class N) — carriers → lists, optional scipylfilterlazy (numpy-absent falls through), the no-scipy path is the existing direct-form-I difference-equation reference on lists, returnslist[float]. Smoke tests.shape==(N,)→len()==N. No new public op (describe tools.total stays 287, classes 2); ABI 3; no C change. - rc76 (done, v0.7.5rc76) — the scalar-export layer
One.to_scalar(matrix/vector → scalar; EXACT (num,den) default + opt-in numpy-FREE float export). Carrier-removal #564, follow-on to the rc75 Hurwitz TOML-class reframe (lets a TOML class chain matrix-math → scalar output). User rule: return float sometimes, never receive float —the_onestays integer-only (no float input), andas_float=Truedoes the single terminalnum/dencast to a plain Python float with no numpy (pointedly UNLIKE the numpy-tierOne.to_numpy/to_matrixexports #564 is retiring). Three exact modes:trace=3+3σ+8σ·cosθ(uses the SAMErational.cos_series_truncatethe trigonometry/asymptotic_calculus catalogs validate — those catalogs are the target test),sqnorm=Σ(num/den)²(sign-free),component= the i-th of the 14 rationals. New public callable ⟹ rosettabignum_reference(exact oracle tier, outside the debt ceilings) +__all__; takes a structuredOne(no MCP coercer) so it is tool-schema-coverage-EXEMPT (likethe_one), bindable for TOML classes by dotted path. describe tools.total stays 287, classes stays 2; CEIL_NUMPY_CARRIER stays 52. ABI 3; no C change. Newtest_to_scalar_rc76.py(15 tests). - rc75 (done, v0.7.5rc75) — the numpy-Rosetta-peer DISSOLUTION: delete
qm.hurwitz.hurwitz_matrix; declare the Hurwitz operator as a[class]over the EXACTthe_one; FIRST carrier-ceiling decrement CEIL_NUMPY_CARRIER 53 → 52. Carrier-removal #564. rc50'shurwitz_matrixwas a numpy 14×14 FLOAT builder (np.zeros + cos/sin float-divided) DUPLICATINGOne.to_matrix— both float-cast the SAME exact cascadethe_one. There was never supposed to be a continuous-float peer (chained float ops SUM rounding error every op; not correct-for-science). The exactOne(14 exact (num,den) rationals) is the correct realisation; the float 14×14 is a lossy projection, legitimate only as the opt-inOne.to_matrixexport. The Hurwitz operator is now declared the class-from-TOML way (make_class): newclass_catalog/hurwitz.toml[class]whose methodgenerateBINDSop="srmech.amsc.cascade.the_one"→ returns the EXACTOne, bit-for-bitto_flat_rational()-identical, zero Python/numpy.qm/hurwitz.pyis now numpy-free at top level (noimport numpy) ⟹ CEIL_NUMPY_CARRIER 53 → 52 (FIRST flip of the carrier arc).hurwitz_planessurvives — the GENUINE Fano-plane cross-derivation fromoctonion_mult_table(exact integer-tuple structure, matchesOne.FANO_PLANES; still octonion-gated numpy untilqm.octonionflips). New[class]⟹ describe classes.total 1 → 2 (Hurwitz joins Genome); deleted ToolEntry + rosetta line ⟹ describe tools.total 288 → 287 (7 count-tests). Maths ratchets at floor; ABI 3; no C change.test_hurwitz_rc50.pyrewritten (class-is-the-exact-the_one + dissolution + surviving plane cross-derivation). - rc74 (done, v0.7.5rc74) — Mat bridge primitive #3 (the LAST): numpy-FREE
Hermitian eigendecomposition (
mat_hermitian_eigendecompose; CEIL_NUMPY_CARRIER stays 53; new capability, not a flip). Completes the bridge family with rc72mat_matmul+ rc73mat_solve.laplacian.mat_hermitian_eigendecompose(H: Mat) -> (eigvals, eigvecs)diagonalisesH = V·diag(λ)·Vᴴ: the real/complexMat.buffer(interleaved-(re,im) row-major) feeds the nativesrmech_hermitian_eigendecompose(n, H_il, out_eigvals, out_eigvecs_il)ZERO-COPY (complex Mat) / interleaved-(re,0) once (real Mat);eigvals→ (n,1) REAL Mat ascending,eigvecs→ (n,n) COMPLEX unitary Mat (always complex, mirroringhermitian_eigendecompose). Unconditionally numpy-free: no-native / n>256 / convergence-miss falls back to srmech's own cyclic Jacobi (_jacobi_eig_py, the eigenvector-accumulating sibling of_jacobi_eigvals_py) — real-symmetric directly, complex-Hermitian via the real 2n×2n embedding[[A,-B],[B,A]]withvⱼ = topⱼ + i·botⱼreconstruction - same-eigenvalue Gram–Schmidt (unitary inside a degenerate eigenspace). No
abs() / libm (Class-K sign-branch; Class-N rational.sqrt). SUBPROCESS-proven
numpy-free (native + forced-embedding-fallback); reconstruction +
unitarity-pinned (NOT element-wise — eigenvector phase / degenerate basis is
solver-chosen); eigenvalues match
numpy.linalg.eigvalsh~1e-13 both paths. New public callable ⟹ ToolEntry + rosettac_dispatched+ all/LAPLACIAN_OPS; describe tools.total 287 → 288. rc72 Mat MCP coercer + sample already cover it (no ratchet change). Maths ratchets at floor; ABI 3; no C change. Newtest_mat_hermitian_eig_bridge_rc74.py(8 tests). The Mat↔native-dense-kernel bridge family (matmul + solve + eig) is now COMPLETE — aqm.*module's full numpy surface has Mat-native peers; the first module flip can now decrement the carrier ceiling. - rc73 (done, v0.7.5rc73) — Mat bridge primitive #2: numpy-FREE dense solve
(
mat_solve; CEIL_NUMPY_CARRIER stays 53; new capability, not a flip). The peer of rc72mat_matmul.laplacian.mat_solve(A: Mat, B: Mat) -> MatsolvesA·X = B: the realMat.buffers (row-major f64) feed the nativesrmech_dense_solve_f64(n, nrhs, A, B, out)ZERO-COPY via(c_double*n²).from_buffer(...)(C side const → Mats not mutated), outputarray('d')→ Mat. Unconditionally numpy-free: no-native / dim>256 / singular falls back to srmech's own exact-rational Gauss–Jordan (_solve_exact, Class-N Fraction) → float64. REAL-f64 only (complex = real 2n×2n block embedding, later rc → NotImplementedError). SUBPROCESS-proven numpy-free (native + forced-fallback); value-faithful vsnumpy.linalg.solve~1e-15. New public callable ⟹ ToolEntry + rosettac_dispatched+ all/LAPLACIAN_OPS; describe tools.total 286 → 287. rc72 Mat MCP coercer + sample already cover it (no ratchet change). Maths ratchets at floor; ABI 3. Newtest_mat_solve_bridge_rc73.py(8 tests). rc74 =mat_hermitian_eigendecomposecompletes the bridge family. - rc72 (done, v0.7.5rc72) — the Mat↔native-dense-kernel bridge: numpy-FREE
2-D matmul (
mat_matmul; CEIL_NUMPY_CARRIER stays 53; new capability, not a flip). Carrier-removal foundation #2. rc69 built the numpy-free 2-DMatcarrier (flat array('d'), row-major, interleaved-(re,im) = C99 double Complex); this rc addslaplacian.mat_matmul(a: Mat, b: Mat) -> Mat, the numpy-free@the 2-D qm.* matmul callsites flip onto. Because Mat.buffer IS already the exact layoutsrmech_dense_matmul_complexreads, a complex operand feeds the kernel ZERO-COPY via(c_double*2n).from_buffer(mat.buffer)(C side const); a real operand is interleaved (re,0) once; output is a fresh array('d') → Mat. Follows the rc#563 numpy-free ctypes-marshalling precedent (_jacobi_eigvals_native_listmarshal) — HAS_NATIVE True with numpy absent. Unconditionally numpy-free: no-native / dim>256 falls back to a pure-Python triple loop (cascade, never numpy @). SUBPROCESS-proven (numpy blocked at sys.meta_path): computes on native + forced-fallback + real paths numpy-free; native complex kernel BIT-EXACT vs numpy (max-err 0.0). New public callable ⟹ ToolEntry + rosettac_dispatched+ __all_/LAPLACIAN_OPS; describe tools.total 285 → 286. Maths ratchets at floor; ABI 3. Newtest_mat_matmul_bridge_rc72.py(9 tests). rc73+ flips the qm.* matmul callsites onto it (lowering the carrier ceiling);mat_solve/mat_hermitian_eigendecomposeare the follow-on rcs. - rc71 (done, v0.7.5rc71) — lazy op-registration:
signal_processingis IMPORT-reachable numpy-FREE (CEIL_NUMPY_CARRIER stays 53; reachability, not a flip). rc70 made the FFT family's MATH numpy-free, but a freshimport srmech.signal_processingon a numpy-absent install still raised: two layers ABOVE the carrier ratchet. (1) the eager_require_numpy(...)package gate (rc47), and (2) eager all-op registration (from . import path_b_ops→ every op imported for registry population → the numpy ops'import numpyfired transitively, even for numpy-free ops). rc71 dissolves both:closed_form_ops/path_b_opsswap their eagerfrom . import (…every op…)for a PEP-562__getattr__(_scientific.make_lazy_op_getattr) deferring each op-module import to first attribute access; numpy-free Path-B ops still eager-register, numpy Path-B ops (matched_filter/sign_quantise/wiener) register a deferred LOADER withpath_registrysolookup/has_path/dispatchresolve them on-demand;registered_ops()is declarative (lists pending lazy ops without importing). Eager__init__gate removed. SUBPROCESS-proven:test_signal_processing_numpy_free_reachable_rc71.pyblocks numpy atsys.meta_pathbefore the first import (a real numpy-absent install —monkeypatchcan't prove fresh-import-numpy-freedom) and asserts import + FFT-family run + dispatch succeed numpy-free, a numpy op raises the clean[scientific]hint, plus a down-only static guard the eager gate stays gone. No carrier flip (ceiling 53) / no public op (describe stays 285); maths ratchets at floor. ABI 3. Next: theMat↔native-dense-kernel bridge (the 2-D qm/* foundation). - rc70 (done, v0.7.5rc70) — carrier-flip #1: the FFT op-family runs 1-D
numpy-FREE (CEIL_NUMPY_CARRIER 61 → 53). First real module flip of the
carrier arc.
_fft_carrier's numpy was pure carrier-shaping (asarray / moveaxis / reshape / pad / output-alloc) while the transform already rode the numpy-free 1-Dspectral_cascades. The common 1-D/default-axis path is now numpy-free (list → cascade → list; framework-native list when numpy absent, ndarray for parity when present); n-D/non-default-axis still uses numpy lazily. 8 modules drop their top-levelimport numpy— leaf_fft_carrier+closed_form_ops.{fft,ifft,rfft}(redundantnp.asarrayremoved;_fccoerces) +closed_form_ops.spectrogram(annotation-only import) +path_b_ops.{fft,ifft,rfft}(numpy-free length via the input's own.shape/len). HONEST-not-theater:test_fft_family_numpy_absent_rc70.pyhides numpy and proves fft/ifft/rfft/fftfreq + n-pad/truncate still compute value-faithful (~1e-9). Same cascade ⟹ existing 788-test FFT suite green. No public-op change (describe stays 285); maths ratchets unchanged at floor. ABI 3. 2-D qm/* matrix modules come later behind a Mat↔native-kernel bridge. - rc69 (done, v0.7.5rc69) — carrier-removal arc Phase 0 (infra): numpy-free
2-D
Matcarrier + down-only carrier ratchet. The numpy-MATH sweep (rc53–rc68) is floored; what's left is numpy-as-CARRIER — 61 submodules stillimport numpyat module level (lazy submodule imports; the package imports numpy-free). Newsrmech.amsc.mat.Mat: the 2-D peer of theHVcarrier — a dense matrix over a flatarray('d'), row-major, interleaved(re,im)for complex (C99double _Complexlayout →.bufferdirectly ctypes-castable to the native dense kernels, no copy/no numpy on HAS_NATIVE). numpy-free at import (lazy.to_numpy()bridge; numpy-free path is.tolist()/.buffer). New down-only ratchettest_numpy_carrier_ratchet.py(CEIL_NUMPY_CARRIER=61) - a guard that
mat.pystays numpy-free. NO module flips this rc (ratchet stays 61; mat.py doesn't bump it). Unregistered handle (likeHV) ⟹ no ToolEntry/introspect/rosetta gate (describe stays 285). ABI 3. rc70+ flips modules ontoMatone cluster per rc, lowering the ceiling toward 0. - rc68 (done, v0.7.5rc68) — real-antisymmetric eig → iS-Hermitian cascade
route (linalg_fft decrement; the maths-engine sweep's last bulletproof step).
Both
np.linalg.eigsites inqm/so8.pytake a REAL antisymmetric matrix — theJcomplex-structure op (584) andad(H)for a Cartan element (646), each with a purely-imaginary±i·weightspectrum. For real skewS,iSis Hermitian, so eigenpairs come from the shipped C-backedhermitian_eigendecompose(iS):λ_S = −i·μ(μ real, ascending), SAMEV. New PRIVATEso8._eig_real_skewroutes both — nonp.linalg.eig. Sound under degeneracy ONLY because so8 consumesVinvariantly: 584 rebuilds the triplet from the+ieigenspace SPAN (projector basis-invariant, even forJ's mult-3 eigenspace), 646 reads scale/phase-invariant Rayleigh-quotient weights. Both differential-tested vsnp.linalg.eigon a degenerate (mult-3) real-skew matrix; the full so8/triality/an_embedding/quaternion/killing/semisimple suite (80 tests) stays green with BOTH sites routed. numpy-math ratchet linalg_fft 25 → 23 (2 genuine eig calls; helper docstringsnumpy.linalg.-free). Private helper ⟹ no ToolEntry gate (describe stays 285). ABI 3. The 1 remainingnp.linalg.eig(pseudo_hermitian:182, generalO— NOT skew, so iS trick N/A) needs a general non-Hermitian eigenvector cascade that does not exist; rest of linalg_fft is numpy-as-accuracy or cascade-internal fallbacks. Maths-engine sweep floored; next numpy removal is the carrier layer. - rc67 (done, v0.7.5rc67) — real-symmetric eigh → C-backed hermitian +
eigenvector sign-canon (linalg_fft decrement; lane 1 past the floor).
laplacian.symmetric_eigendecompose'snp.linalg.eighroutes onto the already-shipped, C-backedhermitian_eigendecompose(real-symmetric IS complex-Hermitian — native Jacobi peer when present; numpy eigh only as THAT op's own shared fallback). Real-symmetric eigenvectors come back real (imag ~0), so we take.realand a new PRIVATE_canonicalize_eigenvector_signspins each column's ±1 sign (Class-K: flip so the largest-|·| entry is positive, magnitude viacol²— noabs(), no float sqrt; there²+im²+√ draft tripped the A-Nfloat_powratchet). LAPACK's arbitrary eigenvector sign was a HIDDEN non-settable convention; this makes it a deterministic SETTABLE one (endianness precedent). Degenerate-subspace U(k) basis stays solver-chosen + reconstruction-invariant; sign-canon pins the per-column Z₂. Correctness via basis-INVARIANT props (ascending eigvals == eigvalsh, reconstruction, orthonormality) on degenerate cases (4-cycle / identity / block-degen), real V on both native + numpy-fallback paths. numpy-math ratchet linalg_fft 28 → 25 (1 genuine call + 2 textual docstring mentions). Rosetta: symmetric_eigendecompose python_only_irreducible → composition_of_c (now composes c_dispatched hermitian), CEIL_PYTHON_ONLY_IRREDUCIBLE 108 → 107 (debt closed). Private helper ⟹ no ToolEntry gate (describe stays 285). ABI 3. - rc66 (done, v0.7.5rc66) — complex inv → real 2n×2n block embedding of the
native dense_solve (linalg_fft decrement; FIRST new-capability step past the
carrier-swap floor). The lone complex
np.linalg.inv— pseudo_hermitian'sη = (V·Vᴴ)⁻¹Mostafazadeh metric — routes onto a new PRIVATE Class-L helperlaplacian._dense_solve_complex.(Aᵣ+iAᵢ)(u+iv)=(bᵣ+ibᵢ)⟺ the real 2n×2n[[Aᵣ,-Aᵢ],[Aᵢ,Aᵣ]]·[u;v]=[bᵣ;bᵢ](X=u+iv); EXACT embedding (concatenate / slice / .real / .imag — numpy a carrier only) riding the shipped NATIVE real dense_solve. HPD GramV·Vᴴ→ well-conditioned →== numpyinv/solve ~1e-9 (verified incl.M·M⁻¹=I, general vector+matrix complex solve, η-metricO†η=ηO). Underscore-private ⟹ NO registry/ToolEntry gate (describe stays 285). numpy-math ratchet linalg_fft 29 → 28; np.linalg.inv → 0. ABI 3. ROUTE-SAFE FLOOR reached — the remaining 20 genuine sites are numpy-as-accuracy (6 matrix_rank + 7 so8 svd nullspace), irreducible numpy fallbacks inside the cascade ops (dense_solve / hermitian_eigendecompose), or need eigenvector-sign-canonicalization (eig×3 / eigh-1062 / mimo svd) — a legitimate residual, not unfinished carrier work. - rc65 (done, v0.7.5rc65) — np.linalg.pinv → cascade SVD reconstruct
(linalg_fft decrement). Cashes in rc64's "value-faithful for LARGE singular
values" on the lone genuine
np.linalg.pinvsite — the qm/so8.py_killing_formstructure-constant least-squares solve. New_pinvhelper:A⁺ = V·diag(1/s)·Uᴴfrommatrix_cascades.svd, NumPy'srcond=1e-15cutoff. Route-safe BECAUSE (a) the pseudoinverse is UNIQUE → the per-factor U/V column-sign ambiguity cancels, and (b) the generator stack is full-column-rank (the semisimplicity certificate) → everyswell clear of the cutoff (no nullspace/small-saccuracy exercised — the exact regime rc64 flagged as numpy-as-accuracy for matrix_rank). Verified_pinv == numpy.pinv(~1e-7) incl. thepinv·bracketmatvec usage + theA·A⁺·A=Aidentity; reconstruction usesdense_matmul_real(NOT@, matmul ledger untouched). 23 so8/Killing/stabiliser tests pass (incl. g₂ full-rank-14 Cartan check the rc63b matrix_rank route had broken). 1 textualnumpy.linalg.pinvreworded. numpy-math ratchet linalg_fft 30 → 29. No rosetta / ToolEntry change (describe tools.total stays 285). ABI 3. Deferred: complex inv (complex solve), eigh/eigvalsh (eigenvector-sign-delicate), eig/svd-direct; matrix_rank stays on numpy PERMANENTLY (numpy-as-accuracy). - rc63b (done, v0.7.5rc64) — map_ml real inv → dense_solve (linalg_fft
decrement). The 2 real
np.linalg.invcovariance-inverse sites in signal_processing/closed_form_ops/map_ml.py route ontolaplacian.dense_solve(M, np.eye(n))(the inverse IS A·X=I; unique, well- conditioned; == numpy ~1e-9). The so8np.linalg.matrix_ranksites were INVESTIGATED and KEPT ON NUMPY — the cascade SVD's nullspace (small-singular- value) accuracy is too low for an absolute-tol rank count on a rank-deficient matrix (over-counted rank_with_so4 6→9); a numpy-as-ACCURACY site, deferred with the dense_solve numpy fallback + matmul-4 kernel-internal tail. numpy-math ratchet linalg_fft 32 → 30. No rosetta / ToolEntry change (describe tools.total stays 285). ABI 3. Later sub-rcs: pinv (svd-reconstruct, sign-invariant), complex inv (complex solve), eigh (hermitian_eigendecompose), eig/svd-direct. - rc63a (done) — np.linalg.* cluster, first sub-batch (linalg_fft
decrement). Route the sign/order-invariant decompositions onto the
already-shipped value-faithful
matrix_cascadescascades. 5q, _ = np.linalg.qr(X)(qm/so8.py) →matrix_cascades.qr: Q consumed only viaQ @ Qᵀprojectors / column span / sum-of-squares leak (all invariant to QR per-column sign; verified Q@Qᵀ == numpy ~1e-9). 2np.linalg.eigvals(X)(pseudo_hermitian max|imag|, esprit rotation set) →.eigvals(multiset, set-faithful ~1e-12). 83 op-tests pass. ~22 textualnumpy.linalg.Xmentions in matrix_cascades / tool_schema / triality (no genuine calls) reworded to "NumPy X". numpy-math ratchet linalg_fft 61 → 32. No rosetta / ToolEntry change (describe tools.total stays 285). ABI 3. rc63b/c: the delicate svd / matrix_rank / inv / eig / eigh / solve / lstsq / pinv (mostly qm/so8.py + amsc/laplacian.py), with sign/order handling per callsite. - rc62 (done) — DRAIN the np.fft.* family (linalg_fft decrement). New
signal_processing/_fft_carrier.pylifts the 1-Dspectral_cascadesfft/ifft to NumPy's ndarray + n= (zero-pad / truncate) + axis= contract (NumPy a carrier only: moveaxis / reshape / pad / slice), plus a real-input rfft (full-transform-then-slice; complex input raises TypeError mirroring NumPy) and an fftfreq carrier (integer bin indices / (nd); no transcendentals). The 8 remaining genuine np.fft. calls route onto it — the 6 Path-A/Path-B fft/ifft/rfft ops (closed_form_ops + path_b_ops) + the 2 cross_spectral fftfreq sites — and the ~18 textualnumpy.fft.Xdoc/summary mentions are reworded to "NumPy fft". Carriers bit-faithful to NumPy (real+complex, 1-D + n-D, every axis, n pad/truncate, ~1e-9); rfft is value-faithful (~1 ULP) NOT bit-identical to pocketfft (even np.fft.fft(real)[:n//2+1] != np.fft.rfft), so the rfft-vs-NumPy tests are now allclose (Path-A == Path-B cascade identity stays exact). np.fft. -> 0. numpy-math ratchet linalg_fft 87 -> 61. No rosetta / ToolEntry change (describe tools.total stays 285). ABI 3. Next: the np.linalg.{svd,qr,eig,solve,inv,...} cluster (~36 sites, mostly qm/so8.py). - rc61 (done) — the np.fft.* family, first batch (linalg_fft decrement).
The 15 one-dimensional
np.fft.fft(x)/np.fft.ifft(x)callsites across signal_processing route onto the EXISTING value-faithfulcascade.spectral_cascades.fft/.ifft(rc36/rc37 radix-2 Cooley–Tukey + dft fallback for non-power-of-2 N; exact-until-rotation). NO new public op — rc61 swaps only the carrier (NumPy FFT → cascade), value-for-value, wrappingnp.asarray(...)so the result stays an ndarray. 15 sites: closed_form_ops/{cross_spectral ×4, ofdm ×2, spectral_subtraction ×2, multitaper ×1, stft ×2, wiener ×2} + path_b_ops/wiener ×2. Cascade verified == numpy (real+complex, N=2…100, ~1e-9); DSP-op invariants (coherence, Wiener gain, OFDM round-trip, STFT, multitaper) preserved — 447 signal_processing tests pass. numpy-math ratchetlinalg_fft102 → 87. No rosetta / ToolEntry change (describe tools.total stays 285). ABI 3. Remaining np.fft.* = the n=/axis= Path-A ops (fft.py/ifft.py/rfft.py + fftfreq) needing an array-aware (n-pad + axis) wrapper — next batch; then np.linalg.{svd,qr,eig, solve,inv} (~36 sites, mostly qm/so8.py). - Next batches — migrate
@-callsites ontodense_matmul_complex. The 108python_only_irreducibleARE the numpy-math ratchet's 359 callsites seen at the op level; the QM /matrix_cascades@matmuls now have their kernel. Each migration decrements BOTH the ratchet'smatmulceiling AND moves a Rosetta oppython_only_irreducible → composition_of_c. Aftermatmul: FFT/DFT,eig/SVD/QR/lstsq,kron,einsum. The 5 remainingc_exists_unboundstay the Klein-4 family, gated on W5.
Standing tracker. Issue #928
is the consolidated srmech wishlist (bugs · schema · enhancements · new ops,
W1–W18). Consult it at every rc boundary: (1) the do-not-mirror gate above
before any Python→C port, and (2) the stale-vs-missed sweep per
[[feedback_tracker_lookback_stale_vs_missed_each_sprint]].