Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions autofit/non_linear/search/abstract_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,9 +872,24 @@ def _fit_bypass_test_mode(
log_likelihood = -1.0e99
if call_likelihood:
instance = model.instance_from_vector(vector=parameter_vector)
log_likelihood = float(
analysis.log_likelihood_function(instance)
)
try:
log_likelihood = float(
analysis.log_likelihood_function(instance)
)
except exc.FitException as e:
# A `FitException` means this particular instance is pathological
# (e.g. a non-positive-definite inversion, or a degenerate mesh
# that yields NaN vertices). In a real search the sampler absorbs
# this by resampling; test mode has no sampler, so a single
# unlucky verification eval must not hard-fail the run. Keep the
# `-1.0e99` sentinel — the same effect a resample-to-reject has —
# and log the cause so a genuinely broken likelihood stays visible.
# Only `FitException` is caught: real code errors still propagate.
logger.warning(
"TEST MODE 2: likelihood verification raised FitException "
f"({e.__cause__ or e!r}); treating as a resample-rejected "
"instance and continuing with the sentinel log likelihood."
)

sample_list = self._build_fake_samples(
model=model,
Expand Down
53 changes: 53 additions & 0 deletions test_autofit/non_linear/search/test_abstract_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,3 +420,56 @@ def _poison(*args, **kwargs):
)

assert result is not None


class _FitExceptionAnalysis(af.m.MockAnalysis):
"""Analysis whose likelihood always raises `FitException`, mimicking a
pathological instance (non-PD inversion / NaN Delaunay mesh) that a real
sampler would resample away."""

def log_likelihood_function(self, instance):
raise af.exc.FitException("pathological instance (test)")


class TestBypassToleratesFitException:
"""
In test mode 2 the bypass calls the likelihood once to verify it works. A
single unlucky instance raising `FitException` (a resample signal a real
sampler absorbs) must NOT hard-fail the run — otherwise flaky pixelization
scripts intermittently break CI smoke (autolens_workspace#307, PyAutoLens#640).
Only `FitException` is tolerated; genuine errors still propagate.
"""

def test__test_mode_2__fitexception_tolerated__fit_completes_with_sentinel(
self, monkeypatch
):
monkeypatch.setenv("PYAUTO_TEST_MODE", "2")

search = af.DynestyStatic(
name="bypass_fitexception", unique_tag="bypass_fitexception_test"
)

result = search.fit(
model=af.Model(af.m.MockClassx2), analysis=_FitExceptionAnalysis()
)

assert result is not None
# The failed verification eval falls back to the -1.0e99 sentinel, the
# same effect as a resample-to-reject.
assert float(result.samples.max_log_likelihood_sample.log_likelihood) == pytest.approx(
-1.0e99
)

def test__test_mode_2__non_fitexception_still_propagates(self, monkeypatch):
monkeypatch.setenv("PYAUTO_TEST_MODE", "2")

class _BrokenAnalysis(af.m.MockAnalysis):
def log_likelihood_function(self, instance):
raise ValueError("a genuine bug, not a resample signal")

search = af.DynestyStatic(
name="bypass_broken", unique_tag="bypass_broken_test"
)

with pytest.raises(ValueError):
search.fit(model=af.Model(af.m.MockClassx2), analysis=_BrokenAnalysis())
Loading