Repository navigation
Description
Overview
Side finding from #639 / PR #641. Every imaging sparse-inversion consumer (data vector, curvature diagonal and off-diagonal, mixed blocks, reconstructed image) reads Mapper.sparse_triplets_data / sparse_triplets_curvature, which are built by mapper_util.sparse_triplets_from without the slot-size and negative-index zeroing that the interferometer path applies in _sparse_triplets_slim_from. A -1 source index would silently wrap to the last source pixel and unused stencil slots contribute if their weights are non-zero. The two imaging scatter-add helpers also import JAX inside the function on every call, so the numpy branch receives JAX arrays and cannot run without JAX installed.
Plan
- Centralise the masking in
mapper_util.sparse_triplets_frombehind an optionalpix_sizes_for_subargument;Mapper.sparse_triplets_data/sparse_triplets_curvaturepass it; the interferometer_sparse_triplets_slim_fromswitches to the shared masking and drops its local copy (existing tests prove no change). - Make
data_vector_via_psf_weighted_data_fromandmapped_reconstructed_operated_data_via_sparse_operator_fromdispatch on the backend:np.bincounton numpy,segment_sumunder JAX, JAX imported only on that branch. - Tests: mapper-util test with a hand-built index table (negative index + unused slot with non-zero weight) zeroed on numpy and JAX; imaging sparse parity (data vector, curvature, reconstructed image) vs the dense mapping inversion on such a mapper; nojax-leg check that the imaging sparse numpy path imports and runs.
- First step: establish whether any current mesh (Rectangular, Delaunay, DelaunayNN, Voronoi) emits negative indices or non-zero unused-slot weights; if none does, the PR body states the defect is latent and the change is a guard.
- Tier: glance — merge mode: auto-merge on green if the Witness passes, post-merge summary
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| array/PyAutoArray | main | clean (feature/interferometer-primary-beam in flight #640; #642 queued) |
Suggested branch: feature/imaging-sparse-triplets-masking
Implementation Steps
autoarray/inversion/mappers/mapper_util.pysparse_triplets_from(~270): addpix_sizes_for_sub=None; when given, buildslot = tile(arange(P), M_sub),valid = (slot < repeat(pix_sizes, P)) & (cols >= 0),vals = where(valid, vals, 0),cols = where(valid, cols, 0)on both numpy (~328) and JAX (~355) branches.autoarray/inversion/mappers/abstract.pysparse_triplets_data/sparse_triplets_curvature(~440-470): passpix_sizes_for_sub=self.pix_sizes_for_sub_slim_index.autoarray/inversion/inversion/interferometer/sparse.py_sparse_triplets_slim_from(~131-172): passpix_sizes_for_sub, remove the local masking block;test_interferometer.pyparity tests stay green.autoarray/inversion/inversion/imaging/inversion_imaging_util.pydata_vector_via_psf_weighted_data_from(~70-104) andmapped_reconstructed_operated_data_via_sparse_operator_from(~280-303): addxp=np; numpy branch usesnp.bincount(..., minlength=), JAX branch keepssegment_sum; callers inimaging/sparse.py(~90, ~144, ~168, ~526) passxp=self._xp.- Tests in
test_autoarray/inversion/mappers/(mapper_util) andtest_autoarray/inversion/inversion/imaging/test_imaging.py; emulate the nojax leg (import-hook block) locally before ship.
Key Files
autoarray/inversion/mappers/mapper_util.py— shared triplet builderautoarray/inversion/mappers/abstract.py— Mapper triplet propertiesautoarray/inversion/inversion/imaging/inversion_imaging_util.py— scatter-add helpersautoarray/inversion/inversion/imaging/sparse.py— consumersautoarray/inversion/inversion/interferometer/sparse.py— local masking to retire
Original Prompt
Click to expand starting prompt
Imaging sparse inversion: unmasked mapper triplets in the reconstructed-image path and an unconditional JAX import
Type: bug
Target: PyAutoArray
Repos:
- PyAutoArray
Difficulty: small
Autonomy: safe
Priority: medium
Status: formalised
Consequence: glance
Witness: a test asserting the imaging sparsemapped_reconstructed_data_dictequalsmapping_matrix @ reconstructionfor a mapper whosepix_indexes_for_sub_slim_indexcontains unused stencil slots (slot >= pix_sizes) and a negative index, on numpy without JAX importable (nojax leg) and under JAX
Review-minutes: 3
Unattended: ready
Filed: 2026-10-21
Why
Side finding from PyAutoArray#639 (merged as #641, 2026-10-21). The interferometer sparse path zeroes invalid triplet slots in _sparse_triplets_slim_from (slot >= pix_sizes_for_sub_slim_index, or cols < 0 → weight 0, column 0). The imaging sparse path does not.
Where
autoarray/inversion/inversion/imaging/sparse.pyaround line 526,_mapped_reconstructed_data_dict_from, useslinear_obj.sparse_triplets_curvature, built ininversion/mappers/abstract.py:460-470viamapper_util.sparse_triplets_from(..., return_rows_slim=False)with no masking. A-1source index silently wraps to the last source pixel and unused slots contribute if their weights are non-zero.- The same helper,
inversion_imaging_util.mapped_reconstructed_operated_data_via_sparse_operator_from(~line 280), imports JAX unconditionally even on the numpy path (nojax leg risk). - The imaging data-vector and curvature paths that consume the same unmasked
Mapper.sparse_triplets_*likely share the gap; audit them in the same PR.
Fix
Apply the slot/negative-index masking once at the Mapper.sparse_triplets_* builders (or in sparse_triplets_from behind a flag) so every consumer is covered; make the JAX import lazy (xp-dispatched) in the imaging util. First establish whether any current mesh (Rectangular, Delaunay, DelaunayNN, Voronoi) actually emits negative indices or non-zero weights in unused slots; if none does, the fix is a guard plus the regression test, and the prompt notes the latent nature.
Activity
Jammy2211 commentedon Oct 21, 2026
Library PR created: #649 (head dd26822). Defect confirmed latent (Delaunay/DelaunayNN unused slots hold -1 with weight 0); masking is a prerequisite for the new numpy bincount branch. Follow-up: ImagingSparseOperator and the imaging curvature methods still import JAX unconditionally. Heart YELLOW acknowledged by the human 2026-10-21. Tier glance: auto-merge on green if the Witness passes.
Jammy2211 commentedon Oct 21, 2026
Shipped: #649 merged (glance tier, auto-merged on green: unittest 3.12 / 3.13 / nojax all pass; Witness masking + parity tests in-run). Pending release. Follow-up (not fixed here): ImagingSparseOperator.from_noise_map_and_psf and the imaging curvature methods import JAX unconditionally, so InversionImagingSparse still needs JAX on the numpy backend.
Jammy2211 commentedon Oct 21, 2026
Follow-up resolved without a change:
ImagingSparseOperatoris the JAX implementation by design (batched FFT via lax.fori_loop / dynamic_update_slice), with the NumPy/CPU alternative being the separately namedImaging.apply_sparse_operator_cpu→SparseLinAlgImagingNumba/InversionImagingSparseNumba(see theapply_sparse_operatordocstring). The numpy branches added in #649 cover the only numpy-reachable helpers.