Skip to content

fix: sparse interferometer fits with ordinary light profiles use the profile-subtracted dirty image (PyAutoArray#575) - #629

Merged
Jammy2211 merged 3 commits into
mainfrom
feature/interferometer-mge-w-tilde-route
Sep 26, 2026
Merged

Jammy2211 merged 3 commits into
mainfrom
feature/interferometer-mge-w-tilde-route

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

This fixes a silent correctness bug in sparse (W~) interferometer fits that also contain ordinary (non-linear) light profiles. The sparse data vector was formed from the dirty image cached at apply_sparse_operator() time, which is built from the unsubtracted visibilities, while the chi-squared used the profile-subtracted visibilities. With a Sersic + linear Gaussian, the sparse fit gave logL −26.772 against −24.342 dense; the test fails on the old code and passes with the fix. At sma, D was off by 23% once a lens Sersic was present. Mixed mapper + MGE fits were already affected; PyAutoArray#575 routes MGE-only fits through W~ too.

The fix uses linearity: Re(Fᴴ W (d − F i_p)) = d~ − W~ i_p. W~ is the operator the sparse curvature matrix already uses, so the correction is one FFT convolution on the real-space grid (≈2 ms at alma). An adjoint NUFFT over every visibility would take ≈4 s, and there is no new noise assumption. All-linear fits (including an MGE Basis) are detected structurally and pay nothing. At alma under CPU jit, MGE + lens Sersic takes 1.30 s sparse against 13.8 s dense, with identical logL.

Stacked on PyAutoLabs/PyAutoArray#576 (PyAutoArray#575) (merge that first).

API Changes

  • New module function autogalaxy.interferometer.fit_interferometer.sparse_dirty_image_from(dataset, galaxies, image, xp=np): the corrected dirty image for the sparse inversion, or None when the cached one is right.
  • New cached property FitInterferometer.profile_image: the summed ordinary-light image on grids.lp. profile_visibilities now transforms it, so the light is evaluated once. The values are unchanged.
    See full details below.

Test Plan

  • test_autogalaxy full suite: 1240 passed
  • New sparse vs dense parity tests (log_likelihood and log_evidence to 1e-8): Sersic + linear Gaussian, and the same inside a Basis. Controls: all-linear gives a None dirty image, and profile_image transforms to profile_visibilities to 1e-12.
  • Workspace smoke: 24/24 targeted interferometer scripts pass (details in the Heart RED override section)
Full API Changes (for automation & release notes)

Added

  • autogalaxy.interferometer.fit_interferometer.sparse_dirty_image_from(dataset, galaxies, image, xp=np)
  • ag.FitInterferometer.profile_image (cached property)

Changed Behaviour

  • FitInterferometer.galaxies_to_inversion passes sparse_dirty_image to aa.DatasetInterface when the dataset has a sparse operator and any galaxy has an ordinary light profile. A Basis made only of linear profiles does not count as an ordinary light profile.
  • FitInterferometer.profile_visibilities is computed from profile_image. The values are unchanged.

Migration

  • None required. Sparse fits with ordinary light profiles now return different (correct) likelihoods.

Heart RED override (development only)

The human authorized the AUTONOMY.md "Human override for Heart RED (development only)" for this task (PyAutoArray#575, branch feature/interferometer-mge-w-tilde-route) on 2026-09-26. When asked how to proceed with the three library PRs, they chose "Override, open PRs". The override covers commit, push and opening pending-release PRs only. Merging needs a separate /prm with every required check green. This branch does not claim to fix Heart.

Exact RED reasons at ship time:

  • release validation FAILED (stage integrate)
  • workspace validation not passing (4 failed, cloud#35579888156: autolens notebooks/cluster/modeling.ipynb, autolens notebooks/weak/a2744.ipynb, autolens scripts/cluster/modeling.py, +1 more)
  • manifest drift: hub organism blurb (organs present) — 7 mismatch(es) vs PyAutoMind/repos.yaml
  • manifest drift: organism-map blocks (generated) — 1 mismatch(es) vs PyAutoMind/repos.yaml

Branch gates passed: 24/24 targeted interferometer smoke scripts pass on the branch (autolens_workspace 11, autogalaxy_workspace 5, autolens_workspace_test 5, autogalaxy_workspace_test 3: MGE, pixelization, linear light profiles, sparse-operator JAX assertions and likelihood pins). autolens_workspace_test/.../jax_likelihood/mge.py first failed identically on the branch and on the mains because the local dataset was stale (2026-08-06, older than the 09-18 simulator change); after regenerating it, the script passes on both. Unit suites: PyAutoArray 1706 passed, PyAutoGalaxy 1240 passed, PyAutoLens 757 passed + 1 xfailed. My own diff review is in-session only; the human reviews at merge.

🤖 Generated with Claude Code

Jammy2211 and others added 2 commits September 26, 2026 11:53
…cted visibilities (PyAutoArray#575)

FitInterferometer passes sparse_dirty_image (the noise-weighted adjoint
transform of profile_subtracted_visibilities, as apply_sparse_operator builds
it) whenever the dataset has a sparse operator and a galaxy has an ordinary
light profile (Basis contents checked, so an all-linear MGE keeps the cached
image at no cost). Shared helper sparse_dirty_image_from is reused by
PyAutoLens. Tests: sparse vs dense log_likelihood / log_evidence to 1e-8 with
and without an ordinary light profile.

Refs PyAutoLabs/PyAutoArray#575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T (PyAutoArray#575)

sparse_dirty_image_from now returns d~ - W~ i_p, where i_p is the ordinary
light-profile image the fit's profile_visibilities transform. W~ is the
operator the sparse curvature matrix already uses, so this adds no new
assumption and replaces a type-1 NUFFT over every visibility with one FFT
convolution (alma CPU jit, MGE + lens Sersic: 4.54 s -> 1.30 s; dense 13.8 s).
FitInterferometer caches profile_image and builds profile_visibilities from it.
Tests assert profile_image transforms to the fit's profile_visibilities.

(The TransformerDFT.image_from xp change in PyAutoArray, an incidental fix for
a latent jit crash, is kept.)

Refs PyAutoLabs/PyAutoArray#575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inary light (PyAutoArray#575)

An MGE Basis counts as a LightProfile, so profile_visibilities forward-NUFFTed
an all-zero image on every call of an MGE-only fit. It now returns zeros
unless _has_light_profile_non_linear (Basis-aware, structural, jit-safe) and
only then evaluates profile_image. alma CPU jit, MGE-only sparse:
0.995 s -> 0.579 s. Test: MGE-only profile_visibilities are zeros and the
transformer is not called.

Refs PyAutoLabs/PyAutoArray#575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jammy2211

Copy link
Copy Markdown
Collaborator Author

Added a follow-up commit (approved by the human, 2026-09-26): profile_visibilities now returns zero visibilities without a Fourier transform when the fit has no ordinary light profile. It uses the same Basis-aware predicate, so an MGE-only fit no longer runs a forward NUFFT of an all-zero image on every call. Tests: new spy test (fails on the previous HEAD, passes now). PyAutoGalaxy full suite 1241 passed; PyAutoLens 758 passed + 1 xfailed. On alma, CPU jit, MGE-only sparse log_likelihood drops from 0.995 s to 0.579 s, with identical logL. This is still covered by the Heart RED override above; no release.

@Jammy2211
Jammy2211 merged commit da84468 into main Sep 26, 2026
4 checks passed
@Jammy2211
Jammy2211 deleted the feature/interferometer-mge-w-tilde-route branch September 26, 2026 15:33
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