Repository navigation
Description
Overview
Replace the empirical point-rank CDF in the adaptive rectangular mesh with a smooth kernel-density CDF (Enzi et al. arXiv:2606.30620 RTU formulation) as a new opt-in mesh variant. The 2026-07-09 JAX gradient audit (autolens_workspace_developer#87) proved the linear rank-CDF makes the likelihood piecewise-constant in mass/shear whenever interp queries coincide with knots — imaging at os_pix=1 and the whole interferometer sparse path have zero usable gradients today, and os_pix=4 carries ~1–3% FD contamination from rank swaps. The kernel CDF is strictly monotone by construction, C^∞ in queries and point positions, and duplicate-safe, so the mesh carries correct smooth gradients in every configuration.
Success criterion: strict FD assertions pass on ALL parameters (mass/shear included) in the jax_grad workspace-test scripts — imaging at os_pix=1 and 4, interferometer sparse path — plus eager-vs-JIT consistency and fit-quality parity with the linear meshes. Sampler (NUTS/HMC) work is explicitly OUT of scope (future task once gradients are green).
Plan
- Preamble: inspect PyAutoArray
stash@{0}(parked 727-line spline-interpolator refactor); salvage anything useful, record keep/drop here, retire theparked.mdentry. - Implement the kernel-density CDF transform in a new interpolator module mirroring the spline variant's layout, with exact smooth forward transform and table-based inverse.
- Add opt-in mesh classes
RectangularKernelAdaptDensity/RectangularKernelAdaptImagewith abandwidthkwarg (default tied to mesh resolution); export via the mesh package (surfaces asal.mesh.*downstream with no PyAutoGalaxy/PyAutoLens edits). - NumPy-only unit tests in
test_autoarray/(monotonicity, round-trip, duplicate-safety, weight-map variant, areas consistency). - Certify via
autolens_workspace_test/scripts/jax_grad/: strict FD on all params at os_pix=1 AND 4 (imaging) and on the interferometer sparse path; eager-vs-JIT consistency; figure-of-merit parity within a few e-4 of the linear meshes. - Update the gradient audit README rows in
autolens_workspace_developeronce certified. - Ship library PR first, then the workspace-test/developer PR (library-first merge gate).
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary)
- autolens_workspace_test (certification scripts)
- autolens_workspace_developer (gradient README rows)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoArray | main | clean |
| ./autolens_workspace_test | main | clean |
| ./autolens_workspace_developer | main | clean (untracked local AGENTS.md only) |
Suggested branch: feature/rectangular-kernel-cdf-mesh
Worktree: ~/Code/PyAutoLabs-wt/rectangular-kernel-cdf-mesh/
Coordination note
The parked rect-adapt worktree (#372, uncommitted, awaiting ship sign-off) also edits autoarray/inversion/mesh/mesh_geometry/rectangular.py, but only the edges_transformed hunk (node-midpoint edges); this task adds dispatch in areas_transformed / transformed-grid / ctor — distinct hunks, clean merge expected in either order. If #372 merges first, the kernel path inherits the corrected edges automatically.
Implementation Steps
- Stash preamble: diff PyAutoArray
stash@{0}against currentrectangular.py; salvage or reject (likely reject — it predates the audit and belongs to the rejected spline chain); comment the decision here; retire theparked.mdrectangular-spline-cdfentry; keep the stash until the decision comment lands. - New module
autoarray/inversion/mesh/interpolator/rectangular_kernel.py(mirrorrectangular_spline.py's independently-auditable-copy layout):create_transforms_kernel(traced_points, mesh_weight_map=None, bandwidth=None, n_knots=64, xp=np)— same contract ascreate_transforms(rectangular.py:70). Per axis: weightsw_i= 1/N (density) or normalizedmesh_weight_map(image);F(x) = Σ_i w_i · Φ((x − x_i)/h), Φ = Gaussian CDF viascipy.special.erf(np) /jax.scipy.special.erf(jax). Forward evaluated exactly at query points, rescaled by[F(lo), F(hi)]to fill [0,1]. Inverse via interp on a fixed K=n_knotsknot grid spanning the data range padded by ~4h — inverse queries are fixed U-grid constants, so gradients flow smoothly through the table values. Bandwidth defaulth = bandwidth_factor × data_range_axis / mesh_pixels_axis, factor ~1.0, overridable via the meshbandwidthkwarg.adaptive_rectangular_mappings_weights_via_interpolation_from_kernel(...)— copy of steps 3–7 of the linear helper (floor/ceil, flatten, bilinear weights) routed through kernel transforms.adaptive_rectangular_areas_from_kernel(...)+adaptive_rectangular_transformed_grid_from_kernel(...)— kernel versions of the geometry helpers.InterpolatorRectangularKernel(InterpolatorRectangular)— overrides_mappings_sizes_weightsandmesh_geometry, mirroringInterpolatorRectangularSpline.
- Geometry dispatch in
autoarray/inversion/mesh/mesh_geometry/rectangular.py:MeshGeometryRectangularalready branches onspline_deg is not None(~lines 478/526); add parallel optionalkernel_bandwidth/kernel_knotsctor kwargs (default None) routingareas_transformedand the transformed-grid property through the*_from_kernelhelpers. - Mesh classes
autoarray/inversion/mesh/mesh/rectangular_kernel_adapt_density.py/rectangular_kernel_adapt_image.py—RectangularKernelAdaptDensity(RectangularAdaptDensity),RectangularKernelAdaptImage(RectangularAdaptImage); templaterectangular_spline_adapt_density.py; storebandwidth/n_knots, overrideinterpolator_cls, forward kwargs ininterpolator_from. Export fromautoarray/inversion/mesh/mesh/__init__.py; mirror any registry mentions the spline classes have inautoarray/inversion/pixelization.py/autoarray/exc.py. Spline meshes stay untouched. - Unit tests (numpy-only):
test_autoarray/inversion/pixelization/interpolator/test_rectangular_kernel.py— strict monotonicity; forward/inverse round-trip; duplicate-point safety; weight-map variant; mesh-class plumbing; areas positive + consistency vs linear variant on a smooth cloud. Fullpytest test_autoarray/green. - Certification (
autolens_workspace_test/scripts/jax_grad/, same branch name):imaging_pixelization.py:RectangularKernelAdaptDensityat os_pix=1 with strict FD on ALL params (no skip_indices — must NOT be a staircase); both kernel meshes at os_pix=4 strict FD;util.assert_eager_jit_consistenteverywhere; eager figure_of_merit within a few e-4 relative of linear AdaptDensity/AdaptImage.interferometer.py: kernel mesh on the sparse-operator path — strict FD on all mass/shear params + eager-vs-JIT.- Existing linear/spline variant assertions unchanged (no regression).
- Docs row:
autolens_workspace_developer/jax_profiling/gradient/README.md— kernel-mesh rows + staircase-section implications.
Key Files
PyAutoArray/autoarray/inversion/mesh/interpolator/rectangular.py— linear transform contract being mirrored (create_transforms, bilinear helper).PyAutoArray/autoarray/inversion/mesh/interpolator/rectangular_spline.py— structural template for the new module (do not build on its math).PyAutoArray/autoarray/inversion/mesh/mesh_geometry/rectangular.py— geometry dispatch site.PyAutoArray/autoarray/inversion/mesh/mesh/rectangular_spline_adapt_density.py— mesh-class template.autolens_workspace_test/scripts/jax_grad/{imaging_pixelization,interferometer,util}.py— certification harness.autolens_workspace_developer/jax_profiling/gradient/README.md— audit table to update.
Risks
- Bandwidth trade-off: h too small re-approaches the staircase; h too large over-smooths geometry and drifts FoM parity. Default tied to mesh resolution + exposed kwarg.
- Inverse-table resolution: K=64 must resolve the CDF better than the mesh shape (~30×30 in jax_test configs);
n_knotskwarg, bump if parity fails.
Original Prompt
Click to expand starting prompt
Kernel-density CDF transform for the adaptive rectangular mesh (differentiable everywhere)
Type: feature
Target: autoarray
Repos:
- PyAutoArray
- autolens_workspace_test
- autolens_workspace_developer
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Replace the empirical point-rank CDF in the adaptive rectangular mesh with a smooth
kernel-density CDF (the Enzi et al. arXiv:2606.30620 RTU formulation) so the mesh
carries correct smooth mass/shear gradients in every configuration — imaging at any
pixelization over-sampling and the interferometer sparse path (which has no
over-sampling and today has zero usable gradients).
Original request (verbatim): "it sounds like you think we can improve the rectangular
gradient method across all use cases? explain how [...]" → recommendation approved:
kernel-CDF leapfrog, one focused task, "drop the nuts stuff for now I only care that
the jax gradient workspace test script shows grad all works and can work on samples
down the line."
Background (from the 2026-07-09 JAX gradient audit, autolens_workspace_developer#87)
- The linear rank-CDF (
create_transformsin
PyAutoArray/autoarray/inversion/mesh/interpolator/rectangular.py) makes the
likelihood exactly piecewise-constant in mass/shear whenever interp queries
coincide with knots: imaging os_pix=1 and the interferometer sparse path
(jax_grad/interferometer.pyvariant B documents the staircase). At os_pix=4
gradients live but every rank swap leaves a micro-kink (~1-3% FD contamination). - Prior attempt:
RectangularSplineAdapt{Density,Image}(PyAutoArray PR Add RectangularSplineAdapt{Density,Image} meshes for gradient-based samplers #289,
2026-04-22, opt-in, built for gradient samplers). Measured 2026-07-09: breaks the
rank invariance (AD live on all 14 params at os_pix=1) BUT eager-vs-JIT LL gap
~15 and erratic FD (noisy surface — Add RectangularSplineAdapt{Density,Image} meshes for gradient-based samplers #289's shipped "oscillations" limitation).
Suspects:_enforce_strict_monotoneeps-jitter, deg-11 polyfit conditioning,
Hermite inversion table. Do not build on this chain; keep the spline meshes
untouched/opt-in. - A 727-line jax-friendly spline-interpolator refactor is PARKED in PyAutoArray
stash@{0}(parked.mdrectangular-spline-cdf, 2026-05-08).
Task
- Preamble: inspect PyAutoArray
stash@{0}— salvage anything useful, then
retire theparked.mdentry either way (keep the stash until a decision is
recorded on the issue). - Implement the kernel-density CDF transform as a new opt-in mesh variant (e.g.
RectangularKernelAdaptDensity/...AdaptImage), slotting into the existing
transform/inv_transformmachinery: per axis
F(x) = sum_i w_i * Phi((x - x_i)/h)evaluated on a small fixed knot grid
(K ~ 64), inverse via interpolation on the same grid. Strictly monotone by
construction (no jitter hack), C-infinity in queries AND point positions,
duplicate-safe (no 1/Delta-knot terms). Weight-map variant usesw_ifrom the
adapt image. Bandwidthhdefaults tied to mesh resolution; expose as a mesh
kwarg. - Certification is the success criterion (no sampler work in this task):
- Extend
autolens_workspace_test/scripts/jax_grad/imaging_pixelization.py
with kernel-mesh variants at os_pix=1 AND os_pix=4: strict FD assertions on
ALL parameters (mass/shear included) using the existingutil.pyharness —
the os_pix=1 variant must NOT be a staircase (that is the point). - Extend
autolens_workspace_test/scripts/jax_grad/interferometer.pywith the
kernel mesh on the sparse-operator path: strict FD on all mass/shear params. - Eager-vs-JIT consistency (
util.assert_eager_jit_consistent) must hold on
every variant. - Fit-quality parity: eager figure_of_merit on the jax_test config within a few
e-4 relative of the linear AdaptDensity/AdaptImage meshes (mesh geometry
changes slightly; reconstruction quality must not degrade). - Update
autolens_workspace_developer/jax_profiling/gradient/README.mdrows +
the staircase-section implications once certified.
- Extend
- Sampler trials (NUTS/HMC) are explicitly OUT of scope — future task once the
jax_grad scripts are green.
Constraints
- PyAutoArray is claimed by
nnls-solver-optimization(active.md) at time of
filing — start when the claim releases or coordinate explicitly (different
files, adjacent territory). - Spline meshes (PR Add RectangularSplineAdapt{Density,Image} meshes for gradient-based samplers #289) stay as-is; no deletion/refactor of them in this task.
- Library unit tests numpy-only per repo rules; all JAX validation through the
workspace_test jax_grad scripts.
Activity
Jammy2211 commentedon Jul 10, 2026
Step 0 — stash@{0} preamble: REJECT (nothing salvaged).
Inspected
PyAutoArray stash@{0}("On rectangular-spline-cdf", 727-linerectangular.pyrefactor + 2 test files, parked 2026-05-08). Findings:create_transformsin place (not opt-in) with the deg-11 polyfit + Hermite-table inversion (InvertPolySpline,_spline_invert) — the same spline chain the 2026-07-09 gradient audit disqualified (eager-vs-JIT LL gap ~15, oscillatory FD) and that this task's prompt explicitly forbids building on.Actions:
parked.mdentryrectangular-spline-cdfretired (PyAutoMind). The stash object itself is untouched — deleting it is destructive and left to the maintainer; it is superseded by this task either way.Jammy2211 commentedon Jul 10, 2026
Library PR created: #374 (
pending-release, merge gated on certification)The kernel-CDF meshes are implemented and shipped on
feature/rectangular-kernel-cdf-mesh:RectangularKernelAdaptDensity/RectangularKernelAdaptImage— opt-in,bandwidth+n_knotskwargs, exact smooth forward transform (no ranks/sorts anywhere, duplicate-safe), fixed-table inverse.Next:
/start_workspace— extendautolens_workspace_test/scripts/jax_grad/{imaging_pixelization,interferometer}.pywith the kernel variants (strict FD on ALL params at os_pix=1 AND 4, sparse path, eager-vs-JIT, FoM parity) and update the gradient README rows inautolens_workspace_developer. That certification is the success criterion and gates the merge of #374.Jammy2211 commentedon Jul 10, 2026
Certification COMPLETE — the success criterion is met. Workspace PRs open: autolens_workspace_test#161 (jax_grad scripts) + autolens_workspace_developer#90 (README rows + alignment verification). All behind the library-first gate on #374.
Results (all against the library branch incl. the #372/#375 edge fix; PyAutoArray suite 893 green):
Eager-vs-JIT consistent everywhere; existing linear/uniform variants unchanged and passing.
Batched sign-off question (supervised --auto; judgment calls taken, validate or amend):
Merge order (human): #375 → #374 → #161 + #90. This branch already contains #375's commit; whichever library PR lands second is a no-op.
Follow-up candidates (not filed — one at a time per queue discipline): (a) chunked kernel forward (O(M×N) memory blocks production-scale imaging, ~60 GB at 15k px × os 4²); (b) solver branch-flip investigation (PDIP tie-break suspect, NNLS-ledger territory); (c) the sampler-trials task the prompt explicitly deferred — gradients are now green, so it is unblocked.
Jammy2211 commentedon Jul 10, 2026
Shipped and merged (human-directed, 2026-07-10). Merge order as planned: #375 → #374 → autolens_workspace_test#161 → autolens_workspace_developer#90, all green. The kernel-CDF meshes are on
main, strict-FD certified in every configuration; the batched sign-off items stand as implemented. Follow-ups now starting as their own tasks: kernel-forward chunking + solver branch-flip investigation. Closing.