Skip to content

Commit dabe993

Browse files
authored
Merge pull request #315 from PyAutoLabs/feature/datacube-sparse-operator
fix(interferometer-sparse): guard against Delaunay mappers (issue #314)
2 parents f484332 + a4cb5ad commit dabe993

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

autoarray/inversion/inversion/interferometer/sparse.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
AbstractInversionInterferometer,
88
)
99
from autoarray.inversion.linear_obj.linear_obj import LinearObj
10+
from autoarray.inversion.mesh.mesh.delaunay import Delaunay
1011
from autoarray.settings import Settings
1112
from autoarray.inversion.mappers.abstract import Mapper
1213
from autoarray.structures.visibilities import Visibilities
@@ -101,6 +102,27 @@ def curvature_matrix_diag(self) -> np.ndarray:
101102

102103
mapper = self.cls_list_from(cls=Mapper)[0]
103104

105+
# The interferometer sparse-operator curvature path
106+
# (``InterferometerSparseOperator.curvature_matrix_via_sparse_operator_from``)
107+
# has only been validated against ``Rectangular*`` meshes (single source
108+
# pixel per image pixel, weight=1). When given a ``Delaunay`` mapper
109+
# (three source pixels per image pixel via barycentric interpolation,
110+
# weights summing to 1) the returned curvature matrix disagrees with the
111+
# mapping path by ~34% Frobenius and the regularized matrix loses
112+
# positive-definiteness, raising a numpy ``LinAlgError`` at the Cholesky
113+
# call site in ``Inversion.log_det_curvature_reg_matrix_term``. Guard
114+
# rather than silently mis-computing.
115+
if isinstance(mapper.mesh, Delaunay):
116+
raise NotImplementedError(
117+
"Interferometer.apply_sparse_operator() is not implemented for "
118+
"Delaunay-mesh pixelizations: the sparse curvature math has only "
119+
"been validated against Rectangular meshes (Pmax=1, weight=1) "
120+
"and is structurally wrong for barycentric-interpolated mappers "
121+
"(Pmax=3). For Delaunay interferometer fits, use the plain DFT "
122+
"or NUFFT path (i.e. omit the apply_sparse_operator step). "
123+
"Tracking issue: https://github.com/PyAutoLabs/PyAutoArray/issues/314"
124+
)
125+
104126
return self.dataset.sparse_operator.curvature_matrix_via_sparse_operator_from(
105127
pix_indexes_for_sub_slim_index=mapper.pix_indexes_for_sub_slim_index,
106128
pix_weights_for_sub_slim_index=mapper.pix_weights_for_sub_slim_index,

test_autoarray/inversion/inversion/interferometer/test_interferometer.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,73 @@ def test__fast_chi_squared(
6262
chi_squared = aa.util.fit.chi_squared_complex_from(chi_squared_map=chi_squared_map)
6363

6464
assert inversion.fast_chi_squared == pytest.approx(chi_squared, 1.0e-4)
65+
66+
67+
def test__apply_sparse_operator__delaunay_mapper__raises_not_implemented():
68+
"""``InversionInterferometerSparse.curvature_matrix_diag`` must raise a
69+
``NotImplementedError`` rather than silently mis-computing when paired
70+
with a Delaunay mapper.
71+
72+
The interferometer sparse-operator curvature path
73+
(``InterferometerSparseOperator.curvature_matrix_via_sparse_operator_from``)
74+
was only validated against ``Rectangular*`` meshes (single source pixel per
75+
image pixel, weight 1). On a Delaunay mapper (three source pixels per image
76+
pixel via barycentric interpolation, weights summing to 1) the returned
77+
curvature matrix disagrees with the mapping path by ~34% Frobenius norm and
78+
the regularized matrix loses positive-definiteness, raising a numpy
79+
``LinAlgError`` deep inside ``Inversion.log_det_curvature_reg_matrix_term``.
80+
The guard at the entry point catches the mis-use early with a clear message.
81+
"""
82+
mask = aa.Mask2D(
83+
mask=[
84+
[True, True, True, True, True, True, True],
85+
[True, True, True, True, True, True, True],
86+
[True, True, True, False, True, True, True],
87+
[True, True, False, False, False, True, True],
88+
[True, True, True, False, True, True, True],
89+
[True, True, True, True, True, True, True],
90+
[True, True, True, True, True, True, True],
91+
],
92+
pixel_scales=2.0,
93+
)
94+
95+
grid = aa.Grid2D.from_mask(mask=mask, over_sample_size=1)
96+
97+
mesh = aa.mesh.Delaunay(pixels=9)
98+
image_mesh = aa.image_mesh.Overlay(shape=(3, 3))
99+
image_mesh_grid = image_mesh.image_plane_mesh_grid_from(
100+
mask=mask, adapt_data=None
101+
)
102+
103+
interpolator = mesh.interpolator_from(
104+
source_plane_data_grid=grid,
105+
source_plane_mesh_grid=image_mesh_grid,
106+
)
107+
mapper = aa.Mapper(interpolator=interpolator)
108+
109+
n_visibilities = 5
110+
rng = np.random.default_rng(seed=0)
111+
data = aa.Visibilities(
112+
visibilities=rng.normal(size=(n_visibilities, 2)).astype(np.float64)
113+
)
114+
noise_map = aa.VisibilitiesNoiseMap(
115+
visibilities=np.ones((n_visibilities, 2), dtype=np.float64)
116+
)
117+
uv_wavelengths = rng.normal(size=(n_visibilities, 2)).astype(np.float64)
118+
119+
dataset = aa.Interferometer(
120+
data=data,
121+
noise_map=noise_map,
122+
uv_wavelengths=uv_wavelengths,
123+
real_space_mask=mask,
124+
transformer_class=aa.TransformerDFT,
125+
)
126+
dataset_sparse = dataset.apply_sparse_operator(use_jax=False)
127+
128+
inversion = aa.Inversion(
129+
dataset=dataset_sparse,
130+
linear_obj_list=[mapper],
131+
)
132+
133+
with pytest.raises(NotImplementedError, match=r"Delaunay"):
134+
_ = inversion.curvature_matrix

0 commit comments

Comments
 (0)