Skip to content

fix: Delaunay areas_for_magnification returns barycentric dual areas, not Voronoi cells (#524) - #525

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/delaunay-dual-area-magnification
Sep 5, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/delaunay-dual-area-magnification

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Fixes #524, the first of the two defects proven by the euclid-dr1-prep phase 8 audit (#522, PR #523).

MeshGeometryDelaunay.areas_for_magnification returned 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/3 per 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.

  • The dual areas were already computed on both interpolator paths (numpy scipy_delaunay, in-graph JAX jax_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 on DelaunayInterface.dual_areas, exposed as InterpolatorDelaunay.dual_areas, and handed to MeshGeometryDelaunay(dual_areas=) by mesh_geometry. On the JAX path the value is traced, so areas_for_magnification is safe inside the per-sample jax.jit PyAutoLens evaluates latents in.
  • areas_for_magnification returns the dual areas. A geometry built standalone (dual_areas=None, e.g. the tests, or the kNN interpolators which build no Delaunay tables and override dual_areas to None) computes the identical value host-side from a scipy triangulation, only if asked. voronoi_areas / voronoi_areas_numpy are 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 (tripcolor defaults to flat, whose integral equals the dual-area integral by linearity), and the mapper's own simplices (padding rows dropped) are passed as triangles= so the figure shows the triangulation the inversion used; mappers without Delaunay tables fall back to matplotlib's own.
  • Tests: the two phase-8 tests that deliberately pinned the Voronoi semantics are flipped (bounded_boundary_cells_are_kept → equals_barycentric_dual_area; uniform_lattice now asserts the hull total (n-1)² instead of (n-2)²). New mapper-level identity test test__areas_for_magnification__integrates_reconstruction_like_mapping_matrix and JAX-parity test test__areas_for_magnification__jax_matches_numpy in mappers/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):

quantity value
F_map = Σ (mapping_matrix @ s) × pixel_area 4.730578587726577
F_dual = Σ s × areas_for_magnification (new) 4.730741058881734 — rel err 3.4e-5
F_voronoi = Σ s × voronoi_areas (old semantics) 3.453335121760202 — rel err 27 %

One base file, test_autoarray/inversion/pixelization/interpolator/test_delaunay.py, was not black-clean on main; 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 (magnification latent 0/0 for pixelized sources — separate PyAutoLens prompt).

API Changes

Changed behaviour: MeshGeometryDelaunay.areas_for_magnification now returns the barycentric dual areas (was Voronoi cell areas with unbounded cells zeroed). Any consumer summing reconstruction × areas_for_magnification gets the exact integral of the reconstruction; the only known consumers are the four source_science.py workspace 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_delaunay return 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 skipped
  • pytest test_autoarray -q -n auto — 1420 passed, 1 skipped
  • black --check on every changed file
  • CI green on this PR
Full 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 returned voronoi_areas with the -1 unbounded-cell sentinel zeroed.
  • autoarray.plot.inversion._plot_delaunay — passes the mapper's simplices as triangles= 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_areas returns None.
  • scipy_delaunay / jax_delaunay return (points, simplices_padded, mappings, split_points, splitted_mappings, areas); scipy_delaunay_matern / jax_delaunay_matern return (points, simplices_padded, mappings, areas).
  • _dual_areas_padded_jnp(points, simplices_padded) — private in-graph helper shared by the JAX paths.

Migration

  • Callers unpacking the scipy_delaunay / jax_delaunay / matern tuples directly (one in-repo test did) must accept the extra trailing element.
  • Code that relied on areas_for_magnification zeroing hull cells should use voronoi_areas and zero the -1 entries itself.

Generated by the PyAutoLabs agent workflow.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VVLuDrRfkZ81TBh8yVbFrN


Generated by Claude Code

…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
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Sep 4, 2026 — with Claude
@Jammy2211
Jammy2211 merged commit de92d09 into main Sep 5, 2026
3 checks passed
@Jammy2211
Jammy2211 deleted the feature/delaunay-dual-area-magnification branch September 5, 2026 01:16
@Jammy2211 Jammy2211 removed the pending-release PR queued for the next release build label Sep 26, 2026
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.

fix: Delaunay areas_for_magnification returns barycentric dual areas, not Voronoi cells

2 participants