Skip to content

test: make the MGE bitwise sigma-ladder guards portable across CPUs - #551

Merged
Jammy2211 merged 1 commit into
mainfrom
claude/pyautogalaxy-mge-sigma-test-3neq07
Aug 5, 2026
Merged

test: make the MGE bitwise sigma-ladder guards portable across CPUs#551
Jammy2211 merged 1 commit into
mainfrom
claude/pyautogalaxy-mge-sigma-test-3neq07

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Closes #550.

What changed

test__mge_model_from__default_sigma_list_is_bitwise_unchanged and
test__mge_point_model_from__default_sigma_list_is_bitwise_unchanged (added with #549)
asserted exact equality between two different numpy code paths. The implementation
builds each sigma with a per-element scalar power
(gaussian.sigma = 10 ** log10_sigma_list[i], autogalaxy/analysis/model_util.py:190
and :271); the tests built their expectation with a vectorised 10 ** np.linspace(...).
numpy does not guarantee its scalar and SIMD power loops agree bit for bit, so the two
disagree by 1 ULP on AVX-512 hardware — green on GitHub's runners, red on an AVX-512
developer machine. The regression guard was not portable.

This builds the expected ladder element by element, so both sides take the same
numpy path. The comparison stays bitwise; only the code path producing the expectation
changes.

Test-only. No library source is touched.

What was deliberately not done

pytest.approx(rel=1e-8) is still not used. The test docstring rules it out and the
reasoning holds — it only fails once the ladder has moved by a relative ~1e-7, already
past the point where the PyAutoFit identifier (RESOLUTION = 1e-8) changes. That
reasoning is kept verbatim; both docstrings now additionally carry a PORTABILITY TRAP
note explaining why the expectation must stay element-wise, so a later tidy-up does not
re-vectorise it.

Verification

The host used for this change is itself AVX-512 (avx512f/bw/cd/dq/vl/vnni) with numpy
2.4.6, so the failure was reproduced, not assumed — both tests fail on the unmodified
tree and pass after.

The reported footprint was narrower than the real one. Drift occurs at:

test case index
mge_model_from mask_radius=3.0, total_gaussians=20 18 (the reported one)
mge_model_from mask_radius=3.5, total_gaussians=30 8
mge_point_model_from pixel_scales=0.1, total_gaussians=10 4, 9
mge_point_model_from pixel_scales=0.05, total_gaussians=5 3

Both guards were broken, not just the first.

Control test — an element-wise expectation risks becoming vacuous (the test merely
restating the implementation), so this was checked rather than assumed: perturbing the
implementation defaults by a relative 1e-7 (sigma_min 1e-41.0000001e-4,
0.010.010000001) still fails both tests. The exactness guarantee survives the
change. The implementation was restored afterwards; only the test file is modified here.

Also verified: np.log10(1e-4) == -4.0 and np.log10(0.01) == -2.0 exactly, so the
tests' literal endpoints remain a faithful stand-in for the implementation's
np.log10(sigma_min).

Suites: test_autogalaxy/analysis/test_model_util.py 29 passed; full
test_autogalaxy/ 1004 passed, 3 skipped. Run locally on Python 3.11 (the only
interpreter with a stack in that sandbox) — CI grades 3.12/3.13, and the change is
pure-Python test code with no version-sensitive surface.

Scope

Test-only, so no downstream workspace impact and no pending-release gate. The
neighbouring pytest.approx(..., 1.0e-8) assertions (L129, L237) are tolerance-based by
design and are untouched. PyAutoLens has no equivalent exact-equality
10 ** np.linspace assertion.

🤖 Generated with Claude Code

https://claude.ai/code/session_011tJFsEesnF7rZmn2xvfxUe


Generated by Claude Code

The two `*_default_sigma_list_is_bitwise_unchanged` tests added with #549
asserted exact equality between two different numpy code paths: the
implementation builds each sigma with a per-element scalar power
(`gaussian.sigma = 10 ** log10_sigma_list[i]`, model_util.py:190 and :271)
while the tests built their expectation with a vectorised
`10 ** np.linspace(...)`. numpy does not guarantee its scalar and SIMD power
loops agree bit for bit, so the two disagree by 1 ULP on AVX-512 hardware --
green on GitHub's runners, red on an AVX-512 developer machine. The guard was
therefore not portable.

Build the expected ladder element by element instead, so both sides take the
same numpy path. The comparison stays exact; only the code path producing the
expectation changes. `pytest.approx(rel=1e-8)` is still deliberately not used
-- the existing docstring reasoning for that is kept verbatim, and both
docstrings now record the portability trap so the expectation is not
re-vectorised later.

Verified on an AVX-512 host with numpy 2.4.6: both tests failed before this
change (mask_radius=3.0/20 at index 18, pixel_scales=0.1/10 at index 4) and
pass after. Control-tested by perturbing the implementation defaults by a
relative 1e-7, which both tests still catch, so the exactness guarantee is
intact. Full test_autogalaxy suite: 1004 passed, 3 skipped.

Closes #550

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011tJFsEesnF7rZmn2xvfxUe
@Jammy2211
Jammy2211 merged commit b898820 into main Aug 5, 2026
2 checks passed
@Jammy2211
Jammy2211 deleted the claude/pyautogalaxy-mge-sigma-test-3neq07 branch August 6, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: make the MGE bitwise sigma-ladder guards portable across CPUs

2 participants