Repository navigation
fix: exclude zero-weight Delaunay mappings from adaptive pixel signals (NumPy/JAX log-det parity) - #631
Merged
Conversation
adaptive_pixel_signals_from normalised each pixel's summed adapt signal by
a count that included mapping entries with zero weight. For Delaunay tables
those entries are not unique. A point lying exactly on a Delaunay vertex has
barycentric weights (0, 0, 1) in any triangle around that vertex. A point on
an edge has a zero weight in either neighbouring triangle. scipy's
find_simplex and the JAX Delaunay walk legitimately pick different
triangles, so the two backends counted different phantom neighbours. That
gave different pixel signals, AdaptSplit regularization weights and
regularization matrices H from identical inputs (PyAutoArray#627). On the
125x125 Aris setup this was the whole 118.76 log det H gap between NumPy and
JAX. That setup is a stride-2 grid-subsample mesh with 1,375
vertex-coincident data points. The log-det factorizations agree with a
50-digit reference to <=2e-8 on each H.
adaptive_pixel_signals_from gains a keyword exclude_zero_weight_mappings
(default False, which keeps the previous result exactly). When True, the
validity mask also requires a non-zero weight, so zero-weight entries add
to neither the summed signal nor pixel_counts. The set of non-zero (index,
weight) pairs is unique, so the result no longer depends on which simplex
was chosen. The flag is a static Python bool, so the function stays
jit-safe and differentiable under JAX.
Scope: Delaunay tables only. The live human chose this on the issue
("Delaunay only (Recommended by review)"). The flag is the class attribute
AbstractInterpolator.exclude_zero_weight_mappings:
- False by default, which covers the rectangular interpolators.
- True on InterpolatorDelaunay, inherited by InterpolatorDelaunayNN.
- False again on InterpolatorKNearestNeighbor and so on KNNBarycentric.
Mapper.pixel_signals_from passes the attribute through.
KNN is excluded because KNNBarycentric clips negative barycentric weights to
exactly zero for every data point outside its 3-nearest triangle. Those
zeros cover whole regions, not rare coincidences. Excluding them would
change KNN pixel signals broadly and would make the count, and so the
likelihood, jump wherever a clipped weight reaches zero. On the 16x16 test
geometry, 48% (vertex-coincident mesh) to 65% (jittered mesh) of
KNNBarycentric's mapping slots are exact zeros. Delaunay and DelaunayNN have
0-2 such slots out of about 4,200.
Behaviour change: pixel signals, adaptive regularization weights, H and
the Bayesian evidence change on both backends, but only for Delaunay and
DelaunayNN meshes that have data points exactly on vertices or edges. These
are typically Overlay or grid-subsample image meshes. KNN, KNNBarycentric
and rectangular meshes are unchanged bit for bit. So are Delaunay meshes
without zero-weight entries.
Tests:
- scipy and JAX-walk Delaunay tables give identical pixel signals on a
vertex-coincident grid. Without the fix they differed by up to 0.10.
- A NumPy check that one vertex point described through two triangles
gives identical signals.
- AdaptSplit H parity at rtol 1e-12, and NumPy and JAX log det H agreeing
with each other and with a high-precision reference to <1e-6. With the
Delaunay flag forced off, the H check fails.
- The default flag still counts zero-weight entries, checked against
hand-computed values.
- KNNBarycentric mapper signals equal the default (counting) util path,
on a table that has clipped zero weights.
- Delaunay and DelaunayNN mappers route the exclusion.
Closes #627
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The KNN interpolator computes its weights with JAX, so the test added for
PyAutoArray#627 failed with ModuleNotFoundError on the unittest-nojax leg
(run 38795253065). importorskip("jax") matches the file's other JAX tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Before this change, NumPy and JAX gave different AdaptSplit regularization matrices H, and so different
log_det_regularization_matrix_termvalues, from identical inputs. On the Aris 125×125 interferometer setup the log-det came out as 2443.22 on NumPy and 2561.98 on JAX.Cause. The log-det code is not at fault.
mapper_util.adaptive_pixel_signals_fromdivides each pixel's summed adapt signal by a count, and that count included mapping entries with zero weight.find_simplexand the JAX Delaunay walk legitimately pick different triangles for such points, so each backend counted different phantom neighbours.Fix (scoped to Delaunay tables).
adaptive_pixel_signals_fromgainsexclude_zero_weight_mappings: bool = False. When it is True, zero-weight entries are dropped from both the summed signal andpixel_counts.Mapper.pixel_signals_frompasses it through:AbstractInterpolator: False. This also covers the rectangular interpolators and the mocks.InterpolatorDelaunay: True.InterpolatorDelaunayNNinherits True.InterpolatorKNearestNeighbor: False.InterpolatorKNNBarycentricinherits False.Closes #627
Decision taken
The human made this decision; the agent did not. The first version of the branch excluded zero-weight entries for every interpolator. The independent review then found that
KNNBarycentricclips negative barycentric weights to exactly 0 for every point outside its 3-nearest triangle (knn.py).The run parked with three options posted on #627. The live human chose (b) "Delaunay only (Recommended by review)" (decision). KNN and rectangular keep the previous counting.
git revertthis PR's commit.Because a human chose the scope, this PR does not carry the
decision-takenlabel.API Changes
There is no signature break. One new keyword and one new class attribute were added, and there is one behaviour change, limited to Delaunay-family meshes:
See full details below.
Test Plan
pytest test_autoarray -x -q: 2039 passed, 4 xfailed.pytest -x -q, run against this branch's autoarray: PyAutoGalaxy 1357 passed; PyAutoLens 832 passed, 1 xfailed. No existing expectation changed.pyauto-heart smoke autogalaxy autolens autolens_test euclid howtolensagainst this branch: 151 passed, 0 failed (rc 0; covers adapt-pixelization and interferometer scripts). The earlier global-scope version also passed 151/151.Witness: "numba and JAX log_det_regularization_matrix_term agree to <1e-3 on the Aris setup, with a unit test pinning the value on an ill-conditioned AdaptSplit matrix".
Reviewer observations (non-blocking):
Validation checklist (--auto run — plan was not pre-approved)
release validation FAILED (stage integrate)), shipped under the development override below.Heart RED development override
Heart was RED at launch (2026-10-15 evening). The reasons recorded on #626/#627 were:
Heart: RED — Release RED; monitoring RED · 10/100 · 415 unresolved[red] release validation FAILED (stage integrate)[red] PyAutoHands/workflows/Python Version Matrix: failure[red] PyAutoHeart/workflows/Release Integrate: failure[red] PyAutoMind/workflows/Lifecycle Drift: failure[red] PyAutoArray/Tests: red[red] autofit_workspace_test/Smoke Tests: red[red] autogalaxy_workspace_test/Smoke Tests: red[red] autolens_workspace_test/Smoke Tests: red[red] Release validation: NOT release_ready[red] Worktree drift[red] autolens_inference: red[red] autolens_profiling: red[red] euclid_strong_lens_modeling_pipeline: redAt ship time the reason was
release validation FAILED (stage integrate).The live human authorized a development-only override in session 3e3dea38 (AskUserQuestion: "Yes, override for development (Recommended)").
/prm). Heart stays RED for release purposes.Campaign
Logged under the PyAutoPulse interferometer campaign, PyAutoLabs/PyAutoPulse#62, task
interferometer_large_mask_aris. Record the before/after witness there at close-out.Full API Changes (for automation & release notes)
Added
autoarray.inversion.mappers.mapper_util.adaptive_pixel_signals_from(..., exclude_zero_weight_mappings: bool = False, xp=np): if True, mapping entries with weight exactly 0 are not counted toward a pixel's mapped-data count.AbstractInterpolator.exclude_zero_weight_mappingsclass attribute:Falseby default,TrueonInterpolatorDelaunay(and so onInterpolatorDelaunayNN), andFalseagain onInterpolatorKNearestNeighbor(and so onInterpolatorKNNBarycentric).Changed Behaviour
Mapper.pixel_signals_fromnow passesself.interpolator.exclude_zero_weight_mappings. For Delaunay and DelaunayNN meshes with data points exactly on vertices or edges, pixel signals change, and so do adaptive regularization (AdaptSplit / Adapt…) matrices and Bayesian evidence. The change is the same on NumPy and JAX, and both backends now agree.Migration
adaptive_pixel_signals_fromkeep the previous result by default.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code