"""
Truncation.
Example usage
-------------
Simple distribution to start with::
>>> distribution = chaospy.Normal(0, 1)
>>> distribution.inv([0.9, 0.99, 0.999]).round(4)
array([1.2816, 2.3263, 3.0902])
Same distribution, but with a upper truncation::
>>> right_trunc = chaospy.Trunc(chaospy.Normal(0, 1), upper=1)
>>> right_trunc
Trunc(Normal(mu=0, sigma=1), upper=1)
>>> right_trunc.inv([0.9, 0.99, 0.999]).round(4)
array([0.6974, 0.9658, 0.9965])
Same, but with lower truncation::
>>> left_trunc = chaospy.Trunc(chaospy.Normal(0, 1), lower=1)
>>> left_trunc
Trunc(Normal(mu=0, sigma=1), lower=1)
>>> left_trunc.inv([0.001, 0.01, 0.1]).round(4)
array([1.0007, 1.0066, 1.0679])
"""
import numpy
import chaospy
from ..baseclass import Distribution, OperatorDistribution
[docs]class Trunc(Distribution):
"""Truncation."""
[docs] 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 get_parameters(self, idx, cache, assert_numerical=True):
parameters = super(Trunc, self).get_parameters(
idx, cache, assert_numerical=assert_numerical
)
assert set(parameters) == {"cache", "lower", "upper", "idx"}
if isinstance(parameters["lower"], Distribution):
parameters["lower"] = parameters["lower"]._get_cache(
idx, cache=parameters["cache"], get=0
)
elif len(parameters["lower"]) > 1 and idx is not None:
parameters["lower"] = parameters["lower"][idx]
if isinstance(parameters["upper"], Distribution):
parameters["upper"] = parameters["upper"]._get_cache(
idx, cache=parameters["cache"], get=0
)
elif len(parameters["upper"]) > 1 and idx is not None:
parameters["upper"] = parameters["upper"][idx]
if assert_numerical:
assert not isinstance(parameters["lower"], Distribution) or not isinstance(
parameters["upper"], Distribution
)
if idx is None:
del parameters["idx"]
return parameters
def _lower(self, idx, lower, upper, cache):
"""
Distribution lower bound.
Examples:
>>> chaospy.Trunc(chaospy.Uniform(), upper=0.6).lower
array([0.])
>>> chaospy.Trunc(chaospy.Uniform(), lower=0.6).lower
array([0.6])
"""
del upper
if isinstance(lower, Distribution):
lower = lower._get_lower(idx, cache=cache)
return lower
def _upper(self, idx, lower, upper, cache):
"""
Distribution lower bound.
Examples:
>>> chaospy.Trunc(chaospy.Uniform(), upper=0.6).upper
array([0.6])
>>> chaospy.Trunc(chaospy.Uniform(), lower=0.6).upper
array([1.])
"""
del lower
if isinstance(upper, Distribution):
upper = upper._get_upper(idx, cache=cache)
return upper
def _cdf(self, xloc, idx, lower, upper, cache):
"""
Cumulative distribution function.
Example:
>>> chaospy.Uniform().fwd([-0.5, 0.3, 0.7, 1.2])
array([0. , 0.3, 0.7, 1. ])
>>> chaospy.Trunc(chaospy.Uniform(), upper=0.4).fwd([-0.5, 0.2, 0.8, 1.2])
array([0. , 0.5, 1. , 1. ])
>>> chaospy.Trunc(chaospy.Uniform(), lower=0.6).fwd([-0.5, 0.2, 0.8, 1.2])
array([0. , 0. , 0.5, 1. ])
"""
assert not isinstance(lower, Distribution)
assert not isinstance(upper, Distribution)
lower = numpy.broadcast_to(lower, xloc.shape)
upper = numpy.broadcast_to(upper, xloc.shape)
lower = self._dist._get_fwd(lower, idx, cache=cache.copy())
upper = self._dist._get_fwd(upper, idx, cache=cache.copy())
uloc = self._dist._get_fwd(xloc, idx, cache)
return (uloc - lower) / (1 - lower) / upper
def _pdf(self, xloc, idx, lower, upper, cache):
"""
Probability density function.
Example:
>>> dist = chaospy.Trunc(chaospy.Uniform(), upper=0.6)
>>> dist.pdf([-0.25, 0.25, 0.5, 0.75, 1.25])
array([0. , 1.66666667, 1.66666667, 0. , 0. ])
>>> dist = chaospy.Trunc(chaospy.Uniform(), upper=0.4)
>>> dist.pdf([-0.25, 0.25, 0.5, 0.75, 1.25])
array([0. , 2.5, 0. , 0. , 0. ])
>>> dist = chaospy.Trunc(chaospy.Uniform(), lower=0.4)
>>> dist.pdf([-0.25, 0.25, 0.5, 0.75, 1.25])
array([0. , 0. , 1.66666667, 1.66666667, 0. ])
>>> dist = chaospy.Trunc(chaospy.Uniform(), lower=0.6)
>>> dist.pdf([-0.25, 0.25, 0.5, 0.75, 1.25])
array([0. , 0. , 0. , 2.5, 0. ])
"""
assert not isinstance(lower, Distribution)
assert not isinstance(upper, Distribution)
lower = numpy.broadcast_to(lower, xloc.shape)
upper = numpy.broadcast_to(upper, xloc.shape)
lower = self._dist._get_fwd(lower, idx, cache=cache.copy())
upper = self._dist._get_fwd(upper, idx, cache=cache.copy())
uloc = self._dist._get_pdf(xloc, idx, cache=cache)
return uloc / (1 - lower) / upper
def _ppf(self, qloc, idx, lower, upper, cache):
"""
Point percentile function.
Example:
>>> chaospy.Uniform().inv([0.1, 0.2, 0.9])
array([0.1, 0.2, 0.9])
>>> chaospy.Trunc(chaospy.Uniform(), upper=0.4).inv([0.1, 0.2, 0.9])
array([0.04, 0.08, 0.36])
>>> chaospy.Trunc(chaospy.Uniform(), lower=0.6).inv([0.1, 0.2, 0.9])
array([0.64, 0.68, 0.96])
"""
assert not isinstance(lower, Distribution)
assert not isinstance(upper, Distribution)
lower = numpy.broadcast_to(lower, qloc.shape)
upper = numpy.broadcast_to(upper, qloc.shape)
lower = self._dist._get_fwd(lower, idx, cache=cache.copy())
upper = self._dist._get_fwd(upper, idx, cache=cache.copy())
return self._dist._get_inv(qloc * upper * (1 - lower) + lower, idx, cache=cache)