fix: Delaunay areas_for_magnification returns barycentric dual areas, not Voronoi cells (#524) - #525
Merged
Conversation
…524) MeshGeometryDelaunay.areas_for_magnification returned scipy Voronoi cell areas (unbounded cells zeroed), but the Delaunay mapper is a barycentric-linear interpolant whose exact quadrature weights are the barycentric dual areas (sum of triangle_area / 3 per vertex). The phase 8 audit (#522) measured the Voronoi denominator biasing magnification by -13% to -99% on realistic meshes; the dual areas recover the identity-lens mu = 1.0 to 3e-5. - scipy_delaunay / jax_delaunay (and the Matern variants) now return the dual areas they already compute; DelaunayInterface carries them as `dual_areas`, InterpolatorDelaunay.mesh_geometry hands them to MeshGeometryDelaunay(dual_areas=), so the value is in-graph and jit-safe on the JAX path. The kNN interpolators return None and the geometry falls back to a host-side scipy triangulation only if asked. - areas_for_magnification returns the dual areas; voronoi_areas is unchanged and stays available under its own name. Docstring rewritten. - _plot_delaunay: the docstring no longer claims Gouraud shading (tripcolor defaults to flat, integral-equivalent), and the mapper's own simplices are passed as triangles= so the figure shows the mesh the inversion used. - Tests: the two phase-8 tests pinning the Voronoi semantics are flipped deliberately; new mapper-level identity test (sum(mapping_matrix @ s) * pixel_area == sum(s * areas), rel 3.4e-5, Voronoi off by 27%) and a JAX-vs-NumPy parity test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVLuDrRfkZ81TBh8yVbFrN
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
Fixes #524, the first of the two defects proven by the euclid-dr1-prep phase 8 audit (#522, PR #523).
MeshGeometryDelaunay.areas_for_magnificationreturned scipy Voronoi cell areas (only unbounded cells zeroed). The Delaunay mapper is a barycentric-linear interpolant, so the exact quadrature weight forΣ s_i × area_i— the integral of the reconstruction over the mesh hull — is the barycentric dual area (Σ triangle_area/3per vertex). Bounded boundary Voronoi cells can be orders of magnitude larger than their dual areas, which the audit measured biasing magnification by −13 % to −99 % on realistic meshes.scipy_delaunay, in-graph JAXjax_delaunay) and discarded after the split-point offsets. They are now returned (sixth tuple element; the Matérn variants return them too, as a fourth element, through one shared in-graph helper_dual_areas_padded_jnp), carried onDelaunayInterface.dual_areas, exposed asInterpolatorDelaunay.dual_areas, and handed toMeshGeometryDelaunay(dual_areas=)bymesh_geometry. On the JAX path the value is traced, soareas_for_magnificationis safe inside the per-samplejax.jitPyAutoLens evaluates latents in.areas_for_magnificationreturns the dual areas. A geometry built standalone (dual_areas=None, e.g. the tests, or the kNN interpolators which build no Delaunay tables and overridedual_areastoNone) computes the identical value host-side from a scipy triangulation, only if asked.voronoi_areas/voronoi_areas_numpyare unchanged and remain available under their own name. Docstring rewritten to say which quadrature this is and why._plot_delaunay: docstring no longer claims Gouraud shading (tripcolordefaults to flat, whose integral equals the dual-area integral by linearity), and the mapper's own simplices (padding rows dropped) are passed astriangles=so the figure shows the triangulation the inversion used; mappers without Delaunay tables fall back to matplotlib's own.bounded_boundary_cells_are_kept→equals_barycentric_dual_area;uniform_latticenow asserts the hull total(n-1)²instead of(n-2)²). New mapper-level identity testtest__areas_for_magnification__integrates_reconstruction_like_mapping_matrixand JAX-parity testtest__areas_for_magnification__jax_matches_numpyinmappers/test_delaunay.py.Identity-test numbers (8×8 mesh, outer ring pinned to the data footprint, interior jittered, random positive reconstruction; hull area = footprint area = 4.0 exactly, no out-of-hull fallback):
F_map = Σ (mapping_matrix @ s) × pixel_areaF_dual = Σ s × areas_for_magnification(new)F_voronoi = Σ s × voronoi_areas(old semantics)One base file,
test_autoarray/inversion/pixelization/interpolator/test_delaunay.py, was not black-clean onmain; black touched about a dozen pre-existing lines in it alongside this PR's edits.Out of scope, as filed: the rectangular-mesh lead (cluster epic), DelaunayNN/KNN area definitions, a library-level magnification API, and the second audit defect (
magnificationlatent 0/0 for pixelized sources — separate PyAutoLens prompt).API Changes
Changed behaviour:
MeshGeometryDelaunay.areas_for_magnificationnow returns the barycentric dual areas (was Voronoi cell areas with unbounded cells zeroed). Any consumer summingreconstruction × areas_for_magnificationgets the exact integral of the reconstruction; the only known consumers are the foursource_science.pyworkspace scripts, which call the same attribute name and need no code change.Added:
MeshGeometryDelaunay(dual_areas=),DelaunayInterface.dual_areas,InterpolatorDelaunay.dual_areas;scipy_delaunay/jax_delaunayreturn a sixth element and the Matérn variants a fourth (internal helpers).See full details below.
Test Plan
pytest test_autoarray/inversion -q— 476 passed, 1 skippedpytest test_autoarray -q -n auto— 1420 passed, 1 skippedblack --checkon every changed fileFull API Changes (for automation & release notes)
Changed Behaviour
autoarray.inversion.mesh.mesh_geometry.delaunay.MeshGeometryDelaunay.areas_for_magnification— returns the barycentric dual area of every mesh vertex (sums to the convex-hull area; exact quadrature of the barycentric-linear reconstruction). Previously returnedvoronoi_areaswith the-1unbounded-cell sentinel zeroed.autoarray.plot.inversion._plot_delaunay— passes the mapper's simplices astriangles=when available (rendering unchanged where matplotlib's triangulation coincided, which it did on every audited mesh).Added
MeshGeometryDelaunay.__init__(..., dual_areas=None, xp=np, **kwargs)— accepts the interpolator's dual areas.DelaunayInterface.__init__(..., xp=np, dual_areas=None)/DelaunayInterface.dual_areas— trailing keyword, so positional subclass callers (DelaunayNNInterface) are unaffected.InterpolatorDelaunay.dual_areas(property) — the interface's dual areas;InterpolatorKNearestNeighbor.dual_areasreturnsNone.scipy_delaunay/jax_delaunayreturn(points, simplices_padded, mappings, split_points, splitted_mappings, areas);scipy_delaunay_matern/jax_delaunay_maternreturn(points, simplices_padded, mappings, areas)._dual_areas_padded_jnp(points, simplices_padded)— private in-graph helper shared by the JAX paths.Migration
scipy_delaunay/jax_delaunay/ matern tuples directly (one in-repo test did) must accept the extra trailing element.areas_for_magnificationzeroing hull cells should usevoronoi_areasand zero the-1entries itself.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VVLuDrRfkZ81TBh8yVbFrN
Generated by Claude Code