From f51acd5c41a942e629124c1f0eb8895e2f5eecc4 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 27 Aug 2026 14:11:54 +0000 Subject: [PATCH 1/3] prompt: re-park point-source JSON regime task (gate still blocked) /start_dev gate check on 2026-08-27. Both expiring facts the prompt says to re-check rather than re-derive still hold, so nothing was built: no issue, no branch, no worktree. - PyAutoLens#480 open, updated_at == created_at (2026-04-28), no assignees, no closing PRs. - autolens_workspace scripts/weak/simulator.py:122 still calls via_tracer_from and reads no os.environ, so weak/simple stays regime-invariant. Also records a standing risk the previous re-check missed: that simulator's own prose advertises via_tracer_random_positions_from, the one change that would reverse fact 2, with no test guarding it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0188mKnNzXptgLi1CUZ4H7NZ --- ...t_source_json_datasets_record_no_regime.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md b/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md index 35031b57d..f6e1067ac 100644 --- a/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md +++ b/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md @@ -110,6 +110,35 @@ so they are fixed here rather than left to bite again: Likewise the weak simulator is `scripts/weak/simulator.py` in @autolens_workspace. +**2026-08-27 — both facts re-verified, STILL BLOCKED. Do not build.** +Second gate check in four days, same verdict: no issue was opened, no branch was +cut, no worktree claimed. + +1. **PyAutoLens#480 is still open.** `updated_at` is still byte-identical to + `created_at` (2026-04-28T21:01:57Z) — not one edit, comment, label or + assignment since it was filed; still no assignees, still + `closed_by_pull_requests: 0`. Four months untouched. +2. **`weak/simple` is still regime-invariant.** `scripts/weak/simulator.py:122` + still calls `simulator.via_tracer_from(tracer=tracer, grid=positions, + name=dataset_name)`, and the file still contains no `os.environ` read of any + kind. Its last commit is 2026-07-27, i.e. it has not moved since the previous + re-check either. + +The `no_run.yaml` entries happen to sit at `config/build/no_run.yaml:41-42` in +@autolens_workspace again, matching the original citation the 2026-08-23 note +found drifted. That is coincidence, not a reason to trust line numbers — keep +matching on the entry text. + +One standing risk the previous note did not record (it predates that note; the +file has not changed since 2026-07-27, so this is an omission, not a change): +`weak/simulator.py` *advertises* the helper that would break fact 2. Its prose +blocks at `:94-95` and `:116-117` name +`via_tracer_random_positions_from(tracer=..., n_galaxies=..., grid_extent=...)` +as the "quick uniform-square catalogue" alternative to the explicit annulus this +script passes. So the invariant is one plausible simplification away from being +reversed, with nothing in the suite to catch it. Not a reason to build now — but +if fact 2 ever expires, that is the likely route. + Next re-check: when PyAutoLens#480 closes. That is the only trigger — fact 2 is a standing invariant, not a countdown, and only breaks if someone switches `weak/simulator.py` to the random-positions helper. From be17520591f4c725197683db061dfb3c6d0eda57 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 27 Aug 2026 14:28:11 +0000 Subject: [PATCH 2/3] prompt: issue point-solver plane_redshift magnification fix (PyAutoLens#480) Files the Mind record for PyAutoLens#480 and registers it active. No new GitHub issue was opened: #480 already describes the bug, so /create_issue was skipped deliberately and the entry points at the existing issue. Unblocks a chain: the fix clears the two multiple_sources scripts excluded in @autolens_workspace config/build/no_run.yaml, which is the single re-check trigger on the parked point_source_json_datasets_record_no_regime prompt. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0188mKnNzXptgLi1CUZ4H7NZ --- active.md | 15 ++ ...ification_filter_ignores_plane_redshift.md | 175 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 active/point_solver_magnification_filter_ignores_plane_redshift.md diff --git a/active.md b/active.md index fb25a1653..85e4e11c7 100644 --- a/active.md +++ b/active.md @@ -1,5 +1,20 @@ # Active Tasks +## point-solver-magnification-plane-redshift +- issue: https://github.com/PyAutoLabs/PyAutoLens/issues/480 (filed 2026-04-28, pre-existing) +- issued: 2026-08-27 +- prompt: active/point_solver_magnification_filter_ignores_plane_redshift.md +- status: library-dev +- branch: claude/point-source-json-regime-tmk7la (@PyAutoLens) +- repos: + - PyAutoLens +- worktree-none: remote web session — no task worktree; the container checkout at + /home/user/pyautolens is the working tree and is discarded with the session. +- note: no new issue was opened — PyAutoLens#480 already describes this bug, so /create_issue + was deliberately skipped. Unblocks the two multiple_sources scripts in @autolens_workspace + config/build/no_run.yaml and the parked + draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md. + ## transformed-message-factor-gradient-unpack - issue: https://github.com/PyAutoLabs/PyAutoFit/issues/1501 (issued 2026-08-19) - issued: 2026-08-19 diff --git a/active/point_solver_magnification_filter_ignores_plane_redshift.md b/active/point_solver_magnification_filter_ignores_plane_redshift.md new file mode 100644 index 000000000..5171192eb --- /dev/null +++ b/active/point_solver_magnification_filter_ignores_plane_redshift.md @@ -0,0 +1,175 @@ +# PointSolver magnification filter ignores plane_redshift + +Type: bug +Target: pyautolens +Repos: +- @PyAutoLens +Difficulty: small +Autonomy: supervised +Priority: high +Status: formalised +Filed: 2026-08-27 +Issue: https://github.com/PyAutoLabs/PyAutoLens/issues/480 + +GitHub issue **PyAutoLens#480** already exists (filed 2026-04-28 by Jammy2211, +untouched since). This prompt is the Mind-side record for fixing it; no new issue +should be opened. + +## Why now + +#480 is the gate on two other pieces of work, which is what surfaced it: + +- `point_source/features/multiple_sources/{simulator,modeling}` are excluded from + harness execution in @autolens_workspace `config/build/no_run.yaml:41-42`, + named as blocked by #480. +- `draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md` is parked + behind exactly that exclusion — its re-check log names #480 closing as the + single trigger for doing the work. + +So this is one fix that clears a workspace exclusion, two skipped scripts and a +parked Mind task. + +## The bug + +`PointSolver.solve(..., plane_redshift=z)` returns 0 positions whenever `z` is an +**intermediate** plane of a multi-plane tracer. The triangle search is fine — +`magnification_threshold=0.0` recovers the candidates — only the magnification +post-filter rejects them, because it measures magnification at the wrong plane. + +Two methods on the same class disagree about which plane is being solved for: + +- `autolens/point/solver/shape_solver.py:224` — `_plane_grid` **honours** + `plane_redshift`: `plane_index_via_redshift_from(redshift=...)`, then + `tracer.deflections_between_planes_from(grid=..., plane_i=0, plane_j=plane_index)`. +- `autolens/point/solver/shape_solver.py:322` — `_filter_low_magnification` takes + no `plane_redshift` at all. It builds `ag.LensCalc.from_mass_obj(tracer)`, whose + deflection callable is the whole tracer, i.e. the **last** plane. + +The search therefore solves to plane *j* and the filter judges those candidates +as if they were at plane *-1*. In the issue's repro (z = 0.5 / 1.0 / 2.0, solving +for z=1.0) the candidates come back at magnification ~1e-3–1e-5 — correct for a +z=2.0 source, meaningless for the z=1.0 one — and the 0.1 threshold discards +every one of them. + +`point_solver.py:242` is the call site that drops the argument on the floor: it +has `plane_redshift` in scope (it passed it to `solve_triangles` seven lines +earlier) and does not forward it. + +## The fix already exists as API — do not write new physics + +@PyAutoGalaxy `autogalaxy/operate/lens_calc.py:165` already provides: + +```python +LensCalc.from_tracer(tracer, use_multi_plane=True, plane_i=0, plane_j=-1) +``` + +which binds `tracer.deflections_between_planes_from` with those plane indices via +`functools.partial` — precisely the callable `_filter_low_magnification` should +be measuring against. The solver is simply calling the wrong constructor. + +**It is a provable no-op for the default case**, which is what #480 asks for. +`autolens/lens/tracer.py:879-881`: + +```python +if self.total_planes > 1: + return self.deflections_between_planes_from(grid=grid, xp=xp) +return self.deflections_of_planes_summed_from(grid=grid, xp=xp) +``` + +`deflections_between_planes_from` defaults to `plane_i=0, plane_j=-1`, so for any +multi-plane tracer `from_mass_obj(tracer)` and `from_tracer(tracer, plane_j=-1)` +wrap the *same* callable. Single-plane tracers have one plane, so the summed and +between-planes forms agree there too. Passing `plane_j=-1` when `plane_redshift is +None` changes nothing — this is not an argument that needs a tolerance, it is the +same function. + +## The fit side already does this — the solver is the half that was never wired + +Worth stating because it changes what this task is: not "choose how to measure magnification at a +plane", but "finish a feature the rest of the point-source code already has". + +@PyAutoLens `autolens/point/fit/abstract.py:125-134`, `magnifications_at_positions`: + +```python +use_multi_plane = len(self.tracer.planes) > 2 +plane_j = ( + self.tracer.extract_plane_index_of_profile(profile_name=self.name) + if use_multi_plane else -1 +) +od = ag.LensCalc.from_tracer( + tracer=self.tracer, use_multi_plane=use_multi_plane, plane_j=plane_j +) +``` + +`_lens_calc_for` in `autolens/point/fit/solved.py:65-82` mirrors it for the Jacobian weighting. So +`LensCalc.from_tracer(..., plane_j=...)` is the established pattern for exactly this measurement, +and `from_tracer` exists in @PyAutoGalaxy to serve it. The solver is the single place it never +reached. The two resolve `plane_j` by different keys, each correct for its own API: the fit knows +the profile *name* (`extract_plane_index_of_profile`), the solver is handed a *redshift* +(`plane_index_via_redshift_from`). + +**The two halves of one likelihood evaluation therefore disagreed.** +`autolens/point/fit/positions/image/abstract.py:124-130` already passes the redshift down: + +```python +return self.solver.solve( + tracer=self.tracer, + source_plane_coordinate=self.source_plane_coordinate, + xp=self._xp, + plane_redshift=self.plane_redshift, + remove_infinities=False, +) +``` + +and `plane_redshift`'s own docstring (`abstract.py:187-199`) says it exists "to ensure that if +multi-plane tracing is used when solving the model image-plane positions, the correct source-plane +is used". So for an intermediate-plane source, `model_data` measured magnification at the last +plane and returned nothing, while `magnifications_at_positions` measured at the source's own plane +and was right. That is the mechanism behind #480's "AnalysisPoint log-likelihood evaluation fails" +impact line. + +`autolens/analysis/result.py:115` and `:184` pass `plane_redshift` too. The argument was plumbed +end-to-end through the callers; only the last consumer ignored it. + +## Scope + +1. Give `_filter_low_magnification` a `plane_redshift: Optional[float] = None` + parameter and switch its constructor to `ag.LensCalc.from_tracer(...)` with the + resolved `plane_j`. +2. Forward `plane_redshift` from `point_solver.py:242`. +3. Extract the `plane_redshift -> plane_index` resolution (currently inline in + `_plane_grid`) into one helper both methods call. The bug was two methods + disagreeing about the plane; leaving two copies of the resolution invites the + same drift back. +4. Handle the unmatched-redshift case while in there. `plane_index_via_redshift_from` + returns `None` when no plane is within its 1e-8 tolerance, and `_plane_grid` + then evaluates `traced_grids_list[None]` and raises a bare `TypeError`. A + mistyped redshift should say which redshifts the tracer actually has. This is a + latent bug on the same lines, not scope creep. + +## Testing + +**The reason this shipped is that nothing tests it.** No test under +`test_autolens/point/triangles/` passes `plane_redshift` to the solver at all — +the multi-plane solve path has zero coverage. Adding that coverage is the larger +half of this task. + +- The #480 repro becomes the regression test: a z=0.5/1.0/2.0 tracer, solve for + the z=1.0 source, assert 4 positions rather than 0. +- Assert the no-op directly: for a multi-plane tracer, `plane_redshift=None` and + `plane_redshift=` return identical positions to the current + implementation. +- Cover the unmatched-redshift error from scope item 4. +- The JAX path shares `_filter_low_magnification`, so check the padded/static-shape + path is unaffected (`test_use_jax.py`, `test_implicit_diff.py`). + +## Downstream, out of scope here + +#480's own text notes that @autolens_workspace simplified its multi-source example +to work around this (dropping the intermediate source's mass profile), and wants +that reverted once fixed. That is a separate workspace task — this prompt is the +library fix. Removing the `no_run.yaml` entries should follow the fix, not ride +along with it. + + From e768719351f9c8d815043335e352c59589ed778b Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 27 Aug 2026 14:43:42 +0000 Subject: [PATCH 3/3] intake: file three follow-ups from the PyAutoLens#480 fix All three came out of cross-checking #480 rather than from the fix itself. bug/autogalaxy/lenscalc_numpy_hessian_step_is_too_coarse -- LensCalc's NumPy Richardson Hessian uses a hardcoded 0.01 arcsec step. At the last plane of the #480 tracer it disagrees with both JAX autodiff and an independent ray-traced Jacobian by 122%, with the wrong sign on three of four points; the same comparison at the intermediate plane agrees to 1.7e-08. Pre-existing and separate from #480, but it is the default path and magnifications_at_positions uses it. feature/autolens/multi_plane_time_delays -- the time-delay equivalent of #480. tracer_util.time_delays_from raises on more than two planes, which #480 now makes reachable, and LensCalc.from_tracer binds the deflection callable to the plane while leaving potential_2d_from as the tracer's all-planes sum, so a plane-bound LensCalc computes a Fermat potential that mixes planes. refactor/autolens/one_construction_path_for_plane_bound_lensing -- three call sites each hand-roll their own plane-bound LensCalc and resolve the plane by different keys (profile name twice, redshift once). That duplication is why #480 could be wrong in one of them for four months. Dashboard regenerated in the same commit; intake dashboard --check is current. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0188mKnNzXptgLi1CUZ4H7NZ --- dashboard.html | 78 +++++++------ dashboard.md | 106 ++++++++++++------ ...nscalc_numpy_hessian_step_is_too_coarse.md | 55 +++++++++ .../autolens/multi_plane_time_delays.md | 60 ++++++++++ ...nstruction_path_for_plane_bound_lensing.md | 57 ++++++++++ 5 files changed, 282 insertions(+), 74 deletions(-) create mode 100644 draft/bug/autogalaxy/lenscalc_numpy_hessian_step_is_too_coarse.md create mode 100644 draft/feature/autolens/multi_plane_time_delays.md create mode 100644 draft/refactor/autolens/one_construction_path_for_plane_bound_lensing.md diff --git a/dashboard.html b/dashboard.html index 46b7925cb..1b7ef25ed 100644 --- a/dashboard.html +++ b/dashboard.html @@ -4,7 +4,7 @@ PyAutoMind Dashboard - +