Overview
Corrective follow-up to phase 1 of https://github.com/orgs/PyAutoLabs/discussions/13 (PyAutoArray#589, PyAutoGalaxy#637, PyAutoLens#757). An independent Codex review reproduced three defects on the sparse data=None path (record: PyAutoLabs/PyAutoLens#757 (comment)): AbstractInversion.data_subtracted_dict raises TypeError / yields {mapper: None} when the inversion was built without data, which breaks subplot_of_mapper / subplot_mappings and the two workspace interferometer/features/pixelization/fit.py scripts; check_noise_map_real_imag_equal uses an absolute tolerance of 1e-8; and apply_sparse_operator squares complex64 data in float32 so the cached data_term disagrees with the chunked builder.
Plan
- Raise a clear
InversionException from data_subtracted_dict when the inversion has no data, pointing at fit.inversion_with_data.
- Catch that exception in the two mapper subplots so the data-subtracted panel is skipped cleanly.
- Set
atol=0.0 on the real/imag noise equality check.
- Promote to complex128 before computing the cached data term so one-shot equals chunked for any input dtype.
- Add tests for all four; then migrate the two workspace pixelization
fit.py scripts to fit.inversion_with_data (library first, workspace follows).
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary)
- autogalaxy_workspace, autolens_workspace (workspace follow-up,
/ship_workspace)
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoArray |
main |
clean |
| ./autogalaxy_workspace |
main |
clean |
| ./autolens_workspace |
main |
clean |
Worktree guard clear on all three.
Suggested branch: feature/sparse-data-none-guard
Implementation Steps
autoarray/inversion/inversion/abstract.py data_subtracted_dict (~L868): if self.data is None: raise exc.InversionException(...) naming fit.inversion_with_data / fit.data.
autoarray/inversion/plot/inversion_plots.py: the except (AttributeError, KeyError) guards at ~L87 (subplot_of_mapper) and ~L397 (subplot_mappings) also catch exc.InversionException; do not widen to bare Exception.
autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py check_noise_map_real_imag_equal (~L58, L61): atol=0.0 on np.allclose and np.isclose, default rtol kept.
autoarray/dataset/interferometer/dataset.py apply_sparse_operator (~L391-394): compute data_term from complex128-promoted local copies of data and noise; dirty image expression unchanged.
- Tests:
DatasetInterface(data=None) inversion → pytest.raises(exc.InversionException, match="inversion_with_data"); both subplots run when data_subtracted_dict raises; sigma 1e-9+2e-9j → exc.DatasetException, equal 1e-9 sigmas pass; complex64 apply_sparse_operator data_term == complex128 value == sparse_terms_from_chunks(...).data_term.
- Workspace (after the library PR):
scripts/interferometer/features/pixelization/fit.py in autogalaxy_workspace (~L319) and autolens_workspace (~L291): inversion = fit.inversion_with_data plus one sentence of prose; regenerate notebooks per each workspace's convention; smoke both scripts.
Key Files
autoarray/inversion/inversion/abstract.py — guard
autoarray/inversion/plot/inversion_plots.py — handlers
autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py — noise check
autoarray/dataset/interferometer/dataset.py — data term dtype
autogalaxy_workspace / autolens_workspace scripts/interferometer/features/pixelization/fit.py — accessor migration
Original Prompt
Click to expand starting prompt
Sparse data=None path: guard data_subtracted_dict, harden noise check and data-term dtype (Discussion #13 review fixes)
Type: bug
Target: PyAutoArray
Repos:
- PyAutoArray
- autogalaxy_workspace
- autolens_workspace
Themes:
- interferometer
- sparse-operator
- visualization
Difficulty: small
Autonomy: supervised
Priority: high
Status: draft
Consequence: glance
Witness: on a sparse-operator dataset with the precomputed-data-term gate on (fit.inversion.dataset.data is None), fit.inversion.data_subtracted_dict raises InversionException naming inversion_with_data (no TypeError, no {mapper: None}), and InversionPlotter.subplot_of_mapper / subplot_mappings on that inversion skip cleanly instead of raising; check_noise_map_real_imag_equal rejects sigma 1e-9 vs 2e-9; apply_sparse_operator on complex64 data gives data_term equal to the complex128 value and to sparse_terms_from_chunks; the two workspace interferometer/features/pixelization/fit.py scripts run under the smoke profile using fit.inversion_with_data.
Review-minutes: 5
Unattended: ready
Parent: complete/2026/09/interferometer-streaming-visibilities.md
Source: Codex gpt-6-astra review of PyAutoArray#589 / PyAutoGalaxy#637 / PyAutoLens#757
(record: PyAutoLabs/PyAutoLens#757 (comment)), all four
claims reproduced 2026-09-30.
Why
Phase 1 of https://github.com/orgs/PyAutoLabs/discussions/13 builds the sparse inversion with
data=None when no galaxy has a non-linear light profile. Three review findings follow from
the merged code:
- B (introduced, user-facing).
AbstractInversion.data_subtracted_dict
(autoarray/inversion/inversion/abstract.py ~L868-885) does copy.copy(self.data) then
-=; with data=None a multi-object inversion raises
TypeError: unsupported operand type(s) for -: 'NoneType' and 'complex' and a single-mapper
inversion yields {mapper: None}, which makes subplot_of_mapper fail with a ValueError
in plot/array.py. The handlers in autoarray/plot/.../inversion_plots.py (~L87, ~L397)
catch only (AttributeError, KeyError). The library visualizers already pass
fit.inversion_with_data, but autogalaxy_workspace/scripts/interferometer/features/pixelization/fit.py
(~L321) passes fit.inversion and now fails on PyAutoGalaxy main; the autolens_workspace copy
(~L293) fails once PyAutoLens#757 lands.
- D (pre-existing, hardening).
check_noise_map_real_imag_equal
(inversion_interferometer_util.py ~L58, L61) uses np.allclose/np.isclose at default
atol=1e-8, so sigma 1e-9 vs 2e-9 passes and the sparse curvature is 1.5x wrong. Unreachable
with Jy-unit data but a scale-dependent check is wrong on principle.
- A (edge, pre-existing path).
Interferometer.apply_sparse_operator
(autoarray/dataset/interferometer/dataset.py ~L391-394) squares data.real/data.imag in
the data's own dtype; complex64 data + complex128 noise gives data_term 700140000.0 vs exact
700140007.0 (log_evidence +3.5), and disagrees with sparse_terms_from_chunks, which promotes
to complex128. Loaders always yield complex128, so only hand-built complex64 arrays hit it.
Finding C (noise map mutated after apply_sparse_operator stales the operator) predates phase 1
and is out of scope; document only if convenient.
What
AbstractInversion.data_subtracted_dict: when self.data is None, raise
exc.InversionException with a message pointing at fit.inversion_with_data (and fit.data).
inversion_plots.py handlers around data_subtracted_dict reads: also catch
exc.InversionException (and TypeError), so the mapper subplots skip that panel cleanly.
check_noise_map_real_imag_equal: atol=0.0 on both calls (keep the default rtol).
apply_sparse_operator: cast data (and noise) to float64 components before squaring so
one-shot == chunked for any input dtype.
- Tests in
test_autoarray: (1) raises with the named message; (2) plotter skips; (3) the
1e-9/2e-9 rejection; (4) complex64 data_term == complex128 == chunked.
- Workspace: both
interferometer/features/pixelization/fit.py scripts → fit.inversion_with_data
in the inversion plotter call, with a one-line prose note on why; regenerate notebooks per
each workspace's convention. Library first (/ship_library), workspace follows
(/ship_workspace).
🤖 Generated with Claude Code
Overview
Corrective follow-up to phase 1 of https://github.com/orgs/PyAutoLabs/discussions/13 (PyAutoArray#589, PyAutoGalaxy#637, PyAutoLens#757). An independent Codex review reproduced three defects on the sparse
data=Nonepath (record: PyAutoLabs/PyAutoLens#757 (comment)):AbstractInversion.data_subtracted_dictraisesTypeError/ yields{mapper: None}when the inversion was built without data, which breakssubplot_of_mapper/subplot_mappingsand the two workspaceinterferometer/features/pixelization/fit.pyscripts;check_noise_map_real_imag_equaluses an absolute tolerance of 1e-8; andapply_sparse_operatorsquares complex64 data in float32 so the cacheddata_termdisagrees with the chunked builder.Plan
InversionExceptionfromdata_subtracted_dictwhen the inversion has no data, pointing atfit.inversion_with_data.atol=0.0on the real/imag noise equality check.fit.pyscripts tofit.inversion_with_data(library first, workspace follows).Detailed implementation plan
Affected Repositories
/ship_workspace)Branch Survey
Worktree guard clear on all three.
Suggested branch:
feature/sparse-data-none-guardImplementation Steps
autoarray/inversion/inversion/abstract.pydata_subtracted_dict(~L868):if self.data is None: raise exc.InversionException(...)namingfit.inversion_with_data/fit.data.autoarray/inversion/plot/inversion_plots.py: theexcept (AttributeError, KeyError)guards at ~L87 (subplot_of_mapper) and ~L397 (subplot_mappings) also catchexc.InversionException; do not widen to bareException.autoarray/inversion/inversion/interferometer/inversion_interferometer_util.pycheck_noise_map_real_imag_equal(~L58, L61):atol=0.0onnp.allcloseandnp.isclose, default rtol kept.autoarray/dataset/interferometer/dataset.pyapply_sparse_operator(~L391-394): computedata_termfrom complex128-promoted local copies of data and noise; dirty image expression unchanged.DatasetInterface(data=None)inversion →pytest.raises(exc.InversionException, match="inversion_with_data"); both subplots run whendata_subtracted_dictraises; sigma1e-9+2e-9j→exc.DatasetException, equal 1e-9 sigmas pass; complex64apply_sparse_operatordata_term== complex128 value ==sparse_terms_from_chunks(...).data_term.scripts/interferometer/features/pixelization/fit.pyin autogalaxy_workspace (~L319) and autolens_workspace (~L291):inversion = fit.inversion_with_dataplus one sentence of prose; regenerate notebooks per each workspace's convention; smoke both scripts.Key Files
autoarray/inversion/inversion/abstract.py— guardautoarray/inversion/plot/inversion_plots.py— handlersautoarray/inversion/inversion/interferometer/inversion_interferometer_util.py— noise checkautoarray/dataset/interferometer/dataset.py— data term dtypeautogalaxy_workspace/autolens_workspacescripts/interferometer/features/pixelization/fit.py— accessor migrationOriginal Prompt
Click to expand starting prompt
Sparse data=None path: guard data_subtracted_dict, harden noise check and data-term dtype (Discussion #13 review fixes)
Type: bug
Target: PyAutoArray
Repos:
Themes:
Difficulty: small
Autonomy: supervised
Priority: high
Status: draft
Consequence: glance
Witness: on a sparse-operator dataset with the precomputed-data-term gate on (
fit.inversion.dataset.data is None),fit.inversion.data_subtracted_dictraisesInversionExceptionnaminginversion_with_data(no TypeError, no{mapper: None}), andInversionPlotter.subplot_of_mapper/subplot_mappingson that inversion skip cleanly instead of raising;check_noise_map_real_imag_equalrejects sigma 1e-9 vs 2e-9;apply_sparse_operatoron complex64 data givesdata_termequal to the complex128 value and tosparse_terms_from_chunks; the two workspaceinterferometer/features/pixelization/fit.pyscripts run under the smoke profile usingfit.inversion_with_data.Review-minutes: 5
Unattended: ready
Parent: complete/2026/09/interferometer-streaming-visibilities.md
Source: Codex gpt-6-astra review of PyAutoArray#589 / PyAutoGalaxy#637 / PyAutoLens#757
(record: PyAutoLabs/PyAutoLens#757 (comment)), all four
claims reproduced 2026-09-30.
Why
Phase 1 of https://github.com/orgs/PyAutoLabs/discussions/13 builds the sparse inversion with
data=Nonewhen no galaxy has a non-linear light profile. Three review findings follow fromthe merged code:
AbstractInversion.data_subtracted_dict(
autoarray/inversion/inversion/abstract.py~L868-885) doescopy.copy(self.data)then-=; withdata=Nonea multi-object inversion raisesTypeError: unsupported operand type(s) for -: 'NoneType' and 'complex'and a single-mapperinversion yields
{mapper: None}, which makessubplot_of_mapperfail with aValueErrorin
plot/array.py. The handlers inautoarray/plot/.../inversion_plots.py(~L87, ~L397)catch only
(AttributeError, KeyError). The library visualizers already passfit.inversion_with_data, butautogalaxy_workspace/scripts/interferometer/features/pixelization/fit.py(~L321) passes
fit.inversionand now fails on PyAutoGalaxy main; the autolens_workspace copy(~L293) fails once PyAutoLens#757 lands.
check_noise_map_real_imag_equal(
inversion_interferometer_util.py~L58, L61) usesnp.allclose/np.iscloseat defaultatol=1e-8, so sigma 1e-9 vs 2e-9 passes and the sparse curvature is 1.5x wrong. Unreachablewith Jy-unit data but a scale-dependent check is wrong on principle.
Interferometer.apply_sparse_operator(
autoarray/dataset/interferometer/dataset.py~L391-394) squaresdata.real/data.imaginthe data's own dtype; complex64 data + complex128 noise gives
data_term700140000.0 vs exact700140007.0 (log_evidence +3.5), and disagrees with
sparse_terms_from_chunks, which promotesto complex128. Loaders always yield complex128, so only hand-built complex64 arrays hit it.
Finding C (noise map mutated after
apply_sparse_operatorstales the operator) predates phase 1and is out of scope; document only if convenient.
What
AbstractInversion.data_subtracted_dict: whenself.data is None, raiseexc.InversionExceptionwith a message pointing atfit.inversion_with_data(andfit.data).inversion_plots.pyhandlers arounddata_subtracted_dictreads: also catchexc.InversionException(andTypeError), so the mapper subplots skip that panel cleanly.check_noise_map_real_imag_equal:atol=0.0on both calls (keep the default rtol).apply_sparse_operator: cast data (and noise) to float64 components before squaring soone-shot == chunked for any input dtype.
test_autoarray: (1) raises with the named message; (2) plotter skips; (3) the1e-9/2e-9 rejection; (4) complex64 data_term == complex128 == chunked.
interferometer/features/pixelization/fit.pyscripts →fit.inversion_with_datain the inversion plotter call, with a one-line prose note on why; regenerate notebooks per
each workspace's convention. Library first (
/ship_library), workspace follows(
/ship_workspace).🤖 Generated with Claude Code