Skip to content

IterFitDpsiSrcImaging underperforms the one-shot fit on the imaging potential-correction example #666

Closed

Description

@Jammy2211

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:

  1. 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";
  2. 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

  1. Re-run the imaging example with x0 warm-started from the one-shot solution and confirm whether the peak snaps back onto the subhalo and the evidence rises above the one-shot.
  2. If it does: fix the example to warm-start (matching the interferometer sibling), and consider whether solve_joint_optimization should warn — or refuse — when cold-started, since the interferometer script already treats warm-starting as required practice rather than an option.
  3. If it does not: this is an engine-level issue in IterFitDpsiSrcImaging and 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 / autogalaxy 2026.7.23.1
  • Linux (WSL2), numpy path (PYAUTO_DISABLE_JAX=0 but the dense kernels default to numpy)
  • Full run: exit 0, 534s

Activity

  1. Jammy2211 commented on Aug 1, 2026

    @Jammy2211
    CollaboratorAuthor

    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 small n_iter budgets — 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 made damping="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 iterative (2026.7.23.1, this issue) iterative (main, post-#676)
    log evidence 9.1506e+03 4.2083e+03 9.0625e+03
    dkappa peak (y, x) (1.45, 0.15) (-0.55, 0.85) (1.45, 0.15)

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions