Source code for pystra.distributions.distribution

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-

import numpy as np
from scipy import special as sp
import matplotlib.pyplot as plt
from scipy.stats._distn_infrastructure import rv_frozen


[docs]class StdNormal: """ A performant implementation of the standard normal distribution providing the basic functions PDF, CDF, and inverse CDF, since these are used frequently in the algorithms. """ @staticmethod def pdf(u): p = np.exp(-0.5 * u**2) / np.sqrt(2 * np.pi) return p @staticmethod def cdf(u): p = 0.5 + sp.erf(u / np.sqrt(2)) / 2 return p @staticmethod def ppf(p): u = sp.erfinv(2 * p - 1) * np.sqrt(2) return u
[docs]class Constant: """ A deterministic variable in the limit state function """ def __init__(self, name, val): self.name = name self.val = val def getName(self): return self.name def getValue(self): return self.val
[docs]class Distribution: """Probability distribution Attributes: name (str): Name of the random variable\n dist_obj (SciPy rv): if subclassing SciPy distribution mean (float): Mean or other variable\n stdv (float): Standard deviation or other variable\n startpoint (float): Start point for seach\n p1 (float): Parameter for the distribution\n p2 (float): Parameter for the distribution\n p3 (float): Parameter for the distribution\n p4 (float): Parameter for the distribution\n input_type (any): Change meaning of mean and stdv\n Default: all values """ std_normal = StdNormal() def __init__(self, name="", dist_obj=None, mean=None, stdv=None, startpoint=None): self.name = name self.dist_type = "BaseCls" # This is the key object that is to be defined in derived classes that # are using the base class functionality self.dist_obj = dist_obj self._update_moments(mean, stdv) self.setStartPoint(startpoint) def __repr__(self): string = self.name + ": " + self.dist_type + " distribution" return string def _update_moments(self, mean=None, stdv=None): if self.dist_obj is not None: self.mean = self.dist_obj.mean() self.stdv = self.dist_obj.std() elif mean is None or stdv is None: raise Exception("Mean and std dev must be defined in derived classes") else: self.mean = mean self.stdv = stdv if not np.isfinite(self.stdv): raise Exception("Std. deviation must be a positive noninfinite number.") def getName(self): return self.name def getMean(self): return self.mean def getStdv(self): return self.stdv def getStartPoint(self): return self.startpoint def setStartPoint(self, startpoint=None): if startpoint is None: self.startpoint = self.mean else: self.startpoint = startpoint # The following can be overridden by derived classes to implement more # efficient calculations where desirable
[docs] def pdf(self, x): """ Probability density function """ return self.dist_obj.pdf(x)
[docs] def cdf(self, x): """ Cumulative distribution function """ return self.dist_obj.cdf(x)
[docs] def ppf(self, u): """ Inverse cumulative distribution function """ return self.dist_obj.ppf(u)
[docs] def u_to_x(self, u): """ Transformation from u to x """ return self.dist_obj.ppf(self.std_normal.cdf(u))
[docs] def x_to_u(self, x): """ Transformation from x to u """ u = self.std_normal.ppf(self.cdf(x)) return u
[docs] def jacobian(self, u, x): """ Compute the Jacobian """ pdf1 = self.pdf(x) pdf2 = self.std_normal.pdf(u) J = np.diag(pdf1 / pdf2) return J
[docs] def sample(self, n=1000): """ Return a sample of the distribution of length n """ u = np.random.rand(n) samples = self.ppf(u) return samples
[docs] def plot(self, ax=None, **kwargs): """ Plots the PDF of the distribution """ # auto-range samples = self.sample() x = np.linspace(np.min(samples), np.max(samples), 100) show = False if ax is None: show = True _, ax = plt.subplots() ax.plot(x, self.pdf(x), label=self.name, **kwargs) ax.legend() if show: plt.show() return ax
# The following must be overidden in derived classes that are not based on a # SciPy distribution object, or using the SciPy object for calculations. def set_location(self, loc=0): if isinstance(self.dist_obj, rv_frozen): pdict = self.dist_obj.kwds pdict["loc"] = loc self._update_moments() else: raise Exception("Distribution is not a SciPy object") def set_scale(self, scale=1): if isinstance(self.dist_obj, rv_frozen): pdict = self.dist_obj.kwds pdict["scale"] = scale self._update_moments() else: raise Exception("Distribution is not a SciPy object")