Skip to content

feat: rectangular mesh split — Bilinear (CPU default) vs RTU #461

Description

@Jammy2211

Overview

Split the rectangular adaptive mesh family into two user-facing variants: Bilinear (RectangularBilinearAdaptDensity / RectangularBilinearAdaptImage), a resurrected empirical rank-CDF lattice transform that is fast on CPU, and RTU (RectangularRTUAdaptDensity / RectangularRTUAdaptImage), pure renames of the current kernel-CDF classes. The RTU kernel-CDF transform dominates the numba CPU likelihood (55% of a euclid eval, 89% at hst; O(M_sub × N_data) erf sum), so the conceptually simpler rank-CDF variant becomes the workspace default while RTU is documented as the advanced option (GPU / gradient samplers / interferometer). Resolves option (2) of the Phase 14 default-mesh decision (autolens_profiling PROGRAMME Phase 14 / autolens_profiling#153).

Plan

  • Resurrect the empirical rank-CDF adaptive transform (sort + cumsum) deleted in the July mesh consolidation and pair it with the existing 4-pixel bilinear interpolation as RectangularBilinearAdaptDensity / RectangularBilinearAdaptImage.
  • Pure-rename the current kernel-CDF classes to RectangularRTUAdaptDensity / RectangularRTUAdaptImage — implementation and likelihood values unchanged; the RTU implementation is never deleted (it is the only certified gradient mesh on the interferometer sparse path).
  • RectangularUniform stays as-is.
  • Update exports, configs and unit tests; PR release notes carry an ## API Changes heading (breaking rename).
  • Docs must state: gradient inference on the Bilinear default needs over_sample_size_pixelization >= 4 (imaging); interferometer gradient work needs RTU.
  • Workspace follow-up (separate PRs once the library API lands): default examples switch to Bilinear; RTU documented separately as the advanced option, folding in the queued Enzi-citation docs draft; prior-config yamls added/renamed; _test likelihood pins regenerated for Bilinear scripts (overwrite) while RTU pin scripts survive as pure renames; update autolens_profiling#153 / PROGRAMME Phase 14 with the decision.
Detailed implementation plan

Work Classification

Both (library first — workspace PRs follow once the PyAutoArray API lands).

Affected Repositories

  • PyAutoArray (primary)
  • autolens_workspace (follow-up)
  • autogalaxy_workspace (follow-up)
  • autolens_workspace_test (follow-up)

Branch Survey

Repository Current Branch Dirty?
./PyAutoArray main (90f6f0c) clean
./PyAutoMind claude/bilinear-rtu-mesh-docs-pbhsxg (tracks main) clean

Workspace repos are not checked out in this session; the workspace phase attaches them after the library PR merges. Local-dev worktree root (created by /start_library when resumed locally): ~/Code/PyAutoLabs-wt/rectangular-bilinear-rtu-mesh-split/.

Suggested branch: feature/rectangular-bilinear-rtu-mesh-split

Implementation Steps (library phase, PyAutoArray)

  1. Recover the empirical rank-CDF transform from the pre-consolidation tree — git show 22b28463^:autoarray/inversion/mesh/interpolator/rectangular.py (create_transforms with argsort/take_along_axis/cumsum, plus its forward_interp/reverse_interp jax+numpy pairs). Do not reinvent it. Add it to autoarray/inversion/mesh/interpolator/rectangular.py alongside the kernel-CDF create_transforms, selectable by the interpolator (e.g. a transform="rank"|"kernel" seam threaded through adaptive_rectangular_transformed_grid_from, adaptive_rectangular_areas_from, adaptive_rectangular_mappings_weights_via_interpolation_from and InterpolatorRectangular).
  2. New mesh classes RectangularBilinearAdaptDensity / RectangularBilinearAdaptImage in autoarray/inversion/mesh/mesh/ — mirror the current RectangularAdaptDensity / RectangularAdaptImage shape (overlay grid, mesh_weight_map_from, interpolator_from) but with the rank-CDF transform and no bandwidth/n_knots hyperparameters (that simplicity is the point of the split).
  3. Pure-rename RectangularAdaptDensityRectangularRTUAdaptDensity and RectangularAdaptImageRectangularRTUAdaptImage (kernel-CDF internals untouched). Update autoarray/inversion/mesh/mesh/__init__.py and top-level autoarray/__init__.py exports. No back-compat aliases — the rename is intentionally breaking, recorded under ## API Changes.
  4. Docstrings encode the guidance split: Bilinear = fast CPU default, piecewise-constant likelihood at over_sample_size_pixelization=1 (gradients exactly zero — gradient users set os_pix>=4 or use RTU); RTU = smooth C-inf transform, correct gradients in every configuration incl. the interferometer sparse path, recommended on GPU (cite Enzi et al. arXiv:2606.30620).
  5. Tests: recover/adapt the relevant deleted empirical-CDF unit tests from 22b28463^ (test_autoarray/inversion/pixelization/...), keep the existing rectangular tests running against the renamed RTU classes, add Bilinear cases to the interpolator + mesh test_rectangular.py files.
  6. Grep for any config/registry references to the old class names inside PyAutoArray (e.g. autoarray/config, JSON/dict serialization of mesh classes) and update.
  7. Run the mesh/pixelization test suite; open the library PR with ## API Changes release notes.

Workspace follow-up (separate PRs, after library merge)

  1. autolens_workspace (~224 uses), autogalaxy_workspace, HowToLens (~42): default examples → RectangularBilinearAdaptDensity; RTU documented as the recommended advanced option (GPU / gradient samplers / interferometer), folding in PyAutoMind draft/docs/workspaces/rectangular_mesh_enzi_citation_examples.md (retire that draft when folded in).
  2. Prior configs: add mesh/rectangular_bilinear_adapt_*.yaml; rename RTU yamls to the new class names (autoconf lowercases keys).
  3. autolens_workspace_test: RTU pin scripts survive as pure renames (values unchanged); add/switch default-mesh scripts to Bilinear and regenerate pins, overwriting (pre-consolidation historical values are not reusable — over-sampling configs changed since: 3b4156e, 602ffce).
  4. Measure the Bilinear classes in the existing autolens_profiling cells (scripts/imaging/likelihood_runtime/pixelization_numba.py etc.), record a versioned result, and update autolens_profiling#153 / PROGRAMME Phase 14 with the decision.

Key Files

  • autoarray/inversion/mesh/interpolator/rectangular.py — kernel-CDF create_transforms today; gains the resurrected rank-CDF variant + selection seam.
  • autoarray/inversion/mesh/mesh/rectangular_adapt_density.py / rectangular_adapt_image.py — current kernel-CDF mesh classes → renamed RTU classes; templates for the new Bilinear classes.
  • autoarray/inversion/mesh/mesh/__init__.py, autoarray/__init__.py — exports.
  • test_autoarray/inversion/pixelization/interpolator/test_rectangular.py, test_autoarray/inversion/pixelization/mesh/test_rectangular.py — test homes.
  • Reference for resurrection: PyAutoArray 22b28463^ (pre-consolidation tree; deletion commit refactor: consolidate rectangular meshes — kernel takes plain names, retire linear/spline/rotated #402, 2026-07-23).

Gradient evidence (why the split is safe)

Certified in the July gradient audit (autolens_workspace_developer/jax_profiling/gradient/README.md): at os_pix=1 the rank-CDF likelihood is exactly piecewise-constant in mass/shear (zero gradients — the reason it was deleted); at os_pix=4 full FD sweeps validate both adaptive meshes (AdaptImage <=~1% production shape, AdaptDensity <=~3%). The interferometer sparse path has no over-sampling, so Bilinear is permanently gradient-dead there — RTU (or RectangularUniform) must remain the interferometer gradient mesh. Sampler-level mesh-family ranking is already PROGRAMME Phase 5 — not duplicated here.

Original Prompt

Click to expand starting prompt

Rectangular mesh split: Bilinear (fast CPU default) vs RTU (advanced/GPU)

Type: feature
Target: autoarray
Repos:

  • @PyAutoArray
  • @autolens_workspace
  • @autogalaxy_workspace
  • @autolens_workspace_test
    Difficulty: medium
    Autonomy: supervised
    Priority: high

Original request (verbatim, 2026-08-21)

Ok, I think we want RectangularBilinearAdaptDensity (workspace default)
RectangularBilinearAdaptImage, RectangularRTUAdaptDensity and
RectangularRTUAdaptImage. Confirm RectangularBilinearAdaptDensity is CPU fast
and gradient robust. Thsi will prob means we need to again updated all _test
workspace likelihood values in certain scripts, see if history has them but
over write if not (and keep RTU files somewhere)? make prompt to hand off to
mobile

Preceding context (same conversation): the RTU kernel-CDF transform dominates
the numba CPU likelihood (55% of a euclid eval, 89% at hst; O(M_sub x N_data)
erf sum), making the workspace-default rectangular mesh slow on CPU and on the
interferometer path. Proposal: a simpler/faster rectangular mesh becomes the
workspace default; RTU is documented separately as the recommended option for
advanced modeling, especially on GPU. This subsumes option (2) of the Phase 14
default-mesh decision (autolens_profiling PROGRAMME.md Phase 14, issue
autolens_profiling#153) — resolve/update that issue as part of this work.

Grounding (verified 2026-08-21, this session)

  • Current classes (PyAutoArray autoarray/inversion/mesh/mesh/__init__.py):
    RectangularAdaptDensity / RectangularAdaptImage (RTU kernel-CDF,
    Enzi et al. arXiv:2606.30620) and RectangularUniform (plain uniform
    lattice + bilinear, no transform). There is no bare Rectangular.
  • Naming caveat: BOTH the RTU meshes and every historical adaptive variant
    use the same 4-pixel bilinear interpolation on the warped lattice
    (interpolator/rectangular.py:435-442). What "Bilinear vs RTU" actually
    distinguishes is the lattice transform: the resurrect candidate is the
    empirical rank-CDF adaptive transform (sort + cumsum), deleted in
    PyAutoArray 22b28463 (refactor: consolidate rectangular meshes — kernel takes plain names, retire linear/spline/rotated #402, 2026-07-23, -3738 lines) when kernel-CDF took
    the plain names. Recover its implementation from that commit
    (create_transforms with argsort/cumsum) — do NOT reinvent it.
  • CPU fast: YES. The rank-CDF transform is O(N log N) sort/cumsum and
    eliminates the erf sum that is 55-89% of the numba CPU eval (post-perf: windowed numba fast path for the kernel-CDF forward transform #458 the
    RTU eval is euclid 1.17 s / hst 10.1 s on a 4-core container). Measure the
    new class in the existing autolens_profiling cells
    (scripts/imaging/likelihood_runtime/pixelization_numba.py etc.) and record
    a versioned result.
  • Gradient robust: CONDITIONALLY. Certified evidence from the July
    gradient audit (autolens_workspace_developer/jax_profiling/gradient/README.md):
    • os_pix=1 (the current default): rank-CDF likelihood is exactly
      piecewise-constant
      in mass/shear — gradients exactly zero. Unusable.
    • over_sample_size_pixelization=4: full FD sweeps validate both adaptive
      meshes — AdaptImage production shape <=~1% on mass, AdaptDensity <=~3%.
      Acceptable for most JAX gradient samplers.
    • Interferometer sparse path: no over-sampling exists — the staircase with
      no escape hatch. RTU (and RectangularUniform) are the only certified
      gradient meshes there.
      So docs MUST say: gradient inference on the Bilinear default needs
      os_pix>=4 (imaging); interferometer gradient work needs RTU. Sampler-level
      mesh-family ranking is already PROGRAMME Phase 5 — do not duplicate it here.
  • Alternative implementation considered: interpolated-kernel-CDF forward
    (K=8192 -> dlnL <= +4e-3, 18-55x on the step, measured in feature/triangle array typing #151/feature/revert to float #153 work).
    Rejected for the Bilinear pair (it is still RTU with a bandwidth
    hyperparameter, defeating "conceptually simple"); it remains feature/revert to float #153's lever
    for making RTU itself faster on CPU.

Goal

  1. PyAutoArray: four adaptive classes —
    RectangularBilinearAdaptDensity (resurrected rank-CDF transform +
    bilinear) and RectangularBilinearAdaptImage;
    RectangularRTUAdaptDensity / RectangularRTUAdaptImage (pure renames of
    the current kernel-CDF classes, values unchanged). RectangularUniform
    stays as-is (library-test baseline; certified interferometer gradient
    mesh). Never delete the RTU implementation. Breaking rename → release
    notes need the ## API Changes heading.
  2. Workspaces (autolens_workspace ~224 uses, autogalaxy_workspace,
    HowToLens ~42): default examples switch to
    RectangularBilinearAdaptDensity; RTU documented separately as the
    recommended advanced option (GPU / gradient samplers / interferometer),
    folding in the queued Enzi-citation docs draft
    (draft/docs/workspaces/rectangular_mesh_enzi_citation_examples.md).
    Prior configs: add mesh/rectangular_bilinear_adapt_*.yaml, rename the
    RTU yamls to match the new class names (autoconf lowercases keys).
  3. _test workspace: likelihood pin scripts
    (scripts/{imaging,multi_dataset,interferometer}/jax_likelihood/rectangular*.py
    and siblings) — keep RTU pin scripts alive under the renamed classes
    (values unchanged by a pure rename), add/switch default-mesh scripts to
    Bilinear and regenerate pins, overwriting. History does hold
    pre-consolidation (empirical-CDF era) values but under older paths/configs
    (over-sampling changed since: 3b4156e, 602ffce) — not reusable.
  4. Update autolens_profiling#153 / PROGRAMME Phase 14 with the decision.

Library first, workspace follow-up once the API lands (standard both-routing).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions