Repository: rhayes777/PyAutoFit
Pull request #944 (2024-02-19 10:39:35)

ignore assertions flag passed recursively when creating instances
Pass the ignore_assertions flag to children recursively.

This likely solves issues observed with assertions and derived quantities



Patch:
diff --git a/autofit/mapper/model.py b/autofit/mapper/model.py
index 72ec97965..c6e6042a4 100644
--- a/autofit/mapper/model.py
+++ b/autofit/mapper/model.py
@@ -190,7 +190,7 @@ class AbstractModel(ModelObject):
     def path_instance_tuples_for_class(
         self,
         cls: Union[Tuple, Type],
-        ignore_class: bool = None,
+        ignore_class: Optional[type] = None,
         ignore_children: bool = True,
     ):
         """
diff --git a/autofit/mapper/prior/abstract.py b/autofit/mapper/prior/abstract.py
index b964fb2f1..35f80496e 100644
--- a/autofit/mapper/prior/abstract.py
+++ b/autofit/mapper/prior/abstract.py
@@ -168,7 +168,12 @@ class Prior(Variable, ABC, ArithmeticMixin):
             self.assert_within_limits(result)
         return result
 
-    def instance_for_arguments(self, arguments):
+    def instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        _ = ignore_assertions
         return arguments[self]
 
     def project(self, samples, weights):
diff --git a/autofit/mapper/prior/arithmetic/assertion.py b/autofit/mapper/prior/arithmetic/assertion.py
index f6b48da42..2e7315d2b 100644
--- a/autofit/mapper/prior/arithmetic/assertion.py
+++ b/autofit/mapper/prior/arithmetic/assertion.py
@@ -24,7 +24,7 @@ class ComparisonAssertion(CompoundPrior, Compound, ABC):
 
 
 class GreaterThanLessThanAssertion(ComparisonAssertion):
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(self, arguments, ignore_assertions=False):
         """
         Assert that the value in the dictionary associated with the lower
         prior is lower than the value associated with the greater prior.
@@ -39,13 +39,23 @@ class GreaterThanLessThanAssertion(ComparisonAssertion):
         FitException
             If the assertion is not met
         """
-        lower = self.left_for_arguments(arguments)
-        greater = self.right_for_arguments(arguments)
+        lower = self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
+        greater = self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
         return lower < greater
 
 
 class GreaterThanLessThanEqualAssertion(ComparisonAssertion):
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         """
         Assert that the value in the dictionary associated with the lower
         prior is lower than the value associated with the greater prior.
@@ -60,7 +70,13 @@ class GreaterThanLessThanEqualAssertion(ComparisonAssertion):
         FitException
             If the assertion is not met
         """
-        return self.left_for_arguments(arguments) <= self.right_for_arguments(arguments)
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) <= self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class CompoundAssertion(AbstractPriorModel, Compound):
@@ -70,11 +86,17 @@ class CompoundAssertion(AbstractPriorModel, Compound):
         self.assertion_2 = assertion_2
         self._name = name
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         return self.assertion_1.instance_for_arguments(
             arguments,
+            ignore_assertions,
         ) and self.assertion_2.instance_for_arguments(
             arguments,
+            ignore_assertions,
         )
 
     def dict(self) -> dict:
diff --git a/autofit/mapper/prior/arithmetic/compound.py b/autofit/mapper/prior/arithmetic/compound.py
index 1049db9ee..a0aa70a53 100644
--- a/autofit/mapper/prior/arithmetic/compound.py
+++ b/autofit/mapper/prior/arithmetic/compound.py
@@ -141,7 +141,11 @@ class CompoundPrior(AbstractPriorModel, ArithmeticMixin, Compound, ABC):
             pass
         return new
 
-    def left_for_arguments(self, arguments: dict):
+    def left_for_arguments(
+        self,
+        arguments: dict,
+        ignore_assertions=False,
+    ):
         """
         Instantiate the left object.
 
@@ -149,6 +153,8 @@ class CompoundPrior(AbstractPriorModel, ArithmeticMixin, Compound, ABC):
         ----------
         arguments
             A dictionary mapping priors to values
+        ignore_assertions
+            If True, ignore assertions
 
         Returns
         -------
@@ -157,11 +163,16 @@ class CompoundPrior(AbstractPriorModel, ArithmeticMixin, Compound, ABC):
         try:
             return self._left.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
         except AttributeError:
             return self._left
 
-    def right_for_arguments(self, arguments: dict):
+    def right_for_arguments(
+        self,
+        arguments: dict,
+        ignore_assertions=False,
+    ):
         """
         Instantiate the right object.
 
@@ -169,6 +180,8 @@ class CompoundPrior(AbstractPriorModel, ArithmeticMixin, Compound, ABC):
         ----------
         arguments
             A dictionary mapping priors to values
+        ignore_assertions
+            If True, ignore assertions
 
         Returns
         -------
@@ -177,6 +190,7 @@ class CompoundPrior(AbstractPriorModel, ArithmeticMixin, Compound, ABC):
         try:
             return self._right.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
         except AttributeError:
             return self._right
@@ -187,8 +201,18 @@ class SumPrior(CompoundPrior):
     The sum of two objects, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) + self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) + self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
     def __str__(self):
         return f"{self._left} + {self._right}"
@@ -202,8 +226,18 @@ class MultiplePrior(CompoundPrior):
     def __str__(self):
         return f"{self._left} * {self._right}"
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) * self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) * self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class DivisionPrior(CompoundPrior):
@@ -211,8 +245,18 @@ class DivisionPrior(CompoundPrior):
     One object divided by another, computed after realisation
     """
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) / self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) / self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class FloorDivPrior(CompoundPrior):
@@ -220,8 +264,18 @@ class FloorDivPrior(CompoundPrior):
     One object divided by another and floored, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) // self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) // self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class ModPrior(CompoundPrior):
@@ -229,8 +283,18 @@ class ModPrior(CompoundPrior):
     The modulus of a pair of objects, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) % self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) % self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class PowerPrior(CompoundPrior):
@@ -238,8 +302,18 @@ class PowerPrior(CompoundPrior):
     One object to the power of another, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
-        return self.left_for_arguments(arguments) ** self.right_for_arguments(arguments)
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
+        return self.left_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        ) ** self.right_for_arguments(
+            arguments,
+            ignore_assertions=ignore_assertions,
+        )
 
 
 class ModifiedPrior(AbstractPriorModel, ABC, ArithmeticMixin):
@@ -281,9 +355,14 @@ class NegativePrior(ModifiedPrior):
     The negation of an object, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         return -self.prior.instance_for_arguments(
             arguments,
+            ignore_assertions=ignore_assertions,
         )
 
 
@@ -292,10 +371,15 @@ class AbsolutePrior(ModifiedPrior):
     The absolute value of an object, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         return abs(
             self.prior.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
         )
 
@@ -305,10 +389,15 @@ class Log(ModifiedPrior):
     The natural logarithm of an object, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         return np.log(
             self.prior.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
         )
 
@@ -318,9 +407,14 @@ class Log10(ModifiedPrior):
     The base10 logarithm of an object, computed after realisation.
     """
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         return np.log10(
             self.prior.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
         )
diff --git a/autofit/mapper/prior_model/abstract.py b/autofit/mapper/prior_model/abstract.py
index d99467b19..c02a4ae7a 100644
--- a/autofit/mapper/prior_model/abstract.py
+++ b/autofit/mapper/prior_model/abstract.py
@@ -1290,6 +1290,7 @@ class AbstractPriorModel(AbstractModel):
     def _instance_for_arguments(
         self,
         arguments: Dict[Prior, float],
+        ignore_assertions: bool = False,
     ):
         raise NotImplementedError()
 
@@ -1320,6 +1321,7 @@ class AbstractPriorModel(AbstractModel):
         logger.debug(f"Creating an instance for arguments")
         instance = self._instance_for_arguments(
             arguments,
+            ignore_assertions=ignore_assertions,
         )
         for key, value in self.custom_derived_quantities.items():
             setattr(instance, key, value(instance))
diff --git a/autofit/mapper/prior_model/collection.py b/autofit/mapper/prior_model/collection.py
index 6bac48a76..20a2181fb 100644
--- a/autofit/mapper/prior_model/collection.py
+++ b/autofit/mapper/prior_model/collection.py
@@ -229,7 +229,11 @@ class Collection(AbstractPriorModel):
 
         return collection
 
-    def _instance_for_arguments(self, arguments):
+    def _instance_for_arguments(
+        self,
+        arguments,
+        ignore_assertions=False,
+    ):
         """
         Parameters
         ----------
@@ -246,7 +250,10 @@ class Collection(AbstractPriorModel):
             if key.startswith("_"):
                 continue
             if isinstance(value, AbstractPriorModel):
-                value = value.instance_for_arguments(arguments)
+                value = value.instance_for_arguments(
+                    arguments,
+                    ignore_assertions=ignore_assertions,
+                )
             elif isinstance(value, Prior):
                 value = arguments[value]
             setattr(result, key, value)
diff --git a/autofit/mapper/prior_model/prior_model.py b/autofit/mapper/prior_model/prior_model.py
index e92882b74..ddf3b4ee3 100644
--- a/autofit/mapper/prior_model/prior_model.py
+++ b/autofit/mapper/prior_model/prior_model.py
@@ -414,7 +414,11 @@ class Model(AbstractPriorModel):
         return len(self.direct_deferred_tuples) > 0
 
     # noinspection PyUnresolvedReferences
-    def _instance_for_arguments(self, arguments: {ModelObject: object}):
+    def _instance_for_arguments(
+        self,
+        arguments: {ModelObject: object},
+        ignore_assertions=False,
+    ):
         """
         Returns an instance of the associated class for a set of arguments
 
@@ -444,6 +448,7 @@ class Model(AbstractPriorModel):
                 prior_model_tuple.name
             ] = prior_model.instance_for_arguments(
                 arguments,
+                ignore_assertions=ignore_assertions,
             )
 
         prior_arguments = dict()
@@ -478,7 +483,10 @@ class Model(AbstractPriorModel):
                 and not key.startswith("_")
             ):
                 if isinstance(value, Model):
-                    value = value.instance_for_arguments(arguments)
+                    value = value.instance_for_arguments(
+                        arguments,
+                        ignore_assertions=ignore_assertions,
+                    )
                 elif isinstance(value, Prior):
                     value = arguments[value]
                 try:


Test patch:
diff --git a/test_autofit/non_linear/test_regression.py b/test_autofit/non_linear/test_regression.py
index c14222581..82591b64d 100644
--- a/test_autofit/non_linear/test_regression.py
+++ b/test_autofit/non_linear/test_regression.py
@@ -34,7 +34,8 @@ def test_serialize_grid_search(optimizer):
     assert loaded.logger is not None
 
 
-def test_skip_assertions():
+@pytest.fixture(name="model")
+def make_model():
     one = af.Model(af.Gaussian)
     two = af.Model(af.Gaussian)
     model = af.Collection(
@@ -42,7 +43,18 @@ def test_skip_assertions():
         two=two,
     )
     model.add_assertion(one.centre < two.centre)
+    return model
 
+
+def test_skip_assertions(model):
+    with pytest.raises(exc.FitException):
+        model.instance_from_prior_medians()
+
+    model.instance_from_prior_medians(ignore_prior_limits=True)
+
+
+def test_recursive_skip_assertions(model):
+    model = af.Collection(model=model)
     with pytest.raises(exc.FitException):
         model.instance_from_prior_medians()