Skip to content

Commit 6df3ebf

Browse files
Jammy2211Jammy2211
authored andcommitted
Merge remote-tracking branch 'origin/feature/rect-adapt' into feature/rectangular-kernel-cdf-mesh
2 parents e0f1e5a + c177391 commit 6df3ebf

2 files changed

Lines changed: 71 additions & 8 deletions

File tree

autoarray/inversion/mesh/mesh_geometry/rectangular.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -526,16 +526,29 @@ def areas_for_magnification(self):
526526
@property
527527
def edges_transformed(self):
528528
"""
529-
A class packing the ndarrays describing the neighbors of every pixel in the rectangular pixelization (see
530-
`Neighbors` for a complete description of the neighboring scheme).
531-
532-
The neighbors of a rectangular pixelization are computed by exploiting the uniform and symmetric nature of the
533-
rectangular grid, as described in the method `rectangular_neighbors_from`.
529+
The source-plane cell edges of every mesh pixel, transformed through
530+
the adaptive CDF — one more edge than pixels per axis, shape
531+
``(n + 1, 2)`` packing (y_edges, x_edges).
532+
533+
The interpolation mapper is node-based: reconstruction value
534+
``(row r, col c)`` lives at node ``U_y = (n_y - r - 1)/(n_y - 3)``,
535+
``U_x = (c - 1)/(n_x - 3)`` in unit CDF space (a guard ring occupies
536+
the border; rows are flipped by the mapper's ``row = n - i``). The
537+
cell edges are therefore the node midpoints — a uniform ``[0, 1]``
538+
partition drew every cell up to ~1.5 mesh pixels away from where the
539+
mapper scatters its flux (issue #372). Guard-node edges fall outside
540+
the CDF domain and clamp to the data span in the inverse interp,
541+
squashing the border guard cells to the region they actually cover.
534542
"""
535543

536-
# edges defined in 0 -> 1 space, there is one more edge than pixel centers on each side
537-
edges_y = self._xp.linspace(1, 0, self.shape_native[0] + 1)
538-
edges_x = self._xp.linspace(0, 1, self.shape_native[1] + 1)
544+
# Node-midpoint edges in unit CDF space (see docstring); the guard
545+
# denominator keeps the degenerate n <= 3 meshes finite (their
546+
# interpolator maps every point to the single interior node anyway).
547+
n_y, n_x = self.shape_native
548+
rows = self._xp.arange(n_y + 1)
549+
cols = self._xp.arange(n_x + 1)
550+
edges_y = (n_y - rows - 0.5) / max(n_y - 3, 1)
551+
edges_x = (cols - 1.5) / max(n_x - 3, 1)
539552

540553
edges_reshaped = self._xp.stack([edges_y, edges_x]).T
541554

test_autoarray/inversion/pixelization/mesh_geometry/test_rectangular.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,53 @@ def test__edges_transformed(mask_2d_7x7):
200200
),
201201
abs=1e-8,
202202
)
203+
204+
205+
def test__edges_transformed__aligned_with_interpolation_node_convention():
206+
"""
207+
Regression test for issue #372: the pcolormesh plotting path draws value
208+
(row r, col c) inside the cell bounded by edges_transformed — that cell
209+
must be centred on the interpolation mapper's node for that value, not on
210+
a uniform [0, 1] partition (which shifted plots by ~1.5 mesh pixels).
211+
212+
A delta function scattered through the mapper must land in plotted cells
213+
whose weighted centroid matches the input point to sub-cell precision.
214+
"""
215+
from autoarray.inversion.mesh.interpolator.rectangular import (
216+
adaptive_rectangular_mappings_weights_via_interpolation_from,
217+
adaptive_rectangular_transformed_grid_from,
218+
)
219+
220+
n = 10
221+
rng = np.random.default_rng(0)
222+
data_grid = rng.uniform(-1.0, 1.0, (5000, 2))
223+
test_point = np.array([[0.3, -0.2]])
224+
225+
flat_indices, weights = (
226+
adaptive_rectangular_mappings_weights_via_interpolation_from(
227+
source_grid_size=n,
228+
data_grid=data_grid,
229+
data_grid_over_sampled=test_point,
230+
)
231+
)
232+
233+
# The node-midpoint unit-space edges (what edges_transformed now builds),
234+
# pushed through the same CDF transform.
235+
rows = np.arange(n + 1)
236+
edges_y = (n - rows - 0.5) / (n - 3)
237+
edges_x = (rows - 1.5) / (n - 3)
238+
edges = np.stack([edges_y, edges_x]).T
239+
edges_t = adaptive_rectangular_transformed_grid_from(data_grid, edges)
240+
y_edges, x_edges = edges_t.T
241+
242+
centroid_y = 0.0
243+
centroid_x = 0.0
244+
for flat, weight in zip(flat_indices[0], weights[0]):
245+
r, c = flat // n, flat % n
246+
centroid_y += weight * 0.5 * (y_edges[r] + y_edges[r + 1])
247+
centroid_x += weight * 0.5 * (x_edges[c] + x_edges[c + 1])
248+
249+
# Half a mesh cell in these units is ~0.15; the pre-fix uniform edges
250+
# missed by ~0.4 in y.
251+
assert centroid_y == pytest.approx(test_point[0, 0], abs=0.1)
252+
assert centroid_x == pytest.approx(test_point[0, 1], abs=0.1)

0 commit comments

Comments
 (0)