fix: return the polished raw-forward PDIP iterate as the forward value (#594) - #595
Merged
Merged
Conversation
…#594) Fixture mge_solver_reference_systems.npz: the 8 #571 SLaM systems and the euclid vis_lp system with fnnls x_ref, copied from the autolens_profiling solver corpus at 3ad68af. The new test gates inactive-column, total and source-column flux at 1e-3, jit == eager, primal == differentiated forward, and finite non-zero gradients. Red on bd03e09 (12/107): euclid inactive + total flux, source flux on k1, k2, k3, k5, on both the library entry and the dispatch path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#594) The raw mode's data-scaled KKT stop also judges complementarity, so a column fnnls holds at zero kept x ~ tol / z: 11.5 % of the fnnls flux on inactive columns of the euclid vis_lp system. The #573 polish (<= 10 tight warm-started PDIP iterations on the Jacobi system) now feeds the forward value as well as the backward pass, through one _raw_forward_polished helper shared by the custom_vjp primal and fwd rule, so plain, jitted and differentiated calls agree bit-for-bit. converged / iterations remain the raw forward solve's; RAW_POLISH_MAX_ITER added, RAW_BACKWARD_POLISH_MAX_ITER kept as an alias. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…association (#594) The Python 3.12 Actions leg reproduced eager to 5.4e-11 relative on every fixture system while 3.13 and local runs were bit-exact (PR #595). The end-to-end check is now a tight allclose (rtol 1e-9, atol 1e-12 * max|x|); the solver-alone and primal-vs-vjp checks stay bit-exact. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
CI 3.12 leg was red on |
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #594 (epic
linear-solver-programme, phase 2). The mapper-less positive-only solver (nnls_preconditioning_no_mapper: raw, #572) stops on an absolute infinity-norm KKT residual with a data-scaled tolerance, so complementaritys·zis judged against a threshold scaled bymax|q|and a column fnnls holds at zero can keepx ≈ tol/zwhile reporting converged. Phase 1 (autolens_profiling#355) measured this on 81 captured systems: raw PDIP is converged 81/81 with objective gap ~1e-12, yet its source-column flux is >1e-3 off fnnls on 53/81 systems and on the euclid vis_lp system 11.5 % of the reference amplitude sits on reference-inactive columns (total_source_flux+5.76 %, the red euclid latent test since 2026-09-25). Log-likelihood, objective and KKT are all blind to it.The fix returns the #573 (PR #574) polished iterate — ≤ 10 tight-tolerance PDIP iterations on the Jacobi-preconditioned system, warm-started from the raw iterate — as the forward value, not only as the start of the backward relaxed-KKT solve.
_raw_forward_polished()is shared by the custom_vjp primal and forward sojit,gradand eager return the identical value; the backward pass is unchanged; the polish falls back to the raw iterate under the sameokrule as before. Measured on the 81-system corpus with this branch:pdip_rawis now bit-identical to phase 1'spdip_raw_polishcandidate on 81/81 (all 48 SLaM points converged), worst inactive-column flux 3.3e-4 (was 0.115), source-flux misses 1/81 (was 53/81; the remaining one is the euclid source-column proxy, see below), polish accepted every time in 1–7 iterations (mostly 5). Euclidtotal_source_fluxjit-vs-eager is now +7.47e-5 (was +5.76e-2) andtests/test_compute_latent_variable.pypasses 19/19 on the pipeline's main with no config override.Not admissible under phase 1's pre-registered rule as written (criteria 2–4 still fail: the euclid source-column proxy at 4.96e-2 because its single active source column carries ~0.4 % of the reference flux, one flat-direction system at
amp_rel_max_sig0.219 shared by every accurate candidate incl. tight solves, and 5/52 KKT ratios at the 3e-16 floating-point floor). Those are recorded rule weaknesses, not solver defects; the regression test gates on the flux metrics that the phase-1 evidence supports.API Changes
No public signature changes. The raw-mode forward reconstruction (
reconstruction_positive_only_from(..., preconditioning="raw")andsolve_nnls_primal_raw_forward) now returns the polished iterate, so mapper-less (MGE / linear light profile) JAX reconstructions and every latent derived from them move at the ~1e-4 relative level on affected systems (euclidtotal_source_flux−5.4 %; log-likelihoods move at ~1e-11 relative — autolens_workspace_test pins unchanged,mge_group.pyJIT now equals NumPy to 6e-12).converged/iterationsstill describe the raw forward solve;raw_forward_backward_statusstill reports the polish flag and count.RAW_POLISH_MAX_ITERadded;RAW_BACKWARD_POLISH_MAX_ITERkept as an alias.See full details below.
Test Plan
test_nnls_raw_forward_amplitude.pyfails 12/107 on the basebd03e09e(euclid inactive-column + total flux 1.152e-1; source flux k1 1.638e-3, k2 1.220e-3, k3 4.044e-3, k5 6.442e-3 vs 1e-3), both call paths; green 107/107 after the fixtest_nnls_mge_convergence.py+test_jax_nnls.pyunchanged green (173 with the new file); fullpytest test_autoarray1890 passed; black clean on changed filesreconstruction_positive_only_from;jax.vjpoutput == plain call;jax.gradfinite and non-zero on all 9 fixture systemstests/test_compute_latent_variable.py19/19 with this branch (no edit, no override);total_source_fluxjit-vs-eager +7.47e-5scripts/lens/solver/accuracy.py(+--posthoc) on 81 systems:pdip_raw==pdip_raw_polish81/81, 48/48 SLaM convergedjax_likelihood/{lp,mge_group,rectangular_mge,rectangular_mge_rtu,delaunay_mge}.py, interferometerjax_likelihood/delaunay_mge.py,jax_grad/pixelization.pyall pass; no pin movedFull API Changes (for automation & release notes)
Changed
autoarray.util.jax_nnls.solve_nnls_primal_raw_forward(...)/reconstruction_positive_only_from(..., preconditioning="raw")— the forward value is now the polished iterate (≤RAW_POLISH_MAX_ITERtight warm-started PDIP iterations on the Jacobi system, falling back to the raw iterate when the polish does not converge or leaves the interior). Reconstructions and derived latents on mapper-less positive-only JAX inversions move at ~1e-4 relative on ill-conditioned MGE systems; log-likelihoods at ~1e-11.convergedanditerationsunchanged in meaning (raw forward solve).autoarray/config/general.yamlnnls_preconditioning_no_mappercomment,Settings.nnls_preconditioning_no_mapperdocstring — describe the polished forward value.Added
autoarray.util.jax_nnls.RAW_POLISH_MAX_ITER = 10(RAW_BACKWARD_POLISH_MAX_ITERkept as an alias)autoarray.util.jax_nnls._raw_forward_polished(Q_pc, q_pc, Q, q, D, solver_tol, max_iter)(private; shared by the custom_vjp primal and forward)test_autoarray/inversion/inversion/files/mge_solver_reference_systems.npz— 8 fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571 systems + euclid vis_lp with fnnlsx_ref(from the autolens_profiling phase-1 corpus at3ad68af);test_nnls_raw_forward_amplitude.pyRemoved
Migration
rawmode before this change move by up to ~5 % on systems where the raw stop left flux on inactive columns (euclid-like); re-pin against the polished value.Heart RED override (development only)
feature/raw-pdip-forward-polishdirectly (~16:35 BST) in response to the reported RED reasons, after the auto-mode classifier denied the agent push. Scope: development shipping (push + this pending-release PR); merge only via a separate/prmon all-green required checks; no release.release validation FAILED (stage integrate); YELLOWworkspace validation not passing (0 failed, 1 timeout, cloud#36404726969: autolens_test scripts/multi_dataset/rectangular.py); YELLOWmanifest drift: public front-door organ tables (generated) — 1 mismatch(es) vs PyAutoMind/repos.yaml. None involve PyAutoArray. This PR does not repair Heart; Heart remains RED for release.test_nnls_mge_convergence.py+test_jax_nnls.pyunchanged; fullpytest test_autoarray1890 passed; black clean on changed files; euclid pipelinetest_compute_latent_variable.py19/19 with this branch; corpus 81/81 converged incl. 48/48 SLaM; autolens_workspace_test 7 mapper-less/MGE scripts pass with no pin moved; in-session review (supervised, human-approved plan).Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code