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
-
PyAutoLens/autolens/analysis/result.py — positions_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.
-
PyAutoLens/autolens/point/solver/point_solver.py — PointSolver.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.
-
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.
-
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.
-
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.py — PYAUTO_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.
Overview
When
PYAUTO_TEST_MODE=2is active the random/garbage mass model can produce empty or NaN image-plane multiple-image positions, causingpositions_likelihood_fromto crash insidenp.nanmaxwithValueError: zero-size array to reduction operation fmax. Separately, simulators currently work aroundPointSolverby poppingPYAUTO_SMALL_DATASETSfrom 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
Result.positions_likelihood_fromthat swaps empty/NaN positions for[(1.0, 0.0), (-1.0, 0.0)]so threshold +PositionsLHstill build cleanly.PYAUTO_SMALL_DATASETS=1short-circuit at the top ofPointSolver.solvethat returns[(1.0, 0.0), (0.0, 1.0)]directly, skipping the triangle-tiling solve.test_autolens/.os.environ.pop("PYAUTO_SMALL_DATASETS", None)/ restore dance fromscripts/group/simulator.py,scripts/group/features/multi_gaussian_expansion/simulator.py, andscripts/group/features/no_lens_light/simulator.py.PYAUTO_TEST_MODE=2 python scripts/imaging/features/pixelization/delaunay.pymust run cleanly.Detailed implementation plan
Affected Repositories
PyAutoArray is intentionally not changed — the fix lives one layer up so the underlying
Grid2DIrregular.furthest_distances_to_other_coordinatesprimitive stays pure.Work Classification
Both — library first, workspace after.
Branch Survey
Suggested branch:
feature/positions-test-mode-fallbackWorktree root:
~/Code/PyAutoLabs-wt/positions-test-mode-fallback/(created later by/start_library)Implementation Steps
PyAutoLens/autolens/analysis/result.py—positions_likelihood_fromis_test_modealongside the existingskip_checksimport fromautoconf.test_mode.positions = aa.Grid2DIrregular(np.asarray(...))normalization (~line 329) and beforepositions_threshold_from(...), add a test-mode guard:arr.shape[0] < 2ORnp.isnan(arr).any()ORnp.isinf(arr).any(), replacepositionswithaa.Grid2DIrregular(values=[(1.0, 0.0), (-1.0, 0.0)])and emit a singlelogger.warning(...).PyAutoLens/autolens/point/solver/point_solver.py—PointSolver.solveimport osat module level.solve, before thesuper().solve_triangles(...)call:Grid2DIrregularis fine —PYAUTO_SMALL_DATASETSis smoke-test land, never insidejax.jit.PyAutoLens unit tests
test_autolens/analysis/test_result.py: monkeypatchos.environ[\"PYAUTO_TEST_MODE\"] = \"2\", build aResultwhoseimage_plane_multiple_image_positionsreturns an empty / NaN grid, callpositions_likelihood_from, assert returnedPositionsLH.positionsequals[(1.0, 0.0), (-1.0, 0.0)].test_autolens/point/solver/test_point_solver.py: withPYAUTO_SMALL_DATASETS=1, callPointSolver(...).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.autolens_workspace cleanup (after library PR merges)
scripts/group/simulator.pylines 294–309: removeimport os,small_datasets = os.environ.pop(...), and theif small_datasets is not None:restore block.scripts/group/features/multi_gaussian_expansion/simulator.pylines ~228–245: same cleanup.scripts/group/features/no_lens_light/simulator.pylines ~257–273: same cleanup.PointSolverthen returns the fallback positions automatically whenPYAUTO_SMALL_DATASETS=1.Smoke verification before pushing
cd autolens_workspace && PYAUTO_TEST_MODE=2 python scripts/imaging/features/pixelization/delaunay.py— must not raise thezero-size arrayerror.pytest test_autolens/analysis/andpytest test_autolens/point/via/ship_library.Key Files
PyAutoLens/autolens/analysis/result.py— empty/NaN guard inpositions_likelihood_fromPyAutoLens/autolens/point/solver/point_solver.py—PYAUTO_SMALL_DATASETSshort-circuit insolvetest_autolens/analysis/test_result.py— fallback unit testtest_autolens/point/solver/test_point_solver.py— solver short-circuit unit testautolens_workspace/scripts/group/simulator.pyautolens_workspace/scripts/group/features/multi_gaussian_expansion/simulator.pyautolens_workspace/scripts/group/features/no_lens_light/simulator.pyDecisions / Trade-offs
positions_likelihood_fromlayer (not inGrid2DIrregular.furthest_distances_to_other_coordinates) so the autoarray primitive stays pure and production fits still surface bad positions loudly.PointSolver.solve(the common entry point), notsolve_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.