def __init__(self, left, right): """ Constructor. Args: left (Distribution, numpy.ndarray): Left hand side. right (Distribution, numpy.ndarray): Right hand side. """ repr_args = [left, right] exclusion = None if isinstance(left, Distribution): if left.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Joint distribution with dependencies not supported.") else: left = numpy.atleast_1d(left) if isinstance(right, Distribution): if right.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Joint distribution with dependencies not supported.") else: right = numpy.atleast_1d(right) super(Trunc, self).__init__( left=left, right=right, repr_args=repr_args, )
def __init__(self, dist, lower=None, upper=None): """ Constructor. Args: dist (Distribution): Distribution to be truncated. lower (Distribution, numpy.ndarray): Lower truncation bound. upper (Distribution, numpy.ndarray): Upper truncation bound. """ assert isinstance(dist, Distribution) repr_args = [dist] repr_args += chaospy.format_repr_kwargs(lower=(lower, None)) repr_args += chaospy.format_repr_kwargs(upper=(upper, None)) exclusion = set() for deps in dist._dependencies: exclusion.update(deps) if isinstance(lower, Distribution): if lower.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Joint distribution with dependencies not supported." ) assert len(dist) == len(lower) lower_ = lower.lower elif lower is None: lower = lower_ = dist.lower else: lower = lower_ = numpy.atleast_1d(lower) if isinstance(upper, Distribution): if upper.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Joint distribution with dependencies not supported." ) assert len(dist) == len(upper) upper_ = upper.upper elif upper is None: upper = upper_ = dist.upper else: upper = upper_ = numpy.atleast_1d(upper) assert numpy.all( upper_ > lower_ ), "condition `upper > lower` not satisfied: %s <= %s" % (upper_, lower_) dependencies, parameters, rotation = chaospy.declare_dependencies( distribution=self, parameters=dict(lower=lower, upper=upper), length=len(dist), ) super(Trunc, self).__init__( parameters=parameters, dependencies=dependencies, exclusion=exclusion, repr_args=repr_args, ) self._dist = dist
def _mom(self, key, left, right, cache): """ Statistical moments. Example: >>> chaospy.Uniform().mom([0, 1, 2, 3]).round(4) array([1. , 0.5 , 0.3333, 0.25 ]) >>> Multiply(chaospy.Uniform(), 2).mom([0, 1, 2, 3]).round(4) array([1. , 1. , 1.3333, 2. ]) >>> Multiply(2, chaospy.Uniform()).mom([0, 1, 2, 3]).round(4) array([1. , 1. , 1.3333, 2. ]) >>> Multiply(chaospy.Uniform(), chaospy.Uniform()).mom([0, 1, 2, 3]).round(4) array([1. , 0.25 , 0.1111, 0.0625]) """ del cache if isinstance(left, Distribution): if chaospy.shares_dependencies(left, right): raise chaospy.StochasticallyDependentError( "product of dependent distributions not feasible: " "{} and {}".format(left, right) ) left = left._get_mom(key) else: left = (numpy.array(left).T ** key).T if isinstance(right, Distribution): right = right._get_mom(key) else: right = (numpy.array(right).T ** key).T return numpy.prod(left) * numpy.prod(right)
def cdf(self, x_data): """ Cumulative distribution function. Note that chaospy only supports cumulative distribution functions for stochastically independent distributions. Args: x_data (numpy.ndarray): Location for the distribution function. Assumes that ``len(x_data) == len(distribution)``. Returns: (numpy.ndarray): Evaluated distribution function values, where output has shape ``x_data.shape`` in one dimension and ``x_data.shape[1:]`` in higher dimensions. """ check_dependencies(self) if self.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Cumulative distribution does not support dependencies.") x_data = numpy.asarray(x_data) if self.interpret_as_integer: x_data = x_data + 0.5 q_data = self.fwd(x_data) if len(self) > 1: q_data = numpy.prod(q_data, 0) return q_data
def declare_dependencies( distribution, parameters, rotation=None, is_operator=False, dependency_type="iid", length=None, ): """ Convenience function for declaring distribution dependencies. Iterates through parameters to figure out what a distribution dependency structure should be. Args: distribution (chaospy.Distribution): The distributions to to declare dependencies for. parameters (Dict[str, Any]): The distribution parameters that should be included in the declaration. is_operator (bool): Operators do not themselves contain uncertainty, but only inherits from parameters and/or wrapped distribution. wrapper_dist (Optional[chaospy.Distribution]): Distributions that are thin-wrappers to some other distribution should inherent dependencies. """ parameters = parameters.copy() for name, parameter in list(parameters.items()): if not isinstance(parameter, chaospy.Distribution): parameters[name] = numpy.atleast_1d(parameter) if length is None: if rotation is None: length = max([len(parameter) for parameter in parameters.values()] + [1]) else: length = len(rotation) if rotation is None: rotation = numpy.arange(length, dtype=int) else: rotation = numpy.asarray(rotation) if is_operator: dependencies = [set() for _ in range(length)] else: dependencies = init_dependencies(distribution, rotation, dependency_type=dependency_type) for name, parameter in list(parameters.items()): if isinstance(parameter, chaospy.Distribution): if len(parameter) != length: raise chaospy.StochasticallyDependentError( "dependencies must be same length as parent") for dep1, dep2 in zip(dependencies, parameter._dependencies): dep1.update(dep2) else: parameters[name] = parameter * numpy.ones(length, dtype=int) return dependencies, parameters, rotation
def _ttr(self, kloc, idx, mean, sigma, dim, mut, cache): if dim > 1: raise chaospy.StochasticallyDependentError( "TTR require stochastically independent components.") coeff0, coeff1 = self._dist._get_ttr(kloc, idx) coeff0 = coeff0 * sigma + mean[dim] coeff1 = coeff1 * sigma * sigma return coeff0, coeff1
def _ttr(self, kloc, idx, left, right, cache): """Three terms recurrence coefficients.""" del cache if isinstance(right, Distribution): if isinstance(left, Distribution): raise chaospy.StochasticallyDependentError( "product of distributions not feasible: " "{} and {}".format(left, right)) left, right = right, left coeff0, coeff1 = left._get_ttr(kloc, idx) return coeff0 * right, coeff1 * right * right
def get_parameters(self, idx, cache, assert_numerical=True): parameters = super(MeanCovarianceDistribution, self).get_parameters( idx, cache, assert_numerical=assert_numerical) mean = parameters["mean"] if idx is None: return dict(mean=mean, sigma=self._covariance, cache=cache) if isinstance(mean, Distribution): mean = [ mean._get_cache(dim, cache, get=0) for dim in range(len(mean)) ] if any([ isinstance(condition, chaospy.Distribution) for condition in mean ]): raise chaospy.StochasticallyDependentError( "Dangling dependency: %s | %s" % (self, mean)) mean = numpy.array(mean) mean = mean[self._rotation] dim = self._rotation.index(idx) if dim: covinv = numpy.linalg.inv(self._pcovariance[:dim, :dim]) sigma = numpy.sqrt(self._pcovariance[dim, dim] - self._pcovariance[ dim, :dim].dot(covinv).dot(self._pcovariance[:dim, dim])) mu_transform = self._pcovariance[dim, :dim].dot(covinv) assert numpy.isfinite(sigma), (dim, self._pcovariance) else: sigma = numpy.sqrt(self._pcovariance[0, 0]) mu_transform = 0 return dict(idx=idx, mean=mean, sigma=sigma, dim=dim, mut=mu_transform, cache=cache)
def _mom(self, keys, left, right, cache): """ Statistical moments. Example: >>> chaospy.Uniform().mom([0, 1, 2, 3]).round(4) array([1. , 0.5 , 0.3333, 0.25 ]) >>> chaospy.Add(chaospy.Uniform(), 2).mom([0, 1, 2, 3]).round(4) array([ 1. , 2.5 , 6.3333, 16.25 ]) >>> chaospy.Add(2, chaospy.Uniform()).mom([0, 1, 2, 3]).round(4) array([ 1. , 2.5 , 6.3333, 16.25 ]) """ del cache keys_ = numpy.mgrid[tuple(slice(0, key + 1, 1) for key in keys)] keys_ = keys_.reshape(len(self), -1) if isinstance(left, Distribution): if chaospy.shares_dependencies(left, right): raise chaospy.StochasticallyDependentError( "%s: left and right side of sum stochastically dependent." % self) left = [left._get_mom(key) for key in keys_.T] else: left = list(reversed(numpy.array(left).T**keys_.T)) if isinstance(right, Distribution): right = [right._get_mom(key) for key in keys_.T] else: right = list( reversed(numpy.prod(numpy.array(right).T**keys_.T, -1))) out = 0.0 for idx in range(keys_.shape[1]): key = keys_.T[idx] coef = numpy.prod(comb(keys, key)) out += coef * left[idx] * right[idx] * numpy.all(key <= keys) return out
def __init__(self, distribution): from openturns import ComposedDistribution, ContinuousDistribution if isinstance(distribution, ComposedDistribution): if not distribution.hasIndependentCopula(): raise chaospy.StochasticallyDependentError( "Stochastically dependent " "OpenTURNS distribution unsupported") distributions = [ openturns_dist(dist) for dist in distribution.getDistributionCollection() ] elif isinstance(distribution, ContinuousDistribution): distributions = [openturns_dist(distribution)] else: assert isinstance(distribution, Iterable) and all( [ isinstance(dist, ContinuousDistribution) for dist in distribution ] ), "Only (iterable of) continuous OpenTURNS distributions supported" distributions = [openturns_dist(dist) for dist in distribution] super(OpenTURNSDist, self).__init__(*distributions) self._repr_args = [distributions]
def check_dependencies(distribution): """ Check if the dependency structure is valid. Rosenblatt transformations, density calculations etc. assumes that the input and output of transformation is the same. It also assumes that there is a order defined in `distribution._rotation` so an decomposition on the form `p(x0), p(x1|x0), ...` is possible. This should be checked on function calls, not init, as it does not apply to e.g. moment and three-terms-recurrence calculations. Args: distribution: The distribution to verify is correctly set up. Raises: StochasticallyDependentError: If invalid dependency structure is present. Examples: >>> dist = chaospy.Uniform(0, 1) >>> chaospy.check_dependencies(dist) >>> chaospy.check_dependencies(chaospy.Normal(mu=dist)) Traceback (most recent call last): ... chaospy.StochasticallyDependentError: \ Normal(mu=Uniform(), sigma=1) has dangling dependencies """ current = set() for idx in distribution._rotation: length = len(current) current.update(distribution._dependencies[idx]) if len(current) != length + 1: raise chaospy.StochasticallyDependentError( "%s has dangling dependencies" % distribution)
def quad_leja( order, dist, rule="fejer", accuracy=100, recurrence_algorithm="", ): """ Generate Leja quadrature node. Args: order (int): The order of the quadrature. dist (chaospy.distributions.baseclass.Distribution): The distribution which density will be used as weight function. rule (str): In the case of ``lanczos`` or ``stieltjes``, defines the proxy-integration scheme. accuracy (int): In the case ``rule`` is used, defines the quadrature order of the scheme used. In practice, must be at least as large as ``order``. recurrence_algorithm (str): Name of the algorithm used to generate abscissas and weights. If omitted, ``analytical`` will be tried first, and ``stieltjes`` used if that fails. Returns: (numpy.ndarray, numpy.ndarray): abscissas: The quadrature points for where to evaluate the model function with ``abscissas.shape == (len(dist), N)`` where ``N`` is the number of samples. weights: The quadrature weights with ``weights.shape == (N,)``. Example: >>> abscissas, weights = quad_leja(3, chaospy.Normal(0, 1)) >>> abscissas.round(4) array([[-2.7173, -1.4142, 0. , 1.7635]]) >>> weights.round(4) array([0.022 , 0.1629, 0.6506, 0.1645]) """ if len(dist) > 1: if dist.stochastic_depedent: raise chaospy.StochasticallyDependentError( "Leja quadrature do not supper distribution with dependencies." ) if isinstance(order, int): out = [quad_leja(order, _) for _ in dist] else: out = [quad_leja(order[_], dist[_]) for _ in range(len(dist))] abscissas = [_[0][0] for _ in out] weights = [_[1] for _ in out] abscissas = combine(abscissas).T weights = combine(weights) weights = numpy.prod(weights, -1) return abscissas, weights abscissas = [dist.lower, dist.mom(1).flatten(), dist.upper] for _ in range(int(order)): def objective(abscissas_): """Local objective function.""" out = -numpy.sqrt(dist.pdf(abscissas_)) * numpy.prod( numpy.abs(abscissas[1:-1] - abscissas_)) return out def fmin(idx): """Bound minimization.""" try: x, fx = fminbound(objective, abscissas[idx], abscissas[idx + 1], full_output=1)[:2] except UnboundLocalError: x = abscissas[idx] + 0.5 * (3 - 5**0.5) * (abscissas[idx + 1] - abscissas[idx]) fx = objective(x) return x, fx opts, vals = zip(*[fmin(idx) for idx in range(len(abscissas) - 1)]) index = numpy.argmin(vals) abscissas.insert(index + 1, opts[index]) abscissas = numpy.asfarray(abscissas).flatten()[1:-1] weights = create_weights(abscissas, dist, rule, accuracy, recurrence_algorithm) abscissas = abscissas.reshape(1, abscissas.size) return numpy.asfarray(abscissas), numpy.asfarray(weights).flatten()
def _mom(self, k, lo, up): if self.unbound: return numpy.prod(numpy.mean(self.samples.T**k, -1)) raise chaospy.StochasticallyDependentError("component lack support")
def _ttr(self, kloc, parent, parameters): raise chaospy.StochasticallyDependentError("TTR not supported")
def declare_dependencies( distribution, parameters, rotation=None, is_operator=False, dependency_type="iid", length=None, extra_parameters=None, ): """ Convenience function for declaring distribution dependencies. Iterates through parameters to figure out what a distribution dependency structure should be. Args: distribution (chaospy.Distribution): The distributions to to declare dependencies for. parameters (Dict[str, Any]): The distribution parameters that should be included in the declaration. rotation (Optional[Sequence[int]]): The order of which the dependencies should be resolved. Automatically calculated if omitted. is_operator (bool): Operators do not themselves contain uncertainty, but only inherits from parameters and/or wrapped distribution. wrapper_dist (Optional[chaospy.Distribution]): Distributions that are thin-wrappers to some other distribution should inherent dependencies. extra_parameters(Optional[Dict[str, Any]]): Extra parameters that should be included in the declaration, but not considered a direct part of the distribution. Assumed to be pre-processed. Returns: dependencies (List[Set[int]]): Dependency reference numbers, one collection per dimension. Single element implies stochstically independent. parameters (Dict[str, Any]): Same as `parameters`, but updated to all conform to the same size. rotation (List[int]): Same as `rotation` if provided. If not, the automatically calculated one is returned. """ extra_parameters = extra_parameters.copy() if extra_parameters else {} parameters = parameters.copy() for name, parameter in list(parameters.items()): if not isinstance(parameter, chaospy.Distribution): parameters[name] = numpy.atleast_1d(parameter) if length is None: if rotation is None: length = max( [len(parameter) for parameter in extra_parameters.values()] + [len(parameter) for parameter in parameters.values()] + [1]) else: length = len(rotation) if rotation is None: rotation = numpy.arange(length, dtype=int) else: rotation = numpy.asarray(rotation) if is_operator: dependencies = [set() for _ in range(length)] else: dependencies = init_dependencies(distribution, rotation, dependency_type=dependency_type) for name, parameter in list(parameters.items()): if isinstance(parameter, chaospy.Distribution): if len(parameter) != length: raise chaospy.StochasticallyDependentError( "dependencies must be same length as parent") else: parameters[name] = parameter * numpy.ones(length, dtype=int) for name, parameter in list(parameters.items()) + list( extra_parameters.items()): if isinstance(parameter, chaospy.Distribution): for dep1, dep2 in zip(dependencies, parameter._dependencies): dep1.update(dep2) assert len(dependencies) == length return dependencies, parameters, rotation
def quad_leja( order, dist, rule="fejer", ): """ Generate Leja quadrature node. Args: order (int): The order of the quadrature. dist (chaospy.distributions.baseclass.Distribution): The distribution which density will be used as weight function. rule (str): In the case of ``lanczos`` or ``stieltjes``, defines the proxy-integration scheme. Returns: (numpy.ndarray, numpy.ndarray): abscissas: The quadrature points for where to evaluate the model function with ``abscissas.shape == (len(dist), N)`` where ``N`` is the number of samples. weights: The quadrature weights with ``weights.shape == (N,)``. Notes: Implemented as proposed in Narayan and Jakeman :cite:`narayan_adaptive_2014`. Example: >>> abscissas, weights = quad_leja( ... 2, chaospy.Iid(chaospy.Normal(0, 1), 2)) >>> abscissas.round(2) array([[-1.41, -1.41, -1.41, 0. , 0. , 0. , 1.76, 1.76, 1.76], [-1.41, 0. , 1.76, -1.41, 0. , 1.76, -1.41, 0. , 1.76]]) >>> weights.round(3) array([0.05 , 0.133, 0.04 , 0.133, 0.359, 0.107, 0.04 , 0.107, 0.032]) """ if len(dist) > 1: if dist.stochastic_dependent: raise chaospy.StochasticallyDependentError( "Leja quadrature do not supper distribution with dependencies." ) order = numpy.broadcast_to(order, len(dist)) out = [quad_leja(order[_], dist[_]) for _ in range(len(dist))] abscissas = [_[0][0] for _ in out] weights = [_[1] for _ in out] abscissas = combine(abscissas).T weights = combine(weights) weights = numpy.prod(weights, -1) return abscissas, weights abscissas = [dist.lower, dist.mom(1).flatten(), dist.upper] for _ in range(int(order)): def objective(abscissas_): """Local objective function.""" out = -numpy.sqrt(dist.pdf(abscissas_)) * numpy.prod( numpy.abs(abscissas[1:-1] - abscissas_)) return out def fmin(idx): """Bound minimization.""" try: xopt, fval, _, _ = fminbound(objective, abscissas[idx], abscissas[idx + 1], full_output=True) # Hard coded solution to scipy/scipy#11207 for scipy < 1.5.0. except UnboundLocalError: # pragma: no cover xopt = abscissas[idx] + 0.5 * (3 - 5**0.5) * ( abscissas[idx + 1] - abscissas[idx]) fx = objective(xopt) return xopt, fval opts, vals = zip(*[fmin(idx) for idx in range(len(abscissas) - 1)]) index = numpy.argmin(vals) abscissas.insert(index + 1, opts[index]) abscissas = numpy.asfarray(abscissas).flatten()[1:-1] weights = create_weights(abscissas, dist, rule) abscissas = abscissas.reshape(1, abscissas.size) return numpy.asfarray(abscissas), numpy.asfarray(weights).flatten()
def __init__( self, parameters, dependencies, rotation=None, exclusion=None, repr_args=None, ): """ Distribution initializer. In addition to assigning some object variables, also checks for some consistency issues. Args: parameters (Optional[Distribution[str, Union[ndarray, Distribution]]]): Collection of model parameters. dependencies (Optional[Sequence[Set[int]]]): Dependency identifiers. One collection for each dimension. rotation (Optional[Sequence[int]]): The order of which to resolve dependencies. exclusion (Optional[Sequence[int]]): Distributions that has been "taken out of play" and therefore can not be reused other places in the dependency hierarchy. repr_args (Optional[Sequence[str]]): Positional arguments to place in the object string representation. The repr output will then be: `<class name>(<arg1>, <arg2>, ...)`. Raises: StochasticallyDependentError: For dependency structures that can not later be rectified. This include under-defined distributions, and inclusion of distributions that should be exclusion. """ assert isinstance(parameters, dict) self._parameters = parameters self._dependencies = list(dependencies) if rotation is None: rotation = sorted(enumerate(self._dependencies), key=lambda x: len(x[1])) rotation = [key for key, _ in rotation] rotation = list(rotation) assert len(set(rotation)) == len(dependencies) assert min(rotation) == 0 assert max(rotation) == len(dependencies) - 1 self._rotation = rotation if exclusion is None: exclusion = set() self._exclusion = set(exclusion) if repr_args is None: repr_args = ("{}={}".format(key, self._parameters[key]) for key in sorted(self._parameters)) self._repr_args = list(repr_args) self._mom_cache = {(0, ) * len(dependencies): 1.} self._ttr_cache = {} self._indices = {} self._all_dependencies = { dep for deps in self._dependencies for dep in deps } if len(self._all_dependencies) < len(dependencies): raise chaospy.StochasticallyDependentError( "%s is an under-defined probability distribution." % self) for key, param in list(parameters.items()): if isinstance(param, Distribution): if self._all_dependencies.intersection(param._exclusion): raise chaospy.StochasticallyDependentError( ("%s contains dependencies that can not also exist " "other places in the dependency hierarchy") % param) self._exclusion.update(param._exclusion) else: self._parameters[key] = numpy.asarray(param)