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
18 changes: 17 additions & 1 deletion autofit/non_linear/mock/mock_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ def samples_with_log_likelihood_list(log_likelihood_list):
]


def prior_median_kwargs(model):
"""
Placeholder sample values for a model, taken as the median of each prior.

A blanket value like ``1.0`` is invalid for any parameter whose prior
constrains it, so filling a mock sample with one builds an unphysical
instance. For example an elliptical profile's ``ell_comps`` must have a
magnitude below 1, and ``(1.0, 1.0)`` raises on construction. Prior medians
are always in range, so they are safe for every model.
"""
if not model:
return {}

return {path: prior.value_for(0.5) for path, prior in model.path_priors_tuples}


class MockSamples(SamplesPDF):
def __init__(
self,
Expand Down Expand Up @@ -40,7 +56,7 @@ def default_sample_list(self):
log_likelihood=log_likelihood,
log_prior=0.0,
weight=0.0,
kwargs={path: 1.0 for path in self.model.paths} if self.model else {},
kwargs=prior_median_kwargs(self.model),
)
for log_likelihood in range(3)
]
Expand Down
3 changes: 2 additions & 1 deletion autofit/non_linear/mock/mock_samples_summary.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from autofit.mapper.prior_model.collection import Collection
from autofit.non_linear.mock.mock_samples import prior_median_kwargs
from autofit.non_linear.samples.sample import Sample
from autofit.non_linear.samples.summary import SamplesSummary

Expand All @@ -23,7 +24,7 @@ def __init__(

self._max_log_likelihood_instance = max_log_likelihood_instance
self._prior_means = prior_means
self._kwargs = {path: 1.0 for path in self.model.paths} if self.model else {}
self._kwargs = prior_median_kwargs(self.model)

@property
def max_log_likelihood_sample(self):
Expand Down
4 changes: 2 additions & 2 deletions autofit/non_linear/mock/mock_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from autofit import exc
from autofit.graphical import FactorApproximation
from autofit.graphical.utils import Status
from autofit.non_linear.mock.mock_samples import MockSamples
from autofit.non_linear.mock.mock_samples import MockSamples, prior_median_kwargs
from autofit.non_linear.search.abstract_search import NonLinearSearch
from autofit.non_linear.mock.mock_result import MockResult
from autofit.non_linear.mock.mock_samples_summary import MockSamplesSummary
Expand All @@ -25,7 +25,7 @@ def samples_with_log_likelihood_list(log_likelihood_list, kwargs):


def _make_samples(model):
return {path: prior.value_for(0.5) for path, prior in model.path_priors_tuples}
return prior_median_kwargs(model)


class MockSearch(NonLinearSearch):
Expand Down
63 changes: 63 additions & 0 deletions test_autofit/non_linear/samples/test_mock_placeholders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pytest

import autofit as af


class _RejectsUnitMagnitude:
"""
Mirrors an elliptical profile's ``ell_comps`` guard: the pair is only
meaningful while its magnitude is below 1.
"""

def __init__(self, ell_comps=(0.0, 0.0)):
if ell_comps[0] ** 2 + ell_comps[1] ** 2 >= 1.0:
raise ValueError(f"ell_comps magnitude is not below 1: {ell_comps}")

self.ell_comps = ell_comps


def _guarded_model():
model = af.Model(_RejectsUnitMagnitude)
model.ell_comps.ell_comps_0 = af.UniformPrior(lower_limit=0.0, upper_limit=0.5)
model.ell_comps.ell_comps_1 = af.UniformPrior(lower_limit=0.0, upper_limit=0.5)
return model


def test__mock_samples_summary__fills_with_prior_medians():
summary = af.m.MockSamplesSummary(model=_guarded_model())

assert list(summary.max_log_likelihood_sample.kwargs.values()) == [0.25, 0.25]
assert list(summary.median_pdf_sample.kwargs.values()) == [0.25, 0.25]


def test__mock_samples__default_sample_list_fills_with_prior_medians():
samples = af.m.MockSamples(model=_guarded_model())

for sample in samples.sample_list:
assert list(sample.kwargs.values()) == [0.25, 0.25]


def test__mock_result_without_samples_summary__builds_a_valid_instance():
"""
A ``MockResult`` given a model but no ``samples_summary`` builds its own.
That placeholder used to fill every parameter with ``1.0``, which is an
invalid ``ell_comps`` — so reading ``.instance`` raised.
"""
instance = af.m.MockResult(model=_guarded_model()).instance

assert instance.ell_comps == (0.25, 0.25)


def test__all_ones_placeholder_would_be_rejected():
"""
Guards the test above: confirms the model really does reject the old
blanket ``1.0`` fill, so it cannot silently stop testing anything.
"""
model = _guarded_model()

assert model.instance_from_prior_medians().ell_comps == (0.25, 0.25)

with pytest.raises(ValueError, match="magnitude is not below 1"):
model.instance_for_arguments(
{prior: 1.0 for prior in model.priors_ordered_by_id}
)
Loading