Skip to content

fix(inversion): guard data_subtracted_dict on data=None, harden sparse noise check and data-term dtype - #591

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/sparse-data-none-guard
Sep 30, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/sparse-data-none-guard

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Corrective follow-up to phase 1 of https://github.com/orgs/PyAutoLabs/discussions/13 (#589, PyAutoLabs/PyAutoGalaxy#637, PyAutoLabs/PyAutoLens#757). Closes #590. An independent Codex review reproduced three defects on the sparse data=None path (record: PyAutoLabs/PyAutoLens#757 (comment)). This PR:

  • data_subtracted_dict on an inversion built without data (introduced by phase 1): previously a multi-object inversion raised TypeError: unsupported operand type(s) for -: 'NoneType' and 'complex' and a single-mapper inversion returned {mapper: None}, which made subplot_of_mapper fail with a ValueError inside plot_array. It now raises a clear exc.InversionException naming fit.inversion_with_data / fit.data, and the two mapper subplots (subplot_of_mapper, subplot_mappings) catch it and skip the data-subtracted panel as they already do for AttributeError / KeyError. The workspace interferometer/features/pixelization/fit.py scripts (autogalaxy_workspace, autolens_workspace) are migrated to fit.inversion_with_data in sibling PRs.
  • check_noise_map_real_imag_equal absolute tolerance (pre-existing): np.allclose / np.isclose ran at the default atol=1e-8, so sigma 1e-9 vs 2e-9 passed and the sparse curvature was 1.5x wrong. Now atol=0.0 with the default relative tolerance, so the check is scale-free.
  • Cached data_term dtype (pre-existing path, exposed by the cache): apply_sparse_operator squared data.real / data.imag in the data's own dtype, so complex64 data gave 700140000.0 instead of 700140007.0 (log_evidence +3.5) and disagreed with sparse_terms_from_chunks, which promotes to complex128. The scalar is now computed from complex128-promoted local copies; complex128 inputs are bit-identical to before.

Not in scope: mutating noise_map after apply_sparse_operator stales the operator (W~, dirty image and now the two scalars); that predates phase 1 and is unchanged.

API Changes

Behaviour only. AbstractInversion.data_subtracted_dict raises exc.InversionException (was TypeError / a dict of None) when the inversion has no data; subplot_of_mapper / subplot_mappings tolerate it. check_noise_map_real_imag_equal is stricter for sub-1e-8 sigmas. apply_sparse_operator computes data_term in float64 for any input dtype. No symbol added, removed or renamed; no default changed for complex128 data with Jy-scale noise.
See full details below.

Test Plan

  • pytest test_autoarray — 1765 passed
  • New tests: data_subtracted_dict raises InversionException matching inversion_with_data; both subplots run when it raises; check_noise_map_real_imag_equal rejects 1e-9+2e-9j and accepts 1e-9+1e-9j; complex64 apply_sparse_operator data_term == complex128 value == sparse_terms_from_chunks (rtol 1e-12)
  • Red check: each of the four new tests fails with its source change reverted and passes with it
  • Review reproduction scripts re-run: B → InversionException, both subplots run; D → rejects 1e-9/2e-9; A → 700140007.0 on complex64
  • Workspace scripts: both pixelization fit.py scripts fail on this PR's base with fit.inversion (TypeError / ValueError) and pass with fit.inversion_with_data under the smoke profile
  • CI green on unittest 3.12 / 3.13 / nojax
Full API Changes (for automation & release notes)

Changed Behaviour

  • AbstractInversion.data_subtracted_dict — raises exc.InversionException when inversion.data is None (sparse-operator path with a precomputed data term); message points at fit.inversion_with_data / fit.data
  • autoarray.inversion.plot.inversion_plots.subplot_of_mapper / subplot_mappings — the data-subtracted panel is skipped (not an error) when data_subtracted_dict raises exc.InversionException
  • inversion_interferometer_util.check_noise_map_real_imag_equal — np.allclose / np.isclose with atol=0.0 (default rtol kept)
  • Interferometer.apply_sparse_operator — sparse_operator.data_term computed from complex128-promoted copies of data / noise_map; equals sparse_terms_from_chunks(...).data_term for any input dtype

Migration

  • Code calling subplot_of_mapper(inversion=fit.inversion, ...) (or reading fit.inversion.data_subtracted_dict) on a sparse-operator interferometer fit with no non-linear light profile should pass fit.inversion_with_data.

Generated by the PyAutoLabs agent workflow.

🤖 Generated with Claude Code

…e noise check and data-term dtype (#590)

Corrective follow-up to Discussion #13 phase 1 (#589), from the independent
review on PyAutoLens#757:

- AbstractInversion.data_subtracted_dict raises a clear InversionException
  (naming fit.inversion_with_data) when the inversion was built without data,
  instead of TypeError / {mapper: None}; subplot_of_mapper and subplot_mappings
  catch it and skip the panel.
- check_noise_map_real_imag_equal uses atol=0.0 so tiny unequal sigmas are
  rejected rather than waved through by the default absolute tolerance.
- apply_sparse_operator computes the cached data_term from complex128-promoted
  copies so one-shot equals sparse_terms_from_chunks for complex64 data.

Tests for all four; red-checked against the reverted source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Jammy2211

Copy link
Copy Markdown
Collaborator Author

Workspace PR: PyAutoLabs/autogalaxy_workspace#253

@Jammy2211

Copy link
Copy Markdown
Collaborator Author

Workspace PR: PyAutoLabs/autolens_workspace#581

@Jammy2211
Jammy2211 merged commit d429844 into main Sep 30, 2026
3 checks passed
@Jammy2211
Jammy2211 deleted the feature/sparse-data-none-guard branch September 30, 2026 09:51
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.

fix(inversion): guard data_subtracted_dict on data=None, harden sparse noise check and data-term dtype

1 participant