Skip to content

fix: reject non-Galaxy galaxies input to Tracer (#532 B4) - #696

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/autolens-tracer-validation-guards
Aug 9, 2026
Merged

fix: reject non-Galaxy galaxies input to Tracer (#532 B4)#696
Jammy2211 merged 1 commit into
mainfrom
feature/autolens-tracer-validation-guards

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Closes #532. Phase 2 (PyAutoLens half) of the @rhayes777 API audit epic PyAutoArray#415, which stays open for phases 3-4.

What this fixes

t = al.Tracer(galaxies="not a list")   # constructed happily
t.image_2d_from(grid=grid)             # AttributeError: 'str' object has no attribute 'redshift'

The failure named nothing the caller passed, three frames from the constructor that accepted it. It now raises TypeError at construction, naming galaxies and the type given.

Broader than reported. The issue named the string case; verified on main that 42, None and {'a': 1} were all accepted at construction too.

The string is the trap worth naming. A string is iterable, so an isinstance(x, Iterable) check does not catch it — the element type is what matters, which the issue called out explicitly.

Elements are duck-typed on redshift rather than required to be exactly ag.Galaxy, so mocks and Galaxy subclasses keep working — and redshift is precisely the attribute whose absence produced the original error. af.ModelInstance is accepted unexamined, since that is how PyAutoFit hands the tracer a model's galaxies during a fit.

Where the redshift half went — and why not here

The negative-redshift finding on this issue is fixed in PyAutoGalaxy#566, not here.

It was filed on this issue because the reporter reached it through al.Galaxy — but al.Galaxy is ag.Galaxy, and both the class and its redshift assignment live in PyAutoGalaxy (autogalaxy/galaxy/galaxy.py:52). Guarding at Tracer level would have missed a bare al.Galaxy(redshift=-0.5), which is exactly the reported reproduction. So the guard went where the attribute is set.

Merging order does not matter — the two changes are independent.

Phase 4 stays held

z_lens > z_source is not implemented here. Multi-plane lensing genuinely supports geometries that look inverted under two-plane naming, so it must warn at most, never raise, and the question of whether even a warning is noise is still open with @rhayes777 on this issue.

Rather than leave that implicit, this PR adds a guard-rail test pinning today's permissive behaviour: a z_lens=1.0 / z_source=0.5 tracer must still construct and evaluate to a finite image. Phase 4 cannot quietly turn it into an error without that test failing. (PyAutoGalaxy#566 adds the matching guard-rail at Galaxy level.)

API Changes

No signatures, names or return types change. The change is in accepted input domain: Tracer(galaxies=...) now requires a list/tuple of objects with a redshift, or an af.ModelInstance. Strings, scalars, None and dicts were previously accepted and now raise TypeError.

An empty list remains valid — it is degenerate but legal, and used in tests and chaining.

Test Plan

  • New: test_autolens/lens/test_tracer_validation.py — 13 cases from the reporter's snippet, including the string trap, the wrong-container cases, an index-naming test for a bad list element, and controls for list, tuple, empty list and af.ModelInstance.
  • Full suite: 519 passed, 1 skipped. Zero regressions.

Out of scope

  • Negative redshift — PyAutoGalaxy#566, per the reasoning above.
  • z_lens > z_source — phase 4, HELD, with a guard-rail test added here.
  • PyAutoArray#332 adapt_images precondition — phase 3.
  • Sibling PyAutoLens#531 (PointSolver) — already closed in phase 1.

Generated by Claude Code

Tracer(galaxies="not a list") constructed happily and failed much later with
AttributeError: 'str' object has no attribute 'redshift' — an error naming
nothing the caller passed. It now raises TypeError at construction, naming
`galaxies` and the type that was given.

Broader than reported: 42, None and {'a': 1} were all accepted at construction
too, not only the reported string.

A string is the trap worth naming — it IS iterable, so an isinstance(x, Iterable)
check does not catch it. Elements are duck-typed on `redshift` rather than
required to be exactly ag.Galaxy, so mocks and Galaxy subclasses keep working;
`redshift` is precisely the attribute whose absence caused the original failure.
af.ModelInstance is accepted unexamined, since that is how PyAutoFit hands the
tracer a model's galaxies during a fit.

The negative-redshift half of #532 is NOT here: al.Galaxy IS ag.Galaxy, so both
the class and its redshift assignment live in PyAutoGalaxy, and the guard went
there (PyAutoGalaxy#566) where a bare Galaxy construction is also covered. A
Tracer-level redshift check would have missed that.

z_lens > z_source is phase 4 and stays HELD pending the reporter's answer. A
control test pins today's permissive behaviour so phase 4 cannot regress it
silently.

Tests: 13 new cases in test_autolens/lens/test_tracer_validation.py, built from
the reporter's snippet, with controls for list, tuple, empty list and
af.ModelInstance. Suite 519 passed, zero regressions.

Closes #532.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PgqSCLTemK5bApVAwhVM4
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Aug 9, 2026 — with Claude
@Jammy2211
Jammy2211 merged commit 65183d1 into main Aug 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tracer accepts non-iterable galaxies; multi-plane allows z_lens > z_source

2 participants