Source code for autofit.mapper.prior.uniform

import numpy as np
from typing import Optional, Tuple

from autofit.messages.normal import UniformNormalMessage
from .abstract import Prior
from .abstract import epsilon
from ...messages.composed_transform import TransformedMessage
from ...messages.transform import LinearShiftTransform

from autofit import exc

[docs] class UniformPrior(Prior): __identifier_fields__ = ("lower_limit", "upper_limit") __database_args__ = ("lower_limit", "upper_limit", "id_") def __init__( self, lower_limit: float = 0.0, upper_limit: float = 1.0, id_: Optional[int] = None, ): """ A prior with a uniform distribution, defined between a lower limit and upper limit. The conversion of an input unit value, ``u``, to a physical value, ``p``, via the prior is as follows: .. math:: For example for ``prior = UniformPrior(lower_limit=0.0, upper_limit=2.0)``, an input ``prior.value_for(unit=0.5)`` is equal to 1.0. [Rich describe how this is done via message] Parameters ---------- lower_limit The lower limit of the uniform distribution defining the prior. upper_limit The upper limit of the uniform distribution defining the prior. Examples -------- prior = af.UniformPrior(lower_limit=0.0, upper_limit=2.0) physical_value = prior.value_for(unit=0.2) """ self.lower_limit = float(lower_limit) self.upper_limit = float(upper_limit) if self.lower_limit >= self.upper_limit: raise exc.PriorException( "The upper limit of a prior must be greater than its lower limit" ) message = TransformedMessage( UniformNormalMessage, LinearShiftTransform(shift=self.lower_limit, scale=self.upper_limit - self.lower_limit), ) super().__init__( message, id_=id_, )
[docs] def tree_flatten(self): """Flatten this prior into a JAX-compatible PyTree representation. Returns ------- tuple A (children, aux_data) pair where children are (lower_limit, upper_limit, id). """ return (self.lower_limit, self.upper_limit, self.id), ()
@property def width(self): """The width of the uniform distribution (upper_limit - lower_limit).""" return self.upper_limit - self.lower_limit
[docs] def with_limits( self, lower_limit: float, upper_limit: float, ) -> "Prior": """Create a new UniformPrior with different bounds. Parameters ---------- lower_limit The new lower bound. upper_limit The new upper bound. """ return UniformPrior( lower_limit=lower_limit, upper_limit=upper_limit, )
[docs] def logpdf(self, x): """Compute the log probability density at x. Adjusts boundary values by epsilon to avoid evaluating exactly at the distribution edges where the PDF is undefined. Parameters ---------- x The value at which to evaluate the log PDF. """ # TODO: handle x as a numpy array if x == self.lower_limit: x += epsilon elif x == self.upper_limit: x -= epsilon return self.message.logpdf(x)
[docs] def dict(self) -> dict: """ Return a dictionary representation of this GaussianPrior instance, including mean and sigma. Returns ------- Dictionary containing prior parameters. """ prior_dict = super().dict() return {**prior_dict, "lower_limit": self.lower_limit, "upper_limit": self.upper_limit}
@property def parameter_string(self) -> str: """A human-readable string summarizing the prior's lower and upper limits.""" return f"lower_limit = {self.lower_limit}, upper_limit = {self.upper_limit}"
[docs] def value_for(self, unit, xp=np): """ Returns a physical value from an input unit value according to the limits of the uniform prior. Parameters ---------- unit A unit value between 0 and 1. xp Array-module to dispatch on (``numpy`` or ``jax.numpy``). Default ``numpy``. The NumPy path preserves the historical ``float(round(..., 14))`` snap (used as a hash key in ``model.priors``); the JAX path uses the closed-form ``lower + (upper - lower) * unit`` so the trace stays symbolic. Returns ------- value The unit value mapped to a physical value according to the prior. Examples -------- prior = af.UniformPrior(lower_limit=0.0, upper_limit=2.0) physical_value = prior.value_for(unit=0.2) """ if xp is np: return float( round(super().value_for(unit), 14) ) return self.lower_limit + (self.upper_limit - self.lower_limit) * unit
[docs] def log_prior_from_value(self, value, xp=np): """ Returns the log prior of a physical value, so the log likelihood of a model evaluation can be converted to a posterior as log_prior + log_likelihood. This is used by certain non-linear searches (e.g. Emcee) in the log likelihood function evaluation. For a UniformPrior this is always zero, provided the value is between the lower and upper limit. """ if xp is np: return 0.0 in_bounds = (value >= self.lower_limit) & (value <= self.upper_limit) return xp.where(in_bounds, xp.zeros_like(value), -xp.inf)
@property def limits(self) -> Tuple[float, float]: """The (lower_limit, upper_limit) bounds of this uniform prior.""" return self.lower_limit, self.upper_limit