Skip to content

Commit 98c817d

Browse files
committed
fix: stabilize degenerate border PCA axes
1 parent 6184492 commit 98c817d

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

autoarray/inversion/mesh/border_relocator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,15 @@ def ellipse_params_via_border_pca_from(border_grid, xp=np, eps=1e-12):
249249

250250
phi = xp.arctan2(v_major[1], v_major[0])
251251

252+
# PCA eigenvectors are undefined for an isotropic covariance. NumPy and
253+
# JAX may therefore choose different, equally valid orientations whose
254+
# downstream max-extent ellipses are not equivalent. Use a deterministic
255+
# axis-aligned frame when the eigenvalue gap is at floating-point scale.
256+
eigenvalue_scale = xp.maximum(xp.max(xp.abs(evals)), eps)
257+
relative_gap = (evals[-1] - evals[0]) / eigenvalue_scale
258+
isotropy_tolerance = xp.sqrt(xp.finfo(C.dtype).eps)
259+
phi = xp.where(relative_gap <= isotropy_tolerance, 0.0, phi)
260+
252261
# Rotate border points into ellipse-aligned frame
253262
c = xp.cos(phi)
254263
s = xp.sin(phi)

test_autoarray/inversion/pixelization/test_border_relocator.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import autoarray as aa
55

66
from autoarray.inversion.mesh.border_relocator import (
7+
ellipse_params_via_border_pca_from,
78
sub_border_pixel_slim_indexes_from,
89
)
910

@@ -376,3 +377,40 @@ def test__relocated_grid_from__positive_origin_included_in_relocate():
376377
relocated_grid = border_relocator.relocated_grid_from(grid=grid)
377378

378379
assert relocated_grid.over_sampled[1] == pytest.approx([1.95, 1.0], 1e-4)
380+
381+
def test__ellipse_params__near_isotropic_border_uses_deterministic_axis():
382+
border_grid = np.array(
383+
[
384+
[-0.0960155108, 0.0960155108],
385+
[-0.55, 0.0],
386+
[-0.0960155108, -0.0960155108],
387+
[0.0, 0.55],
388+
[0.0, -0.55],
389+
[0.0960155108, 0.0960155108],
390+
[0.55, 0.0],
391+
[0.0960155108, -0.0960155108],
392+
]
393+
)
394+
395+
_, a, b, phi = ellipse_params_via_border_pca_from(border_grid=border_grid)
396+
397+
assert float(phi) == 0.0
398+
assert float(a) == pytest.approx(0.55 + 1.0e-12)
399+
assert float(b) == pytest.approx(0.55 + 1.0e-12)
400+
401+
402+
def test__ellipse_params__anisotropic_border_retains_pca_major_axis():
403+
border_grid = np.array(
404+
[
405+
[-2.0, 0.0],
406+
[0.0, 1.0],
407+
[2.0, 0.0],
408+
[0.0, -1.0],
409+
]
410+
)
411+
412+
_, a, b, phi = ellipse_params_via_border_pca_from(border_grid=border_grid)
413+
414+
assert abs(float(phi)) == pytest.approx(np.pi / 2.0)
415+
assert float(a) > float(b)
416+

0 commit comments

Comments
 (0)