Skip to content

epic: @rhayes777 API audit — 16 verified findings across 3 repos #415

Closed

Description

@Jammy2211

Overview

@rhayes777 filed five issues on 2026-05-23 while auditing released 2026.5.21.1
against the documented public API: PyAutoArray#332, PyAutoArray#333,
PyAutoGalaxy#440, PyAutoLens#531, PyAutoLens#532. They received zero replies for
66 days
. All five were answered on 2026-07-28, and every one of the 16 findings
was re-verified against current main first — all 16 still reproduce.

This epic tracks the campaign that fixes them. It exists because all five replies
promise @rhayes777 a single place to watch.

Decision (2026-07-28): we implement in-house. His "Happy to PR if useful" offer
was declined warmly — the affected constructors sit on JAX-traced hot paths and
guards that stay correct under tracing are not a fair hand-off.

Verified against main

PyAutoArray@616e8b4c · PyAutoGalaxy@f0b65f39 · PyAutoLens@6567b3b1c — all clean,
zero ahead/behind.

Issue Findings Verified
#332 ConstantSplit×RectangularUniform; adaptive-mesh precondition error 3/3
#333 B5, B6, B7, B8, B13 — input validation 5/5
PyAutoGalaxy#440 B9, B10, B11, B12 — profile validation 4/4
PyAutoLens#531 PointSolver AxisError + IndexError 2/2
PyAutoLens#532 B4 + redshift ordering 2/2

Correction to #332's headline

#332 reports that Delaunay and KNNBarycentric are "unusable in FitImaging".
They are not. They require an image-plane mesh grid via adapt_images, and with
it both fit cleanly:

Delaunay(pixels=100)       + Constant + adapt_images  ->  log_evidence 5084.7513
KNNBarycentric(pixels=100) + Constant + adapt_images  ->  log_evidence 5211.7226
Delaunay(pixels=100)       + Constant, NO adapt       ->  AttributeError 'NoneType'
RectangularUniform(15,15)  + ConstantSplit            ->  AttributeError  (Split unsupported)
RectangularAdaptDensity    + ConstantSplit            ->  IndexError      (Split unsupported)
Delaunay(pixels=100)       + ConstantSplit + adapt    ->  log_evidence 5096.4420

The real defect is that a missing precondition surfaces as
AttributeError: 'NoneType' object has no attribute 'array' from inside
border_relocator.py, naming nothing the caller controls.

This inverts the fix direction. A regression test must assert that bare
construction fails clearly, naming adapt_images — not that it succeeds.

Phases

Phase 1 — genuine crash bugs (real user impact, no workaround) — #416, in progress

  • Split regularization on rectangular meshes. Rectangular does not support
    Split — the fix is a clear "unsupported" exception, not the missing capability.
    Verified scope is 9 combinations in 2 failure modes (3 rectangular meshes x 3
    Split regularizations): RectangularUniform → AttributeError (property absent);
    RectangularAdaptDensity / RectangularAdaptImage → IndexError (the pass-through
    at rectangular.py:460 claims split "reuses the same mappings", which crashes one
    frame later in reg_split_from). Delaunay + ConstantSplit works, so this is
    specific to the rectangular family.
  • PointSolver.solve on a source outside the caustic → numpy AxisError
  • PointSolver.solve with loose pixel_scale_precision → IndexError
    (same source at 0.001 returns 4 images)

Phase 2 — constructor validation sweep (9 findings: B4–B13)
One shared _validate_* helper. Where it lives must be decided before writing
it
— PyAutoArray is the natural floor, since Galaxy and Lens both depend on it —
or the three repos ship inconsistent messages.

Phase 3 — error legibility
adapt_images precondition fails fast and legibly; regression test asserts the
clear failure. Plus B10 (2.357e-06 Isothermal/IsothermalSph difference at the
degenerate point) pinned at an explicit tolerance — not chased to bit-identity.

Phase 4 — HELD, awaiting @rhayes777
z_lens > z_source should warn rather than raise (multi-plane genuinely supports
geometries that look wrong under two-plane naming); negative redshift can raise
outright. The #532 reply asks him whether even a warning would be noise in a real
multi-plane setup. Held until he answers — everything else is unblocked.

Constraint carried into every phase

These constructors are JAX-traced. Guards must stay correct under tracing and
cost nothing when traced — no Python if on values that may be tracers.

Sharpened finding on B13

constant.py:43 squares the coefficient, which is why log_evidence is identical
for +1.0 and -1.0. But regularization_weights_from (constant.py:127) returns
the raw un-squared value, so a negative coefficient does leak negative
regularization weights into every consumer of that method. Not inert.

Activity

  1. Jammy2211 commented on Jul 29, 2026

    @Jammy2211
    CollaboratorAuthor

    Status update — phase 1 merged (2026-07-28)

    @rhayes777 — the first tranche of the audit is in main. This is the tranche with real
    user impact and no workaround: three documented public features that crashed on realistic
    inputs.

    PR Fix Merged
    PyAutoArray#417 Split regularization on rectangular meshes now raises a clear "unsupported" exception at construction 9411904d
    PyAutoLens#662 PointSolver.solve returns the single image when the source is outside the caustic; raises a clear error naming pixel_scale_precision when the precision is too coarse to resolve any image 2a3f1a63

    Two notes on scope, both of which grew from evidence rather than from the reports:

    1. The ConstantSplit × RectangularUniform failure you reported is 9 combinations in
      2 failure modes
      — 3 rectangular meshes × 3 Split regularizations. RectangularUniform
      gave the AttributeError you saw; RectangularAdaptDensity and RectangularAdaptImage
      gave an IndexError instead, via a pass-through in rectangular.py that claimed split
      "reuses the same mappings". It does not. That pass-through was deleted.
    2. Rectangular meshes do not support Split regularization by design, so the fix is the
      explicit exception, not the missing capability. Delaunay + ConstantSplit works and is
      covered as the control (log_evidence 5096.4420).

    PyAutoLens#531 is closed. PyAutoArray#416 (the phase-1 tracker) is closed.

    Remaining

    Phase Scope Covers State
    2 9 constructor-validation guards #333 (B5–B8, B13), PyAutoGalaxy#440 (B9, B11, B12) scoped, not started
    3 adapt_images precondition error legibility + B10 tolerance regression test #332, PyAutoGalaxy#440 (B10) scoped, not started
    4 z_lens > z_source in multi-plane #532 held on your answer

    Phase 4 is held on the open question already put to you in
    PyAutoLens#532 —
    whether a z_lens > z_source warning would be noise in a real multi-plane setup. No rush; the
    negative-redshift half of that finding is not blocked on it and rides with phase 2.

    This issue stays open until phases 2–3 land; #332, #333, PyAutoGalaxy#440 and #532 stay
    open with it.

  2. Jammy2211 commented on Aug 9, 2026

    @Jammy2211
    CollaboratorAuthor

    Campaign complete — closing

    All 16 findings from @rhayes777's 2026-05-23 audit are addressed, all five of his issues are closed, and phase 4 shipped today.

    Phase Scope Issue(s) PR(s)
    1 Crash bugs — Split×rectangular (9 combinations), two PointSolver failures #332 (part 2), PyAutoLens#531 #417, PyAutoLens#662
    2 Constructor validation sweep #333, PyAutoGalaxy#440, PyAutoLens#532 #440, PyAutoGalaxy#566, PyAutoLens#696
    3 adapt_images precondition legibility + B10 tolerance #332 #442
    4 z_lens > z_source PyAutoLens#532 PyAutoLens#697

    The phase-2 helper-home question, settled

    autoarray/validate.py — is_concrete_scalar, validate_positive_finite, validate_non_negative_finite, validate_pixel_scales, validate_shape_native, validate_radii_ordered. PyAutoGalaxy and PyAutoLens import it; PyAutoGalaxy adds only per-parameter explanations. One message shape for one class of mistake across all three repos, which was the stated risk.

    Guards are tracer-safe by a single gate (is_concrete_scalar), verified against real JAX 0.11.0 — construction under jax.jit works and jax.grad flows through, while concrete bad values are still rejected.

    Phase 4 — resolved without the answer we asked for

    @rhayes777 was asked on PyAutoLens#532 whether warning on z_lens > z_source would be noise in a real multi-plane setup, and did not reply. Rather than hold indefinitely, the warning ships behind its own filterable category (MultiPlaneRedshiftWarning), so a user for whom it is noise silences exactly that. It warns and never raises — multi-plane genuinely supports geometries that look inverted under two-plane naming.

    The noise question then answered itself empirically. A naive rule counting only LightProfile fired in ~10 legitimate existing tests, taking the suite's warning count 18 → 44. Pixelized sources (which carry no light profile at all), point sources, and empty placeholder galaxies were all false positives. With those handled it fires in zero tests outside its own regression module, back to 18.

    Coverage went wider than reported in several places

    Because guards went in at chokepoints rather than at the reported call sites:

    • B13 covered all 14 regularization schemes, not just Constant — and closed a real leak, since regularization_weights_from returns the coefficient unsquared while regularization_matrix_from squares it.
    • B12 needed one call at EllProfile, covering every elliptical light and mass profile.
    • B9 covered the whole halo family (29 dark profile classes).
    • B5 covered every AbstractDataset subclass, not just Imaging.
    • B7 also caught elliptical_annular, which had the identical hole.

    One correction to the record

    The Ell/Sph potential discrepancy reported in PyAutoGalaxy#566 and on PyAutoGalaxy#440 was overstated. It is not an accuracy defect: with over-sampling disabled the potential agrees to 3.2e-06, the same order as the deflections. The disagreement appears only under the default over_sample_size=4 and is concentrated at the profile's singular centre, where it reaches ~7% locally — an over-sampling-at-a-cusp artefact. The benign ~1e-6 floor is the elliptical form clipping axis_ratio to 0.99999 while IsothermalSph hardcodes 1.0, which is precisely B10 and is correctly pinned. Tracked separately; not part of this audit.

    Thanks again @rhayes777 — the audit found real bugs, and in two cases (B13's unsquared weights, and the adapt_images error) the underlying problem was more substantial than the report suggested.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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