Skip to content

feat: PointSolver refinement figure (2×2 + teaching GIF + visualizer output) #787

Closed

Description

@Jammy2211

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 and magnification_threshold annotation, 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 JAX MAX_CONTAINING_SIZE.
  • Clusters: cap the per-image annotation so many-image panels stay readable.
  • Retire the unused debug autolens/point/visualise.py; fix Step.tree_flatten (omits plane_triangles).
  • Thin the model ✕ in positions_panel.py.
  • Re-render in autolens_visualization: real-solver point_solver.png for point_source_image_chi_squared and cluster, a coarse teaching PNG + GIF, and the re-rendered fit.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

  1. 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×).
  2. Export via autolens/plot/__init__.py as plot_point_solver.
  3. autolens/point/model/plotter.py PlotterPoint.point_solver(fit); autolens/point/model/visualizer.py calls it after the quick-update return (best fit only); autolens/config/visualize/plots.yaml adds fit_point_dataset: subplot_point_solver: false.
  4. Delete autolens/point/visualise.py; fix autolens/point/solver/step.py tree_flatten.
  5. autolens/point/plot/positions_panel.py: thinner, smaller model ✕.
  6. Unit tests in test_autolens/point/plot/ and test_autolens/point/solver/.
  7. autolens_visualization: producers write point_solver.png (point_source_image_chi_squared, cluster per point) and a coarse teaching point_solver.png + point_solver.gif; enable the toggle in config/visualize/plots.yaml; rebuild GALLERY.md + manifest.

Key Files

  • autolens/point/plot/point_solver_plots.py — new figure
  • autolens/point/solver/step.py — tree_flatten fix
  • autolens/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:469 steps(...) yields Step objects (autolens/point/solver/step.py) with initial_triangles, up_sampled, neighbourhood and filtered_triangles. There is a second steps at line 664, for the other solver class.
  • autolens/point/visualise.py (last touched 2025-06) has debug-only helpers: visualise(step), plot_triangles and plot_triangles_compare. They are raw matplotlib with plt.show() and X/Y labels, 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 wrap point/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 plots triangle[:,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_triangles
  • plane_triangles (undocumented; the source-plane copy)
  • filtered_triangles
  • neighbourhood
  • up_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_SIZE warning
  • retiring point/visualise.py and fixing tree_flatten in the same PR

Human decision: figure layout (2026-10-21, after variants D/E)

  • Layout: a 2×2 point_solver.png showing 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 the magnification_threshold annotation).
  • 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) and variant_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.animation with PillowWriter as 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.py loads 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.png is 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 no x (") / 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_SIZE warning
  • retiring point/visualise.py and fixing Step.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

  1. Jammy2211 commented on Oct 21, 2026

    @Jammy2211
    CollaboratorAuthor

    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.

  2. Jammy2211 commented on Oct 21, 2026

    @Jammy2211
    CollaboratorAuthor

    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.

    • PyAutoLens#788 (57f1e65), pending release. Adds aplt.plot_point_solver: a 2×2 figure of steps 0, 1, 2 and final, an animate=True GIF, and a visualizer output that is off by default. Also the Step.tree_flatten fix, removal of point/visualise.py, and a thinner model ✕.
    • autolens_visualization#12 (33e23a10). Real-solver figures for point_source_image_chi_squared and cluster, plus the teaching still and GIF under point_source_image_chi_squared/.../teaching/.

    The cluster labels were fixed before merge. A dedicated cluster-figures pass is filed in Mind.

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