Repository navigation
Description
Overview
Phase 2 of the point-source figures work: a point_solver.png figure showing how the PointSolver refines its triangle tiling until it finds the multiple images, plus a teaching GIF in the same style and an optional (off by default) visualizer output during modelling. It is both a modelling diagnostic (did the grid cover every image, how many triangles survive) and teaching material for how the solver works. Also carries over the phase-1 tweak of thinning the model ✕ in the Fit Positions panel.
Plan
- Add a point-solver plot module: one shared per-panel draw function used by a 2×2 still (steps 0, 1, 2 and final) and a looping GIF (one panel per frame, final frame held longer).
- Style as approved in prototype
variant_f_coarse.png: tested grey, kept blue, kept-then-rejected hatched muted blue, neighbours orange, up-sampled green, critical curve, observed circles, solved red ✕, step-0 image numbering andmagnification_thresholdannotation, shared legend, same fixed full extent on every panel, sub-3px kept triangles drawn as a centroid ▲. - Standard PyAutoLens arc-second tick labels via the library tick/extent machinery; no axis-label text.
- Public API
aplt.plot_point_solver(solver, tracer, source_plane_coordinate, ..., animate=False). - Visualizer: config-toggled (
fit_point_dataset: subplot_point_solver, off by default), best-fit only, re-solving with NumPy, warning when the kept count nears JAXMAX_CONTAINING_SIZE. - Clusters: cap the per-image annotation so many-image panels stay readable.
- Retire the unused debug
autolens/point/visualise.py; fixStep.tree_flatten(omitsplane_triangles). - Thin the model ✕ in
positions_panel.py. - Re-render in autolens_visualization: real-solver
point_solver.pngfor point_source_image_chi_squared and cluster, a coarse teaching PNG + GIF, and the re-renderedfit.pngs.
Detailed implementation plan
Affected Repositories
- PyAutoLens (primary)
- autolens_visualization
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./lens/PyAutoLens | main (d7a14b0) | clean |
| ./lens/autolens_visualization | main (39a8547) | clean |
Suggested branch: feature/point-solver-refinement-figure
Implementation Steps
autolens/point/plot/point_solver_plots.py(new): step collection (NumPy), role split (kept / kept-then-rejected by magnification / neighbour rim / up-sampled),draw_point_solver_panel(ax, ...),subplot_point_solver(...)and the GIF writer (matplotlib.animation.FuncAnimation+PillowWriter, ~1.25 s per frame, final held 3×).- Export via
autolens/plot/__init__.pyasplot_point_solver. autolens/point/model/plotter.pyPlotterPoint.point_solver(fit);autolens/point/model/visualizer.pycalls it after the quick-update return (best fit only);autolens/config/visualize/plots.yamladdsfit_point_dataset: subplot_point_solver: false.- Delete
autolens/point/visualise.py; fixautolens/point/solver/step.pytree_flatten. autolens/point/plot/positions_panel.py: thinner, smaller model ✕.- Unit tests in
test_autolens/point/plot/andtest_autolens/point/solver/. - autolens_visualization: producers write
point_solver.png(point_source_image_chi_squared, cluster per point) and a coarse teachingpoint_solver.png+point_solver.gif; enable the toggle inconfig/visualize/plots.yaml; rebuild GALLERY.md + manifest.
Key Files
autolens/point/plot/point_solver_plots.py— new figureautolens/point/solver/step.py—tree_flattenfixautolens/point/plot/positions_panel.py— thinner ✕autolens/point/model/{plotter,visualizer}.py,autolens/config/visualize/plots.yaml
Original Prompt
Click to expand starting prompt
Point-source phase 2: PointSolver refinement figure
Consequence: judge
Autonomy: human-required
builds-on: complete/2026/10/point-dataset-positions-panel.md (phase 1 shipped 2026-10-21; shares styling and the solver threading)
@PyAutoLens @autolens_visualization
Request (human, 2026-10-21)
I think we should also plan a "point solver" image which basically shows how for a given model the point solver gets to its solution, which is guess is a load of triangles being iteratively refined and displayed by each panel? We have an image of this in the workspace already, but maybe we lost the code to produce it? This is both a great diagnostic for users running lens modeling and also a great way to illustrate how the lens solver works to a user. Add this as another phase to this work, and also include a follow up which scans the workspace for places to slot it in and describe it (e.g. HowToLens's section of position solviung, which is either in chapter 1 or has a placeholder).
What exists (checked 2026-10-21)
autolens/point/solver/shape_solver.py:469steps(...)yieldsStepobjects (autolens/point/solver/step.py) withinitial_triangles,up_sampled,neighbourhoodandfiltered_triangles. There is a secondstepsat line 664, for the other solver class.autolens/point/visualise.py(last touched 2025-06) has debug-only helpers:visualise(step),plot_trianglesandplot_triangles_compare. They are raw matplotlib withplt.show()andX/Ylabels, are not in the plot API or visualizers, and nothing calls them.- The image the human remembers was not found in autolens_workspace, HowToLens or the PyAutoLens docs (search by filename and "triangle"). Ask the human where it lived, in case the old producer survives.
Intent
A proper point_solver.png subplot: one panel per refinement step (or a chosen subset; first, middle and last when there are many). Each panel shows:
- the image-plane triangles kept at that step, coloured by role (initial, up-sampled, neighbourhood, filtered)
- the observed positions (phase 1 marker style)
- the final solved images
- the critical curves for context
It serves two purposes:
- a modelling diagnostic: did the grid cover every image, and how many triangles survive?
- teaching material for how the solver works.
Design questions (settle in /start_dev)
- Panel count and selection. The number of steps depends on
pixel_scale_precision. Options are every step, a log-spaced subset, or zoomed insets for the late steps (the triangles become sub-pixel at the full extent). - Optional source-plane row. A second row could show the ray-traced triangles containing the source centre; this connects to the source-plane follow-up.
- API placement. Something like
aplt.plot_point_solver(solver, tracer, source_plane_coordinate), plus a config-toggled visualizer output during modelling (off by default, since it is costly). Retire or wrappoint/visualise.py. - JAX path. Decide whether the JAX solver path exposes the same steps, or the figure is numpy-only.
- Re-render. Producers in autolens_visualization for point_source_image_chi_squared and cluster.
Research findings (2026-10-21, read-only prototype)
Prototypes are in /mnt/c/Users/Jammy/Science/scratch/eyes/autolens/point_solver_prototype/variant_{a,b,c}.png. The scripts are in the session scratchpad; they are not tracked, so re-derive them from this section.
Lost image. It was never committed anywhere. There are two display-only ancestors:
- autolens_workspace
scripts/guides/mappings.py:309-338__Solver Refinement Steps__(feat(weak): FitWeak class + plotters (#524) #525, 2026-09-03), which plots only the kept-triangle means. - PyAutoLens
point/visualise.py(2024). Its only caller was removed in e8db9e2. It has a bug: it plotstriangle[:,0]as x, so every plot is transposed.
API. solver.steps(tracer=..., shape=Point(*beta)) works today with no library change. Each Step holds:
initial_trianglesplane_triangles(undocumented; the source-plane copy)filtered_trianglesneighbourhoodup_sampled
n_steps = ceil(log2(scale/pixel_scale_precision)). That gives 6 steps for the 0.05″ grid at 0.001. The counts are 23,283 tested at step 0, then 80 per step, with 5 kept: four images plus the lens-centre triangle that magnification_threshold rejects.
Cost. Collecting the steps with NumPy takes 0.15 s; rendering takes about 3 s, mostly the critical curves.
JAX. Steps work eagerly but come back NaN-padded (MAX_CONTAINING_SIZE=20). Inside jit they are hidden behind implicit_diff. So the figure is NumPy-only, re-solving with xp=np at the instance being visualised.
Latent bug. Step.tree_flatten returns 5 children for 6 fields (it omits plane_triangles), so tree_unflatten raises TypeError. It is harmless today.
Variants.
- A: all steps zoomed on one image. Steps 1-5 look identical (the refinement is self-similar), so drop it.
- B: steps 0/1/3/5 for one image, with a source-plane row of the traced triangles and the source star at the cusp. The research worker and the session judged it the teaching form.
- C: a step-0 overview with numbered images and the rejected centre annotated, plus zoom panels per image at steps 1, middle and last. Recommended as the default diagnostic
point_solver.png.
Human design direction (2026-10-21, after seeing A/B/C)
The human prefers the old visualise(step) style, a fixed-axes panel showing every image, because it shows the triangles shrinking. A/B/C lose that because their axes rescale.
Direction:
- Panel 0 is the variant-C overview: numbered images, and the centre triangle annotated as rejected by
magnification_threshold. - Later panels show every image at a fixed extent.
- Zooms happen only when a zoom is unavoidable, and are explicit: a dashed box on the previous panel and "zoom ×k" in the title, held fixed across the steps that follow.
- Colours. Kept-then-rejected (μ < threshold) triangles get a colour paired with the kept colour on every panel.
- Teaching mode may use a coarse grid; the diagnostic uses the real solver.
- Fixes. Correct the (y,x) transposition; no axis labels.
Variant D (real grid and coarse grid) is being prototyped into the same scratch folder.
Still open with the human:
- cluster row/image capping
- visualizer output: config-toggled, off by default, best-fit only, with the JAX
MAX_CONTAINING_SIZEwarning - retiring
point/visualise.pyand fixingtree_flattenin the same PR
Human decision: figure layout (2026-10-21, after variants D/E)
- Layout: a 2×2
point_solver.pngshowing steps 0, 1, 2 and the final step; the steps in between are skipped. - Style: exactly
variant_d_coarse.png's top-row panels (tested grey, kept blue, kept-then-rejected hatched muted blue, neighbours orange, up-sampled green, critical curve, observed circles, solved red ✕, plus the step-0 numbering and themagnification_thresholdannotation). - Extent: the same fixed full extent on all four panels, with no insets or zooms. Sub-pixel kept triangles on the final panel are drawn as a centroid ▲ marker.
- Rejected alternatives: the per-image inset rows were judged information overload, and the simplified-triangle variant E was not chosen.
- Prototype:
variant_f_coarse.png(teaching) andvariant_f.png(real solver), in the scratch prototype folder.
Human addition: teaching animation (2026-10-21)
The teaching figure also gets an animation in exactly the same style: one panel at a time, playing steps 0 → 1 → 2 → final in sequence.
Implementation intent:
- One shared per-panel draw function used by both the 2×2 still and the animation, so the two cannot drift apart.
- Output via
matplotlib.animationwithPillowWriteras a looping GIF, so there is no ffmpeg dependency. The final frame is held longer. - An API option alongside the still, e.g.
aplt.plot_point_solver(..., animate=True).
Phase 3 places the GIF next to the still in HowToLens tutorial 4 and the workspace point_source/fit.py placeholder.
The prototype is variant_f_coarse.gif in the scratch prototype folder.
Prototype producer preserved (2026-10-21)
The scratch producer is at /mnt/c/Users/Jammy/Science/scratch/eyes/autolens/point_solver_prototype/producer/. Run python variant_f.py coarse|real [gif].
- Its
draw_panel(ax, i, step)is shared by the 2×2 PNG and the GIF. common.pyloads the viz true model.- Use it as the reference implementation for phase 2.
variant_f_coarse.gif is 0.14 MB: 4 frames at 1.25 s each, with the final frame held for 3.75 s.
On the real grid, steps 1, 2 and final look identical (sub-pixel triangles). The proposed diagnostic is therefore two panels (step 0 and final), pending the human's answer.
Human decisions (2026-10-21, final design round)
- Teaching figure:
variant_f_coarse.pngis approved, along with the GIF in the same style. - Modelling output (option C): the visualizer saves the same 2×2 (steps 0, 1, 2, final) using the model's real solver, accepting that on fine grids steps 1, 2 and final look alike. Config-toggled.
- Axes: show arc-second coordinate tick labels in the standard PyAutoLens style (e.g.
1.9", as in the phase-1 positions panels), produced with the library's own tick/extent machinery rather than raw matplotlib ticks. There are still nox (")/y (")axis-label text, consistent with phase 1. This applies to the still and to every GIF frame.
Still to confirm at /start_dev:
- cluster image capping
- default-off toggle, best-fit only, and the JAX
MAX_CONTAINING_SIZEwarning - retiring
point/visualise.pyand fixingStep.tree_flatten
Carry-over from phase 1 (approved in chat 2026-10-21)
Thin the model ✕ in the Fit Positions panel (positions_panel.py), using a thinner line and smaller marker, so the observed dot stays visible under it when the fit is exact. Re-render the point_source_* and cluster figures.
Activity
Jammy2211 commentedon Oct 21, 2026
PRs open (Consequence: judge → human /prm, library first):
Defaults confirmed by implementing (flagged for review): cluster numbering capped at 10 images + one label with arrows to ≤5 rejected candidates + dense step-0 tilings shaded; point/visualise.py removed; Step.tree_flatten fixed; GIF tracked beside its PNG but not in the PNG manifest.
Jammy2211 commentedon Oct 21, 2026
Shipped. Merged via /prm on 2026-10-21, library first. Every CI leg was green: #788 unit tests (3.12, 3.13, nojax) and docs. #12 lint passed on its re-run after #788 merged; the first run failed only on the new
PlotterPoint.point_solver, against the old library main.aplt.plot_point_solver: a 2×2 figure of steps 0, 1, 2 and final, ananimate=TrueGIF, and a visualizer output that is off by default. Also theStep.tree_flattenfix, removal ofpoint/visualise.py, and a thinner model ✕.point_source_image_chi_squared/.../teaching/.The cluster labels were fixed before merge. A dedicated cluster-figures pass is filed in Mind.