From 3d765c8aa04f18bd9a0a455df054afc60d0dcf23 Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 20 May 2026 14:45:21 +0100 Subject: [PATCH 1/4] ENH: raise an error if constraint key is missing from samples --- bilby/core/prior/dict.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bilby/core/prior/dict.py b/bilby/core/prior/dict.py index 3ac54622e..c3541553d 100644 --- a/bilby/core/prior/dict.py +++ b/bilby/core/prior/dict.py @@ -55,13 +55,29 @@ def __init__(self, dictionary=None, filename=None, conversion_function=None): self.conversion_function = self.default_conversion_function def evaluate_constraints(self, sample): + """Evaluate the constraints for a given sample. + + Applies the conversion function to the sample and evaluates the + constraints on the converted sample. + + Raises + ====== + ValueError: + If a constraint parameter is not present in the sample after + conversion. + """ out_sample = self.conversion_function(sample) try: prob = np.ones_like(next(iter(out_sample.values()))) except TypeError: prob = np.ones_like(out_sample) for key in self: - if isinstance(self[key], Constraint) and key in out_sample: + if isinstance(self[key], Constraint): + if key not in out_sample: + raise ValueError( + f"Constraint {key} is not present in the sample. " + "Cannot evaluate constraints." + ) prob *= self[key].prob(out_sample[key]) return prob From 4f6eab6031ae79521eade4d8265909bce9f97cf7 Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 20 May 2026 14:46:03 +0100 Subject: [PATCH 2/4] MAINT: remove unreachable if statement --- bilby/core/prior/dict.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bilby/core/prior/dict.py b/bilby/core/prior/dict.py index c3541553d..050ee7a01 100644 --- a/bilby/core/prior/dict.py +++ b/bilby/core/prior/dict.py @@ -527,9 +527,6 @@ def normalize_constraint_factor( def _estimate_normalization(self, keys, min_accept, sampling_chunk): samples = self.sample_subset(keys=keys, size=sampling_chunk) keep = np.atleast_1d(self.evaluate_constraints(samples)) - if len(keep) == 1: - self._cached_normalizations[keys] = 1 - return 1 all_samples = {key: np.array([]) for key in keys} while np.count_nonzero(keep) < min_accept: samples = self.sample_subset(keys=keys, size=sampling_chunk) From 2f973eb01874875c7337dbcd40bf67a18af938fe Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 20 May 2026 18:48:04 +0100 Subject: [PATCH 3/4] TST: add tests for evaluate constraints --- test/core/prior/dict_test.py | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/core/prior/dict_test.py b/test/core/prior/dict_test.py index 089611aee..f1841cb2a 100644 --- a/test/core/prior/dict_test.py +++ b/test/core/prior/dict_test.py @@ -392,6 +392,62 @@ def test_redundancy(self): for key in self.prior_set_from_dict.keys(): self.assertFalse(self.prior_set_from_dict.test_redundancy(key=key)) + def test_evaluate_constraints(self): + + def conversion_function(parameters): + converted_parameters = parameters.copy() + converted_parameters["delta_mass"] = ( + parameters["mass_1"] - parameters["mass_2"] + ) + return converted_parameters + + priors = bilby.core.prior.PriorDict(conversion_function=conversion_function) + priors["mass_1"] = bilby.core.prior.Uniform(minimum=1.38, maximum=2) + priors["mass_2"] = bilby.core.prior.Uniform(minimum=1, maximum=1.4) + priors["delta_mass"] = bilby.core.prior.Constraint(minimum=0.4, maximum=1.4) + + theta = {"mass_1": 1.7, "mass_2": 1.2} + self.assertTrue(priors.evaluate_constraints(theta)) + + theta = {"mass_1": 1.5, "mass_2": 1.2} + self.assertFalse(priors.evaluate_constraints(theta)) + + def test_evaluate_constraints_batches(self): + + def conversion_function(parameters): + converted_parameters = parameters.copy() + converted_parameters["delta_mass"] = ( + parameters["mass_1"] - parameters["mass_2"] + ) + return converted_parameters + + priors = bilby.core.prior.PriorDict(conversion_function=conversion_function) + priors["mass_1"] = bilby.core.prior.Uniform(minimum=1.38, maximum=2) + priors["mass_2"] = bilby.core.prior.Uniform(minimum=1, maximum=1.4) + priors["delta_mass"] = bilby.core.prior.Constraint(minimum=0.4, maximum=1.4) + + theta = {"mass_1": np.array([1.7, 1.5]), "mass_2": np.array([1.2, 1.2])} + expected = np.array([True, False]) + self.assertTrue(np.array_equal(expected, priors.evaluate_constraints(theta))) + + def test_evaluate_constraints_missing_keys(self): + + def conversion_function(parameters): + return parameters.copy() + + priors = bilby.core.prior.PriorDict(conversion_function=conversion_function) + priors["mass_1"] = bilby.core.prior.Uniform(minimum=1.38, maximum=2) + priors["mass_2"] = bilby.core.prior.Uniform(minimum=1, maximum=1.4) + priors["delta_mass"] = bilby.core.prior.Constraint(minimum=0.4, maximum=1.4) + + theta = {"mass_1": 1.5, "mass_2": 1.2} + + with self.assertRaises( + ValueError, + msg="Constraint delta_mass is not present in the sample. Cannot evaluate constraints." + ): + priors.evaluate_constraints(theta) + class TestJsonIO(unittest.TestCase): def setUp(self): From d0a2ebfe127b39478a09c8514d42dbb082a630fe Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 20 May 2026 19:19:33 +0100 Subject: [PATCH 4/4] TST: add test for prior normalization --- test/core/prior/dict_test.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/core/prior/dict_test.py b/test/core/prior/dict_test.py index f1841cb2a..6aa4d1a00 100644 --- a/test/core/prior/dict_test.py +++ b/test/core/prior/dict_test.py @@ -448,6 +448,23 @@ def conversion_function(parameters): ): priors.evaluate_constraints(theta) + def test_normalize_constraint_keys(self): + + def conversion_function(parameters): + converted_parameters = parameters.copy() + converted_parameters["mass_ratio"] = parameters["mass_2"] / parameters["mass_1"] + return converted_parameters + + priors = bilby.core.prior.PriorDict(conversion_function=conversion_function) + priors["mass_1"] = bilby.core.prior.Uniform(minimum=1, maximum=2) + priors["mass_2"] = bilby.core.prior.Uniform(minimum=1, maximum=2) + priors["mass_ratio"] = bilby.core.prior.Constraint(minimum=0.0, maximum=1.0) + + # Factor should close to 2 since half the prior volume is removed by the constraint + keys = ("mass_1", "mass_2") + factor = priors.normalize_constraint_factor(keys) + self.assertAlmostEqual(factor, 2.0, delta=0.01) + class TestJsonIO(unittest.TestCase): def setUp(self):