You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When jax_zero_contour isn't installed, the default-enabled effective_einstein_radius latent crashes the post-fit metric write of
otherwise-converged searches (surfaced on autolens_profiling job
322552 against PyAutoLens #557). Fix with caller fallback + NaN
backstop: PyAutoLens's effective_einstein_radius detects the
missing dep and routes to the existing NumPy einstein_radius_from
path (user keeps a real value); PyAutoGalaxy's lens_calc.py
soft-fails to NaN / [] as a defensive backstop for any other direct
caller.
Plan
PyAutoLens — caller fallback. In effective_einstein_radius,
detect missing jax_zero_contour on the JAX path and route through
the existing NumPy einstein_radius_from(grid=fit.dataset.grids.lp)
branch with a one-time-per-process warning ("falling back to NumPy —
slower; install jax_zero_contour to enable JIT path").
PyAutoGalaxy — backstop soft-fail. Add _maybe_optional_dep_warn
helper in lens_calc.py; replace the two try/except → raise blocks
with NaN return (einstein_radius_jit_from) and [] return
(_critical_curve_list_via_zero_contour). Empty list matches the
existing ValueError → return [] path at line 1167.
Tests in both repos. Mock the import failure and assert: PyAutoLens
fallback returns a real (finite) Einstein radius; PyAutoGalaxy
producer returns NaN / [] with exactly one warning per process per
feature.
Detailed implementation plan
Affected Repositories
PyAutoGalaxy (primary — soft-fail backstop)
PyAutoLens (caller fallback)
Work Classification
Library (both repos)
Branch Survey
Repository
Current Branch
Dirty?
./PyAutoGalaxy
main
clean
./PyAutoLens
main
clean
Suggested branch:feature/optional-dep-latents-soft-fail (both repos) Worktree root:~/Code/PyAutoLabs-wt/optional-dep-latents-soft-fail/ (created later by /start_library)
Implementation Steps
PyAutoGalaxy first (backstop, no caller dependency).
autogalaxy/operate/lens_calc.py — add helper at module scope:
Add import importlib and _OPTIONAL_DEP_WARNED: set = set() near the top. logger already exists in this module.
Add _maybe_optional_dep_warn(import_name: str, feature_name: str) -> bool: tries importlib.import_module(import_name); returns False on success; on ModuleNotFoundError emits one logger.warning per feature_name and returns True. Message: "Optional dependency '%s' not installed; '%s' returning NaN/empty. pip install %s to enable it."
Add tests to test_autogalaxy/operate/test_deflections.py:
test__einstein_radius_jit_from__missing_jax_zero_contour__returns_nan_and_warns(monkeypatch, caplog) — patch importlib.import_module to raise ModuleNotFoundError for "jax_zero_contour", build a minimal LensCalc, assert return is NaN and one warning record matches feature_name.
test__tangential_critical_curve_list_via_zero_contour__missing_dep__returns_empty_list_and_warns(...) — same approach, assert [].
test__maybe_optional_dep_warn__logs_only_once_per_name(...) — call twice, assert one record.
Each test resets _OPTIONAL_DEP_WARNED.discard(name) at the start for determinism.
PyAutoLens second (caller fallback).
autolens/analysis/latent.py — add fallback logic to effective_einstein_radius:
Add helper _jax_zero_contour_available() -> bool that does importlib.import_module("jax_zero_contour") inside try/except and warns once when missing: "jax_zero_contour not installed; effective_einstein_radius falling back to NumPy path (slower). pip install jax_zero_contour to enable the JIT path."
Net effect: JAX path with dep present → unchanged JIT path. JAX path without dep → falls through to NumPy path with a one-time warning. NumPy path → unchanged.
Add tests to test_autolens/analysis/test_latent.py:
test__effective_einstein_radius__jax_path__missing_jax_zero_contour__falls_back_to_numpy(monkeypatch, caplog) — patch importlib.import_module to raise for "jax_zero_contour", call effective_einstein_radius(fit=fit, magzero=None, xp=jnp), assert finite return (not NaN) and one warning emitted.
Reset _latent_module._JAX_ZERO_CONTOUR_FALLBACK_WARNED = False at start.
PyAutoGalaxy/test_autogalaxy/operate/test_deflections.py — 3 new unit tests.
PyAutoLens/autolens/analysis/latent.py — helper + reroute in effective_einstein_radius (~10 LOC net).
PyAutoLens/test_autolens/analysis/test_latent.py — 1 new fallback test.
Merge Order
PyAutoGalaxy PR first (the backstop). PyAutoLens PR second; does not strictly depend on the PyAutoGalaxy merge because the PyAutoLens fallback sidesteps the producer entirely. Library-first gate still applies.
Out of Scope
Promoting jax_zero_contour to a hard dep (alternative; not preferred per the prompt).
autogalaxy/util/plot_utils.py:337-360 — has its own ImportError handling for the plot helper, not reached by default latents.
Auditing every optional-dep import in PyAutoGalaxy; scope is the latent-computation surface.
Adding a fallback for _critical_curve_list_via_zero_contour to the marching-squares sibling — its callers are plot utilities; [] (no curves drawn) is acceptable.
Performance Note
The NumPy fallback (einstein_radius_from) uses a dense ~250k-evaluation grid vs. the JIT'd contour tracer. Cost is post-fit, runs once per converged search, so order-of-seconds is acceptable. The fallback warning makes the cost discoverable to the user.
Original Prompt
Click to expand starting prompt
Optional-dep latents should soft-fail to NaN, not re-raise ModuleNotFoundError
Context
Discovered while validating the PyAutoLens #557 magzero fix on the
first-class A100 search profile (autolens_profiling job 322552). The
magzero _mujy latents now soft-fail correctly, but a second post-fit
latent crashed the same metric-JSON write path with a parallel bug:
File "PyAutoGalaxy/autogalaxy/operate/lens_calc.py", line 1567,
in einstein_radius_jit_from
from jax_zero_contour import ZeroSolver
ModuleNotFoundError: No module named 'jax_zero_contour'
The above exception was the direct cause of the following exception:
File "PyAutoLens/autolens/analysis/latent.py", line 225,
in effective_einstein_radius
return lens_calc.einstein_radius_jit_from(init_guess=init_guess)
raise ModuleNotFoundError(
"jax_zero_contour is required for einstein_radius_jit_from. "
"Install it with: pip install jax_zero_contour"
) from exc
The raise lives at PyAutoGalaxy:autogalaxy/operate/lens_calc.py:1566-1572:
try:
fromjax_zero_contourimportZeroSolverexceptModuleNotFoundErrorasexc:
raiseModuleNotFoundError(
"jax_zero_contour is required for einstein_radius_jit_from. ""Install it with: pip install jax_zero_contour"
) fromexc
This is the same default-config-crashes-by-default pattern as the
magzero family (fixed in PyAutoLens #557 with _maybe_magzero_warn),
just keyed on an optional dependency instead of an Analysis kwarg.
The bug
effective_einstein_radius is enabled by default in PyAutoLens:autolens/config/latent.yaml, and is computed during every SearchUpdater._compute_latent_samples call as part of the standard
post-fit pipeline. When jax_zero_contour isn't installed (it isn't
in PyAuto's default pyproject.toml deps — it's a JAX-only optional
extra), every search.fit() that reaches the post-fit step crashes
with the message above. The search itself converged; the metric-JSON
write never happens.
This is the same failure shape as PyAutoPrompt/autolens/magzero_required_latents_crash_search.md
(landed as PyAutoLens #557). The PyAutoLens fix only addressed the
magzero family of latents — the optional-dependency family was not
touched, and surfaces with the same symptoms.
Desired fix
Mirror PyAutoLens #557's _maybe_magzero_warn: replace the re-raise
with a per-process warning + xp.nan return. The "module is missing"
case is structurally identical to the "user kwarg is missing" case —
both mean the latent value is unknown, and the right behaviour is
return-NaN-and-warn, not kill the search.
Sketch (in PyAutoGalaxy:autogalaxy/operate/lens_calc.py or wherever
the latent function lives — PyAutoLens:autolens/analysis/latent.py:225
calls into this):
_OPTIONAL_DEP_WARNED: set[str] =set()
def_maybe_optional_dep_warn(import_name: str, name: str) ->bool:
"""Return True (and warn once) if the optional dependency is missing."""try:
importlib.import_module(import_name)
returnFalseexceptModuleNotFoundError:
ifnamenotin_OPTIONAL_DEP_WARNED:
logger.warning(
"Optional dependency '%s' not installed; '%s' latent will ""be NaN. pip install %s to enable it, or disable in ""config/latent.yaml to silence this warning.",
import_name, name, import_name,
)
_OPTIONAL_DEP_WARNED.add(name)
returnTrue
einstein_radius_jit_from (and any sibling that does the same try/except ModuleNotFoundError → re-raise pattern) becomes:
Unit test (mirror test_autolens/analysis/test_latent.py's magzero
cases): mock jax_zero_contour import to raise, assert the latent
returns NaN and emits one warning per process.
End-to-end on a node WITHOUT jax_zero_contour installed: construct AnalysisImaging(..., use_jax=True) with default config/latent.yaml, run af.Nautilus(...).fit(...) in PYAUTO_TEST_MODE=1. Confirm search completes, JSON write happens, latent.csv has NaN values for the affected columns.
Smoke check: re-run autolens_profiling/searches/nautilus/imaging/mge.py
on a clean venv (no jax_zero_contour) and confirm no crash.
Affected callers / interaction surface
Every PyAutoLens search using default latents on a venv without jax_zero_contour — the HPC PyAutoNSS venv on Euclid SAAS in
particular, where jax_zero_contour was not installed at venv
creation time (added manually post-hoc to unblock job 322552).
Sibling functions in PyAutoGalaxy:autogalaxy/operate/lens_calc.py
that do the same try / except ModuleNotFoundError → raise pattern:
grep for raise ModuleNotFoundError in lens_calc.py and adjacent
modules; any that gate optional-dep imports for default-on latents
should switch to the soft-fail pattern.
Why not just add jax_zero_contour as a hard dep?
That's the pragmatic short-term fix (it's what unblocked job 322552
on the HPC). But it makes a JAX-only utility a required dep of every
PyAutoGalaxy install, including non-JAX numpy-only users — wider blast
radius than this fix needs. Soft-fail keeps the optional dep optional
while making default configs runnable.
Out of scope
Promoting jax_zero_contour to a hard dep (alternative; not the
preferred fix).
Auditing every optional-dep code path in PyAutoGalaxy. Scope to
the latent-computation surface where default configs hit them.
Overview
When
jax_zero_contourisn't installed, the default-enabledeffective_einstein_radiuslatent crashes the post-fit metric write ofotherwise-converged searches (surfaced on
autolens_profilingjob322552 against PyAutoLens #557). Fix with caller fallback + NaN
backstop: PyAutoLens's
effective_einstein_radiusdetects themissing dep and routes to the existing NumPy
einstein_radius_frompath (user keeps a real value); PyAutoGalaxy's
lens_calc.pysoft-fails to NaN /
[]as a defensive backstop for any other directcaller.
Plan
effective_einstein_radius,detect missing
jax_zero_contouron the JAX path and route throughthe existing NumPy
einstein_radius_from(grid=fit.dataset.grids.lp)branch with a one-time-per-process warning ("falling back to NumPy —
slower; install jax_zero_contour to enable JIT path").
_maybe_optional_dep_warnhelper in
lens_calc.py; replace the twotry/except → raiseblockswith NaN return (
einstein_radius_jit_from) and[]return(
_critical_curve_list_via_zero_contour). Empty list matches theexisting
ValueError → return []path at line 1167.fallback returns a real (finite) Einstein radius; PyAutoGalaxy
producer returns NaN /
[]with exactly one warning per process perfeature.
Detailed implementation plan
Affected Repositories
Work Classification
Library (both repos)
Branch Survey
Suggested branch:
feature/optional-dep-latents-soft-fail(both repos)Worktree root:
~/Code/PyAutoLabs-wt/optional-dep-latents-soft-fail/(created later by/start_library)Implementation Steps
PyAutoGalaxy first (backstop, no caller dependency).
autogalaxy/operate/lens_calc.py— add helper at module scope:import importliband_OPTIONAL_DEP_WARNED: set = set()near the top.loggeralready exists in this module._maybe_optional_dep_warn(import_name: str, feature_name: str) -> bool: triesimportlib.import_module(import_name); returnsFalseon success; onModuleNotFoundErroremits onelogger.warningperfeature_nameand returnsTrue. Message:"Optional dependency '%s' not installed; '%s' returning NaN/empty. pip install %s to enable it."Replace
_critical_curve_list_via_zero_contour(lines 1155–1161):Replace
einstein_radius_jit_from(lines 1566–1572):Add tests to
test_autogalaxy/operate/test_deflections.py:test__einstein_radius_jit_from__missing_jax_zero_contour__returns_nan_and_warns(monkeypatch, caplog)— patchimportlib.import_moduleto raiseModuleNotFoundErrorfor"jax_zero_contour", build a minimalLensCalc, assert return is NaN and one warning record matchesfeature_name.test__tangential_critical_curve_list_via_zero_contour__missing_dep__returns_empty_list_and_warns(...)— same approach, assert[].test__maybe_optional_dep_warn__logs_only_once_per_name(...)— call twice, assert one record._OPTIONAL_DEP_WARNED.discard(name)at the start for determinism.PyAutoLens second (caller fallback).
autolens/analysis/latent.py— add fallback logic toeffective_einstein_radius:_JAX_ZERO_CONTOUR_FALLBACK_WARNED: bool = False._jax_zero_contour_available() -> boolthat doesimportlib.import_module("jax_zero_contour")inside try/except and warns once when missing:"jax_zero_contour not installed; effective_einstein_radius falling back to NumPy path (slower). pip install jax_zero_contour to enable the JIT path."Add tests to
test_autolens/analysis/test_latent.py:test__effective_einstein_radius__jax_path__missing_jax_zero_contour__falls_back_to_numpy(monkeypatch, caplog)— patchimportlib.import_moduleto raise for"jax_zero_contour", calleffective_einstein_radius(fit=fit, magzero=None, xp=jnp), assert finite return (not NaN) and one warning emitted._latent_module._JAX_ZERO_CONTOUR_FALLBACK_WARNED = Falseat start.Key Files
PyAutoGalaxy/autogalaxy/operate/lens_calc.py— helper + replace 2 raise sites (~12 LOC net).PyAutoGalaxy/test_autogalaxy/operate/test_deflections.py— 3 new unit tests.PyAutoLens/autolens/analysis/latent.py— helper + reroute ineffective_einstein_radius(~10 LOC net).PyAutoLens/test_autolens/analysis/test_latent.py— 1 new fallback test.Merge Order
PyAutoGalaxy PR first (the backstop). PyAutoLens PR second; does not strictly depend on the PyAutoGalaxy merge because the PyAutoLens fallback sidesteps the producer entirely. Library-first gate still applies.
Out of Scope
jax_zero_contourto a hard dep (alternative; not preferred per the prompt).autogalaxy/util/plot_utils.py:337-360— has its own ImportError handling for the plot helper, not reached by default latents._critical_curve_list_via_zero_contourto the marching-squares sibling — its callers are plot utilities;[](no curves drawn) is acceptable.Performance Note
The NumPy fallback (
einstein_radius_from) uses a dense ~250k-evaluation grid vs. the JIT'd contour tracer. Cost is post-fit, runs once per converged search, so order-of-seconds is acceptable. The fallback warning makes the cost discoverable to the user.Original Prompt
Click to expand starting prompt
Optional-dep latents should soft-fail to NaN, not re-raise ModuleNotFoundError
Context
Discovered while validating the PyAutoLens #557 magzero fix on the
first-class A100 search profile (
autolens_profilingjob 322552). Themagzero
_mujylatents now soft-fail correctly, but a second post-fitlatent crashed the same metric-JSON write path with a parallel bug:
The raise lives at
PyAutoGalaxy:autogalaxy/operate/lens_calc.py:1566-1572:This is the same default-config-crashes-by-default pattern as the
magzero family (fixed in PyAutoLens #557 with
_maybe_magzero_warn),just keyed on an optional dependency instead of an Analysis kwarg.
The bug
effective_einstein_radiusis enabled by default inPyAutoLens:autolens/config/latent.yaml, and is computed during everySearchUpdater._compute_latent_samplescall as part of the standardpost-fit pipeline. When
jax_zero_contourisn't installed (it isn'tin PyAuto's default
pyproject.tomldeps — it's a JAX-only optionalextra), every
search.fit()that reaches the post-fit step crasheswith the message above. The search itself converged; the metric-JSON
write never happens.
This is the same failure shape as
PyAutoPrompt/autolens/magzero_required_latents_crash_search.md(landed as PyAutoLens #557). The PyAutoLens fix only addressed the
magzero family of latents — the optional-dependency family was not
touched, and surfaces with the same symptoms.
Desired fix
Mirror PyAutoLens #557's
_maybe_magzero_warn: replace the re-raisewith a per-process warning +
xp.nanreturn. The "module is missing"case is structurally identical to the "user kwarg is missing" case —
both mean the latent value is unknown, and the right behaviour is
return-NaN-and-warn, not kill the search.
Sketch (in
PyAutoGalaxy:autogalaxy/operate/lens_calc.pyor whereverthe latent function lives —
PyAutoLens:autolens/analysis/latent.py:225calls into this):
einstein_radius_jit_from(and any sibling that does the sametry/except ModuleNotFoundError → re-raisepattern) becomes:Test plan
test_autolens/analysis/test_latent.py's magzerocases): mock
jax_zero_contourimport to raise, assert the latentreturns NaN and emits one warning per process.
jax_zero_contourinstalled: constructAnalysisImaging(..., use_jax=True)with defaultconfig/latent.yaml, runaf.Nautilus(...).fit(...)inPYAUTO_TEST_MODE=1. Confirm search completes, JSON write happens,latent.csvhas NaN values for the affected columns.autolens_profiling/searches/nautilus/imaging/mge.pyon a clean venv (no
jax_zero_contour) and confirm no crash.Affected callers / interaction surface
jax_zero_contour— the HPCPyAutoNSSvenv on Euclid SAAS inparticular, where
jax_zero_contourwas not installed at venvcreation time (added manually post-hoc to unblock job 322552).
PyAutoGalaxy:autogalaxy/operate/lens_calc.pythat do the same
try / except ModuleNotFoundError → raisepattern:grep for
raise ModuleNotFoundErrorin lens_calc.py and adjacentmodules; any that gate optional-dep imports for default-on latents
should switch to the soft-fail pattern.
Why not just add
jax_zero_contouras a hard dep?That's the pragmatic short-term fix (it's what unblocked job 322552
on the HPC). But it makes a JAX-only utility a required dep of every
PyAutoGalaxy install, including non-JAX numpy-only users — wider blast
radius than this fix needs. Soft-fail keeps the optional dep optional
while making default configs runnable.
Out of scope
jax_zero_contourto a hard dep (alternative; not thepreferred fix).
the latent-computation surface where default configs hit them.
covered; this is a parallel-but-independent surface.
Cross-references
pattern this prompt proposes for optional deps.
the upstream prompt for fix: Sph profile inverse transform name-check asymmetry #557.
completed (64,200 evals, log_Z=+31690.49) but post-fit latent
crashed before metric JSON write.