Skip to content

feat(autofit): multi-start gradient auto-convergence (phase 1)#1407

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/multistart-gradient-auto-convergence-phase-1
Jul 21, 2026
Merged

feat(autofit): multi-start gradient auto-convergence (phase 1)#1407
Jammy2211 merged 1 commit into
mainfrom
feature/multistart-gradient-auto-convergence-phase-1

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Give the multi-start gradient searches (MultiStartAdam / MultiStartADABelief / MultiStartLion / MultiStartProdigy) a first-class auto-convergence (early-stopping) mode, default ON, so users no longer hand-tune n_steps. Previously AbstractMultiStartGradient ran a fixed n_steps budget with no convergence criterion. n_steps now serves as a hard ceiling / max budget (the search never runs forever).

Mirrors the AutoCorrelationsSettings precedent (Emcee/Zeus early-stop): a new MultiStartGradientConvergence settings object with check_for_convergence=True, a plateau check on the global best figure-of-merit over a trailing window (rtol/atol + min_steps), and an is_test_mode() shrink hook. The check runs per-step (the best-fom history updates every step) while checkpointing/visualization stays at the iterations_per_full_update boundary — so early-stopping works even with the default single-chunk update cadence (iterations_per_full_update=None), at the cost of a cheap NumPy plateau check per step.

Scope: parametric sources only (MGE / Sersic). Pixelized is out of scope — its best-fom climbs in long plateaus punctuated by breakthrough jumps, which a plateau check would false-stop on. So the check is skipped when resurrect=True (the pixelized regime), where the search leans on the n_steps ceiling — behaviour there is unchanged.

Phase 1 of 2. Phase 2 (results contract: converged/stop_reason in samples_info, the fom_history convergence-trace artifact, and aggregator hardening for variable-length runs) follows. Successor to the multi-start gradient v2 work (#1398/#1400).

JAX compile-cache on recall: the persistent compilation cache is already default-on via autonerves (JAX_COMPILATION_CACHE_DIR), so a resumed search reuses the disk-cached compiled value_and_grad rather than recompiling. This change keeps the resume path's traced computation identical, so the cache still hits; the empirical early-stop → resume → no-recompile check is the autofit_workspace_test follow-up.

API Changes

Additive, plus one default-behaviour change. Added: af.MultiStartGradientConvergence settings object and a convergence= parameter on all four multi-start gradient searches (defaults to auto-convergence ON). Changed behaviour: the searches now stop early by default once the global-best figure-of-merit plateaus (parametric regime); n_steps remains a hard ceiling. No migration required — pass convergence=af.MultiStartGradientConvergence(check_for_convergence=False) to restore the previous always-run-to-n_steps behaviour. resurrect=True behaviour is unchanged (check skipped).
See full details below.

Test Plan

  • python -m pytest test_autofit/ in the worktree → 1521 passed, 1 skipped
  • 6 new numpy-only unit tests for the plateau logic (flat vs climbing history, min_steps/window length, relative tolerance, disabled + non-finite, dict round-trip)
  • Curated smoke autofit_workspace/scripts/searches/mle.py runs end-to-end through the new code (early-stop fires; exit 0)
  • autofit_workspace_test JAX validation: MGE early-stop + resume-asserts-no-recompile (phase-1 workspace follow-up)
Full API Changes (for automation & release notes)

Added

  • af.MultiStartGradientConvergence(check_for_convergence=True, window=50, rtol=1e-4, atol=1e-3, min_steps=100) — auto-convergence (early-stopping) settings for the multi-start gradient searches; exposes check_if_converged(fom_history) (global-best plateau over the trailing window).
  • convergence parameter on AbstractMultiStartGradient.__init__ (inherited by MultiStartAdam / MultiStartADABelief / MultiStartLion / MultiStartProdigy); None builds the default MultiStartGradientConvergence().

Changed Behaviour

  • The multi-start gradient searches now terminate early by default once the global-best figure-of-merit has plateaued (parametric/MGE regime). n_steps remains a hard ceiling / max budget.
  • Skipped when resurrect=True (pixelized regime) — that path runs to the n_steps ceiling exactly as before.
  • search_internal (and the on-disk checkpoint) now carries stop_reason ("converged" | "max_steps"), so a resumed already-converged search short-circuits rather than running more steps.

Migration

  • None required. To restore the previous always-run-to-n_steps behaviour: search = af.MultiStartAdam(..., convergence=af.MultiStartGradientConvergence(check_for_convergence=False)).

Validation checklist (--auto run — plan was not pre-approved)

  • Effective level: supervised (header: supervised, cap: feature@large → supervised)
  • Plan: on the issue (feat(autofit): multi-start gradient auto-convergence (phase 1) #1406), written at start, unmodified since
  • Gate: tests PASS (1521 passed / 1 skipped; no public-API removal → downstream dependents n/a) · smoke PASS (searches/mle.py, the one affected curated script, runs end-to-end; early-stop fires) · review self-CLEAN (additive + one default-behaviour change; faculty runs on the committed diff) · Heart RED — two reasons, both pre-existing and unrelated ("PyAutoFit: 2 commit(s) behind origin", "PyAutoLens: 1 uncommitted source change(s)"), human-waived contemporaneously via AskUserQuestion
  • Human: plan sound in hindsight?
  • Human: diff matches plan (no scope creep)?
  • Human: merge, amend, or reject — then log the outcome

Generated by the PyAutoLabs agent workflow.

…earches

Add a MultiStartGradientConvergence settings object (mirroring
AutoCorrelationsSettings) so MultiStartAdam/ADABelief/Lion/Prodigy stop early
once the global-best figure-of-merit plateaus, defaulting on so users no longer
hand-tune n_steps. n_steps remains a hard ceiling. The plateau check runs
per-step (fom_history updates every step) while checkpointing stays at the
iterations_per_full_update boundary, so early-stop works with the default
single-chunk cadence. Skipped when resurrect=True (pixelized regime). Carries
stop_reason in search_internal for resume and the phase-2 results contract.

Phase 1 of 2 (results-contract + aggregator hardening follows). Refs #1406.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSEf7D5urqPgvffKBqzPsF
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Jul 21, 2026
@Jammy2211
Jammy2211 merged commit 63d4e3a into main Jul 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant