Repository navigation
Description
Overview
At identical parameters, the NumPy/numba and JAX likelihoods of a Delaunay + AdaptSplit interferometer inversion differ by ~20–270 in log likelihood on the 125×125 Aris mask and ~1 on the 512×512 mask. Chi-squared, curvature matrix, data vector and the curvature log-det are identical; the entire difference is log_det_regularization_matrix_term (2443.22 numba vs 2561.98 JAX). The two backends factorize H differently: NumPy offers it to the SuperLU sparse LU route (inversion_util.log_det_sparse_spd_from, diagonal pivoting only), JAX takes a dense Cholesky. On an extremely ill-conditioned AdaptSplit matrix (entries summing to ~1.4e-5) these can disagree — or the two backends may be building different H matrices. Log evidences are therefore not comparable across backends.
Plan
- Reproduce on the 125×125 cache at one parameter vector; first compare the two backends' regularization matrices
Hdirectly, then the log-det of the sameHunder dense NumPy Cholesky, SuperLU, JAX Cholesky, slogdet and an mpmath high-precision reference. - Locate the discrepancy (construction vs factorization) and make both backends compute the term the same numerically robust way (diagonal scaling
H = D^{1/2} Ĥ D^{1/2}is the leading candidate: log det H = Σ log d + log det Ĥ, well-conditioned, works for both backends and stays differentiable). - Check the non-positive-definite behaviour (numba raises
LinAlgError→ resample; JAX returns -inf) is consistent in effect and document it. - Add a regression test pinning the value on an ill-conditioned AdaptSplit matrix against the high-precision reference, on both backends.
- Decide and record whether AdaptSplit's matrix construction should be better conditioned (scope note only unless small).
Tier: glance — merge mode: auto-merge on green if the Witness passes, post-merge summary
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| array/PyAutoArray | main (a84f724) | clean |
Suggested branch: feature/adaptsplit-logdet-backend-parity
Implementation Steps
- Reproduce with the session scratchpad copy of
aris_evidence/step7/bench.py --check(125×125 inputs inaris_evidence/aris_dsandaris_evidence/data) at a fixed parameter vector; dumpregularization_matrix_reducedfrom both backends andnp.max(np.abs(H_np - H_jax)). - If
Hdiffers: traceAdaptSplit.regularization_matrix_from→regularization_utilnumba vs JAX implementations (split-point weights, neighbour ordering, dtype) and fix the divergent one; the regression test then pinsHparity. - If
Hagrees: compute log det of that one matrix five ways (np dense Cholesky,log_det_sparse_spd_from, jnp Cholesky, slogdet, mpmath at 50 digits) and identify which is wrong. Implement the fix inAbstractInversion._log_det_symmetric_from/log_det_regularization_matrix_term(abstract.py ~970–1150) and/orinversion_util.log_det_sparse_spd_from(inversion_util.py ~878): symmetric diagonal scaling before factorization on both backends is the preferred fix; if SuperLU without row pivoting is the culprit, scale or fall back to dense Cholesky above a pivot-ratio threshold. - Non-PD consistency: keep NumPy
LinAlgError(resample) and JAX NaN/-inf semantics as documented in_log_det_symmetric_from, but verify the scaled factorization does not change which draws are non-PD; note in the PR body. - Tests:
test_autoarray/inversion/inversion/test_log_det_sparse.py+ a new ill-conditioned AdaptSplit test (small Delaunay mesh with extreme adapt weights) asserting NumPy and JAXlog_det_regularization_matrix_termagree to <1e-3 and match the mpmath reference to <1e-6; keep the existingtest_log_det_shared_factor.pyand curvature log-det parity green. - Witness: re-run
bench.py --checkon the 125×125 setup; record numba vs JAXlog_det_regularization_matrix_termbefore (2443.22 / 2561.98) and after (agree <1e-3) in the PR body, plus the 512×512 1,459-vertex pair (2576.59 / 2574.73).
Key Files
autoarray/inversion/inversion/abstract.py—_log_det_symmetric_from,log_det_regularization_matrix_termautoarray/inversion/inversion/inversion_util.py—log_det_sparse_spd_fromautoarray/inversion/regularization/adapt_split.py,regularization_util.py— AdaptSplit matrix construction (numba + JAX)test_autoarray/inversion/inversion/test_log_det_sparse.py— regression tests
Evidence
PROGRESS3/PROGRESS4 (step8.3 numerics): all other evidence terms identical to 1e-16 relative; JAX eager vs jit agree to 1e-8; ~1 in 4 random draws non-PD. Evidence copied to this session's scratchpad aris_evidence/ (temporary).
Heart RED development override
Heart was RED at launch (2026-10-15 evening): Heart: RED — Release RED; monitoring RED · 10/100 · 415 unresolved; [red] release validation FAILED (stage integrate); [red] PyAutoHands/workflows/Python Version Matrix: failure; [red] PyAutoHeart/workflows/Release Integrate: failure; [red] PyAutoMind/workflows/Lifecycle Drift: failure; [red] PyAutoArray/Tests: red; [red] autofit_workspace_test/Smoke Tests: red; [red] autogalaxy_workspace_test/Smoke Tests: red; [red] autolens_workspace_test/Smoke Tests: red; [red] Release validation: NOT release_ready — v2026.10.15.1.dev81701; [red] Worktree drift: 5 orphan / 0 missing / 4 dirty; [red] autolens_inference: red; [red] autolens_profiling: red; [red] euclid_strong_lens_modeling_pipeline: red.
The live human authorized the development-only override in session 3e3dea38 (AskUserQuestion answer: "Yes, override for development (Recommended)") for the three PyAutoArray prompts filed from the Aris interferometer profiling. Permitted: commit, push, PR-open behind the autonomous-ship gate (tests, smoke, review must still pass). Merge stays a separate human act. Heart remains RED for release purposes. Launched --auto.
Original Prompt
Click to expand starting prompt
AdaptSplit regularization log-det differs between numba and JAX backends
Type: bug
Target: PyAutoArray
Repos:
- PyAutoArray
Difficulty: large
Autonomy: supervised
Priority: high
Memory: reading-queue.md; wiki/lensing/sources/dark-matter-substructure.md; wiki/galaxies/sources/massive-ellipticals.md
Status: formalised
Consequence: glance
Witness: at identical parameters on the Aris interferometer setup, numba and JAX log_det_regularization_matrix_term agree to <1e-3 (currently 2443.22 vs 2561.98), with a unit test pinning the value on an ill-conditioned AdaptSplit matrix
Review-minutes: 3
Unattended: ready
At identical parameter vectors, the NumPy/numba and JAX likelihoods of a Delaunay + AdaptSplit
interferometer inversion differ by ~20-270 in log likelihood (old 125x125 mask, 1405-5526 vertices)
and ~1 on a 512x512 mask. Chi-squared, curvature matrix, data vector and curvature log-det are
identical; the whole difference is log_det_regularization_matrix_term (e.g. 2443.22 numba vs
2561.98 JAX). The AdaptSplit regularization matrix is extremely ill-conditioned (entries sum to
~1.4e-5), so its log-determinant is backend dependent. JAX eager vs jit agree to 1e-8.
Consequence: log evidences are not comparable across backends; a user mixing backends across
pipeline phases (or comparing models) gets inconsistent evidences. Also ~1 in 4 random draws is
non-positive-definite: numba raises LinAlgError (resample), JAX returns -inf — same behaviour
should be checked for consistency.
Do: locate where each backend computes the regularization log-det (Cholesky vs slogdet, jitter /
diagonal regularisation, dtype), decide the numerically correct value (e.g. compare to an
mpmath/high-precision reference on a small matrix), make both backends compute it the same robust
way, and add a regression test. Consider whether AdaptSplit's matrix construction itself should be
better conditioned.
Evidence: scratchpad /tmp/claude-1000/-home-jammy-Code-PyAutoLabs/50c8c6cd-6ac2-47c2-9290-7ee73f9c4315/scratchpad/aris/PROGRESS3.md, PROGRESS4.md, step7/bench.py (--check mode), step8/. PyAutoArray ccddfba.
Activity
Jammy2211 commentedon Oct 15, 2026
Campaign membership: this task is logged and monitored under the PyAutoPulse interferometer campaign as task
interferometer_large_mask_aris— PyAutoLabs/PyAutoPulse#62 (ledger rows queued in Mind behind the PyAutoPulse claim; the issue holds the campaign intent and baseline numbers until they land). Record the before/after witness table there at close-out.Jammy2211 commentedon Oct 15, 2026
Root cause found (read-only investigation, 2026-10-15) — plan amended
The log-det factorizations are not the cause. On the 125×125 Aris setup (1,405 vertices, one fixed vector) all five methods (NumPy dense Cholesky, SuperLU
log_det_sparse_spd_from, JAX Cholesky x64, slogdet, diagonal scaling) agree with a 50-digit mpmath reference to ≤ 2e-8 on each backend's ownH. The whole 118.76 gap is because the two backends build differentH(max|H_np − H_jax| = 3.21 on max|H| 31.9; same sparsity pattern).Chain:
mapper_util.adaptive_pixel_signals_from(mapper_util.py ~54–74) divides each pixel's summed signal bypixel_counts, and that count includes mapping entries with zero weight. In this setup the mesh is a stride-2 subsample of the data grid, so 1,375 data points sit exactly on vertices with weights (0, 0, 1). For such a point any surrounding triangle is valid; scipyfind_simplexand the JAX walk (pix_indexes_delaunay_walk_from, delaunay.py ~303) choose different ones in 1,114 of 5,496 rows — both geometrically correct (residual 2e-16). The differing zero-weight neighbours change the counts →pixel_signals(max diff 0.23) → AdaptSplit weights (0.53) →H(3.2). Zero weights add nothing to the mapping matrix, which is why chi², curvature, data vector and curvature log-det stay identical. Given identical weights, the split-stencil tables and the numba/JAXHbuilders already agree to 1e-14.Simulated fix (count only non-zero-weight entries): signals agree to 4e-15,
Hto 4.8e-13, log-det 2468.446318 on both backends (Δ 4e-8). Over 20 prior draws: today every draw differs (1.4–822); with the fix 12/20 agree to ≤1e-6 and 2 more to <1e-3; the remaining 6 (max(coeff)⁴ from 5e6 to 3.4e9) still differ by 5e-3–2.2 because the absolute 1e-8 diagonal jitter makes the constant mode's eigenvalue round-off-dominated (cond 1e16–1e17) — exact-arithmetic log-dets of the two near-identicalHdiffer, and PD-ness itself is decided by round-off. No factorization or scaling fixes that regime. No non-PD failures occurred at 125×125 (0/20 either backend); the "~1 in 4" figure was the 512×512 runs.Amended plan
adaptive_pixel_signals_from, require a non-zero weight invalid(& (pixel_weights != 0)), feeding both the masked weights and the counts. Works on both backends, differentiable, matches the docstring's "number of mapped data points". The NumPy evidence changes wherever zero-weight entries exist (Delaunay points on vertices/edges, possibly rectangular bilinear corners) — state this in the PR.log_det_sparse_spd_from, JAX dense Cholesky are accurate whereverHis well-posed).test_mapper_util.py: pixel signals from the scipy and JAX tables agree to atol 1e-12 on the synthetic 16×16 grid with a stride-2 mesh and0.01*sin(7*grid)distortion (today 48/256 rows differ, signals differ by 0.10); plus a pure-NumPy case of one on-vertex point described by two triangles with weights (0,0,1). (b)test_log_det_sparse.py(JAX-marked): AdaptSplit on that geometry with moderate coefficients (inner 0.4, outer 1.7, signal_scale 0.3):Hparity rtol 1e-12, NumPy and JAXlog_det_regularization_matrix_termagree <1e-6 and match a high-precision reference <1e-6. Do not pin parity at high coefficients.1e-8 * max(regularization_weights**2)); that changes evidence values, so it is separate work. The 512×512 pair (2576.59 vs 2574.73) may be partly this effect; not run (RAM).Witness unchanged: numba vs JAX agree <1e-3 on the Aris setup at moderate coefficients. Scripts: session 3e3dea38 scratchpad
ld_investigation/(temporary; the synthetic case will be committed as the test).Jammy2211 commentedon Oct 15, 2026
Patch drafted (not yet applied — PyAutoArray claim held by #626)
Built and verified in a throwaway copy of main a84f724 (no repo touched). Fix in
autoarray/inversion/mappers/mapper_util.pyadaptive_pixel_signals_from:(masked on the flattened weights so the existing
test_abstractmock with(9,)weights for(9,1)mappings keeps passing; there is no numba/JAX twin of this function; no log-det code touched.)test_mapper_util.py::…scipy_and_jax_delaunay_tables__agree(JAX)test_mapper_util.py::…vertex_point_via_two_triangles__identical(NumPy)test_log_det_sparse.py::…vertex_coincident_adapt_split__numpy_matches_jaxpytest test_autoarray/inversion: 935 passed, 4 xfailed; rest oftest_autoarray: 1099 passed; black clean. No existing expectation changed. Behaviour note for the PR: evidence values change on both backends wherever data points fall on Delaunay vertices/edges (Overlay / grid-subsample image meshes); workspace parity scripts with pinned evidences may need updating — the smoke leg will show which.Next: once #626 merges and frees the claim,
/start_libraryon this task and apply the patch (session 3e3dea38 scratchpadpatch_627.patch, temporary) ontofeature/adaptsplit-logdet-backend-parity, then ship under the gate.Jammy2211 commentedon Oct 15, 2026
Parked at the review leg (--auto): no PR opened
This
--autorun parked at the review leg, so no PR was opened. Branchfeature/adaptsplit-logdet-backend-parityis pushed atd78794e3: the drafted patch, plusCloses #627and the Fable co-author trailer. It is based on7cc71238, which includes #630.Gate legs
pyauto-heart smoke autogalaxy autolens autolens_test euclid howtolensran 151 scripts and notebooks, with 0 failures.release validation FAILED (stage integrate). That RED is covered by the development override above.The finding (I verified it): the change reaches well beyond Delaunay meshes, which the commit message does not say.
KNNBarycentricbuilds its weights inbarycentric_weights_from_3_nearest(autoarray/inversion/mesh/interpolator/knn.py:314). It clips negative barycentric weights to exactly 0. That happens for every query outside its 3-nearest triangle, so whole regions are affected, not just the rare points that sit exactly on a vertex.pixel_counts. For a typicalKNNBarycentricfit, that changes the pixel signals, the AdaptSplit H matrix and the evidence.A merge would therefore commit to two behaviour decisions, not one. Both need a human choice; the autonomy gate does not let an
--autorun make them:KNNBarycentricevidence changes broadly and gains these jumps.My recommendation is (b): it keeps the parity fix and avoids introducing a discontinuity in a mesh used for gradient-based fits. The choice is yours.
Witness status: only the synthetic part has been checked on this branch.
To resume: give a decision (a, b or c), then
/start_library adaptsplit-logdet-backend-parity(the worktree and branch are kept), then/ship_library.Jammy2211 commentedon Oct 15, 2026
Human decision: option (b), un-parked
The live human chose option (b) via AskUserQuestion in session 3e3dea38: "Delaunay only (Recommended by review)".
Zero-weight mapping entries will be left out of the adaptive pixel-signal counts for Delaunay tables only. KNNBarycentric and rectangular meshes keep today's counting. The task resumes on
feature/adaptsplit-logdet-backend-paritywith--auto, under the same development override. It stops at PR-open.fix: exclude zero-weight Delaunay mappings from adaptive pixel signals
Jammy2211 commentedon Oct 15, 2026
Library PR Created
PR: #631, head
f052e381, labelpending-release.This
--autorun stops at PR-open. Merging is a human/prm.Scope: Delaunay only, as the human chose: "Delaunay only (Recommended by review)".
adaptive_pixel_signals_from(..., exclude_zero_weight_mappings=False)gets a new keyword.exclude_zero_weight_mappingsis a new interpolator class attribute. It is True for Delaunay and DelaunayNN, and False for KNN, KNNBarycentric and rectangular meshes.d78794e3intof052e381and pushed the unmerged feature branch with--force-with-lease.API changes: no signature break. One keyword and one class attribute are added. Evidence values change, the same way on both backends, but only for Delaunay and DelaunayNN meshes that have data points exactly on vertices or edges. The independent reviewer confirmed KNN, KNNBarycentric and rectangular are bit-identical to main.
Gate
release validation FAILED (stage integrate)), under the recorded development override.Witness: PASS on the synthetic ill-conditioned AdaptSplit test:
The Aris cache was not re-run on this branch. The investigation's simulated fix gave 2468.446318 on both backends.
Workspace impact: recommendation (iii), no workspace change needed.
adaptive_pixel_signals_fromdirectly./start_workspaceis needed.Campaign: at close-out, copy the before/after witness into PyAutoPulse#62, task
interferometer_large_mask_aris.Jammy2211 commentedon Oct 15, 2026
Shipped
Merged in #631 (merge 0377eea; commits f052e38 fix + da5f6ce test guard). Pending release.
Root cause. The two backends built different AdaptSplit
Hmatrices.adaptive_pixel_signals_fromcounted zero-weight mapping entries, and scipy and the JAX Delaunay walk choose different triangles for data points lying exactly on a vertex or edge, so those zero-weight entries landed on different pixels. Every log-det factorization agreed to 2e-8 on the sameH.Fix. A new
exclude_zero_weight_mappingskeyword, driven by an interpolator class attribute:Trueon InterpolatorDelaunay / InterpolatorDelaunayNN,Falseon the base interpolator and KNN. The scope is Delaunay only, per the human's decision (b) above: KNNBarycentric clips negative weights to exactly 0 across whole regions, so excluding zeros there would change whole-region signals and add a discontinuity.Witness. PASS on a synthetic ill-conditioned AdaptSplit problem:
Hparity 3.6e-15, log-dets 2.1e-9 apart, and both within 1.7e-9 of a 50-digit reference. Aris was not re-run.Behaviour change. Evidence values change on Delaunay / DelaunayNN meshes where data points sit exactly on mesh vertices or edges. KNN, KNNBarycentric and rectangular meshes are bit-identical.
Out of scope (unfiled). The absolute 1e-8 jitter in the split regularization builders makes the log-det round-off-dominated once max(coeff)^4 ≳ 1e6.
Gate: tests PyAutoArray 2039 passed / 4 xfailed, PyAutoGalaxy 1357, PyAutoLens 832 / 1 xfailed; smoke 151/151; independent review CLEAN on f052e38; Heart RED under the recorded development override, merged by human
/prm. The first CI run went red on the no-JAX leg (the new KNN test lackedimportorskip("jax")). da5f6ce fixed it before merge.Campaign: PyAutoPulse#62 (
interferometer_large_mask_aris). Next on the PyAutoArray claim: #628.