Repository navigation
Description
Description
Imaging checks the override, interferometer doesn't:
e.g. in operated_mapping_matrix_list:
self.psf.convolved_mapping_matrix_from(...)
if linear_obj.operated_mapping_matrix_override is None
else self.linear_func_operated_mapping_matrix_dict[linear_obj]
(same pattern again in linear_func_operated_mapping_matrix_dict).
autoarray/inversion/inversion/interferometer/abstract.py:63-78 has no such branch:
@Property
def operated_mapping_matrix_list(self) -> List[np.ndarray]:
return [
self.transformer.transform_mapping_matrix(
mapping_matrix=linear_obj.mapping_matrix, xp=self._xp
)
for linear_obj in self.linear_obj_list
]
It always pulls linear_obj.mapping_matrix and pushes it through the NUFFT — linear_obj.operated_mapping_matrix_override is never read on this path.
So if you set operated_mapping_matrix_override on a LinearObj/LinearObjFuncList, it works for imaging fits and is silently ignored for interferometer fits — the raw mapping_matrix is transformed regardless.
Steps to Reproduce
Define a LinearObjFuncList subclass whose operated_mapping_matrix_override returns a distinguishable sentinel matrix, then build an Inversion from it against both an Imaging dataset and an Interferometer dataset and compare inversion.operated_mapping_matrix_list[0] to the sentinel in each case:
import numpy as np
import autoarray as aa
from autoarray.inversion.linear_obj.func_list import AbstractLinearObjFuncList
class OverridingLinearObjFuncList(AbstractLinearObjFuncList):
def init(self, grid, n_data_points):
super().init(grid=grid, regularization=None)
self._n_data_points = n_data_points
@property
def params(self):
return 1
@property
def mapping_matrix(self):
return np.ones((self._n_data_points, 1))
@property
def operated_mapping_matrix_override(self):
return 999.0 * np.ones((self._n_data_points, 1)) # sentinel
def pixel_signals_from(self, signal_scale):
return np.ones(self.params)
mask = aa.Mask2D.circular(shape_native=(9, 9), radius=3.0, pixel_scales=0.1)
grid = aa.Grid2D.from_mask(mask=mask)
--- Imaging: override IS respected ---
imaging = aa.Imaging(
data=aa.Array2D.full(1.0, shape_native=(9, 9), pixel_scales=0.1).apply_mask(mask=mask),
noise_map=aa.Array2D.full(1.0, shape_native=(9, 9), pixel_scales=0.1).apply_mask(mask=mask),
psf=aa.Convolver.no_blur(pixel_scales=(0.1, 0.1)),
)
inv_imaging = aa.Inversion(
dataset=imaging,
linear_obj_list=[OverridingLinearObjFuncList(grid=grid, n_data_points=mask.pixels_in_mask)],
)
print(np.allclose(inv_imaging.operated_mapping_matrix_list[0], 999.0)) # True
--- Interferometer: override is IGNORED ---
n_vis = 5
interferometer = aa.Interferometer(
data=aa.Visibilities.full(1.0, shape_slim=(n_vis,)),
noise_map=aa.VisibilitiesNoiseMap.full(1.0, shape_slim=(n_vis,)),
uv_wavelengths=np.ones((n_vis, 2)),
real_space_mask=mask,
transformer_class=aa.TransformerDFT,
)
inv_interferometer = aa.Inversion(
dataset=interferometer,
linear_obj_list=[OverridingLinearObjFuncList(grid=grid, n_data_points=mask.pixels_in_mask)],
)
print(np.allclose(inv_interferometer.operated_mapping_matrix_list[0], 999.0)) # False — bug
Expected Behavior
The second print should be True — operated_mapping_matrix_override should either be honored consistently across both dataset types, or the property/docstring on LinearObj should say explicitly that it is imaging-only.
Actual Behavior
Imaging operated_mapping_matrix matches override: True
Interferometer operated_mapping_matrix matches override: False
Interferometer operated_mapping_matrix (actual, first row): [80.99999999-4.1e-20j]
Environment
Python -- 3.11.15
OS -- macOS 26.6.1, arm64
autoarray -- 2026.7.29.1
Activity
Jammy2211 commentedon Aug 21, 2026
Hi @HRSAstro — thanks for the report. I'm Jammy's CLI assistant; I'll take a look and post back shortly with a plan, or follow-up questions if I need more detail.
Generated by Claude Code
Jammy2211 commentedon Aug 21, 2026
Here's what I'm planning to do — let me know if anything looks off.
You're right, and thanks for the clean sentinel reproducer — the interferometer inversion never reads
operated_mapping_matrix_override, so it silently falls back to transforming the rawmapping_matrix. We're going to support the override properly for interferometer datasets rather than document it away as imaging-only.One thing worth flagging on semantics: to stay consistent with imaging (where the override is the already-operated matrix placed directly into
operated_mapping_matrix_list), the interferometer override will be treated as data-space — i.e. a complex matrix of shape[total_visibilities, params]that bypasses the NUFFT entirely. Your reproducer's second print then comes backTruewith a visibility-shaped sentinel. If your use case instead needs a real-space matrix that still goes through the transform, say so — that's themapping_matrixpath, and we can talk about whether a separate hook makes sense.Plan
InversionInterferometer(single landing site feeding both formalisms' matrices), with a clear error rather than silent ignoring anywhere it can't apply.operated_light_profileexample packages for interferometer in autogalaxy_workspace and autolens_workspace, mirroring the imaging ones.Detailed implementation plan
Affected repositories
Work classification
Both (library + workspace)
Implementation steps
autoarray/inversion/inversion/interferometer/abstract.py—operated_mapping_matrix_listreturnslinear_obj.operated_mapping_matrix_overridewhen notNone, else the currenttransform_mapping_matrix(...); validate the override's shape/dtype against[total_visibilities, params]with an informative error.autoarray/inversion/linear_obj/linear_obj.py— document the per-dataset contract onoperated_mapping_matrix_override(imaging: real[mask_pixels, params]; interferometer: complex[visibilities, params]).InversionInterferometerMapping(factory), but a mixed mapper + func-list inversion on the sparse path must raise clearly if a func list carries an override it can't honor.autogalaxy/profiles/light/linear/abstract.py—LightProfileLinearObjFuncList.operated_mapping_matrix_overridereturnsNonewhenself.psf is None(interferometer), preventing an attribute crash and preserving the existing mapping-matrix → NUFFT behaviour for linear and operated profiles.test_autoarrayinterferometer inversion tests using the existing override-aware mocks;test_autogalaxytest for thepsf=Noneguard.scripts/interferometer/features/operated_light_profile/(README,modeling.py,simulator.py) to autogalaxy_workspace and autolens_workspace, mirrored from the imaging packages with interferometer idioms taken fromscripts/interferometer/features/linear_light_profiles/; regenerate notebooks via the standard tooling.Branch
feature/interferometer-operated-overrideI'll post back when the work is in progress and again when there's a PR.
Generated by Claude Code
Jammy2211 commentedon Aug 21, 2026
@HRSAstro the fix is implemented and up for review — the override is now honored for interferometer inversions:
operated_light_profileexample packagesOne heads-up for your code: as flagged above, the override is treated as data-space, consistent with imaging — for an interferometer inversion it bypasses the NUFFT entirely, so it must be a complex matrix of shape
[total_visibilities, params](e.g. an analytic Fourier transform of your profile). Your reproducer's real-space-shaped sentinel would now raise a shape error pointing you at exactly this. If you actually need a real-space override that still goes through the transform, that's themapping_matrixpath — tell us about your use case and we can look at a separate hook.I'll post back here once the PRs are merged and the change is in a release.
Generated by Claude Code
Jammy2211 commentedon Aug 21, 2026
@HRSAstro all four PRs are merged — the interferometer inversion now honors
operated_mapping_matrix_overrideonmain, alongside the new interferometeroperated_light_profileexample packages in both workspaces.The change reaches PyPI with the next nightly release; until then you can use it by installing autoarray from
main. Remember the shape contract from my previous comment: for interferometer fits your override must be a complex[total_visibilities, params]matrix (it bypasses the NUFFT entirely), and a real-space-shaped override now raises an error explaining exactly that.Thanks again for the sharp report — the sentinel reproducer made this an easy one to pin down. If the data-space semantics don't fit your use case, or anything misbehaves once you're on the new version, just reply here.
Generated by Claude Code
Jammy2211 commentedon Aug 22, 2026
@HRSAstro — the fix is out.
autoarray2026.8.22.1 is on PyPI and contains #460, sooperated_mapping_matrix_overrideis now honored for interferometer inversions on a released version:PyAutoGalaxy and PyAutoLens went out at the same version if you're on the wider stack. The interferometer
operated_light_profileexample packages are live in both workspaces too (PyAutoLabs/autogalaxy_workspace#220, PyAutoLabs/autolens_workspace#494).Recapping the one contract that bites: for interferometer fits the override is data-space — a complex
[total_visibilities, params]matrix that bypasses the NUFFT entirely. The real-space-shaped sentinel from your reproducer now raises a shape error spelling that out rather than being silently ignored.Leaving this closed, but do reply here if the data-space semantics turn out to be wrong for your use case — a real-space override that still goes through the transform would be a separate hook, and it'd be worth hearing what you're actually computing.
Generated by Claude Code