lanl / SEPIA

Simulation-Enabled Prediction, Inference, and Analysis: physics-informed statistical learning.
Other
29 stars 6 forks source link

allowing custom priors #3

Open natalieklein229 opened 4 years ago

natalieklein229 commented 4 years ago

we only allow certain prior distributions -- could extend so that users can define their own priors, as long as they are extending SepiaPrior (so each prior type should be a child class to SepiaPrior, so they can be easily extended, maybe)

luiarthur commented 2 years ago

I think this is related to #27 .

Something like:

class SepiaDistribution:
    def logpdf(self, x):
        """Evaluate and return the log density at `x`."""
        pass

    def support(self):
        """Return the support."""
        pass

    def in_support(self, x):
        """Return `True` if `x` is within the support. Otherwise, return `False`."""
        pass

    # NOTE: I think the following methods could be helpful if we consider using a multivariate
    # Gaussian proposal for parameters in their unconstrained space. This would involve/require
    # transforming priors so that they are unconstrained. (e.g. If X ~ Gamma(a, b), then propose a
    # new value for log(x), and add the appropriate log absolute Jacobian.)

    def to_constrained_space(self, unconstrained_x):
        """Transform `unconstrained_x` with support on the real line / ball into its support.""" 
        pass

    def log_abs_det_jacobian(self, x, unconstrained_x):
        """
        Compute the log absolute determinant of the jacobian given the parameter's
        constrained and unconstrained values.
        """
        pass

    def logpdf_plus_log_abs_det_jacobian(self, unconstrained_x):
        """
        Compute the log density plus the log absolute value of the determinant of the jacobian
        given a parameter's constrained and unconstrained values.
        """
        x = self.to_constrained_space(unconstrained_x)
        return self.logpdf(x) + self.log_abs_det_jacobian(self, x, unconstrained_x)

class Uniform(SepiaDistribution):
    def __init__(self, lower=0, upper=1):
        self.shape = np.broadcast(lower, upper).shape
        self.lower = np.full(self.shape, lower)
        self.upper = np.full(self.shape, upper)
        self.params = (self.lower, self.upper)

    def support(self):
        return self.params

    def in_support(self, x):
        out_of_support = np.any(x < self.lower) | np.any(x > self.upper)
        return not out_of_support

    def logpdf(self, x):
        if self.in_support(x):
            return -np.log(upper - lower)
        else:
            return -np.NINF

    # etc.

class Gamma(SepiaDistribution):
    pass

class Normal(SepiaDistribution):
    pass

class Beta(SepiaDistribution):
    pass

class MyCustomDistribution(SepiaDistribution):
    pass