Repository navigation
IterFitDpsiSrcImaging underperforms the one-shot fit on the imaging potential-correction example #666
Description
Summary
On the canonical imaging potential-correction example, al.pc.IterFitDpsiSrcImaging performs worse than the one-shot al.pc.FitDpsiSrcImaging it is documented to improve on: it places the dkappa peak far from the true subhalo and reports a substantially lower Bayesian evidence.
Surfaced while validating a file move in autolens_workspace (PyAutoLabs/autolens_workspace#389, PR #393). That PR changed zero code lines — its diff is three docstring hunks — so this is pre-existing behaviour, not a regression from it.
Observed
Running autolens_workspace:scripts/imaging/features/advanced/potential_correction/start_here.py unmodified, under the release profile (PYAUTO_TEST_MODE=1, PYAUTO_SMALL_DATASETS released by the script's ENV: full_datasets declaration), on autolens 2026.7.23.1:
joint source + dpsi log evidence = 9.1506e+03
dkappa peak at (y, x) = (1.45, 0.15) — true subhalo at (1.41, 0.00)
iterative Laplace log evidence = 4.2083e+03
iterative dkappa peak at (y, x) = (-0.55, 0.85)
The simulation is seeded (noise_seed=1), so this is reproducible.
- One-shot: peak at (1.45, 0.15), ~0.16" from the true subhalo at (1.41, 0.00). Good recovery.
- Iterative: peak at (-0.55, 0.85) — roughly 2.2" away, on the far side of the lens centre. Not a refinement of the one-shot solution; a different answer entirely.
- Evidence: 4.21e3 iterative vs 9.15e3 one-shot. The iterative result is disfavoured by ~4.9e3 in log evidence.
Why this is worth looking at
The script's own prose states the iterative engine
optimizes the combined state [source | dpsi] with a Levenberg-Marquardt loop, and after every accepted step re-ray-traces the image grid through the corrected lens … The corrections thus feed back into the source mapping, capturing compact perturbers more faithfully than a single linearization.
On this example it does the opposite. Either the claim needs qualifying, or the engine's configuration here is wrong, or there is a genuine defect. The evidence gap is large enough that it is not a tie-break — the method's own model-selection statistic ranks the iterative solution well below the one-shot.
Note the interferometer sibling (scripts/interferometer/features/advanced/potential_correction/start_here.py) deliberately does two things this imaging example does not:
- warm-starts the iterative engine from the one-shot solution (
x0=fit.src_dpsi_slim), with the stated rationale that it "then refines inside the right basin instead of searching from zero"; - lets the evidence re-optimize the regularization strengths each step (
reg_optimize_every=1).
The imaging example calls iter_fit.solve_joint_optimization() with no x0 and with fixed regularization, i.e. cold-started from zero. That is the most likely explanation — the engine is finding a different, worse basin — which would make this primarily a usage/example problem rather than an engine defect. It is also possible n_iter=5 is simply too few to converge.
Suggested triage
- Re-run the imaging example with
x0warm-started from the one-shot solution and confirm whether the peak snaps back onto the subhalo and the evidence rises above the one-shot. - If it does: fix the example to warm-start (matching the interferometer sibling), and consider whether
solve_joint_optimizationshould warn — or refuse — when cold-started, since the interferometer script already treats warm-starting as required practice rather than an option. - If it does not: this is an engine-level issue in
IterFitDpsiSrcImagingand needs a closer look at the LM loop and the gauge constraints.
Either way the docstring claim in the imaging example should be qualified until the behaviour matches it.
Environment
autolens/autoarray/autofit/autogalaxy2026.7.23.1- Linux (WSL2), numpy path (
PYAUTO_DISABLE_JAX=0but the dense kernels default to numpy) - Full run: exit 0, 534s
Activity
Jammy2211 commentedon Aug 1, 2026
Fixed by #676 (merged, pending release) — verified on the exact reproducer.
Root cause, established in the #672 validation: the iterative engine's Marquardt damping (
mu*diag(H)), chosen for interferometer curvature scales, under-converges dramatically from a cold start within smalln_iterbudgets — the upstream author's original code uses identity damping (mu*I) and converges in ~3 iterations from cold. On the 200x200 demo dataset the two engines match to 4 significant figures per iteration once damping matches, so this was a convergence-rate configuration issue, not an implementation bug. #676 madedamping="identity"the imaging default (interferometer keeps Marquardt).Re-ran this issue's reproducer (
imaging/features/advanced/potential_correction/start_here.py, release profile, unmodified) on source main:One-shot numbers reproduce this issue's exactly; the iterative fit now localizes the subhalo at the same peak as the one-shot, 0.16" from the truth. The remaining ~88-nat evidence gap to the one-shot is the known small-budget/evaluation nuance discussed in the #672 review report, not the collapse reported here. The fix reaches PyPI with the next release.