Repository: rhayes777/PyAutoFit
Pull request #922 (2024-02-05 11:13:52)

feature/custom derived quantities
Adds the ability to define custom derived quantities as an argument of during model creation.

```python
model = af.Model(
    af.Gaussian,
    custom_derived_quantities={
        "custom": lambda instance: 2 * instance.centre,
    },
}
```

These quantities are computed at instantiation and associated with the instance. They appear in the output in the same way as ordinary derived quantities but as yet are not efficiently loaded in the same way



Patch:
diff --git a/autofit/database/model/prior.py b/autofit/database/model/prior.py
index 2be4d958a..665366fc3 100644
--- a/autofit/database/model/prior.py
+++ b/autofit/database/model/prior.py
@@ -69,6 +69,8 @@ class Model(Object):
         instance = object.__new__(prior_model.Model)
         instance.cls = self.cls
         instance._assertions = []
+
+        instance._custom_derived_quantities = {}
         return instance
 
 
diff --git a/autofit/mapper/prior_model/abstract.py b/autofit/mapper/prior_model/abstract.py
index 65678cf19..d99467b19 100644
--- a/autofit/mapper/prior_model/abstract.py
+++ b/autofit/mapper/prior_model/abstract.py
@@ -1,1898 +1,1906 @@
-import copy
-import inspect
-import json
-import logging
-import random
-import types
-from collections import defaultdict
-from typing import Tuple, Optional, Dict, List, Iterable, Generator, Union, Type
-
-import numpy as np
-
-from autoconf import conf
-from autoconf.exc import ConfigException
-from autofit import exc
-from autofit.mapper import model
-from autofit.mapper.model import AbstractModel, frozen_cache
-from autofit.mapper.prior import GaussianPrior
-from autofit.mapper.prior import UniformPrior
-from autofit.mapper.prior.abstract import Prior
-from autofit.mapper.prior.deferred import DeferredArgument
-from autofit.mapper.prior.tuple_prior import TuplePrior
-from autofit.mapper.prior.width_modifier import WidthModifier
-from autofit.mapper.prior_model.attribute_pair import DeferredNameValue
-from autofit.mapper.prior_model.attribute_pair import (
-    cast_collection,
-    PriorNameValue,
-    InstanceNameValue,
-)
-from autofit.mapper.prior_model.recursion import DynamicRecursionCache
-from autofit.mapper.prior_model.util import PriorModelNameValue
-from autofit.text import formatter as frm
-from autofit.text.formatter import TextFormatter
-from autofit.tools.util import info_whitespace
-from autofit.tools.util import split_paths
-
-logger = logging.getLogger(__name__)
-
-
-class Limits:
-    @staticmethod
-    def for_class_and_attributes_name(cls, attribute_name):
-        limit_dict = conf.instance.prior_config.for_class_and_suffix_path(
-            cls, [attribute_name, "gaussian_limits"]
-        )
-        return limit_dict["lower"], limit_dict["upper"]
-
-
-class TuplePathModifier:
-    def __init__(self, model_: "AbstractPriorModel"):
-        """
-        Modifies paths to priors contained in tuples.
-
-        When a tuple is found in a signature a PriorTuple is created.
-
-        The true path to a variable in the tuple is
-        ("some", "preamble", "tuple_name", "tuple_name_0")
-
-        Where 0 is the index of the tuple member. "tuple_name" must be
-        removed from this path for some uses.
-
-        When called, instances of this class remove the name of the tuple
-        i.e:
-        -> ("some", "preamble", "tuple_name_0")
-
-        Parameters
-        ----------
-        model_
-        """
-        tuple_priors = model_.path_instance_tuples_for_class(TuplePrior)
-        try:
-            self.tuple_paths, _ = zip(*tuple_priors)
-        except ValueError:
-            self.tuple_paths = None
-
-    def __call__(self, path):
-        if self.tuple_paths is not None:
-            if path[:-1] in self.tuple_paths:
-                return path[:-2] + (path[-1],)
-        return path
-
-
-Path = Tuple[str, ...]
-
-
-class MeanField:
-    def __init__(self, prior_model: "AbstractPriorModel"):
-        """
-        Implements same interface as graphical code
-
-        Parameters
-        ----------
-        prior_model
-        """
-        self.prior_model = prior_model
-
-    def __getitem__(self, item):
-        """
-        Retrieve a prior by a prior with the same id
-        """
-        for prior in self.prior_model.priors:
-            if prior == item:
-                return prior
-        raise KeyError(f"Could not find {item} in model")
-
-
-def paths_to_tree(paths: List[Tuple[str, ...]], tree: Optional[dict] = None) -> dict:
-    """
-    Recursively convert a list of paths to a tree structure where common paths
-    are matched.
-
-    Parameters
-    ----------
-    paths
-        A list of paths to attributes in the model.
-    tree
-        A tree already embedded in a parent tree.
-
-    Returns
-    -------
-    A tree with depth max(map(len, paths))
-
-    Examples
-    --------
-    paths_to_tree([
-        ("one", "two", "three"),
-        ("one", "two", "four"),
-    ])
-
-    gives
-
-    {
-        "one": {
-            "two": {
-                "three": {},
-                "four": {}
-            }
-        }
-    }
-    """
-    tree = tree or dict()
-    for path in paths:
-        if len(path) == 0:
-            return tree
-        first, *rest = path
-        if first not in tree:
-            child = dict()
-            tree[first] = child
-        tree[first] = paths_to_tree([rest], tree=tree[first])
-    return tree
-
-
-class AbstractPriorModel(AbstractModel):
-    """
-    Abstract model that maps a set of priors to a particular class. Must be
-    overridden by any prior model so that the model mapper recognises its prior \
-    model attributes.
-    @DynamicAttrs
-    """
-
-    def __init__(self, label=None):
-        super().__init__(label=label)
-        self._assertions = list()
-
-    @property
-    def assertions(self):
-        return self._assertions
-
-    @assertions.setter
-    def assertions(self, assertions):
-        self._assertions = assertions
-
-    def check_assertions(self, arguments: Dict[Prior, float]):
-        """
-        Check that all assertions are satisfied by the given arguments.
-
-        Parameters
-        ----------
-        arguments
-            A dictionary mapping priors to values
-
-        Raises
-        ------
-        FitException
-            If any assertion is not satisfied
-        """
-        failed_assertions = [
-            assertion
-            for assertion in self._assertions
-            if assertion is False
-            or assertion is not True
-            and not assertion.instance_for_arguments(
-                arguments,
-            )
-        ]
-        number_of_failed_assertions = len(failed_assertions)
-        if number_of_failed_assertions > 0:
-            name_string = "\n".join(
-                [
-                    assertion.name
-                    for assertion in failed_assertions
-                    if hasattr(assertion, "name") and assertion.name is not None
-                ]
-            )
-            raise exc.FitException(
-                f"{number_of_failed_assertions} assertions failed!\n{name_string}"
-            )
-
-    @property
-    def derived_quantities(self) -> List[Tuple[str, ...]]:
-        """
-        The paths to attributes of the model which are derived (i.e.
-        methods marked with the derived_quantity decorator)
-        """
-        return [
-            (name,) + derived_quantity
-            for name, prior_model in self.direct_prior_model_tuples
-            for derived_quantity in prior_model.derived_quantities
-        ]
-
-    def cast(
-        self,
-        value_dict: Dict["AbstractModel", dict],
-        new_class: type,
-    ) -> "AbstractPriorModel":
-        """
-        Cast models to a new type. Allows selected models in within this
-        model to be given a new type and new arguments.
-
-        Parameters
-        ----------
-        value_dict
-            A dictionary mapping models to dictionaries of argument overrides
-        new_class
-            A new class to which specified models should be converted
-
-        Returns
-        -------
-        A model where specified child models have been updated to a new class
-        and new arguments
-        """
-        from .prior_model import Model
-
-        updated = self
-
-        for path, prior_model in self.path_instance_tuples_for_class(Model):
-            try:
-                model_value_dict = value_dict[prior_model]
-                argument_dict = {
-                    **dict(prior_model.direct_prior_tuples),
-                    **dict(prior_model.direct_tuples_with_type(float)),
-                    **model_value_dict,
-                }
-                updated = updated.replacing_for_path(
-                    path, Model(new_class, **argument_dict)
-                )
-
-            except KeyError:
-                pass
-
-        return updated
-
-    def without_attributes(self) -> "AbstractModel":
-        """
-        Returns a copy of this object with all priors, prior models and
-        constants removed.
-        """
-        without_attributes = copy.copy(self)
-        for key in self.__dict__:
-            if not (key.startswith("_") or key in ("cls", "id")):
-                delattr(without_attributes, key)
-        return without_attributes
-
-    def _with_paths(self, tree: dict) -> "AbstractModel":
-        """
-        Recursively generate a copy of this model retaining only objects
-        specified by the tree.
-
-        Parameters
-        ----------
-        tree
-            A tree formed of dictionaries describing which components of the
-            model should be retained.
-
-        Returns
-        -------
-        A copy of this model with a subset of attributes
-        """
-        if len(tree) == 0:
-            return self
-
-        with_paths = self.without_attributes()
-        for name, subtree in tree.items():
-            # noinspection PyProtectedMember
-            new_value = getattr(self, name)
-            if isinstance(
-                new_value,
-                (
-                    AbstractPriorModel,
-                    TuplePrior,
-                ),
-            ):
-                new_value = new_value._with_paths(subtree)
-            setattr(with_paths, name, new_value)
-        return with_paths
-
-    @split_paths
-    def with_paths(self, paths: List[Tuple[str, ...]]) -> "AbstractModel":
-        """
-        Recursively generate a copy of this model retaining only objects
-        specified by the list of paths.
-
-        Parameters
-        ----------
-        paths
-            A list of tuples of strings each of which points to a retained attribute.
-            All children of a given path are retained.
-
-        Returns
-        -------
-        A copy of this model with a subset of attributes
-        """
-        return self._with_paths(paths_to_tree(paths))
-
-    def _without_paths(self, tree: dict) -> "AbstractModel":
-        """
-        Recursively generate a copy of this model removing objects
-        specified by the tree.
-
-        Parameters
-        ----------
-        tree
-            A tree formed of dictionaries describing which components of the
-            model should be removed.
-
-        Returns
-        -------
-        A copy of this model with a subset of attributes
-        """
-        without_paths = copy.deepcopy(self)
-        for name, subtree in tree.items():
-            # noinspection PyProtectedMember
-            if len(subtree) == 0:
-                delattr(without_paths, name)
-            else:
-                new_value = getattr(without_paths, name)
-                if isinstance(
-                    new_value,
-                    (
-                        AbstractPriorModel,
-                        TuplePrior,
-                    ),
-                ):
-                    new_value = new_value._without_paths(subtree)
-                setattr(without_paths, name, new_value)
-        return without_paths
-
-    @split_paths
-    def without_paths(self, paths: List[Tuple[str, ...]]) -> "AbstractModel":
-        """
-        Recursively generate a copy of this model retaining only objects
-        not specified by the list of paths.
-
-        Parameters
-        ----------
-        paths
-            A list of tuples of strings each of which points to removed attribute.
-            All children of a given path are removed.
-
-        Returns
-        -------
-        A copy of this model with a subset of attributes
-        """
-        return self._without_paths(paths_to_tree(paths))
-
-    def index(self, path: Tuple[str, ...]) -> int:
-        """
-        Retrieve the index of a given path in the model
-        """
-        return self.paths.index(path)
-
-    @property
-    def mean_field(self) -> MeanField:
-        """
-        Implements the same interface as the graphical code
-        """
-        return MeanField(self)
-
-    @classmethod
-    def from_json(cls, file: str):
-        """
-        Loads the model from a .json file, which was written using the model's dictionary (`dict`) attribute as
-        follows:
-
-        import json
-
-        with open(filename, "w+") as f:
-            json.dump(model.dict, f, indent=4)
-
-        Parameters
-        ----------
-        file
-            The path and name of the .json file the model is loaded from.
-
-        Returns
-        -------
-        object
-            The model, which may be a `Collection` of `Model` objects or a single `Model`.
-        """
-
-        with open(file) as json_file:
-            model_dict = json.load(json_file)
-
-        return cls.from_dict(d=model_dict)
-
-    def add_assertion(self, assertion, name=""):
-        """
-        Assert that some relationship holds between physical values associated with
-        priors at the point an instance is created. If this fails a FitException is
-        raised causing the model to be re-sampled.
-        Parameters
-        ----------
-        assertion
-            An assertion that one prior must be greater than another.
-        name
-            A name describing the assertion that is logged when it is violated.
-        """
-        if assertion is True:
-            return
-        try:
-            assertion.name = name
-        except AttributeError:
-            pass
-        self._assertions.append(assertion)
-
-    @property
-    def name(self):
-        return self.__class__.__name__
-
-    # noinspection PyUnusedLocal
-    @staticmethod
-    def from_object(t, *args, **kwargs):
-        if inspect.isclass(t):
-            from .prior_model import Model
-
-            obj = object.__new__(Model)
-            obj.__init__(t, **kwargs)
-        elif isinstance(t, list) or isinstance(t, dict):
-            from autofit.mapper.prior_model import collection
-
-            obj = object.__new__(collection.Collection)
-            obj.__init__(t)
-        else:
-            obj = t
-        return obj
-
-    def take_attributes(self, source: object):
-        """
-        Take all attributes with a matching path from the source prior model.
-
-        For example, if this prior model has a prior "one" and a matching prior
-        is found associated with the source model then that attribute is attached
-        to this model.
-
-        If no matching attribute is found nothing happens.
-
-        Parameters
-        ----------
-        source
-            An instance or prior model from a previous search from which attributes
-            are passed to this model.
-        """
-
-        def assert_no_assertions(obj):
-            if len(getattr(obj, "_assertions", [])) > 0:
-                raise AssertionError(
-                    "take_attributes cannot be called once assertions have been added to the target"
-                )
-
-        assert_no_assertions(self)
-
-        for path, _ in sum(
-            map(self.path_instance_tuples_for_class, (Prior, float, TuplePrior)), []
-        ):
-            try:
-                item = copy.copy(source)
-                if isinstance(item, dict):
-                    from autofit.mapper.prior_model.collection import Collection
-
-                    item = Collection(item)
-                for attribute in path:
-                    if isinstance(attribute, int):
-                        item = item[attribute]
-                    else:
-                        item = copy.copy(getattr(item, attribute))
-
-                target = self
-                for attribute in path[:-1]:
-                    if isinstance(attribute, int):
-                        target = target[attribute]
-                    else:
-                        target = getattr(target, attribute)
-                    assert_no_assertions(target)
-
-                attribute = path[-1]
-                if isinstance(attribute, int):
-                    target[attribute] = item
-                else:
-                    setattr(target, path[-1], item)
-            except AttributeError:
-                pass
-
-    def instance_from_unit_vector(self, unit_vector, ignore_prior_limits=False):
-        """
-        Returns a ModelInstance, which has an attribute and class instance corresponding
-        to every `Model` attributed to this instance.
-        This method takes as input a unit vector of parameter values, converting each to
-        physical values via their priors.
-        Parameters
-        ----------
-        ignore_prior_limits
-            If true then no exception is thrown if priors fall outside defined limits
-        unit_vector: [float]
-            A unit hypercube vector that is mapped to an instance of physical values via the priors.
-        Returns
-        -------
-        model_instance : autofit.mapper.model.ModelInstance
-            An object containing reconstructed model_mapper instances
-        Raises
-        ------
-        exc.FitException
-            If any assertion attached to this object returns False.
-        """
-        exception_tuples = self.attribute_tuples_with_type(ConfigException)
-        if len(exception_tuples) > 0:
-            for name, exception in exception_tuples:
-                logger.exception(f"Could not load {name} because:\n\n{exception}")
-            names = [name for name, _ in exception_tuples]
-            raise ConfigException(
-                f"No configuration was found for some attributes ({', '.join(names)})"
-            )
-
-        if self.prior_count != len(unit_vector):
-            raise AssertionError(
-                f"prior_count ({self.prior_count}) != len(unit_vector) {len(unit_vector)}"
-            )
-
-        arguments = dict(
-            map(
-                lambda prior_tuple, unit: (
-                    prior_tuple.prior,
-                    prior_tuple.prior.value_for(
-                        unit,
-                        ignore_prior_limits=ignore_prior_limits,
-                    ),
-                ),
-                self.prior_tuples_ordered_by_id,
-                unit_vector,
-            )
-        )
-
-        return self.instance_for_arguments(
-            arguments,
-            ignore_assertions=ignore_prior_limits,
-        )
-
-    @property
-    @cast_collection(PriorNameValue)
-    @frozen_cache
-    def unique_prior_tuples(self):
-        """
-        Returns
-        -------
-        prior_tuple_dict: [(Prior, PriorTuple)]
-            The set of all priors associated with this mapper
-        """
-        return list(
-            {
-                prior_tuple[1]: prior_tuple
-                for prior_tuple in self.attribute_tuples_with_type(Prior)
-            }.values()
-        )
-
-    @property
-    @cast_collection(PriorNameValue)
-    @frozen_cache
-    def prior_tuples_ordered_by_id(self):
-        """
-        Returns
-        -------
-        priors: [Prior]
-            An ordered list of unique priors associated with this mapper
-        """
-        return sorted(
-            list(self.unique_prior_tuples), key=lambda prior_tuple: prior_tuple.prior.id
-        )
-
-    @property
-    def priors_ordered_by_id(self):
-        return [prior for _, prior in self.prior_tuples_ordered_by_id]
-
-    def vector_from_unit_vector(self, unit_vector, ignore_prior_limits=False):
-        """
-        Parameters
-        ----------
-        unit_vector: [float]
-            A unit hypercube vector
-        ignore_prior_limits
-            Set to True to prevent an exception being raised if
-            the physical value of a prior is outside the allowable
-            limits
-
-        Returns
-        -------
-        values: [float]
-            A vector with values output by priors
-        """
-        return list(
-            map(
-                lambda prior_tuple, unit: prior_tuple.prior.value_for(
-                    unit, ignore_prior_limits=ignore_prior_limits
-                ),
-                self.prior_tuples_ordered_by_id,
-                unit_vector,
-            )
-        )
-
-    def random_unit_vector_within_limits(
-        self, lower_limit: float = 0.0, upper_limit: float = 1.0
-    ) -> List[float]:
-        """
-        Generate a random vector of unit values by drawing uniform random values between 0 and 1.
-
-        Returns
-        -------
-        unit_values
-            A list of unit values constructed by taking random values from each prior.
-        """
-        return [
-            random.uniform(
-                max(lower_limit, prior.lower_unit_limit),
-                min(upper_limit, prior.upper_unit_limit),
-            )
-            for prior in self.priors_ordered_by_id
-        ]
-
-    def random_vector_from_priors_within_limits(
-        self, lower_limit: float = 0.0, upper_limit: float = 1.0
-    ) -> List[float]:
-        """
-        Returns a random vector of physical values by drawing uniform random values between lower and upper limits
-        defined as unit values, using the model priors to map them from unit values to physical values.
-
-        This function guesses infinite unit vectors, until one is randomly sampled whose physical parameters are within
-        the model's phyiscal limits.
-
-        This is used for MCMC initialization, whereby the starting points of a walker(s) are confined to a restricted
-        range of prior space. In particular, it is used for generate the "ball" initialization of Emcee.
-
-        Parameters
-        ----------
-        lower_limit
-            The lower limit as a unit value within which unit values (which are converted to physical values via the
-            priors) are generated.
-        upper_limit
-            The upper limit as a unit value within which unit values (which are converted to physical values via the
-            priors) are generated.
-
-        Returns
-        -------
-        physical_values
-            A list of physical values constructed by taking the mean possible value from
-            each prior.
-        """
-        vector = []
-
-        for prior in self.priors_ordered_by_id:
-            vector.append(
-                prior.random(
-                    lower_limit=lower_limit,
-                    upper_limit=upper_limit,
-                )
-            )
-
-        return vector
-
-    def random_instance_from_priors_within_limits(
-        self, lower_limit: float = 0.0, upper_limit: float = 1.0
-    ):
-        """
-        Returns a random instance of physical values by drawing uniform random values between lower and upper limits
-        defined as unit values, using the model priors to map them from unit values to physical values.
-
-        This function guesses infinite unit vectors, until one is randomly sampled whose physical parameters are within
-        the model's phyiscal limits. The final unit vector is then mapped to an instance.
-
-        Parameters
-        ----------
-        lower_limit
-            The lower limit as a unit value within which unit values (which are converted to physical values via the
-            priors) are generated.
-        upper_limit
-            The upper limit as a unit value within which unit values (which are converted to physical values via the
-            priors) are generated.
-
-        Returns
-        -------
-        physical_values
-            A list of physical values constructed by taking the mean possible value from
-            each prior.
-        """
-
-        vector = self.random_vector_from_priors_within_limits(
-            lower_limit=lower_limit, upper_limit=upper_limit
-        )
-
-        return self.instance_from_vector(vector=vector)
-
-    @property
-    def random_vector_from_priors(self):
-        """Generate a random vector of physical values by drawing uniform random values between 0 and 1 and using
-        the model priors to map them from unit values to physical values.
-        Returns
-        -------
-        physical_values: [float]
-            A list of physical values constructed by taking random values from each prior.
-        """
-        return self.random_vector_from_priors_within_limits(
-            lower_limit=0.0, upper_limit=1.0
-        )
-
-    @property
-    def physical_values_from_prior_medians(self):
-        """
-        Returns
-        -------
-        physical_values: [float]
-            A list of physical values constructed by taking the mean possible value from
-            each prior.
-        """
-        return self.vector_from_unit_vector([0.5] * len(self.unique_prior_tuples))
-
-    def instance_from_vector(self, vector, ignore_prior_limits=False):
-        """
-        Returns a ModelInstance, which has an attribute and class instance corresponding
-        to every `Model` attributed to this instance.
-        This method takes as input a physical vector of parameter values, thus omitting
-        the use of priors.
-        Parameters
-        ----------
-        vector: [float]
-            A vector of physical parameter values that is mapped to an instance.
-        ignore_prior_limits
-            If True don't check that physical values are within expected limits.
-
-        Returns
-        -------
-        model_instance : autofit.mapper.model.ModelInstance
-            An object containing reconstructed model_mapper instances
-        """
-        if len(vector) != self.prior_count:
-            raise AssertionError(
-                f"Vector length {len(vector)} != prior count {self.prior_count}"
-            )
-        arguments = dict(
-            map(
-                lambda prior_tuple, physical_unit: (prior_tuple.prior, physical_unit),
-                self.prior_tuples_ordered_by_id,
-                vector,
-            )
-        )
-
-        if not ignore_prior_limits:
-            for prior, value in arguments.items():
-                prior.assert_within_limits(value)
-
-        return self.instance_for_arguments(
-            arguments,
-            ignore_assertions=ignore_prior_limits,
-        )
-
-    def has(self, cls: Union[Type, Tuple[Type, ...]]) -> bool:
-        """
-        Parameters
-        ----------
-        cls
-            The type to check for
-
-        Returns
-        -------
-        True iff this model contains an instance or model with the given
-        type recursively. Includes models which have zero priors.
-        """
-        return self.has_instance(cls) or self.has_model(
-            cls, include_zero_dimension=True
-        )
-
-    def has_instance(self, cls) -> bool:
-        """
-        True iff this model contains an instance of type
-        cls, recursively.
-        """
-        return len(self.attribute_tuples_with_type(cls)) > 0
-
-    def has_model(self, cls, include_zero_dimension=False) -> bool:
-        """
-        True iff this model contains a Model of type
-        cls, recursively.
-        """
-        return (
-            len(
-                self.model_tuples_with_type(
-                    cls,
-                    include_zero_dimension=include_zero_dimension,
-                )
-            )
-            > 0
-        )
-
-    def is_only_model(self, cls) -> bool:
-        """
-        True iff this model contains at least one Model
-        of type cls and contains no PriorModels that are not
-        of type cls, recursively.
-        """
-        from .prior_model import Model
-
-        cls_models = self.model_tuples_with_type(cls)
-        other_models = [
-            value
-            for _, value in self.attribute_tuples_with_type(Model)
-            if value.prior_count > 0
-        ]
-        return len(cls_models) > 0 and len(cls_models) == len(other_models)
-
-    def replacing(self, arguments):
-        return self.mapper_from_partial_prior_arguments(arguments)
-
-    @classmethod
-    def product(cls, models: Iterable["AbstractPriorModel"]) -> "AbstractPriorModel":
-        """
-        Combine multiple models with the same structure by replacing priors with
-        priors that contain a message which is the product of the messages of the
-        priors with the same path in each model.
-
-        Parameters
-        ----------
-        models
-            A list of models to be combined
-
-        Returns
-        -------
-        A model where each prior has a message which is the product of the messages
-        associated with that prior across the models.
-        """
-        first, *rest = models
-
-        arguments = dict()
-
-        for path, prior in first.path_priors_tuples:
-            for other in rest:
-                prior = prior.with_message(
-                    prior.message * other.object_for_path(path).message
-                )
-            arguments[prior] = prior
-        return first.mapper_from_prior_arguments(arguments)
-
-    def mapper_from_partial_prior_arguments(self, arguments):
-        """
-        Returns a new model mapper from a dictionary mapping existing priors to
-        new priors, keeping existing priors where no mapping is provided.
-        Parameters
-        ----------
-        arguments: {Prior: Prior}
-            A dictionary mapping priors to priors
-        Returns
-        -------
-        model_mapper: ModelMapper
-            A new model mapper with updated priors.
-        """
-        original_prior_dict = {prior: prior for prior in self.priors}
-        return self.mapper_from_prior_arguments({**original_prior_dict, **arguments})
-
-    def mapper_from_prior_arguments(self, arguments):
-        """
-        Returns a new model mapper from a dictionary mapping existing priors to
-        new priors.
-        Parameters
-        ----------
-        arguments: {Prior: Prior}
-            A dictionary mapping priors to priors
-        Returns
-        -------
-        model_mapper: ModelMapper
-            A new model mapper with updated priors.
-        """
-        logger.debug(f"Creating a new mapper from arguments")
-
-        return self.gaussian_prior_model_for_arguments(arguments)
-
-    def gaussian_prior_model_for_arguments(self, arguments):
-        raise NotImplementedError()
-
-    def mapper_from_gaussian_tuples(
-        self, tuples, a=None, r=None, use_errors=True, use_widths=True, no_limits=False
-    ):
-        """
-        The widths of the new priors are taken from the
-        width_config. The new gaussian priors must be provided in the same order as
-        the priors associated with model.
-        If a is not None then all priors are created with an absolute width of a.
-        If r is not None then all priors are created with a relative width of r.
-        Parameters
-        ----------
-        no_limits
-            If `True` generated priors have infinite limits
-        r
-            The relative width to be assigned to gaussian priors
-        a
-            print(tuples[i][1], width)
-            The absolute width to be assigned to gaussian priors
-        use_errors
-            If True, the passed errors of the model components estimated in a previous `NonLinearSearch` (computed
-            at the prior_passer.sigma value) are used to set the pass Gaussian Prior sigma value (if both width and
-            passed errors are used, the maximum of these two values are used).
-        use_widths
-            If True, the minimum prior widths specified in the prior configs of the model components are used to
-            set the passed Gaussian Prior sigma value (if both widths and passed errors are used, the maximum of
-            these two values are used).
-        tuples
-            A list of tuples each containing the mean and width of a prior
-        Returns
-        -------
-        mapper: ModelMapper
-            A new model mapper with all priors replaced by gaussian priors.
-        """
-
-        prior_tuples = self.prior_tuples_ordered_by_id
-        prior_class_dict = self.prior_class_dict
-        arguments = {}
-
-        for i, prior_tuple in enumerate(prior_tuples):
-            prior = prior_tuple.prior
-            cls = prior_class_dict[prior]
-            mean, sigma = tuples[i]
-
-            name = prior_tuple.name
-            # Use the name of the collection for configuration when a prior's name
-            # is just a number (i.e. its position in a collection)
-            if name.isdigit():
-                name = self.path_for_prior(prior_tuple.prior)[-2]
-
-            width_modifier = (
-                prior.width_modifier
-                or WidthModifier.for_class_and_attribute_name(cls, name)
-            )
-
-            if a is not None and r is not None:
-                raise exc.PriorException(
-                    "Width of new priors cannot be both relative and absolute."
-                )
-            if a is not None:
-                width = a
-            elif r is not None:
-                width = r * mean
-            else:
-                width = width_modifier(mean)
-
-            if no_limits:
-                limits = (float("-inf"), float("inf"))
-            else:
-                try:
-                    limits = Limits.for_class_and_attributes_name(cls, name)
-                except ConfigException:
-                    limits = prior.limits
-
-            if use_errors and not use_widths:
-                sigma = tuples[i][1]
-            elif not use_errors and use_widths:
-                sigma = width
-            elif use_errors and use_widths:
-                sigma = max(tuples[i][1], width)
-            else:
-                raise exc.PriorException(
-                    "use_passed_errors and use_widths are both False, meaning there is no "
-                    "way to pass priors to set up the new model's Gaussian Priors."
-                )
-
-            new_prior = GaussianPrior(mean, sigma, *limits)
-            new_prior.id = prior.id
-            new_prior.width_modifier = prior.width_modifier
-            arguments[prior] = new_prior
-
-        return self.mapper_from_prior_arguments(arguments)
-
-    def with_limits(self, limits: List[Tuple[float, float]]) -> "AbstractPriorModel":
-        """
-        Create a new instance of this model where each prior is updated to
-        lie between new limits.
-
-        Parameters
-        ----------
-        limits
-            A list of pairs of lower and upper limits, one for each prior.
-
-        Returns
-        -------
-        A new model with updated limits
-        """
-        return self.mapper_from_prior_arguments(
-            {
-                prior: prior.with_limits(*prior_limits)
-                for prior, prior_limits in zip(self.priors_ordered_by_id, limits)
-            }
-        )
-
-    def mapper_from_uniform_floats(self, floats, b):
-        """
-        The widths of the new priors are the `floats` value minus and plus the input bound `b`.
-
-        Parameters
-        ----------
-        floats
-            A list of floats each containing the centre of the new uniform priors.
-        b
-            The bound value which is subtracted from each float to calculate the `lower_limit` and `upper_limit`
-            of each uniform prior.
-
-        Returns
-        -------
-        mapper: ModelMapper
-            A new model mapper with all priors replaced by uniform priors.
-        """
-
-        prior_tuples = self.prior_tuples_ordered_by_id
-        arguments = {}
-
-        for i, prior_tuple in enumerate(prior_tuples):
-            prior = prior_tuple.prior
-
-            new_prior = UniformPrior(
-                lower_limit=floats[i] - b, upper_limit=floats[i] + b
-            )
-            new_prior.id = prior.id
-            arguments[prior] = new_prior
-
-        return self.mapper_from_prior_arguments(arguments)
-
-    def instance_from_prior_medians(self, ignore_prior_limits=False):
-        """
-        Returns a list of physical values from the median values of the priors.
-        Returns
-        -------
-        physical_values : [float]
-            A list of physical values
-        """
-        return self.instance_from_unit_vector(
-            unit_vector=[0.5] * self.prior_count,
-            ignore_prior_limits=ignore_prior_limits,
-        )
-
-    def log_prior_list_from_vector(
-        self,
-        vector: [float],
-    ):
-        """
-        Compute the log priors of every parameter in a vector, using the Prior of every parameter.
-        The log prior values are used by Emcee to map the log likelihood to the poserior of the model.
-        Parameters
-        ----------
-        vector : [float]
-            A vector of physical parameter values.
-        Returns
-        -------
-        log_prior_list : []
-            An list of the log prior value of every parameter.
-        """
-        return list(
-            map(
-                lambda prior_tuple, value: prior_tuple.prior.log_prior_from_value(
-                    value=value
-                ),
-                self.prior_tuples_ordered_by_id,
-                vector,
-            )
-        )
-
-    def random_instance(self, ignore_prior_limits=False):
-        """
-        Returns a random instance of the model.
-        """
-        logger.debug(f"Creating a random instance")
-        if ignore_prior_limits:
-            return self.instance_from_unit_vector(
-                unit_vector=[random.random() for _ in range(self.prior_count)],
-                ignore_prior_limits=ignore_prior_limits,
-            )
-        return self.instance_for_arguments(
-            {prior: prior.random() for prior in self.priors}
-        )
-
-    @staticmethod
-    @DynamicRecursionCache()
-    def from_instance(
-        instance,
-        model_classes: Union[type, Iterable[type]] = tuple(),
-        exclude_classes: Union[type, Iterable[type]] = tuple(),
-    ):
-        """
-        Recursively create a prior object model from an object model.
-
-        Parameters
-        ----------
-        model_classes
-            A tuple of classes that should be converted to a prior model
-        exclude_classes
-            A tuple of classes that should not be converted to a prior model
-        instance
-            A dictionary, list, class instance or model instance
-        Returns
-        -------
-        abstract_prior_model
-            A concrete child of an abstract prior model
-        """
-        from autofit.mapper.prior_model import collection
-
-        if isinstance(instance, exclude_classes):
-            return instance
-        if isinstance(instance, (Prior, AbstractPriorModel)):
-            return instance
-        elif isinstance(instance, list):
-            result = collection.Collection(
-                [
-                    AbstractPriorModel.from_instance(
-                        item,
-                        model_classes=model_classes,
-                        exclude_classes=exclude_classes,
-                    )
-                    for item in instance
-                ]
-            )
-        elif isinstance(instance, model.ModelInstance):
-            from autofit.mapper import model_mapper
-
-            result = model_mapper.ModelMapper()
-            for key, value in instance.dict.items():
-                setattr(
-                    result,
-                    key,
-                    AbstractPriorModel.from_instance(
-                        value,
-                        model_classes=model_classes,
-                        exclude_classes=exclude_classes,
-                    ),
-                )
-        elif isinstance(instance, dict):
-            result = collection.Collection(
-                {
-                    key: AbstractPriorModel.from_instance(
-                        value,
-                        model_classes=model_classes,
-                        exclude_classes=exclude_classes,
-                    )
-                    for key, value in instance.items()
-                }
-            )
-        elif isinstance(instance, (np.ndarray, types.FunctionType)):
-            return instance
-        else:
-            from .prior_model import Model
-
-            try:
-                result = Model(
-                    instance.__class__,
-                    **{
-                        key: AbstractPriorModel.from_instance(
-                            value,
-                            model_classes=model_classes,
-                            exclude_classes=exclude_classes,
-                        )
-                        for key, value in instance.__dict__.items()
-                        if key != "cls"
-                    },
-                )
-            except AttributeError:
-                return instance
-        if isinstance(instance, model_classes):
-            return result.as_model()
-        return result
-
-    def items(self):
-        return (
-            self.direct_prior_tuples
-            + self.direct_instance_tuples
-            + self.direct_prior_model_tuples
-            + self.direct_tuple_priors
-        )
-
-    @property
-    @cast_collection(PriorNameValue)
-    def direct_prior_tuples(self):
-        return self.direct_tuples_with_type(Prior)
-
-    @property
-    @cast_collection(InstanceNameValue)
-    def direct_instance_tuples(self):
-        return self.direct_tuples_with_type(float)
-
-    @property
-    @cast_collection(PriorModelNameValue)
-    def prior_model_tuples(self):
-        return self.direct_tuples_with_type(AbstractPriorModel)
-
-    @property
-    @cast_collection(PriorModelNameValue)
-    def direct_prior_model_tuples(self):
-        return self.direct_tuples_with_type(AbstractPriorModel)
-
-    @property
-    @cast_collection(PriorModelNameValue)
-    def direct_tuple_priors(self):
-        return self.direct_tuples_with_type(TuplePrior)
-
-    @property
-    @cast_collection(PriorNameValue)
-    def tuple_prior_tuples(self):
-        """
-        Returns
-        -------
-        tuple_prior_tuples: [(String, TuplePrior)]
-        """
-        return self.direct_tuples_with_type(TuplePrior)
-
-    @property
-    @cast_collection(PriorNameValue)
-    def direct_prior_tuples(self):
-        """
-        Returns
-        -------
-        direct_priors: [(String, Prior)]
-        """
-        return self.direct_tuples_with_type(Prior)
-
-    @property
-    @cast_collection(DeferredNameValue)
-    def direct_deferred_tuples(self):
-        return self.direct_tuples_with_type(DeferredArgument)
-
-    @property
-    @cast_collection(PriorNameValue)
-    def prior_tuples(self):
-        """
-        Returns
-        -------
-        priors: [(String, Prior))]
-        """
-        # noinspection PyUnresolvedReferences
-        return self.attribute_tuples_with_type(Prior, ignore_children=True)
-
-    def __eq__(self, other):
-        return (
-            isinstance(other, AbstractPriorModel)
-            and self.direct_prior_model_tuples == other.direct_prior_model_tuples
-        )
-
-    @property
-    @cast_collection(InstanceNameValue)
-    def instance_tuples(self):
-        """
-        Returns
-        -------
-        instances: [(String, instance)]
-        """
-        return self.attribute_tuples_with_type(float, ignore_class=Prior)
-
-    @property
-    def prior_class_dict(self):
-        from autofit.mapper.prior_model.annotation import AnnotationPriorModel
-
-        d = {prior[1]: self.cls for prior in self.prior_tuples}
-        for prior_model in self.prior_model_tuples:
-            if not isinstance(prior_model[1], AnnotationPriorModel):
-                d.update(prior_model[1].prior_class_dict)
-        return d
-
-    def _instance_for_arguments(
-        self,
-        arguments: Dict[Prior, float],
-    ):
-        raise NotImplementedError()
-
-    def instance_for_arguments(
-        self,
-        arguments: Dict[Prior, float],
-        ignore_assertions: bool = False,
-    ):
-        """
-        Returns an instance of the model for a set of arguments
-
-        Parameters
-        ----------
-        arguments
-            Dictionary mapping priors to attribute analysis_path and value pairs
-        ignore_assertions
-            If True, assertions will not be checked
-
-        Returns
-        -------
-            An instance of the class
-        """
-        if not (
-            conf.instance["general"]["test"]["exception_override"] or ignore_assertions
-        ):
-            self.check_assertions(arguments)
-
-        logger.debug(f"Creating an instance for arguments")
-        return self._instance_for_arguments(
-            arguments,
-        )
-
-    def path_for_name(self, name: str) -> Tuple[str, ...]:
-        """
-        Find the path to a prior in the model that matches
-        a given name.
-
-        For example, name_of_model_name_of_prior could match
-        (name_of_model, name_of_prior). Unfortunately it is
-        possible for ambiguity to occur. For example, another
-        valid path could be (name, of_model, name_of_prior)
-        in which case there is no way to determine which path
-        actually matches the name.
-
-        Parameters
-        ----------
-        name
-            A name for a prior where names of models and the
-            name of the prior have been joined by underscores.
-
-        Returns
-        -------
-        A path to that model
-
-        Raises
-        ------
-        AssertionError
-            Iff no matching path is found
-        """
-
-        def _explode_path(path_):
-            return tuple(string for part in path_ for string in part.split("_"))
-
-        exploded = tuple(name.split("_"))
-        for path, _ in self.path_priors_tuples:
-            exploded_path = _explode_path(path)
-            if exploded_path == exploded:
-                return path
-
-        for path, prior_tuple in self.path_instance_tuples_for_class(TuplePrior):
-            for name, prior in prior_tuple.prior_tuples:
-                total_path = path[:-1] + (name,)
-                exploded_path = _explode_path(total_path)
-                if exploded_path == exploded:
-                    return path + (name,)
-        raise AssertionError(f"No path was found matching {name}")
-
-    def instance_from_prior_name_arguments(
-        self, prior_name_arguments: Dict[str, float]
-    ):
-        """
-        Instantiate the model from the names of priors and
-        corresponding values.
-
-        Parameters
-        ----------
-        prior_name_arguments
-            The names of priors where names of models and the
-            name of the prior have been joined by underscores,
-            mapped to corresponding values.
-
-        Returns
-        -------
-        An instance of the model
-        """
-        return self.instance_from_path_arguments(
-            {
-                self.path_for_name(name): value
-                for name, value in prior_name_arguments.items()
-            }
-        )
-
-    def instance_from_path_arguments(self, path_arguments: Dict[Tuple[str], float]):
-        """
-        Create an instance from a dictionary mapping paths to tuples
-        to corresponding values.
-
-        Parameters
-        ----------
-        path_arguments
-            A dictionary mapping paths to priors to corresponding values.
-            Note that, for linked priors, each path only needs to be
-            specified once. If multiple paths for the same prior are
-            specified then the value for the last path will be used.
-
-        Returns
-        -------
-        An instance of the model
-        """
-        arguments = {
-            self.object_for_path(path): value for path, value in path_arguments.items()
-        }
-        return self._instance_for_arguments(arguments)
-
-    @property
-    def prior_count(self) -> int:
-        """
-        How many unique priors does this model contain?
-        """
-        return len(self.unique_prior_tuples)
-
-    @property
-    def total_free_parameters(self) -> int:
-        """
-        Returns the prior count, but with a name that is more easy to interpret for users.
-        """
-        return self.prior_count
-
-    @property
-    def priors(self):
-        return [prior_tuple.prior for prior_tuple in self.prior_tuples]
-
-    @property
-    def _prior_id_map(self):
-        return {prior.id: prior for prior in self.priors}
-
-    def prior_with_id(self, prior_id):
-        return self._prior_id_map[prior_id]
-
-    def name_for_prior(self, prior):
-        for prior_model_name, prior_model in self.direct_prior_model_tuples:
-            prior_name = prior_model.name_for_prior(prior)
-            if prior_name is not None:
-                return "{}_{}".format(prior_model_name, prior_name)
-        prior_tuples = self.prior_tuples
-        for name, p in prior_tuples:
-            if p == prior:
-                return name
-
-    def __hash__(self):
-        return self.id
-
-    def __add__(self, other):
-        if isinstance(other, Prior):
-            return other + self
-
-        result = copy.deepcopy(self)
-
-        for key, value in other.__dict__.items():
-            if not hasattr(result, key) or isinstance(value, Prior):
-                setattr(result, key, value)
-                continue
-            result_value = getattr(result, key)
-            if isinstance(value, AbstractPriorModel):
-                if isinstance(result_value, AbstractPriorModel):
-                    setattr(result, key, result_value + value)
-                else:
-                    setattr(result, key, value)
-
-        return result
-
-    def copy_with_fixed_priors(self, instance, excluded_classes=tuple()):
-        """
-        Recursively overwrite priors in the mapper with instance values from the
-        instance except where the containing class is the descendant of a listed class.
-        Parameters
-        ----------
-        excluded_classes
-            Classes that should be left model
-        instance
-            The best fit from the previous search
-        """
-        mapper = copy.deepcopy(self)
-        transfer_classes(instance, mapper, excluded_classes)
-        return mapper
-
-    @property
-    def path_priors_tuples(self) -> List[Tuple[Path, Prior]]:
-        path_priors_tuples = self.path_instance_tuples_for_class(Prior)
-        return sorted(path_priors_tuples, key=lambda item: item[1].id)
-
-    @property
-    def paths(self) -> List[Path]:
-        """
-        A list of paths to all the priors in the model, ordered by their
-        ids
-        """
-        return [path for path, _ in self.path_priors_tuples]
-
-    @property
-    def composition(self):
-        return [".".join(path) for path in self.paths]
-
-    def sort_priors_alphabetically(self, priors: Iterable[Prior]) -> List[Prior]:
-        """
-        Sort priors by their paths according to this model.
-
-        Parameters
-        ----------
-        priors
-            A set of priors
-
-        Returns
-        -------
-        Those priors sorted alphabetically by path.
-        """
-        return sorted(priors, key=lambda prior: self.path_for_prior(prior))
-
-    def path_for_prior(self, prior: Prior) -> Optional[Path]:
-        """
-        Find a path that points at the given tuple.
-        Returns the first path or None if no path is found.
-        Parameters
-        ----------
-        prior
-            A prior representing what's known about some dimension of the model.
-        Returns
-        -------
-        A path, a series of attributes that point to one location of the prior.
-        """
-        for path in self.all_paths_for_prior(prior):
-            return path
-        return None
-
-    def all_paths_for_prior(self, prior: Prior) -> Generator[Path, None, None]:
-        """
-        Find all paths that points at the given tuple.
-
-        Parameters
-        ----------
-        prior
-            A prior representing what's known about some dimension of the model.
-        Yields
-        -------
-        Paths, each a series of attributes that point to one location of the prior.
-        """
-        for path, path_prior in reversed(self.path_priors_tuples):
-            if path_prior == prior:
-                yield path
-
-    @property
-    def path_float_tuples(self):
-        return self.path_instance_tuples_for_class(float, ignore_class=Prior)
-
-    @property
-    def unique_prior_paths(self):
-        return [item[0] for item in self.unique_path_prior_tuples]
-
-    @property
-    def unique_path_prior_tuples(self):
-        unique = {item[1]: item for item in self.path_priors_tuples}.values()
-        return sorted(unique, key=lambda item: item[1].id)
-
-    @property
-    def prior_prior_model_dict(self):
-        """
-        Returns
-        -------
-        prior_prior_model_dict: {Prior: Model}
-            A dictionary mapping priors to associated prior models. Each prior will only
-            have one prior model; if a prior is shared by two prior models then one of
-            those prior models will be in this dictionary.
-        """
-        return {
-            prior: prior_model[1]
-            for prior_model in self.prior_model_tuples + [("model", self)]
-            for _, prior in prior_model[1].prior_tuples
-        }
-
-    def log_prior_list_from(self, parameter_lists: List[List]) -> List:
-        return [
-            sum(self.log_prior_list_from_vector(vector=vector))
-            for vector in parameter_lists
-        ]
-
-    @property
-    def info(self) -> str:
-        """
-        Use the priors that make up the model_mapper to generate information on each
-        parameter of the overall model.
-        This information is extracted from each priors *model_info* property.
-        """
-
-        formatter = TextFormatter(line_length=info_whitespace())
-
-        for t in self.path_instance_tuples_for_class(
-            (Prior, float, int, tuple, ConfigException), ignore_children=True
-        ):
-            name = t[0][-1]
-            if name in ("id", "item_number"):
-                continue
-
-            if isinstance(t[1], ConfigException):
-                t = (t[0], "Prior Missing: Enter Manually or Add to Config")
-
-            formatter.add(*t)
-
-        return "\n\n".join(
-            [
-                f"Total Free Parameters = {self.prior_count}",
-                f"{self.parameterization}",
-                formatter.text,
-            ]
-        )
-
-    @property
-    def order_no(self) -> str:
-        """
-        A string that can be used to order models by their
-        parametrisation.
-
-        Priors and constants are ordered by their paths and then
-        joined into a string which means that models with higher
-        associated values are consistently ordered later in a
-        collection.
-        """
-        values = [
-            str(float(value))
-            for _, value in sorted(
-                self.path_instance_tuples_for_class((Prior, float)), key=lambda t: t[0]
-            )
-        ]
-        return ":".join(values)
-
-    @property
-    def parameterization(self) -> str:
-        """
-        Describes the path to each of the PriorModels, its class
-        and its number of free parameters
-        """
-        from .prior_model import Model
-
-        formatter = TextFormatter(line_length=info_whitespace())
-
-        for t in self.path_instance_tuples_for_class(
-            (
-                Prior,
-                float,
-                tuple,
-            ),
-            ignore_children=True,
-        ):
-            for i in range(len(t[0])):
-                path = t[0][:i]
-                obj = self.object_for_path(path)
-                if isinstance(obj, TuplePrior):
-                    continue
-                if isinstance(obj, AbstractPriorModel):
-                    n = obj.prior_count
-                else:
-                    n = 0
-                if isinstance(obj, Model):
-                    name = obj.cls.__name__
-                else:
-                    name = type(obj).__name__
-
-                formatter.add(("model",) + path, f"{name} (N={n})")
-
-        return formatter.text
-
-    @property
-    def all_paths(self) -> List[Tuple[Path]]:
-        """
-        All possible paths to all priors grouped such that all
-        paths to the same prior are collected together in a tuple.
-        """
-        if self.prior_count == 0:
-            return []
-        paths, _ = zip(*self.all_paths_prior_tuples)
-        return paths
-
-    @property
-    def all_paths_prior_tuples(self) -> List[Tuple[Tuple[Path], Prior]]:
-        """
-        Maps a tuple containing all paths to a given prior to that prior.
-        """
-        prior_paths_dict = defaultdict(tuple)
-        for path, prior in self.path_priors_tuples:
-            prior_paths_dict[prior] += (path,)
-        return sorted(
-            [(paths, prior) for prior, paths in prior_paths_dict.items()],
-            key=lambda item: item[1].id,
-        )
-
-    @property
-    def all_names(self) -> List[Tuple[str]]:
-        """
-        All possible names for all priors grouped such that all
-        names for a given prior are collected together in a tuple.
-        """
-        if self.prior_count == 0:
-            return []
-        names, _ = zip(*self.all_name_prior_tuples)
-        return names
-
-    @property
-    def all_name_prior_tuples(self) -> List[Tuple[Tuple[str], Prior]]:
-        """
-        Maps a tuple containing all names for a given prior to that prior.
-        """
-        path_modifier = TuplePathModifier(self)
-        return [
-            (tuple(".".join(path_modifier(path)) for path in paths), prior)
-            for paths, prior in self.all_paths_prior_tuples
-        ]
-
-    @property
-    def model_component_and_parameter_names(self) -> List[str]:
-        """The param_names vector is a list each parameter's analysis_path, and is used
-        for *corner.py* visualization.
-        The parameter names are determined from the class instance names of the
-        model_mapper. Latex tags are properties of each model class."""
-        prior_paths = self.unique_prior_paths
-
-        tuple_filter = TuplePathModifier(self)
-
-        prior_paths = list(map(tuple_filter, prior_paths))
-
-        return [".".join(path) for path in prior_paths]
-
-    @property
-    def joined_paths(self) -> List[str]:
-        prior_paths = self.unique_prior_paths
-
-        return [".".join(path) for path in prior_paths]
-
-    @property
-    def parameter_names(self) -> List[str]:
-        """
-        The param_names vector is a list each parameter's analysis_path, and is used
-        for *corner.py* visualization.
-
-        The parameter names are determined from the class instance names of the
-        model_mapper. Latex tags are properties of each model class.
-        """
-        return [parameter_name[-1] for parameter_name in self.unique_prior_paths]
-
-    @property
-    def parameter_labels(self) -> List[str]:
-        """
-        Returns a list of the label of every parameter in a model.
-
-        This is used for displaying model results as text and for visualization with *corner.py*.
-
-        The parameter labels are defined for every parameter of every model component in the config files label.ini and
-        label_format.ini.
-        """
-
-        parameter_labels = []
-
-        for parameter_name in self.parameter_names:
-            parameter_label = frm.convert_name_to_label(
-                parameter_name=parameter_name, name_to_label=True
-            )
-            parameter_labels.append(parameter_label)
-
-        return parameter_labels
-
-    @property
-    def superscripts(self) -> List[str]:
-        """
-        Returns a list of the model component superscripts for every parameter in a model.
-
-
-        The class superscript labels are defined as the name of every model component in the `ModelMapper`. For
-        the example of a 1D Gaussian, if the model component name is `gaussian` three superscripts
-        with this string (corresponding to the parameters `centre`, `normalization` and `sigma`) will
-        be returned.
-
-        For a `Collection`, the name of the inner model components are used.
-
-        These superscripts may be overwritten by those returned from the `superscripts_config_overwrite` property,
-        which optionally loads the superscripts from a `.json` config file. This allows high levels of customization
-        in what superscripts are used.
-
-        This is used for displaying model results as text and for visualization.
-        """
-
-        prior_paths = self.unique_prior_paths
-
-        tuple_filter = TuplePathModifier(self)
-
-        prior_paths = map(tuple_filter, prior_paths)
-
-        superscripts = [path[-2] if len(path) > 1 else None for path in prior_paths]
-
-        return [
-            superscript if not superscript_overwrite else superscript_overwrite
-            for superscript, superscript_overwrite in zip(
-                superscripts, self.superscripts_overwrite_via_config
-            )
-        ]
-
-    @property
-    def superscripts_overwrite_via_config(self) -> List[str]:
-        """
-        Returns a list of the model component superscripts for every parameter in a model, which can be used to
-        overwrite the default superscripts used in the function above.
-
-        The class superscript labels are defined for a model component in the config file `notation/label.ini`. By
-        default, the model component names are used as superscripts (which are loaded via the method `superscripts`).
-        These are overwritten by the superscripts loaded via a  config in this function. If no value is present in the
-        config the model component names are used.
-
-        For the example of a 1D Gaussian, when instatiated as a model component it is typically given the
-        name `gaussian`. Thus, the string `gaussian` will be used as the supersript of every one its parameter labels
-        (`centre`, `normalization` and `sigma`). However, if the config file `label.ini` reads `Gaussian=g`, every
-        superscript for these parameters will instead be given the superscript `g`.
-
-        This is used for displaying model results as text and for visualization with.
-        """
-
-        superscripts = []
-
-        for prior_name, prior in self.prior_tuples_ordered_by_id:
-            cls = self.prior_class_dict[prior]
-            try:
-                superscript = conf.instance["notation"]["label"]["superscript"][
-                    cls.__name__
-                ]
-
-            except KeyError:
-                superscript = ""
-
-            superscripts.append(superscript)
-
-        return superscripts
-
-    @property
-    def parameter_labels_with_superscripts(self) -> List[str]:
-        """
-        Returns a list of the latex parameter label and superscript of every parameter in a model.
-
-        The parameter labels are defined for every parameter of every model component in the config file `label.ini`.
-        This file can also be used to overwrite superscripts, that are assigned based on the model component name.
-
-        This is used for displaying model results as text and for visualization, for example labelling parameters on a
-        cornerplot.
-        """
-
-        return [
-            f"{label}^{{\\rm {superscript}}}" if superscript else f"{label}"
-            for label, superscript in zip(self.parameter_labels, self.superscripts)
-        ]
-
-    @property
-    def parameter_labels_with_superscripts_latex(self) -> List[str]:
-        """
-        Returns a list of the latex parameter label and superscript of every parameter in a model.
-
-        The parameter labels are defined for every parameter of every model component in the config file `label.ini`.
-        This file can also be used to overwrite superscripts, that are assigned based on the model component name.
-
-        This is used for displaying model results as text and for visualization, for example labelling parameters on a
-        cornerplot.
-        """
-
-        return [f"${label}$" for label in self.parameter_labels_with_superscripts]
-
-
-def transfer_classes(instance, mapper, model_classes=None):
-    """
-    Recursively overwrite priors in the mapper with instance values from the
-    instance except where the containing class is the descendant of a listed class.
-    Parameters
-    ----------
-    model_classes
-        Classes whose descendants should not be overwritten
-    instance
-        The best fit from the previous search
-    mapper
-        The prior model from the previous search
-    """
-    from autofit.mapper.prior_model.annotation import AnnotationPriorModel
-
-    model_classes = model_classes or []
-    for key, instance_value in instance.__dict__.items():
-        try:
-            mapper_value = getattr(mapper, key)
-            if isinstance(mapper_value, Prior) or isinstance(
-                mapper_value, AnnotationPriorModel
-            ):
-                setattr(mapper, key, instance_value)
-                continue
-            if not any(isinstance(instance_value, cls) for cls in model_classes):
-                try:
-                    transfer_classes(instance_value, mapper_value, model_classes)
-                except AttributeError:
-                    setattr(mapper, key, instance_value)
-        except AttributeError:
-            pass
+import copy
+import inspect
+import json
+import logging
+import random
+import types
+from collections import defaultdict
+from typing import Tuple, Optional, Dict, List, Iterable, Generator, Union, Type
+
+import numpy as np
+
+from autoconf import conf
+from autoconf.exc import ConfigException
+from autofit import exc
+from autofit.mapper import model
+from autofit.mapper.model import AbstractModel, frozen_cache
+from autofit.mapper.prior import GaussianPrior
+from autofit.mapper.prior import UniformPrior
+from autofit.mapper.prior.abstract import Prior
+from autofit.mapper.prior.deferred import DeferredArgument
+from autofit.mapper.prior.tuple_prior import TuplePrior
+from autofit.mapper.prior.width_modifier import WidthModifier
+from autofit.mapper.prior_model.attribute_pair import DeferredNameValue
+from autofit.mapper.prior_model.attribute_pair import (
+    cast_collection,
+    PriorNameValue,
+    InstanceNameValue,
+)
+from autofit.mapper.prior_model.recursion import DynamicRecursionCache
+from autofit.mapper.prior_model.util import PriorModelNameValue
+from autofit.text import formatter as frm
+from autofit.text.formatter import TextFormatter
+from autofit.tools.util import info_whitespace
+from autofit.tools.util import split_paths
+
+logger = logging.getLogger(__name__)
+
+
+class Limits:
+    @staticmethod
+    def for_class_and_attributes_name(cls, attribute_name):
+        limit_dict = conf.instance.prior_config.for_class_and_suffix_path(
+            cls, [attribute_name, "gaussian_limits"]
+        )
+        return limit_dict["lower"], limit_dict["upper"]
+
+
+class TuplePathModifier:
+    def __init__(self, model_: "AbstractPriorModel"):
+        """
+        Modifies paths to priors contained in tuples.
+
+        When a tuple is found in a signature a PriorTuple is created.
+
+        The true path to a variable in the tuple is
+        ("some", "preamble", "tuple_name", "tuple_name_0")
+
+        Where 0 is the index of the tuple member. "tuple_name" must be
+        removed from this path for some uses.
+
+        When called, instances of this class remove the name of the tuple
+        i.e:
+        -> ("some", "preamble", "tuple_name_0")
+
+        Parameters
+        ----------
+        model_
+        """
+        tuple_priors = model_.path_instance_tuples_for_class(TuplePrior)
+        try:
+            self.tuple_paths, _ = zip(*tuple_priors)
+        except ValueError:
+            self.tuple_paths = None
+
+    def __call__(self, path):
+        if self.tuple_paths is not None:
+            if path[:-1] in self.tuple_paths:
+                return path[:-2] + (path[-1],)
+        return path
+
+
+Path = Tuple[str, ...]
+
+
+class MeanField:
+    def __init__(self, prior_model: "AbstractPriorModel"):
+        """
+        Implements same interface as graphical code
+
+        Parameters
+        ----------
+        prior_model
+        """
+        self.prior_model = prior_model
+
+    def __getitem__(self, item):
+        """
+        Retrieve a prior by a prior with the same id
+        """
+        for prior in self.prior_model.priors:
+            if prior == item:
+                return prior
+        raise KeyError(f"Could not find {item} in model")
+
+
+def paths_to_tree(paths: List[Tuple[str, ...]], tree: Optional[dict] = None) -> dict:
+    """
+    Recursively convert a list of paths to a tree structure where common paths
+    are matched.
+
+    Parameters
+    ----------
+    paths
+        A list of paths to attributes in the model.
+    tree
+        A tree already embedded in a parent tree.
+
+    Returns
+    -------
+    A tree with depth max(map(len, paths))
+
+    Examples
+    --------
+    paths_to_tree([
+        ("one", "two", "three"),
+        ("one", "two", "four"),
+    ])
+
+    gives
+
+    {
+        "one": {
+            "two": {
+                "three": {},
+                "four": {}
+            }
+        }
+    }
+    """
+    tree = tree or dict()
+    for path in paths:
+        if len(path) == 0:
+            return tree
+        first, *rest = path
+        if first not in tree:
+            child = dict()
+            tree[first] = child
+        tree[first] = paths_to_tree([rest], tree=tree[first])
+    return tree
+
+
+class AbstractPriorModel(AbstractModel):
+    """
+    Abstract model that maps a set of priors to a particular class. Must be
+    overridden by any prior model so that the model mapper recognises its prior \
+    model attributes.
+    @DynamicAttrs
+    """
+
+    def __init__(self, label=None, custom_derived_quantities=None):
+        super().__init__(label=label)
+        self._assertions = list()
+        self._custom_derived_quantities = custom_derived_quantities or dict()
+
+    @property
+    def custom_derived_quantities(self):
+        return self._custom_derived_quantities
+
+    @property
+    def assertions(self):
+        return self._assertions
+
+    @assertions.setter
+    def assertions(self, assertions):
+        self._assertions = assertions
+
+    def check_assertions(self, arguments: Dict[Prior, float]):
+        """
+        Check that all assertions are satisfied by the given arguments.
+
+        Parameters
+        ----------
+        arguments
+            A dictionary mapping priors to values
+
+        Raises
+        ------
+        FitException
+            If any assertion is not satisfied
+        """
+        failed_assertions = [
+            assertion
+            for assertion in self._assertions
+            if assertion is False
+            or assertion is not True
+            and not assertion.instance_for_arguments(
+                arguments,
+            )
+        ]
+        number_of_failed_assertions = len(failed_assertions)
+        if number_of_failed_assertions > 0:
+            name_string = "\n".join(
+                [
+                    assertion.name
+                    for assertion in failed_assertions
+                    if hasattr(assertion, "name") and assertion.name is not None
+                ]
+            )
+            raise exc.FitException(
+                f"{number_of_failed_assertions} assertions failed!\n{name_string}"
+            )
+
+    @property
+    def derived_quantities(self) -> List[Tuple[str, ...]]:
+        """
+        The paths to attributes of the model which are derived (i.e.
+        methods marked with the derived_quantity decorator)
+        """
+        return [
+            (name,) + derived_quantity
+            for name, prior_model in self.direct_prior_model_tuples
+            for derived_quantity in prior_model.derived_quantities
+        ]
+
+    def cast(
+        self,
+        value_dict: Dict["AbstractModel", dict],
+        new_class: type,
+    ) -> "AbstractPriorModel":
+        """
+        Cast models to a new type. Allows selected models in within this
+        model to be given a new type and new arguments.
+
+        Parameters
+        ----------
+        value_dict
+            A dictionary mapping models to dictionaries of argument overrides
+        new_class
+            A new class to which specified models should be converted
+
+        Returns
+        -------
+        A model where specified child models have been updated to a new class
+        and new arguments
+        """
+        from .prior_model import Model
+
+        updated = self
+
+        for path, prior_model in self.path_instance_tuples_for_class(Model):
+            try:
+                model_value_dict = value_dict[prior_model]
+                argument_dict = {
+                    **dict(prior_model.direct_prior_tuples),
+                    **dict(prior_model.direct_tuples_with_type(float)),
+                    **model_value_dict,
+                }
+                updated = updated.replacing_for_path(
+                    path, Model(new_class, **argument_dict)
+                )
+
+            except KeyError:
+                pass
+
+        return updated
+
+    def without_attributes(self) -> "AbstractModel":
+        """
+        Returns a copy of this object with all priors, prior models and
+        constants removed.
+        """
+        without_attributes = copy.copy(self)
+        for key in self.__dict__:
+            if not (key.startswith("_") or key in ("cls", "id")):
+                delattr(without_attributes, key)
+        return without_attributes
+
+    def _with_paths(self, tree: dict) -> "AbstractModel":
+        """
+        Recursively generate a copy of this model retaining only objects
+        specified by the tree.
+
+        Parameters
+        ----------
+        tree
+            A tree formed of dictionaries describing which components of the
+            model should be retained.
+
+        Returns
+        -------
+        A copy of this model with a subset of attributes
+        """
+        if len(tree) == 0:
+            return self
+
+        with_paths = self.without_attributes()
+        for name, subtree in tree.items():
+            # noinspection PyProtectedMember
+            new_value = getattr(self, name)
+            if isinstance(
+                new_value,
+                (
+                    AbstractPriorModel,
+                    TuplePrior,
+                ),
+            ):
+                new_value = new_value._with_paths(subtree)
+            setattr(with_paths, name, new_value)
+        return with_paths
+
+    @split_paths
+    def with_paths(self, paths: List[Tuple[str, ...]]) -> "AbstractModel":
+        """
+        Recursively generate a copy of this model retaining only objects
+        specified by the list of paths.
+
+        Parameters
+        ----------
+        paths
+            A list of tuples of strings each of which points to a retained attribute.
+            All children of a given path are retained.
+
+        Returns
+        -------
+        A copy of this model with a subset of attributes
+        """
+        return self._with_paths(paths_to_tree(paths))
+
+    def _without_paths(self, tree: dict) -> "AbstractModel":
+        """
+        Recursively generate a copy of this model removing objects
+        specified by the tree.
+
+        Parameters
+        ----------
+        tree
+            A tree formed of dictionaries describing which components of the
+            model should be removed.
+
+        Returns
+        -------
+        A copy of this model with a subset of attributes
+        """
+        without_paths = copy.deepcopy(self)
+        for name, subtree in tree.items():
+            # noinspection PyProtectedMember
+            if len(subtree) == 0:
+                delattr(without_paths, name)
+            else:
+                new_value = getattr(without_paths, name)
+                if isinstance(
+                    new_value,
+                    (
+                        AbstractPriorModel,
+                        TuplePrior,
+                    ),
+                ):
+                    new_value = new_value._without_paths(subtree)
+                setattr(without_paths, name, new_value)
+        return without_paths
+
+    @split_paths
+    def without_paths(self, paths: List[Tuple[str, ...]]) -> "AbstractModel":
+        """
+        Recursively generate a copy of this model retaining only objects
+        not specified by the list of paths.
+
+        Parameters
+        ----------
+        paths
+            A list of tuples of strings each of which points to removed attribute.
+            All children of a given path are removed.
+
+        Returns
+        -------
+        A copy of this model with a subset of attributes
+        """
+        return self._without_paths(paths_to_tree(paths))
+
+    def index(self, path: Tuple[str, ...]) -> int:
+        """
+        Retrieve the index of a given path in the model
+        """
+        return self.paths.index(path)
+
+    @property
+    def mean_field(self) -> MeanField:
+        """
+        Implements the same interface as the graphical code
+        """
+        return MeanField(self)
+
+    @classmethod
+    def from_json(cls, file: str):
+        """
+        Loads the model from a .json file, which was written using the model's dictionary (`dict`) attribute as
+        follows:
+
+        import json
+
+        with open(filename, "w+") as f:
+            json.dump(model.dict, f, indent=4)
+
+        Parameters
+        ----------
+        file
+            The path and name of the .json file the model is loaded from.
+
+        Returns
+        -------
+        object
+            The model, which may be a `Collection` of `Model` objects or a single `Model`.
+        """
+
+        with open(file) as json_file:
+            model_dict = json.load(json_file)
+
+        return cls.from_dict(d=model_dict)
+
+    def add_assertion(self, assertion, name=""):
+        """
+        Assert that some relationship holds between physical values associated with
+        priors at the point an instance is created. If this fails a FitException is
+        raised causing the model to be re-sampled.
+        Parameters
+        ----------
+        assertion
+            An assertion that one prior must be greater than another.
+        name
+            A name describing the assertion that is logged when it is violated.
+        """
+        if assertion is True:
+            return
+        try:
+            assertion.name = name
+        except AttributeError:
+            pass
+        self._assertions.append(assertion)
+
+    @property
+    def name(self):
+        return self.__class__.__name__
+
+    # noinspection PyUnusedLocal
+    @staticmethod
+    def from_object(t, *args, **kwargs):
+        if inspect.isclass(t):
+            from .prior_model import Model
+
+            obj = object.__new__(Model)
+            obj.__init__(t, **kwargs)
+        elif isinstance(t, list) or isinstance(t, dict):
+            from autofit.mapper.prior_model import collection
+
+            obj = object.__new__(collection.Collection)
+            obj.__init__(t)
+        else:
+            obj = t
+        return obj
+
+    def take_attributes(self, source: object):
+        """
+        Take all attributes with a matching path from the source prior model.
+
+        For example, if this prior model has a prior "one" and a matching prior
+        is found associated with the source model then that attribute is attached
+        to this model.
+
+        If no matching attribute is found nothing happens.
+
+        Parameters
+        ----------
+        source
+            An instance or prior model from a previous search from which attributes
+            are passed to this model.
+        """
+
+        def assert_no_assertions(obj):
+            if len(getattr(obj, "_assertions", [])) > 0:
+                raise AssertionError(
+                    "take_attributes cannot be called once assertions have been added to the target"
+                )
+
+        assert_no_assertions(self)
+
+        for path, _ in sum(
+            map(self.path_instance_tuples_for_class, (Prior, float, TuplePrior)), []
+        ):
+            try:
+                item = copy.copy(source)
+                if isinstance(item, dict):
+                    from autofit.mapper.prior_model.collection import Collection
+
+                    item = Collection(item)
+                for attribute in path:
+                    if isinstance(attribute, int):
+                        item = item[attribute]
+                    else:
+                        item = copy.copy(getattr(item, attribute))
+
+                target = self
+                for attribute in path[:-1]:
+                    if isinstance(attribute, int):
+                        target = target[attribute]
+                    else:
+                        target = getattr(target, attribute)
+                    assert_no_assertions(target)
+
+                attribute = path[-1]
+                if isinstance(attribute, int):
+                    target[attribute] = item
+                else:
+                    setattr(target, path[-1], item)
+            except AttributeError:
+                pass
+
+    def instance_from_unit_vector(self, unit_vector, ignore_prior_limits=False):
+        """
+        Returns a ModelInstance, which has an attribute and class instance corresponding
+        to every `Model` attributed to this instance.
+        This method takes as input a unit vector of parameter values, converting each to
+        physical values via their priors.
+        Parameters
+        ----------
+        ignore_prior_limits
+            If true then no exception is thrown if priors fall outside defined limits
+        unit_vector: [float]
+            A unit hypercube vector that is mapped to an instance of physical values via the priors.
+        Returns
+        -------
+        model_instance : autofit.mapper.model.ModelInstance
+            An object containing reconstructed model_mapper instances
+        Raises
+        ------
+        exc.FitException
+            If any assertion attached to this object returns False.
+        """
+        exception_tuples = self.attribute_tuples_with_type(ConfigException)
+        if len(exception_tuples) > 0:
+            for name, exception in exception_tuples:
+                logger.exception(f"Could not load {name} because:\n\n{exception}")
+            names = [name for name, _ in exception_tuples]
+            raise ConfigException(
+                f"No configuration was found for some attributes ({', '.join(names)})"
+            )
+
+        if self.prior_count != len(unit_vector):
+            raise AssertionError(
+                f"prior_count ({self.prior_count}) != len(unit_vector) {len(unit_vector)}"
+            )
+
+        arguments = dict(
+            map(
+                lambda prior_tuple, unit: (
+                    prior_tuple.prior,
+                    prior_tuple.prior.value_for(
+                        unit,
+                        ignore_prior_limits=ignore_prior_limits,
+                    ),
+                ),
+                self.prior_tuples_ordered_by_id,
+                unit_vector,
+            )
+        )
+
+        return self.instance_for_arguments(
+            arguments,
+            ignore_assertions=ignore_prior_limits,
+        )
+
+    @property
+    @cast_collection(PriorNameValue)
+    @frozen_cache
+    def unique_prior_tuples(self):
+        """
+        Returns
+        -------
+        prior_tuple_dict: [(Prior, PriorTuple)]
+            The set of all priors associated with this mapper
+        """
+        return list(
+            {
+                prior_tuple[1]: prior_tuple
+                for prior_tuple in self.attribute_tuples_with_type(Prior)
+            }.values()
+        )
+
+    @property
+    @cast_collection(PriorNameValue)
+    @frozen_cache
+    def prior_tuples_ordered_by_id(self):
+        """
+        Returns
+        -------
+        priors: [Prior]
+            An ordered list of unique priors associated with this mapper
+        """
+        return sorted(
+            list(self.unique_prior_tuples), key=lambda prior_tuple: prior_tuple.prior.id
+        )
+
+    @property
+    def priors_ordered_by_id(self):
+        return [prior for _, prior in self.prior_tuples_ordered_by_id]
+
+    def vector_from_unit_vector(self, unit_vector, ignore_prior_limits=False):
+        """
+        Parameters
+        ----------
+        unit_vector: [float]
+            A unit hypercube vector
+        ignore_prior_limits
+            Set to True to prevent an exception being raised if
+            the physical value of a prior is outside the allowable
+            limits
+
+        Returns
+        -------
+        values: [float]
+            A vector with values output by priors
+        """
+        return list(
+            map(
+                lambda prior_tuple, unit: prior_tuple.prior.value_for(
+                    unit, ignore_prior_limits=ignore_prior_limits
+                ),
+                self.prior_tuples_ordered_by_id,
+                unit_vector,
+            )
+        )
+
+    def random_unit_vector_within_limits(
+        self, lower_limit: float = 0.0, upper_limit: float = 1.0
+    ) -> List[float]:
+        """
+        Generate a random vector of unit values by drawing uniform random values between 0 and 1.
+
+        Returns
+        -------
+        unit_values
+            A list of unit values constructed by taking random values from each prior.
+        """
+        return [
+            random.uniform(
+                max(lower_limit, prior.lower_unit_limit),
+                min(upper_limit, prior.upper_unit_limit),
+            )
+            for prior in self.priors_ordered_by_id
+        ]
+
+    def random_vector_from_priors_within_limits(
+        self, lower_limit: float = 0.0, upper_limit: float = 1.0
+    ) -> List[float]:
+        """
+        Returns a random vector of physical values by drawing uniform random values between lower and upper limits
+        defined as unit values, using the model priors to map them from unit values to physical values.
+
+        This function guesses infinite unit vectors, until one is randomly sampled whose physical parameters are within
+        the model's phyiscal limits.
+
+        This is used for MCMC initialization, whereby the starting points of a walker(s) are confined to a restricted
+        range of prior space. In particular, it is used for generate the "ball" initialization of Emcee.
+
+        Parameters
+        ----------
+        lower_limit
+            The lower limit as a unit value within which unit values (which are converted to physical values via the
+            priors) are generated.
+        upper_limit
+            The upper limit as a unit value within which unit values (which are converted to physical values via the
+            priors) are generated.
+
+        Returns
+        -------
+        physical_values
+            A list of physical values constructed by taking the mean possible value from
+            each prior.
+        """
+        vector = []
+
+        for prior in self.priors_ordered_by_id:
+            vector.append(
+                prior.random(
+                    lower_limit=lower_limit,
+                    upper_limit=upper_limit,
+                )
+            )
+
+        return vector
+
+    def random_instance_from_priors_within_limits(
+        self, lower_limit: float = 0.0, upper_limit: float = 1.0
+    ):
+        """
+        Returns a random instance of physical values by drawing uniform random values between lower and upper limits
+        defined as unit values, using the model priors to map them from unit values to physical values.
+
+        This function guesses infinite unit vectors, until one is randomly sampled whose physical parameters are within
+        the model's phyiscal limits. The final unit vector is then mapped to an instance.
+
+        Parameters
+        ----------
+        lower_limit
+            The lower limit as a unit value within which unit values (which are converted to physical values via the
+            priors) are generated.
+        upper_limit
+            The upper limit as a unit value within which unit values (which are converted to physical values via the
+            priors) are generated.
+
+        Returns
+        -------
+        physical_values
+            A list of physical values constructed by taking the mean possible value from
+            each prior.
+        """
+
+        vector = self.random_vector_from_priors_within_limits(
+            lower_limit=lower_limit, upper_limit=upper_limit
+        )
+
+        return self.instance_from_vector(vector=vector)
+
+    @property
+    def random_vector_from_priors(self):
+        """Generate a random vector of physical values by drawing uniform random values between 0 and 1 and using
+        the model priors to map them from unit values to physical values.
+        Returns
+        -------
+        physical_values: [float]
+            A list of physical values constructed by taking random values from each prior.
+        """
+        return self.random_vector_from_priors_within_limits(
+            lower_limit=0.0, upper_limit=1.0
+        )
+
+    @property
+    def physical_values_from_prior_medians(self):
+        """
+        Returns
+        -------
+        physical_values: [float]
+            A list of physical values constructed by taking the mean possible value from
+            each prior.
+        """
+        return self.vector_from_unit_vector([0.5] * len(self.unique_prior_tuples))
+
+    def instance_from_vector(self, vector, ignore_prior_limits=False):
+        """
+        Returns a ModelInstance, which has an attribute and class instance corresponding
+        to every `Model` attributed to this instance.
+        This method takes as input a physical vector of parameter values, thus omitting
+        the use of priors.
+        Parameters
+        ----------
+        vector: [float]
+            A vector of physical parameter values that is mapped to an instance.
+        ignore_prior_limits
+            If True don't check that physical values are within expected limits.
+
+        Returns
+        -------
+        model_instance : autofit.mapper.model.ModelInstance
+            An object containing reconstructed model_mapper instances
+        """
+        if len(vector) != self.prior_count:
+            raise AssertionError(
+                f"Vector length {len(vector)} != prior count {self.prior_count}"
+            )
+        arguments = dict(
+            map(
+                lambda prior_tuple, physical_unit: (prior_tuple.prior, physical_unit),
+                self.prior_tuples_ordered_by_id,
+                vector,
+            )
+        )
+
+        if not ignore_prior_limits:
+            for prior, value in arguments.items():
+                prior.assert_within_limits(value)
+
+        return self.instance_for_arguments(
+            arguments,
+            ignore_assertions=ignore_prior_limits,
+        )
+
+    def has(self, cls: Union[Type, Tuple[Type, ...]]) -> bool:
+        """
+        Parameters
+        ----------
+        cls
+            The type to check for
+
+        Returns
+        -------
+        True iff this model contains an instance or model with the given
+        type recursively. Includes models which have zero priors.
+        """
+        return self.has_instance(cls) or self.has_model(
+            cls, include_zero_dimension=True
+        )
+
+    def has_instance(self, cls) -> bool:
+        """
+        True iff this model contains an instance of type
+        cls, recursively.
+        """
+        return len(self.attribute_tuples_with_type(cls)) > 0
+
+    def has_model(self, cls, include_zero_dimension=False) -> bool:
+        """
+        True iff this model contains a Model of type
+        cls, recursively.
+        """
+        return (
+            len(
+                self.model_tuples_with_type(
+                    cls,
+                    include_zero_dimension=include_zero_dimension,
+                )
+            )
+            > 0
+        )
+
+    def is_only_model(self, cls) -> bool:
+        """
+        True iff this model contains at least one Model
+        of type cls and contains no PriorModels that are not
+        of type cls, recursively.
+        """
+        from .prior_model import Model
+
+        cls_models = self.model_tuples_with_type(cls)
+        other_models = [
+            value
+            for _, value in self.attribute_tuples_with_type(Model)
+            if value.prior_count > 0
+        ]
+        return len(cls_models) > 0 and len(cls_models) == len(other_models)
+
+    def replacing(self, arguments):
+        return self.mapper_from_partial_prior_arguments(arguments)
+
+    @classmethod
+    def product(cls, models: Iterable["AbstractPriorModel"]) -> "AbstractPriorModel":
+        """
+        Combine multiple models with the same structure by replacing priors with
+        priors that contain a message which is the product of the messages of the
+        priors with the same path in each model.
+
+        Parameters
+        ----------
+        models
+            A list of models to be combined
+
+        Returns
+        -------
+        A model where each prior has a message which is the product of the messages
+        associated with that prior across the models.
+        """
+        first, *rest = models
+
+        arguments = dict()
+
+        for path, prior in first.path_priors_tuples:
+            for other in rest:
+                prior = prior.with_message(
+                    prior.message * other.object_for_path(path).message
+                )
+            arguments[prior] = prior
+        return first.mapper_from_prior_arguments(arguments)
+
+    def mapper_from_partial_prior_arguments(self, arguments):
+        """
+        Returns a new model mapper from a dictionary mapping existing priors to
+        new priors, keeping existing priors where no mapping is provided.
+        Parameters
+        ----------
+        arguments: {Prior: Prior}
+            A dictionary mapping priors to priors
+        Returns
+        -------
+        model_mapper: ModelMapper
+            A new model mapper with updated priors.
+        """
+        original_prior_dict = {prior: prior for prior in self.priors}
+        return self.mapper_from_prior_arguments({**original_prior_dict, **arguments})
+
+    def mapper_from_prior_arguments(self, arguments):
+        """
+        Returns a new model mapper from a dictionary mapping existing priors to
+        new priors.
+        Parameters
+        ----------
+        arguments: {Prior: Prior}
+            A dictionary mapping priors to priors
+        Returns
+        -------
+        model_mapper: ModelMapper
+            A new model mapper with updated priors.
+        """
+        logger.debug(f"Creating a new mapper from arguments")
+
+        return self.gaussian_prior_model_for_arguments(arguments)
+
+    def gaussian_prior_model_for_arguments(self, arguments):
+        raise NotImplementedError()
+
+    def mapper_from_gaussian_tuples(
+        self, tuples, a=None, r=None, use_errors=True, use_widths=True, no_limits=False
+    ):
+        """
+        The widths of the new priors are taken from the
+        width_config. The new gaussian priors must be provided in the same order as
+        the priors associated with model.
+        If a is not None then all priors are created with an absolute width of a.
+        If r is not None then all priors are created with a relative width of r.
+        Parameters
+        ----------
+        no_limits
+            If `True` generated priors have infinite limits
+        r
+            The relative width to be assigned to gaussian priors
+        a
+            print(tuples[i][1], width)
+            The absolute width to be assigned to gaussian priors
+        use_errors
+            If True, the passed errors of the model components estimated in a previous `NonLinearSearch` (computed
+            at the prior_passer.sigma value) are used to set the pass Gaussian Prior sigma value (if both width and
+            passed errors are used, the maximum of these two values are used).
+        use_widths
+            If True, the minimum prior widths specified in the prior configs of the model components are used to
+            set the passed Gaussian Prior sigma value (if both widths and passed errors are used, the maximum of
+            these two values are used).
+        tuples
+            A list of tuples each containing the mean and width of a prior
+        Returns
+        -------
+        mapper: ModelMapper
+            A new model mapper with all priors replaced by gaussian priors.
+        """
+
+        prior_tuples = self.prior_tuples_ordered_by_id
+        prior_class_dict = self.prior_class_dict
+        arguments = {}
+
+        for i, prior_tuple in enumerate(prior_tuples):
+            prior = prior_tuple.prior
+            cls = prior_class_dict[prior]
+            mean, sigma = tuples[i]
+
+            name = prior_tuple.name
+            # Use the name of the collection for configuration when a prior's name
+            # is just a number (i.e. its position in a collection)
+            if name.isdigit():
+                name = self.path_for_prior(prior_tuple.prior)[-2]
+
+            width_modifier = (
+                prior.width_modifier
+                or WidthModifier.for_class_and_attribute_name(cls, name)
+            )
+
+            if a is not None and r is not None:
+                raise exc.PriorException(
+                    "Width of new priors cannot be both relative and absolute."
+                )
+            if a is not None:
+                width = a
+            elif r is not None:
+                width = r * mean
+            else:
+                width = width_modifier(mean)
+
+            if no_limits:
+                limits = (float("-inf"), float("inf"))
+            else:
+                try:
+                    limits = Limits.for_class_and_attributes_name(cls, name)
+                except ConfigException:
+                    limits = prior.limits
+
+            if use_errors and not use_widths:
+                sigma = tuples[i][1]
+            elif not use_errors and use_widths:
+                sigma = width
+            elif use_errors and use_widths:
+                sigma = max(tuples[i][1], width)
+            else:
+                raise exc.PriorException(
+                    "use_passed_errors and use_widths are both False, meaning there is no "
+                    "way to pass priors to set up the new model's Gaussian Priors."
+                )
+
+            new_prior = GaussianPrior(mean, sigma, *limits)
+            new_prior.id = prior.id
+            new_prior.width_modifier = prior.width_modifier
+            arguments[prior] = new_prior
+
+        return self.mapper_from_prior_arguments(arguments)
+
+    def with_limits(self, limits: List[Tuple[float, float]]) -> "AbstractPriorModel":
+        """
+        Create a new instance of this model where each prior is updated to
+        lie between new limits.
+
+        Parameters
+        ----------
+        limits
+            A list of pairs of lower and upper limits, one for each prior.
+
+        Returns
+        -------
+        A new model with updated limits
+        """
+        return self.mapper_from_prior_arguments(
+            {
+                prior: prior.with_limits(*prior_limits)
+                for prior, prior_limits in zip(self.priors_ordered_by_id, limits)
+            }
+        )
+
+    def mapper_from_uniform_floats(self, floats, b):
+        """
+        The widths of the new priors are the `floats` value minus and plus the input bound `b`.
+
+        Parameters
+        ----------
+        floats
+            A list of floats each containing the centre of the new uniform priors.
+        b
+            The bound value which is subtracted from each float to calculate the `lower_limit` and `upper_limit`
+            of each uniform prior.
+
+        Returns
+        -------
+        mapper: ModelMapper
+            A new model mapper with all priors replaced by uniform priors.
+        """
+
+        prior_tuples = self.prior_tuples_ordered_by_id
+        arguments = {}
+
+        for i, prior_tuple in enumerate(prior_tuples):
+            prior = prior_tuple.prior
+
+            new_prior = UniformPrior(
+                lower_limit=floats[i] - b, upper_limit=floats[i] + b
+            )
+            new_prior.id = prior.id
+            arguments[prior] = new_prior
+
+        return self.mapper_from_prior_arguments(arguments)
+
+    def instance_from_prior_medians(self, ignore_prior_limits=False):
+        """
+        Returns a list of physical values from the median values of the priors.
+        Returns
+        -------
+        physical_values : [float]
+            A list of physical values
+        """
+        return self.instance_from_unit_vector(
+            unit_vector=[0.5] * self.prior_count,
+            ignore_prior_limits=ignore_prior_limits,
+        )
+
+    def log_prior_list_from_vector(
+        self,
+        vector: [float],
+    ):
+        """
+        Compute the log priors of every parameter in a vector, using the Prior of every parameter.
+        The log prior values are used by Emcee to map the log likelihood to the poserior of the model.
+        Parameters
+        ----------
+        vector : [float]
+            A vector of physical parameter values.
+        Returns
+        -------
+        log_prior_list : []
+            An list of the log prior value of every parameter.
+        """
+        return list(
+            map(
+                lambda prior_tuple, value: prior_tuple.prior.log_prior_from_value(
+                    value=value
+                ),
+                self.prior_tuples_ordered_by_id,
+                vector,
+            )
+        )
+
+    def random_instance(self, ignore_prior_limits=False):
+        """
+        Returns a random instance of the model.
+        """
+        logger.debug(f"Creating a random instance")
+        if ignore_prior_limits:
+            return self.instance_from_unit_vector(
+                unit_vector=[random.random() for _ in range(self.prior_count)],
+                ignore_prior_limits=ignore_prior_limits,
+            )
+        return self.instance_for_arguments(
+            {prior: prior.random() for prior in self.priors}
+        )
+
+    @staticmethod
+    @DynamicRecursionCache()
+    def from_instance(
+        instance,
+        model_classes: Union[type, Iterable[type]] = tuple(),
+        exclude_classes: Union[type, Iterable[type]] = tuple(),
+    ):
+        """
+        Recursively create a prior object model from an object model.
+
+        Parameters
+        ----------
+        model_classes
+            A tuple of classes that should be converted to a prior model
+        exclude_classes
+            A tuple of classes that should not be converted to a prior model
+        instance
+            A dictionary, list, class instance or model instance
+        Returns
+        -------
+        abstract_prior_model
+            A concrete child of an abstract prior model
+        """
+        from autofit.mapper.prior_model import collection
+
+        if isinstance(instance, exclude_classes):
+            return instance
+        if isinstance(instance, (Prior, AbstractPriorModel)):
+            return instance
+        elif isinstance(instance, list):
+            result = collection.Collection(
+                [
+                    AbstractPriorModel.from_instance(
+                        item,
+                        model_classes=model_classes,
+                        exclude_classes=exclude_classes,
+                    )
+                    for item in instance
+                ]
+            )
+        elif isinstance(instance, model.ModelInstance):
+            from autofit.mapper import model_mapper
+
+            result = model_mapper.ModelMapper()
+            for key, value in instance.dict.items():
+                setattr(
+                    result,
+                    key,
+                    AbstractPriorModel.from_instance(
+                        value,
+                        model_classes=model_classes,
+                        exclude_classes=exclude_classes,
+                    ),
+                )
+        elif isinstance(instance, dict):
+            result = collection.Collection(
+                {
+                    key: AbstractPriorModel.from_instance(
+                        value,
+                        model_classes=model_classes,
+                        exclude_classes=exclude_classes,
+                    )
+                    for key, value in instance.items()
+                }
+            )
+        elif isinstance(instance, (np.ndarray, types.FunctionType)):
+            return instance
+        else:
+            from .prior_model import Model
+
+            try:
+                result = Model(
+                    instance.__class__,
+                    **{
+                        key: AbstractPriorModel.from_instance(
+                            value,
+                            model_classes=model_classes,
+                            exclude_classes=exclude_classes,
+                        )
+                        for key, value in instance.__dict__.items()
+                        if key != "cls"
+                    },
+                )
+            except AttributeError:
+                return instance
+        if isinstance(instance, model_classes):
+            return result.as_model()
+        return result
+
+    def items(self):
+        return (
+            self.direct_prior_tuples
+            + self.direct_instance_tuples
+            + self.direct_prior_model_tuples
+            + self.direct_tuple_priors
+        )
+
+    @property
+    @cast_collection(PriorNameValue)
+    def direct_prior_tuples(self):
+        return self.direct_tuples_with_type(Prior)
+
+    @property
+    @cast_collection(InstanceNameValue)
+    def direct_instance_tuples(self):
+        return self.direct_tuples_with_type(float)
+
+    @property
+    @cast_collection(PriorModelNameValue)
+    def prior_model_tuples(self):
+        return self.direct_tuples_with_type(AbstractPriorModel)
+
+    @property
+    @cast_collection(PriorModelNameValue)
+    def direct_prior_model_tuples(self):
+        return self.direct_tuples_with_type(AbstractPriorModel)
+
+    @property
+    @cast_collection(PriorModelNameValue)
+    def direct_tuple_priors(self):
+        return self.direct_tuples_with_type(TuplePrior)
+
+    @property
+    @cast_collection(PriorNameValue)
+    def tuple_prior_tuples(self):
+        """
+        Returns
+        -------
+        tuple_prior_tuples: [(String, TuplePrior)]
+        """
+        return self.direct_tuples_with_type(TuplePrior)
+
+    @property
+    @cast_collection(PriorNameValue)
+    def direct_prior_tuples(self):
+        """
+        Returns
+        -------
+        direct_priors: [(String, Prior)]
+        """
+        return self.direct_tuples_with_type(Prior)
+
+    @property
+    @cast_collection(DeferredNameValue)
+    def direct_deferred_tuples(self):
+        return self.direct_tuples_with_type(DeferredArgument)
+
+    @property
+    @cast_collection(PriorNameValue)
+    def prior_tuples(self):
+        """
+        Returns
+        -------
+        priors: [(String, Prior))]
+        """
+        # noinspection PyUnresolvedReferences
+        return self.attribute_tuples_with_type(Prior, ignore_children=True)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, AbstractPriorModel)
+            and self.direct_prior_model_tuples == other.direct_prior_model_tuples
+        )
+
+    @property
+    @cast_collection(InstanceNameValue)
+    def instance_tuples(self):
+        """
+        Returns
+        -------
+        instances: [(String, instance)]
+        """
+        return self.attribute_tuples_with_type(float, ignore_class=Prior)
+
+    @property
+    def prior_class_dict(self):
+        from autofit.mapper.prior_model.annotation import AnnotationPriorModel
+
+        d = {prior[1]: self.cls for prior in self.prior_tuples}
+        for prior_model in self.prior_model_tuples:
+            if not isinstance(prior_model[1], AnnotationPriorModel):
+                d.update(prior_model[1].prior_class_dict)
+        return d
+
+    def _instance_for_arguments(
+        self,
+        arguments: Dict[Prior, float],
+    ):
+        raise NotImplementedError()
+
+    def instance_for_arguments(
+        self,
+        arguments: Dict[Prior, float],
+        ignore_assertions: bool = False,
+    ):
+        """
+        Returns an instance of the model for a set of arguments
+
+        Parameters
+        ----------
+        arguments
+            Dictionary mapping priors to attribute analysis_path and value pairs
+        ignore_assertions
+            If True, assertions will not be checked
+
+        Returns
+        -------
+            An instance of the class
+        """
+        if not (
+            conf.instance["general"]["test"]["exception_override"] or ignore_assertions
+        ):
+            self.check_assertions(arguments)
+
+        logger.debug(f"Creating an instance for arguments")
+        instance = self._instance_for_arguments(
+            arguments,
+        )
+        for key, value in self.custom_derived_quantities.items():
+            setattr(instance, key, value(instance))
+        return instance
+
+    def path_for_name(self, name: str) -> Tuple[str, ...]:
+        """
+        Find the path to a prior in the model that matches
+        a given name.
+
+        For example, name_of_model_name_of_prior could match
+        (name_of_model, name_of_prior). Unfortunately it is
+        possible for ambiguity to occur. For example, another
+        valid path could be (name, of_model, name_of_prior)
+        in which case there is no way to determine which path
+        actually matches the name.
+
+        Parameters
+        ----------
+        name
+            A name for a prior where names of models and the
+            name of the prior have been joined by underscores.
+
+        Returns
+        -------
+        A path to that model
+
+        Raises
+        ------
+        AssertionError
+            Iff no matching path is found
+        """
+
+        def _explode_path(path_):
+            return tuple(string for part in path_ for string in part.split("_"))
+
+        exploded = tuple(name.split("_"))
+        for path, _ in self.path_priors_tuples:
+            exploded_path = _explode_path(path)
+            if exploded_path == exploded:
+                return path
+
+        for path, prior_tuple in self.path_instance_tuples_for_class(TuplePrior):
+            for name, prior in prior_tuple.prior_tuples:
+                total_path = path[:-1] + (name,)
+                exploded_path = _explode_path(total_path)
+                if exploded_path == exploded:
+                    return path + (name,)
+        raise AssertionError(f"No path was found matching {name}")
+
+    def instance_from_prior_name_arguments(
+        self, prior_name_arguments: Dict[str, float]
+    ):
+        """
+        Instantiate the model from the names of priors and
+        corresponding values.
+
+        Parameters
+        ----------
+        prior_name_arguments
+            The names of priors where names of models and the
+            name of the prior have been joined by underscores,
+            mapped to corresponding values.
+
+        Returns
+        -------
+        An instance of the model
+        """
+        return self.instance_from_path_arguments(
+            {
+                self.path_for_name(name): value
+                for name, value in prior_name_arguments.items()
+            }
+        )
+
+    def instance_from_path_arguments(self, path_arguments: Dict[Tuple[str], float]):
+        """
+        Create an instance from a dictionary mapping paths to tuples
+        to corresponding values.
+
+        Parameters
+        ----------
+        path_arguments
+            A dictionary mapping paths to priors to corresponding values.
+            Note that, for linked priors, each path only needs to be
+            specified once. If multiple paths for the same prior are
+            specified then the value for the last path will be used.
+
+        Returns
+        -------
+        An instance of the model
+        """
+        arguments = {
+            self.object_for_path(path): value for path, value in path_arguments.items()
+        }
+        return self._instance_for_arguments(arguments)
+
+    @property
+    def prior_count(self) -> int:
+        """
+        How many unique priors does this model contain?
+        """
+        return len(self.unique_prior_tuples)
+
+    @property
+    def total_free_parameters(self) -> int:
+        """
+        Returns the prior count, but with a name that is more easy to interpret for users.
+        """
+        return self.prior_count
+
+    @property
+    def priors(self):
+        return [prior_tuple.prior for prior_tuple in self.prior_tuples]
+
+    @property
+    def _prior_id_map(self):
+        return {prior.id: prior for prior in self.priors}
+
+    def prior_with_id(self, prior_id):
+        return self._prior_id_map[prior_id]
+
+    def name_for_prior(self, prior):
+        for prior_model_name, prior_model in self.direct_prior_model_tuples:
+            prior_name = prior_model.name_for_prior(prior)
+            if prior_name is not None:
+                return "{}_{}".format(prior_model_name, prior_name)
+        prior_tuples = self.prior_tuples
+        for name, p in prior_tuples:
+            if p == prior:
+                return name
+
+    def __hash__(self):
+        return self.id
+
+    def __add__(self, other):
+        if isinstance(other, Prior):
+            return other + self
+
+        result = copy.deepcopy(self)
+
+        for key, value in other.__dict__.items():
+            if not hasattr(result, key) or isinstance(value, Prior):
+                setattr(result, key, value)
+                continue
+            result_value = getattr(result, key)
+            if isinstance(value, AbstractPriorModel):
+                if isinstance(result_value, AbstractPriorModel):
+                    setattr(result, key, result_value + value)
+                else:
+                    setattr(result, key, value)
+
+        return result
+
+    def copy_with_fixed_priors(self, instance, excluded_classes=tuple()):
+        """
+        Recursively overwrite priors in the mapper with instance values from the
+        instance except where the containing class is the descendant of a listed class.
+        Parameters
+        ----------
+        excluded_classes
+            Classes that should be left model
+        instance
+            The best fit from the previous search
+        """
+        mapper = copy.deepcopy(self)
+        transfer_classes(instance, mapper, excluded_classes)
+        return mapper
+
+    @property
+    def path_priors_tuples(self) -> List[Tuple[Path, Prior]]:
+        path_priors_tuples = self.path_instance_tuples_for_class(Prior)
+        return sorted(path_priors_tuples, key=lambda item: item[1].id)
+
+    @property
+    def paths(self) -> List[Path]:
+        """
+        A list of paths to all the priors in the model, ordered by their
+        ids
+        """
+        return [path for path, _ in self.path_priors_tuples]
+
+    @property
+    def composition(self):
+        return [".".join(path) for path in self.paths]
+
+    def sort_priors_alphabetically(self, priors: Iterable[Prior]) -> List[Prior]:
+        """
+        Sort priors by their paths according to this model.
+
+        Parameters
+        ----------
+        priors
+            A set of priors
+
+        Returns
+        -------
+        Those priors sorted alphabetically by path.
+        """
+        return sorted(priors, key=lambda prior: self.path_for_prior(prior))
+
+    def path_for_prior(self, prior: Prior) -> Optional[Path]:
+        """
+        Find a path that points at the given tuple.
+        Returns the first path or None if no path is found.
+        Parameters
+        ----------
+        prior
+            A prior representing what's known about some dimension of the model.
+        Returns
+        -------
+        A path, a series of attributes that point to one location of the prior.
+        """
+        for path in self.all_paths_for_prior(prior):
+            return path
+        return None
+
+    def all_paths_for_prior(self, prior: Prior) -> Generator[Path, None, None]:
+        """
+        Find all paths that points at the given tuple.
+
+        Parameters
+        ----------
+        prior
+            A prior representing what's known about some dimension of the model.
+        Yields
+        -------
+        Paths, each a series of attributes that point to one location of the prior.
+        """
+        for path, path_prior in reversed(self.path_priors_tuples):
+            if path_prior == prior:
+                yield path
+
+    @property
+    def path_float_tuples(self):
+        return self.path_instance_tuples_for_class(float, ignore_class=Prior)
+
+    @property
+    def unique_prior_paths(self):
+        return [item[0] for item in self.unique_path_prior_tuples]
+
+    @property
+    def unique_path_prior_tuples(self):
+        unique = {item[1]: item for item in self.path_priors_tuples}.values()
+        return sorted(unique, key=lambda item: item[1].id)
+
+    @property
+    def prior_prior_model_dict(self):
+        """
+        Returns
+        -------
+        prior_prior_model_dict: {Prior: Model}
+            A dictionary mapping priors to associated prior models. Each prior will only
+            have one prior model; if a prior is shared by two prior models then one of
+            those prior models will be in this dictionary.
+        """
+        return {
+            prior: prior_model[1]
+            for prior_model in self.prior_model_tuples + [("model", self)]
+            for _, prior in prior_model[1].prior_tuples
+        }
+
+    def log_prior_list_from(self, parameter_lists: List[List]) -> List:
+        return [
+            sum(self.log_prior_list_from_vector(vector=vector))
+            for vector in parameter_lists
+        ]
+
+    @property
+    def info(self) -> str:
+        """
+        Use the priors that make up the model_mapper to generate information on each
+        parameter of the overall model.
+        This information is extracted from each priors *model_info* property.
+        """
+
+        formatter = TextFormatter(line_length=info_whitespace())
+
+        for t in self.path_instance_tuples_for_class(
+            (Prior, float, int, tuple, ConfigException), ignore_children=True
+        ):
+            name = t[0][-1]
+            if name in ("id", "item_number"):
+                continue
+
+            if isinstance(t[1], ConfigException):
+                t = (t[0], "Prior Missing: Enter Manually or Add to Config")
+
+            formatter.add(*t)
+
+        return "\n\n".join(
+            [
+                f"Total Free Parameters = {self.prior_count}",
+                f"{self.parameterization}",
+                formatter.text,
+            ]
+        )
+
+    @property
+    def order_no(self) -> str:
+        """
+        A string that can be used to order models by their
+        parametrisation.
+
+        Priors and constants are ordered by their paths and then
+        joined into a string which means that models with higher
+        associated values are consistently ordered later in a
+        collection.
+        """
+        values = [
+            str(float(value))
+            for _, value in sorted(
+                self.path_instance_tuples_for_class((Prior, float)), key=lambda t: t[0]
+            )
+        ]
+        return ":".join(values)
+
+    @property
+    def parameterization(self) -> str:
+        """
+        Describes the path to each of the PriorModels, its class
+        and its number of free parameters
+        """
+        from .prior_model import Model
+
+        formatter = TextFormatter(line_length=info_whitespace())
+
+        for t in self.path_instance_tuples_for_class(
+            (
+                Prior,
+                float,
+                tuple,
+            ),
+            ignore_children=True,
+        ):
+            for i in range(len(t[0])):
+                path = t[0][:i]
+                obj = self.object_for_path(path)
+                if isinstance(obj, TuplePrior):
+                    continue
+                if isinstance(obj, AbstractPriorModel):
+                    n = obj.prior_count
+                else:
+                    n = 0
+                if isinstance(obj, Model):
+                    name = obj.cls.__name__
+                else:
+                    name = type(obj).__name__
+
+                formatter.add(("model",) + path, f"{name} (N={n})")
+
+        return formatter.text
+
+    @property
+    def all_paths(self) -> List[Tuple[Path]]:
+        """
+        All possible paths to all priors grouped such that all
+        paths to the same prior are collected together in a tuple.
+        """
+        if self.prior_count == 0:
+            return []
+        paths, _ = zip(*self.all_paths_prior_tuples)
+        return paths
+
+    @property
+    def all_paths_prior_tuples(self) -> List[Tuple[Tuple[Path], Prior]]:
+        """
+        Maps a tuple containing all paths to a given prior to that prior.
+        """
+        prior_paths_dict = defaultdict(tuple)
+        for path, prior in self.path_priors_tuples:
+            prior_paths_dict[prior] += (path,)
+        return sorted(
+            [(paths, prior) for prior, paths in prior_paths_dict.items()],
+            key=lambda item: item[1].id,
+        )
+
+    @property
+    def all_names(self) -> List[Tuple[str]]:
+        """
+        All possible names for all priors grouped such that all
+        names for a given prior are collected together in a tuple.
+        """
+        if self.prior_count == 0:
+            return []
+        names, _ = zip(*self.all_name_prior_tuples)
+        return names
+
+    @property
+    def all_name_prior_tuples(self) -> List[Tuple[Tuple[str], Prior]]:
+        """
+        Maps a tuple containing all names for a given prior to that prior.
+        """
+        path_modifier = TuplePathModifier(self)
+        return [
+            (tuple(".".join(path_modifier(path)) for path in paths), prior)
+            for paths, prior in self.all_paths_prior_tuples
+        ]
+
+    @property
+    def model_component_and_parameter_names(self) -> List[str]:
+        """The param_names vector is a list each parameter's analysis_path, and is used
+        for *corner.py* visualization.
+        The parameter names are determined from the class instance names of the
+        model_mapper. Latex tags are properties of each model class."""
+        prior_paths = self.unique_prior_paths
+
+        tuple_filter = TuplePathModifier(self)
+
+        prior_paths = list(map(tuple_filter, prior_paths))
+
+        return [".".join(path) for path in prior_paths]
+
+    @property
+    def joined_paths(self) -> List[str]:
+        prior_paths = self.unique_prior_paths
+
+        return [".".join(path) for path in prior_paths]
+
+    @property
+    def parameter_names(self) -> List[str]:
+        """
+        The param_names vector is a list each parameter's analysis_path, and is used
+        for *corner.py* visualization.
+
+        The parameter names are determined from the class instance names of the
+        model_mapper. Latex tags are properties of each model class.
+        """
+        return [parameter_name[-1] for parameter_name in self.unique_prior_paths]
+
+    @property
+    def parameter_labels(self) -> List[str]:
+        """
+        Returns a list of the label of every parameter in a model.
+
+        This is used for displaying model results as text and for visualization with *corner.py*.
+
+        The parameter labels are defined for every parameter of every model component in the config files label.ini and
+        label_format.ini.
+        """
+
+        parameter_labels = []
+
+        for parameter_name in self.parameter_names:
+            parameter_label = frm.convert_name_to_label(
+                parameter_name=parameter_name, name_to_label=True
+            )
+            parameter_labels.append(parameter_label)
+
+        return parameter_labels
+
+    @property
+    def superscripts(self) -> List[str]:
+        """
+        Returns a list of the model component superscripts for every parameter in a model.
+
+
+        The class superscript labels are defined as the name of every model component in the `ModelMapper`. For
+        the example of a 1D Gaussian, if the model component name is `gaussian` three superscripts
+        with this string (corresponding to the parameters `centre`, `normalization` and `sigma`) will
+        be returned.
+
+        For a `Collection`, the name of the inner model components are used.
+
+        These superscripts may be overwritten by those returned from the `superscripts_config_overwrite` property,
+        which optionally loads the superscripts from a `.json` config file. This allows high levels of customization
+        in what superscripts are used.
+
+        This is used for displaying model results as text and for visualization.
+        """
+
+        prior_paths = self.unique_prior_paths
+
+        tuple_filter = TuplePathModifier(self)
+
+        prior_paths = map(tuple_filter, prior_paths)
+
+        superscripts = [path[-2] if len(path) > 1 else None for path in prior_paths]
+
+        return [
+            superscript if not superscript_overwrite else superscript_overwrite
+            for superscript, superscript_overwrite in zip(
+                superscripts, self.superscripts_overwrite_via_config
+            )
+        ]
+
+    @property
+    def superscripts_overwrite_via_config(self) -> List[str]:
+        """
+        Returns a list of the model component superscripts for every parameter in a model, which can be used to
+        overwrite the default superscripts used in the function above.
+
+        The class superscript labels are defined for a model component in the config file `notation/label.ini`. By
+        default, the model component names are used as superscripts (which are loaded via the method `superscripts`).
+        These are overwritten by the superscripts loaded via a  config in this function. If no value is present in the
+        config the model component names are used.
+
+        For the example of a 1D Gaussian, when instatiated as a model component it is typically given the
+        name `gaussian`. Thus, the string `gaussian` will be used as the supersript of every one its parameter labels
+        (`centre`, `normalization` and `sigma`). However, if the config file `label.ini` reads `Gaussian=g`, every
+        superscript for these parameters will instead be given the superscript `g`.
+
+        This is used for displaying model results as text and for visualization with.
+        """
+
+        superscripts = []
+
+        for prior_name, prior in self.prior_tuples_ordered_by_id:
+            cls = self.prior_class_dict[prior]
+            try:
+                superscript = conf.instance["notation"]["label"]["superscript"][
+                    cls.__name__
+                ]
+
+            except KeyError:
+                superscript = ""
+
+            superscripts.append(superscript)
+
+        return superscripts
+
+    @property
+    def parameter_labels_with_superscripts(self) -> List[str]:
+        """
+        Returns a list of the latex parameter label and superscript of every parameter in a model.
+
+        The parameter labels are defined for every parameter of every model component in the config file `label.ini`.
+        This file can also be used to overwrite superscripts, that are assigned based on the model component name.
+
+        This is used for displaying model results as text and for visualization, for example labelling parameters on a
+        cornerplot.
+        """
+
+        return [
+            f"{label}^{{\\rm {superscript}}}" if superscript else f"{label}"
+            for label, superscript in zip(self.parameter_labels, self.superscripts)
+        ]
+
+    @property
+    def parameter_labels_with_superscripts_latex(self) -> List[str]:
+        """
+        Returns a list of the latex parameter label and superscript of every parameter in a model.
+
+        The parameter labels are defined for every parameter of every model component in the config file `label.ini`.
+        This file can also be used to overwrite superscripts, that are assigned based on the model component name.
+
+        This is used for displaying model results as text and for visualization, for example labelling parameters on a
+        cornerplot.
+        """
+
+        return [f"${label}$" for label in self.parameter_labels_with_superscripts]
+
+
+def transfer_classes(instance, mapper, model_classes=None):
+    """
+    Recursively overwrite priors in the mapper with instance values from the
+    instance except where the containing class is the descendant of a listed class.
+    Parameters
+    ----------
+    model_classes
+        Classes whose descendants should not be overwritten
+    instance
+        The best fit from the previous search
+    mapper
+        The prior model from the previous search
+    """
+    from autofit.mapper.prior_model.annotation import AnnotationPriorModel
+
+    model_classes = model_classes or []
+    for key, instance_value in instance.__dict__.items():
+        try:
+            mapper_value = getattr(mapper, key)
+            if isinstance(mapper_value, Prior) or isinstance(
+                mapper_value, AnnotationPriorModel
+            ):
+                setattr(mapper, key, instance_value)
+                continue
+            if not any(isinstance(instance_value, cls) for cls in model_classes):
+                try:
+                    transfer_classes(instance_value, mapper_value, model_classes)
+                except AttributeError:
+                    setattr(mapper, key, instance_value)
+        except AttributeError:
+            pass
diff --git a/autofit/mapper/prior_model/prior_model.py b/autofit/mapper/prior_model/prior_model.py
index efee2ee19..e92882b74 100644
--- a/autofit/mapper/prior_model/prior_model.py
+++ b/autofit/mapper/prior_model/prior_model.py
@@ -55,7 +55,14 @@ class Model(AbstractPriorModel):
             )
         return super().__add__(other)
 
-    def __init__(self, cls, **kwargs):
+    def __init__(
+        self,
+        cls,
+        custom_derived_quantities: typing.Optional[
+            typing.Dict[str, typing.Callable]
+        ] = None,
+        **kwargs,
+    ):
         """
         The object a Python class is input into to create a model-component, which has free parameters that are fitted
         by a non-linear search.
@@ -80,6 +87,9 @@ class Model(AbstractPriorModel):
         ----------
         cls
             The class associated with this instance
+        custom_derived_quantities
+            A dictionary of quantities that can be derived from the model and should
+            be output as derived quantities.
 
         Examples
         --------
@@ -98,7 +108,10 @@ class Model(AbstractPriorModel):
 
         model = af.Model(Gaussian)
         """
-        super().__init__(label=namer(cls.__name__) if inspect.isclass(cls) else None)
+        super().__init__(
+            label=namer(cls.__name__) if inspect.isclass(cls) else None,
+            custom_derived_quantities=custom_derived_quantities,
+        )
         if cls is self:
             return
 
@@ -229,11 +242,15 @@ class Model(AbstractPriorModel):
         The paths to attributes of the model which are derived (i.e.
         methods marked with the derived_quantity decorator)
         """
-        return super().derived_quantities + [
-            (key,)
-            for key, value in self.cls.__dict__.items()
-            if isinstance(value, derived_quantity)
-        ]
+        return (
+            super().derived_quantities
+            + [
+                (key,)
+                for key, value in self.cls.__dict__.items()
+                if isinstance(value, derived_quantity)
+            ]
+            + [(key,) for key in self.custom_derived_quantities.keys()]
+        )
 
     def instance_flatten(self, instance):
         """


Test patch:
diff --git a/test_autofit/mapper/model/test_derived_quantities.py b/test_autofit/mapper/model/test_derived_quantities.py
index caed3a39a..431587337 100644
--- a/test_autofit/mapper/model/test_derived_quantities.py
+++ b/test_autofit/mapper/model/test_derived_quantities.py
@@ -122,4 +122,46 @@ def test_derived_quantities_summary_dict(samples):
         "max_log_likelihood_sample": {
             "fwhm": 2.3548200450309493,
         },
-    }
\ No newline at end of file
+    }
+
+
+@pytest.fixture(name="custom_model")
+def make_custom_model():
+    return af.Model(
+        af.Gaussian,
+        custom_derived_quantities={
+            "custom": lambda instance: 2 * instance.centre,
+        },
+    )
+
+
+def test_custom_derived_quantity(custom_model):
+    instance = custom_model.instance_from_prior_medians()
+    assert instance.centre == 0.5
+    assert instance.custom == 1.0
+
+
+def test_custom_derived_in_list(custom_model):
+    assert set(custom_model.derived_quantities) == {
+        ("fwhm",),
+        ("custom",),
+    }
+
+
+def test_custom_derived_samples(samples, custom_model):
+    samples.model = custom_model
+    derived_quantities = samples.derived_quantities_list[0]
+    assert derived_quantities == [2.3548200450309493, 0.0]
+
+
+def test_custom_derived_summary(samples, custom_model):
+    samples.model = custom_model
+    assert (
+        derived_quantity_summary(samples, median_pdf_model=False)
+        == """
+
+Summary (3.0 sigma limits):
+
+fwhm          2.3548 (2.3548, 2.3548)
+custom        0.0000 (0.0000, 0.0000)"""
+    )