Skip to content

fix: synthetic positions fallback in test mode and PYAUTO_SMALL_DATASETS #477

@Jammy2211

Description

@Jammy2211

Overview

When PYAUTO_TEST_MODE=2 is active the random/garbage mass model can produce empty or NaN image-plane multiple-image positions, causing positions_likelihood_from to crash inside np.nanmax with ValueError: zero-size array to reduction operation fmax. Separately, simulators currently work around PointSolver by popping PYAUTO_SMALL_DATASETS from the environment, calling solve, and restoring it — awkward and easy to forget. This task fixes both at the library layer with synthetic position fallbacks, then deletes the workspace workarounds.

Plan

  • Add a test-mode guard in Result.positions_likelihood_from that swaps empty/NaN positions for [(1.0, 0.0), (-1.0, 0.0)] so threshold + PositionsLH still build cleanly.
  • Add a PYAUTO_SMALL_DATASETS=1 short-circuit at the top of PointSolver.solve that returns [(1.0, 0.0), (0.0, 1.0)] directly, skipping the triangle-tiling solve.
  • Add unit tests for both fallback paths in test_autolens/.
  • Delete the os.environ.pop("PYAUTO_SMALL_DATASETS", None) / restore dance from scripts/group/simulator.py, scripts/group/features/multi_gaussian_expansion/simulator.py, and scripts/group/features/no_lens_light/simulator.py.
  • Library PR ships first; workspace cleanup follows after merge.
  • Smoke verify: PYAUTO_TEST_MODE=2 python scripts/imaging/features/pixelization/delaunay.py must run cleanly.
Detailed implementation plan

Affected Repositories

  • Jammy2211/PyAutoLens (primary)
  • Jammy2211/autolens_workspace (follow-up after library merges)

PyAutoArray is intentionally not changed — the fix lives one layer up so the underlying Grid2DIrregular.furthest_distances_to_other_coordinates primitive stays pure.

Work Classification

Both — library first, workspace after.

Branch Survey

Repository Current Branch Dirty?
./PyAutoLens main clean
./autolens_workspace main clean
./PyAutoArray main clean
./PyAutoConf main clean

Suggested branch: feature/positions-test-mode-fallback
Worktree root: ~/Code/PyAutoLabs-wt/positions-test-mode-fallback/ (created later by /start_library)

Implementation Steps

  1. PyAutoLens/autolens/analysis/result.pypositions_likelihood_from

    • Import is_test_mode alongside the existing skip_checks import from autoconf.test_mode.
    • After the existing positions = aa.Grid2DIrregular(np.asarray(...)) normalization (~line 329) and before positions_threshold_from(...), add a test-mode guard:
      • Compute the raw array.
      • If arr.shape[0] < 2 OR np.isnan(arr).any() OR np.isinf(arr).any(), replace positions with aa.Grid2DIrregular(values=[(1.0, 0.0), (-1.0, 0.0)]) and emit a single logger.warning(...).
    • Outside test mode the behaviour is unchanged — bad positions still surface as the original error so production fits aren't silently masked.
  2. PyAutoLens/autolens/point/solver/point_solver.pyPointSolver.solve

    • Add import os at module level.
    • At the top of solve, before the super().solve_triangles(...) call:
      if os.environ.get(\"PYAUTO_SMALL_DATASETS\") == \"1\":
          return aa.Grid2DIrregular(values=[(1.0, 0.0), (0.0, 1.0)])
    • Plain numpy Grid2DIrregular is fine — PYAUTO_SMALL_DATASETS is smoke-test land, never inside jax.jit.
  3. PyAutoLens unit tests

    • test_autolens/analysis/test_result.py: monkeypatch os.environ[\"PYAUTO_TEST_MODE\"] = \"2\", build a Result whose image_plane_multiple_image_positions returns an empty / NaN grid, call positions_likelihood_from, assert returned PositionsLH.positions equals [(1.0, 0.0), (-1.0, 0.0)].
    • test_autolens/point/solver/test_point_solver.py: with PYAUTO_SMALL_DATASETS=1, call PointSolver(...).solve(tracer=<any>, source_plane_coordinate=(0.0, 0.0)), assert result is exactly [(1.0, 0.0), (0.0, 1.0)] and the underlying triangle solver was never invoked.
  4. autolens_workspace cleanup (after library PR merges)

    • scripts/group/simulator.py lines 294–309: remove import os, small_datasets = os.environ.pop(...), and the if small_datasets is not None: restore block.
    • scripts/group/features/multi_gaussian_expansion/simulator.py lines ~228–245: same cleanup.
    • scripts/group/features/no_lens_light/simulator.py lines ~257–273: same cleanup.
    • PointSolver then returns the fallback positions automatically when PYAUTO_SMALL_DATASETS=1.
  5. Smoke verification before pushing

    • cd autolens_workspace && PYAUTO_TEST_MODE=2 python scripts/imaging/features/pixelization/delaunay.py — must not raise the zero-size array error.
    • Standard pytest test_autolens/analysis/ and pytest test_autolens/point/ via /ship_library.

Key Files

  • PyAutoLens/autolens/analysis/result.py — empty/NaN guard in positions_likelihood_from
  • PyAutoLens/autolens/point/solver/point_solver.pyPYAUTO_SMALL_DATASETS short-circuit in solve
  • test_autolens/analysis/test_result.py — fallback unit test
  • test_autolens/point/solver/test_point_solver.py — solver short-circuit unit test
  • autolens_workspace/scripts/group/simulator.py
  • autolens_workspace/scripts/group/features/multi_gaussian_expansion/simulator.py
  • autolens_workspace/scripts/group/features/no_lens_light/simulator.py

Decisions / Trade-offs

  • Fix at the positions_likelihood_from layer (not in Grid2DIrregular.furthest_distances_to_other_coordinates) so the autoarray primitive stays pure and production fits still surface bad positions loudly.
  • Hard-coded fallback positions match the values from the prompt; they're geometrically separated enough for downstream threshold/likelihood code to behave normally.
  • Short-circuit PointSolver.solve (the common entry point), not solve_triangles, to keep the fast path obvious.

Original Prompt

Click to expand starting prompt

The following error is caused when in PYAUTO_TEST_MODE=2 the mass model is some random awful garbage that does
not creatr positions that are good or physical or reliable for the lens model:

((PyAuto) ) jammy@DESKTOP-H143S82:~/Code/PyAutoLabs/autolens_workspace$ PYAUTO_TEST_MODE=2 python scripts/imaging/features/pixelization/delaunay.py

Traceback (most recent call last):
File "/home/jammy/Code/PyAutoLabs/autolens_workspace/scripts/imaging/features/pixelization/delaunay.py", line 983, in
source_pix_result_1 = source_pix_1(
^^^^^^^^^^^^^
File "/home/jammy/Code/PyAutoLabs/autolens_workspace/scripts/imaging/features/pixelization/delaunay.py", line 655, in source_pix_1
source_lp_result.positions_likelihood_from(
File "/home/jammy/Code/PyAutoLabs/PyAutoLens/autolens/analysis/result.py", line 333, in positions_likelihood_from
threshold = self.positions_threshold_from(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jammy/Code/PyAutoLabs/PyAutoLens/autolens/analysis/result.py", line 254, in positions_threshold_from
threshold = factor * np.nanmax(positions_fits.max_separation_of_plane_positions)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jammy/Code/PyAutoLabs/PyAutoLens/autolens/point/max_separation.py", line 87, in max_separation_of_plane_positions
return max(self.furthest_separations_of_plane_positions)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jammy/Code/PyAutoLabs/PyAutoLens/autolens/point/max_separation.py", line 83, in furthest_separations_of_plane_positions
return self.plane_positions.furthest_distances_to_other_coordinates
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jammy/Code/PyAutoLabs/PyAutoArray/autoarray/structures/grids/irregular_2d.py", line 250, in furthest_distances_to_other_coordinates
furthest = self._xp.sqrt(self._xp.nanmax(sq_dists, axis=1))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jammy/venv/PyAuto/lib/python3.12/site-packages/numpy/lib/_nanfunctions_impl.py", line 486, in nanmax
res = np.fmax.reduce(a, axis=axis, out=out, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: zero-size array to reduction operation fmax which has no identity

Can you make it so that a check is performed to see if the positions computed in positions_likelihood_from are
sensible or exist or not nans (check what they are when this script runs) and put in a PYAUTO_TEST_MODE fix
which simply converts the positions to (1.0, 0.0) and (-1.0, 0.0), the rest of the code should then run functionally
as expected.

can you combine this solution with another relatrd issue I drafted:

This is in group/simulator.py and annoying for users:

small_datasets = os.environ.pop("PYAUTO_SMALL_DATASETS", None)

solver = al.PointSolver.for_grid(
grid=al.Grid2D.uniform(shape_native=(500, 500), pixel_scales=0.1),
pixel_scale_precision=0.001,
magnification_threshold=0.01,
)

positions = solver.solve(
tracer=tracer, source_plane_coordinate=source_galaxy.bulge.centre
)

if small_datasets is not None:
os.environ["PYAUTO_SMALL_DATASETS"] = small_datasets

Can you make it so if PYAUTO_SMALL_DATASETS=1, the PointSOlver knows to just return two positions at some
small but convenient values (e.g. (1.0, 0.0), (0.0, 1.0)), so that testing doesnt break.

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