Repository navigation
Description
Overview
The released mapper-less positive-only solver (nnls_preconditioning_no_mapper: raw, #572) stops on a data-scaled absolute KKT residual, so columns fnnls holds at zero keep x ≈ tol / z while reporting "converged": the euclid total_source_flux latent traces +5.76 % off under jax.jit and the euclid latent test has been red since 2026-09-25, invisible to logL, objective and KKT. Phase 1 (autolens_profiling #355) measured this on 81 systems — raw PDIP is converged 81/81 yet its source-column flux is >1e-3 off fnnls on 53/81, and it leaves 11.5 % of the reference amplitude on reference-inactive euclid columns — while applying the #573 polish to the forward value gave 0/81 unconverged, worst inactive-column flux 3.3e-4, source-flux misses 1/81 and euclid latent +7.5e-5 at a median cost of +5 iterations. Decision: return the polished iterate (≤ 10 tight warm-started Jacobi-system PDIP iterations, already in the library from #573 for the backward pass) as the forward value, in both the custom_vjp forward and the primal so jit, grad and eager agree.
Plan
- Factor the raw forward solve + fix: NaN JAX gradient on MGE positive-only solves after #572 #573 polish into one helper in
autoarray/util/jax_nnls.pyand return the polished iterate (falling back to the unpolished one when the polish is not ok) as the forward value, from both the custom_vjp forward and the primal; backward pass unchanged. - Keep
converged/iterationsas the raw forward solve's; keep theRAW_BACKWARD_POLISH_MAX_ITERname (imported downstream) and addRAW_POLISH_MAX_ITERas the preferred alias. - Update the module and function docstrings, the
nnls_preconditioning_no_mappercomment ingeneral.yaml, and thesettings.py/inversion_util.pydocstrings to describe the polished forward value and why. - Add a regression fixture (the 8 fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571 systems + the euclid vis_lp system, with fnnls
x_ref) and a jax-only amplitude test gating on inactive-column flux, total flux and source flux at 1e-3 plus jit==eager and finite non-zero grad — run red-first on unfixedd4298445and record the failing cases here before the fix. - Keep
test_nnls_mge_convergence.pyandtest_jax_nnls.pygreen unchanged; full PyAutoArray pytest. - Verify downstream: the euclid latent jit test passes on the fixed library with no config override; autolens_profiling accuracy re-run (81/81 converged, inactive-column worst ≤ 3.3e-4) becomes the ledger row after the library merge; sweep workspace_test mapper-less likelihood pins for moved values (re-pin is a follow-up).
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary)
- autolens_profiling (downstream: ledger row in
results/notes/linear_solver_accuracy_2026_09.md+ campaign page + regenerated README stats, after the library merge) - euclid_strong_lens_modeling_pipeline (verification only, no edit)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoArray | main (d429844) | clean — parallel claim with streaming-p1-array-free-dataset, disjoint files (human-approved 2026-09-30) |
| ./autolens_profiling | main (3ad68af) | 1 untracked dir (dataset/abell_1201/) |
| ./euclid_strong_lens_modeling_pipeline | main (26e4385) | clean |
Suggested branch: feature/raw-pdip-forward-polish
Deviation from the prompt
The prompt's witness thresholds (amp_rel_max ≤ 1e-3, flux_rel ≤ 1e-4) are not achievable by any candidate: amp_rel_max_sig worst 0.219 is shared by every accurate candidate (fnnls reference noise on near-flat directions), and the euclid "source columns" metric is +4.96e-2 for polish only because its three source columns carry negligible reference flux (the end-to-end latent is +7.5e-5). The regression test therefore gates on inactive-column flux and total/source flux at 1e-3, per system, chosen from the phase-1 rows before the library test is written, and drops the amplitude-max criterion. Recorded as a rule weakness in the phase-1 ledger already.
Implementation Steps
autoarray/util/jax_nnls.py- Factor the raw forward + polish out of
_raw_forward_backward_point(:196-234) into_raw_forward_polished(Q_pc, q_pc, Q, q, D, solver_tol, max_iter)returning(y_out, converged, pdip_iter, (yp, sp, zp), ok, polish_iter)wherey_out = where(ok, yp, y)— the sameokrule as today (polish converged, finite,sp>0,zp>0), falling back to the unpolished iterate. _raw_forward_backward_pointcalls it, runssolve_relaxed_nnlsfrom(yp, sp, zp)as now, and returns(y_out, converged, pdip_iter)asout— the polished forward value._solve_nnls_raw_forward_with(:237-296): the primal (:276-281) must call the same_raw_forward_polishedand returny_out / …exactly as the fwd does, sojit,gradand eager agree bit-for-bit. Backward unchanged.convergedstays the raw forward solve's flag;iterationsstays the forward count (statusalready carriespolish_iter,ok). Keep the nameRAW_BACKWARD_POLISH_MAX_ITER(imported by autolens_profiling_solvers.py:248); addRAW_POLISH_MAX_ITER = RAW_BACKWARD_POLISH_MAX_ITERand prefer the new name internally.- Rewrite the module docstring (:30-34) and the
_solve_nnls_raw_forward_withdocstring (:262-263): the polish is now forward and backward; say why (complementarity under the data-scaled stop leavestol/zon inactive columns; phase-1 numbers).
- Factor the raw forward + polish out of
autoarray/config/general.yaml:11comment fornnls_preconditioning_no_mapperandautoarray/settings.py:227-237docstring: describe the polished forward value.inversion_util.py:362-364note onconverged/iterations if it mentions the unpolished iterate.- Tests
- New fixture
test_autoarray/inversion/inversion/files/mge_solver_reference_systems.npz(≈180 KB): the 8 fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571 systems + the euclid vis_lp system copied from autolens_profilingresults/lens/solver/corpus/{slam_fixture_571,euclid_vis_lp}.npzwithx_ref(fnnls) and ametaJSON carryingsource_column_index_list,max_abs_q, group, provenance (corpus commit 3ad68af). Add a paragraph tofiles/README.md. - New
test_autoarray/inversion/inversion/test_nnls_raw_forward_amplitude.py(jax-only, mirrorstest_nnls_mge_convergence.pyskips/EPSILON guard). Per system, through the library entry pointsolve_nnls_primal_raw_forward(built asinversion_util.py:462-481builds it) and throughreconstruction_positive_only_fromwithpreconditioning="raw":converged == 1,iterations < 50, finite;flux_inactive_rel = Σx[x_ref ≤ 1e-6·max x_ref] / Σx_ref ≤ 1e-3(raw is red on euclid: 0.115);|Σx − Σx_ref| / Σx_ref ≤ 1e-3(raw red on euclid);|flux_rel_source| ≤ 1e-3on the 8 fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571 systems (raw red on k1, k2, k3, k5: 1.6e-3…6.4e-3; polish ≤ 5e-5) — euclid excluded from this one metric with the reason above;jax.jit(f)(…)equals eagerf(…)(assert_array_equal) andjax.gradfinite/non-zero on every system (extends the fix: NaN JAX gradient on MGE positive-only solves after #572 #573 tests to the new fixture).- Docstring records the thresholds, the phase-1 values they were chosen from, and which cases are red on
d4298445.
- Existing
test_nnls_mge_convergence.pyandtest_jax_nnls.pymust stay green unchanged (the objective/control/gradient assertions all tighten under polish;test__control__well_conditioned_pdip_unchangedrequires Jacobi mode bit-identical — untouched). - Red-first: run the new test on unfixed
d4298445and record the failing cases on the issue before the fix commit.
- New fixture
- Full PyAutoArray pytest,
jax_grad-style guards: none new.
Verification beyond the library
- euclid witness (no edit): in the canonical
lens/euclid_strong_lens_modeling_pipeline(clean, main), runpytest tests/test_compute_latent_variable.py -k traces_under_jax_jitwith the worktree PyAutoArray first onPYTHONPATH; must pass atJIT_VS_EAGER_REL = 1e-3with no config override (expected +7.5e-5). Also run the full file to confirm the NumPy path is untouched. - SLaM 48/48 stays converged + corpus re-measure: in the autolens_profiling worktree, run
scripts/lens/solver/accuracy.py --posthocwith the fixed PyAutoArray (its library-entry candidate callssolve_nnls_primal_raw_forward,_solvers.py:163-167); expect 81/81 converged, inactive-column worst ≤ 3.3e-4, euclid latent viaeuclid_latent.pywithin 1e-3. This is the downstream row. - workspace_test pins: grep
lens/autolens_workspace_testfor mapper-less (MGE /light_lplinear) likelihood pins and run those scripts under the smoke profile with the fixed library; report any value that moved (re-pin is a follow-upship_workspace, not this PR). - Standard:
ruff,black --checkper repo convention, fullpytest test_autoarray.
Key Files
autoarray/util/jax_nnls.py—_raw_forward_backward_point,_solve_nnls_raw_forward_with, new_raw_forward_polished, module docstringautoarray/config/general.yaml—nnls_preconditioning_no_mappercommentautoarray/settings.py— preconditioning docstringautoarray/inversion/inversion/inversion_util.py— raw branch /convergednotetest_autoarray/inversion/inversion/test_nnls_raw_forward_amplitude.py— new amplitude regression testtest_autoarray/inversion/inversion/files/mge_solver_reference_systems.npz+files/README.md— new fixturetest_autoarray/inversion/inversion/test_nnls_mge_convergence.py,test_autoarray/util/test_jax_nnls.py— must stay green unchanged
Original Prompt
Click to expand starting prompt
Linear-solver programme phase 2: fix the raw-forward PDIP amplitude bias in PyAutoArray
Type: bug
Target: PyAutoArray
Repos:
- PyAutoArray
- euclid_strong_lens_modeling_pipeline
- autolens_profiling
Difficulty: medium
Autonomy: supervised
Priority: high
Status: formalised
Filed: 2026-09-30
Blocked-by: none (the PyAutoArray claim ofsparse-data-none-guardcleared 2026-09-30 —complete/2026/09/sparse-data-none-guard.md, PyAutoArray#591 merged)
Witness: a PyAutoArray regression test loads the phase-1 corpus npz (copied from autolens_profilingresults/lens/solver/corpus/as a fixture besidetest_autoarray/inversion/inversion/files/mge_slam_nnls_systems.npz) and asserts, per system, converged AND amplitude agreement with fnnls (amp_rel_max ≤ 1e-3, source flux_rel ≤ 1e-4) — an amplitude assertion, not logL — red on unfixed main; plus euclidtests/test_compute_latent_variable.py::test_latent_euclid_variables_traces_under_jax_jitpasses on library main with no config override.
Review-minutes: 3
Consequence: glance
Unattended: needs-slicing
Epic linear-solver-programme, phase 2. Contract: complete/2026/09/linear-solver-accuracy-study.md (phase-1 record with the original programme prompt folded in; the phase-1 verdict blocker is satisfied — autolens_profiling#355 merged 2026-09-30).
Symptom
Raw PDIP (PyAutoArray#572, nnls_preconditioning_no_mapper: raw, the released mapper-less
default) stops on the objective (gap 4.7e-6 vs fnnls) with amplitudes 4.3 % off fnnls, so
euclid total_source_flux traces to 3.511 under jax.jit vs 3.320 eager. The euclid latent
test has been red since 2026-09-25. logL cannot see it (Δχ² ~1e-5); amplitude latents shift ~6 %.
Fix
Candidate: phase 1 verdict (autolens_profiling#355, complete/2026/09/linear-solver-accuracy-study.md): no candidate passes the pre-registered rule — raw PDIP reports converged on 81/81 (KKT ~3e-14) yet leaves 11.5 % of the reference amplitude on euclid columns inactive in the reference (total_source_flux +5.76 %); jacobi diverges on 29/81. Post-hoc, each of these turns the euclid latent test green: forward polish (+7.5e-5), tol 1e-5 (+5.1e-4), jaxnnls tol with cap > 50 (-3e-8); caps ≤ 16 are unsafe. The binding constraint is the stopping test, not the iteration budget. Choose among these on the post-hoc evidence and gate on inactive-column flux, not logL/KKT.
Implement exactly the candidate the phase-1 pre-registered rule admits (converged 100 %, 48/48
SLaM incl.; worst amp_rel_max ≤ 1e-3; worst source flux_rel ≤ 1e-4; KKT ≤ 10× pdip_jacobi's;
lowest median iterations). The three plausible shapes:
- A tighter data-scaled tolerance constant in
data_scaled_solver_tol
(autoarray/util/jax_nnls.py). - Return the PyAutoArray#573 polished iterate (≤ 10 warm-started Jacobi iterations) as the
forward value in_raw_forward_backward_point/_solve_nnls_raw_forward_with. The
custom_vjp forward must return the same value the primal (solve_nnls_primal_raw_forward)
returns, or jit/grad and eager diverge — polish both or neither. - A KKT / solution-based stop instead of the objective-gap stop.
Also touch the inversion_util.py raw branch if the candidate needs it, and update the
nnls_preconditioning_no_mapper comment in general.yaml to describe the new behaviour.
Constraint
The 48/48 SLaM source_lp[1] points must stay converged (#571); the existing
mge_slam_nnls_systems.npz test must stay green.
Rejected
- Loosening
JIT_VS_EAGER_RELin the euclid test. - A pipeline config stopgap to
jacobi(reintroduces fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571).
Downstream
- workspace_test mapper-less likelihood pins: re-pin if the forward value moves.
- autolens_profiling: re-run
scripts/lens/solver/accuracy.pypost-fix and append the row to
results/notes/linear_solver_accuracy_2026_09.md(+ campaign page
wiki/campaigns/linear_solver_accuracy.md).
Ownership and order
PyAutoArray owns the solver; euclid_strong_lens_modeling_pipeline owns the witness test (no edit
expected there); autolens_profiling is the corpus fixture source. Library-first: ship PyAutoArray,
then re-verify the euclid test on library main.
🤖 Generated with Claude Code
Activity
Jammy2211 commentedon Sep 30, 2026
Progress — 2026-09-30: implemented locally, red-first verified, euclid witness green
Branch
feature/raw-pdip-forward-polish(worktree~/Code/PyAutoLabs-wt/raw-pdip-forward-polish/PyAutoArray, basebd03e09e), not yet pushed:7e62fa4dtest: red amplitude regression for the raw-forward PDIP forward value — new fixturetest_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, fnnlsx_ref, from the phase-1 corpus at autolens_profiling3ad68af) andtest_nnls_raw_forward_amplitude.py(107 cases, both the library entry point andreconstruction_positive_only_from).31b1c2d7fix: return the polished raw-forward PDIP iterate as the forward value —_raw_forward_polished()shared by the custom_vjp primal and forward so jit/grad/eager agree bit-for-bit; backward unchanged;RAW_POLISH_MAX_ITERadded,RAW_BACKWARD_POLISH_MAX_ITERkept as alias; docstrings,general.yamlandsettings.pywording updated.Red on the unfixed base (12/107, both call paths): euclid inactive-column flux 1.152e-1 and total flux 1.152e-1; source flux k1 1.638e-3, k2 1.220e-3, k3 4.044e-3, k5 6.442e-3 (threshold 1e-3). jit-vs-eager, primal-vs-vjp and gradient cases were already green.
After the fix: 107/107; every system converges in 16–24 forward iterations with the polish accepted in 5–6; euclid inactive-column flux 3.31e-4, others ≤ 3.4e-7; source flux on k0–k7 ≤ 4.93e-5.
test_nnls_mge_convergence.py+test_jax_nnls.pyunchanged green; fullpytest test_autoarray1890 passed; black clean on the changed files.Euclid witness (no edit, no config override):
tests/test_compute_latent_variable.py19/19 with the worktree library, includingtest_latent_euclid_variables_traces_under_jax_jit;total_source_fluxjit-vs-eager +7.47e-5 (base +5.76e-2),magnification−7.47e-5, other latents ≤ 1.1e-8.Notes: the jit-vs-eager check on the bare entry point builds the Jacobi scaling outside the traced function (XLA reorders
1/sqrtwithx/Dby 1 ULP on the base — not solver behaviour); the end-to-end path stays bit-exact. Euclid is excluded from the source-column flux metric: its single active source column carries ~0.4 % of the reference flux, so that metric reads +4.96e-2 even polished; the end-to-end latent test covers it.Next: corpus re-measure (48/48 SLaM must stay converged) and workspace_test mapper-less pin sweep, then
/ship_library.Jammy2211 commentedon Sep 30, 2026
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.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
Jammy2211 commentedon Sep 30, 2026
Library PR Created — 2026-09-30
PR: #595 (head 31b1c2d, label pending-release), opened under the Heart RED development override recorded below and in the PR body. Merge stays with
/prmon green checks; no release.Workspace impact: option (ii) — autolens_profiling needs its phase-2 ledger row, campaign page and README stats from the post-fix accuracy run (results re-measured in the task worktree, same-stamp collision v2026.8.17.1 to resolve), and
scripts/lens/solver/euclid_latent.py's validation leg is pinned to the pre-fix jit value 3.511 and must be re-based to the eager 3.3199. euclid_strong_lens_modeling_pipeline: no edit (its latent test already passes with this branch). autolens_workspace_test: no pin moved, no edit.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).test: end-to-end jit-vs-eager check tolerates runner-dependent XLA re…
Merge pull request #595 from PyAutoLabs/feature/raw-pdip-forward-polish
Jammy2211 commentedon Sep 30, 2026
Library PR merged — 2026-09-30
PyAutoArray#595 merged (merge commit 7a89e19, head 42c5235) via
/prmon green CI (unittest 3.12 / 3.13 / no-jax). Pending release: not on PyPI yet. Heart remained RED for release throughout (release validation FAILED (stage integrate), unrelated).Issue stays open for the workspace phase in autolens_profiling: commit the post-fix corpus re-run with a non-colliding label (the cells stamp v2026.8.17.1 on source checkouts and overwrote the pre-fix files), re-base
scripts/lens/solver/euclid_latent.py's validation leg from the pre-fix jit 3.511 to the eager 3.3199, append the phase-2 ledger row + campaign page + README stats, then/ship_workspacebehind the library-first gate.Jammy2211 commentedon Sep 30, 2026
Workspace phase ready — autolens_profiling, held at Heart RED ship gate (2026-09-30 ~18:50 BST)
Branch
feature/raw-pdip-forward-polishin autolens_profiling (worktree~/Code/PyAutoLabs-wt/raw-pdip-forward-polish/autolens_profiling), 4 commits on3ad68af, headad365df, all gates green locally, NOT pushed, no PR.Heart readiness (verbatim): RED
PyAutoGalaxy: CI failure; REDrelease 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 in autolens_profiling. Human override + push needed (auto-mode denies the agent's first push of a branch).Drafted PR body
Summary
Phase 2 of epic
linear-solver-programmeon the profiling side (PyAutoArray#594; library fix PyAutoArray#595 merged 2026-09-30 as7a89e19a0, pending release). The library now returns the #573 polished iterate as the raw-forward PDIP forward value. This PR records that in the solver programme: everyscripts/lens/solver/cell re-run against merged library main (device.provenance.library_revisionsnames PyAutoArray7a89e19a0), the euclid latent cell's validation leg re-based from the pre-fix jit value 3.511093374 to the post-fix 3.320127604 measured on main, the ledger's Phase 2 section, campaign page + index row, and regenerated README tables.Measured on the 81-system corpus: fixed
pdip_rawequals phase 1'spdip_raw_polishon every metric on 81/81 systems — converged 81/81 incl. 48/48 SLaM, forward iterations median 18 / max 24 (+1–7 polish, accepted on all), worst inactive-column flux 3.31e-4 (pre-fix 0.115), worst total-flux error 3.31e-4 (0.115), worst source-flux error 4.96e-2 on euclid only (16.4; ≤ 4.9e-5 on the #571 fixtures), objective gap 8.2e-16, KKT 3.2e-16. Euclid latent +7.47e-5 vs eager (pre-fix +5.76e-2); the euclid pipeline latent test passes on library main. Under the pre-registered rule the fixedpdip_rawpasses criterion 1 and still fails 2–4 for the recorded rule weaknesses (euclid source-column proxy far stricter than the latent it stands for; one flat-direction system at 0.219 shared by every accurate candidate; 5/52 KKT ratios at the 3e-16 floor) — the ledger says so rather than re-basing the rule.Artefacts: the cells stamp
al.__version__= 2026.8.17.1 on a source checkout, so the re-run overwrote the*_v2026.8.17.1.{json,png}artefacts in place; they are "current library main" by contract, and the pre-fix versions live at3ad68afand in the ledger's phase-1 tables.pdip_raw_tol_1e-2(directsolve_nnls, no polish) now reproduces the pre-fixpdip_raw; the phase-1 sentence claiming it reproducespdip_rawis annotated, not deleted.Upstream PR
Scripts Changed
scripts/lens/solver/euclid_latent.py—PDIP_RAW_JIT_EXPECTEDre-based to the post-fix: return the polished raw-forward PDIP iterate as the forward value (#594) #595 library-main jit value (comment names the PR, merge sha and the replaced pre-fix value); the JSON now records both the released and the library-main jit valuesscripts/lens/solver/_solvers.py—pdip_raw/pdip_raw_tol_1e-2descriptions: polished forward value, forward-only iteration count; tol_1e-2 has no polishscripts/lens/solver/README.md,results/README.md(viabuild_readme.py) — regenerated tables;scripts/misc/tooling/build_readme.py— euclid renderer shows both jit valuesresults/lens/solver/{accuracy,accuracy_posthoc,early_stopping}_summary_{all,slam_fixture_571}_v2026.8.17.1.{json,png},euclid_latent_by_candidate_v2026.8.17.1.json— re-run against PyAutoArray7a89e19a0results/notes/linear_solver_accuracy_2026_09.md— Phase 2 section;wiki/campaigns/linear_solver_accuracy.md,wiki/index.md— status, verdict, PRs, phase-2 row + journal entryTest Plan
ruff check . && ruff format --check .(250 files)build_readme.py --check,check_wiki.py --check,check_results_layout.py --check,build_dashboard.py --check,check_submits.py --checkpytest scripts/misc/test -q— 984 passed, 5 skippedAUTOLENS_PROFILING_SMOKE=1— the 7 lint.yml smokes +early_stopping.py+euclid_latent.py, all rc=0euclid_latent.pyvalidation leg VALIDATES on library main (x_ref → eager rel −3.3e-8; pdip_raw → jit rel −2.0e-8)Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
Jammy2211 commentedon Sep 30, 2026
Workspace PR opened — 2026-09-30
PR: PyAutoLabs/autolens_profiling#357 (head ad365df), opened under the Heart RED development override below (also in the PR body). Library-first gate satisfied (PyAutoArray#595 MERGED). Merge via
/prmon green lint; no release.feature/raw-pdip-forward-polishdirectly, ~18:55 BST, in response to the reported RED reasons). Scope: development shipping (push + this PR); merge only via a separate/prmon all-green required checks; no release.PyAutoGalaxy: CI failure; REDrelease 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 autolens_profiling. This PR does not repair Heart; Heart remains RED for release.--check; pytest scripts/misc/test 984 passed / 5 skipped; 9 smokes rc=0; euclid_latent validation leg validates on library main; in-session review (supervised, human-approved plan).Jammy2211 commentedon Sep 30, 2026
Shipped
Phase 2 of epic
linear-solver-programme— both PRs merged 2026-09-30 by human/prmon green checks.7a89e19a0(head42c52358). Labelpending-release: merged, not yet released.4652580b2(headad365df).What changed
nnls_preconditioning_no_mapper: raw, fix(inversion): raw-forward PDIP for mapper-less positive-only JAX solves, surface convergence (#571) #572) now returns the fix: NaN JAX gradient on MGE positive-only solves after #572 #573/fix: NaN JAX gradient on MGE positive-only solves after #572 #574 polished iterate (≤10 tight warm-started Jacobi-system PDIP iterations) as its forward value, via_raw_forward_polished()shared by thecustom_vjpprimal and forward, so jit / grad / eager agree. Backward unchanged.RAW_POLISH_MAX_ITERadded (old name aliased); docstrings/config wording updated. New fixturemge_solver_reference_systems.npz(8 fix(inversion): JAX PDIP positive-only solve fails to converge on SLaM MGE systems #571 systems + euclid vis_lp, fnnlsx_ref) andtest_nnls_raw_forward_amplitude.py(107 cases; 12 red on basebd03e09e— euclid inactive/total flux 0.115, source flux k1/k2/k3/k5 1.6e-3..6.4e-3; green after). End-to-end jit-vs-eager relaxed to rtol 1e-9 after the 3.12 Actions leg showed 5.4e-11 runner-dependent XLA reassociation.euclid_latent.pyvalidation re-based to the post-fix jit 3.320127604; all 6 solver cells re-run against PyAutoArray7a89e19a0(provenance recorded; artefacts overwrote the v2026.8.17.1 stamp in place, pre-fix at3ad68af); ledger Phase 2 section, campaign page + index, README regen.Results
pdip_raw== phase-1pdip_raw_polishon 81/81.total_source_fluxjit-vs-eager +5.76e-2 → +7.47e-5; euclid pipeline latent test 19/19 on library main with no override.Gates
active.mdandautonomy_log). Heart stayed RED for release throughout (release validation FAILED at stage integrate; later also a PyAutoGalaxy CI failure — unrelated to this change).Pending release
PyAutoArray#595 is merged but unreleased; the Mind completion record carries
pending-release: PyAutoArray@https://github.com/PyAutoLabs/PyAutoArray/pull/595until/review_releaseclears it on a published release.Next
Phase 3:
draft/research/autoarray/mge_nnls_fix_pyautoarray_571_slam_60.md— GPU/A100 timing + parity rows.