chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
SymPy statistics module
|
||||
|
||||
Introduces a random variable type into the SymPy language.
|
||||
|
||||
Random variables may be declared using prebuilt functions such as
|
||||
Normal, Exponential, Coin, Die, etc... or built with functions like FiniteRV.
|
||||
|
||||
Queries on random expressions can be made using the functions
|
||||
|
||||
========================= =============================
|
||||
Expression Meaning
|
||||
------------------------- -----------------------------
|
||||
``P(condition)`` Probability
|
||||
``E(expression)`` Expected value
|
||||
``H(expression)`` Entropy
|
||||
``variance(expression)`` Variance
|
||||
``density(expression)`` Probability Density Function
|
||||
``sample(expression)`` Produce a realization
|
||||
``where(condition)`` Where the condition is true
|
||||
========================= =============================
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import P, E, variance, Die, Normal
|
||||
>>> from sympy import simplify
|
||||
>>> X, Y = Die('X', 6), Die('Y', 6) # Define two six sided dice
|
||||
>>> Z = Normal('Z', 0, 1) # Declare a Normal random variable with mean 0, std 1
|
||||
>>> P(X>3) # Probability X is greater than 3
|
||||
1/2
|
||||
>>> E(X+Y) # Expectation of the sum of two dice
|
||||
7
|
||||
>>> variance(X+Y) # Variance of the sum of two dice
|
||||
35/6
|
||||
>>> simplify(P(Z>1)) # Probability of Z being greater than 1
|
||||
1/2 - erf(sqrt(2)/2)/2
|
||||
|
||||
|
||||
One could also create custom distribution and define custom random variables
|
||||
as follows:
|
||||
|
||||
1. If you want to create a Continuous Random Variable:
|
||||
|
||||
>>> from sympy.stats import ContinuousRV, P, E
|
||||
>>> from sympy import exp, Symbol, Interval, oo
|
||||
>>> x = Symbol('x')
|
||||
>>> pdf = exp(-x) # pdf of the Continuous Distribution
|
||||
>>> Z = ContinuousRV(x, pdf, set=Interval(0, oo))
|
||||
>>> E(Z)
|
||||
1
|
||||
>>> P(Z > 5)
|
||||
exp(-5)
|
||||
|
||||
1.1 To create an instance of Continuous Distribution:
|
||||
|
||||
>>> from sympy.stats import ContinuousDistributionHandmade
|
||||
>>> from sympy import Lambda
|
||||
>>> dist = ContinuousDistributionHandmade(Lambda(x, pdf), set=Interval(0, oo))
|
||||
>>> dist.pdf(x)
|
||||
exp(-x)
|
||||
|
||||
2. If you want to create a Discrete Random Variable:
|
||||
|
||||
>>> from sympy.stats import DiscreteRV, P, E
|
||||
>>> from sympy import Symbol, S
|
||||
>>> p = S(1)/2
|
||||
>>> x = Symbol('x', integer=True, positive=True)
|
||||
>>> pdf = p*(1 - p)**(x - 1)
|
||||
>>> D = DiscreteRV(x, pdf, set=S.Naturals)
|
||||
>>> E(D)
|
||||
2
|
||||
>>> P(D > 3)
|
||||
1/8
|
||||
|
||||
2.1 To create an instance of Discrete Distribution:
|
||||
|
||||
>>> from sympy.stats import DiscreteDistributionHandmade
|
||||
>>> from sympy import Lambda
|
||||
>>> dist = DiscreteDistributionHandmade(Lambda(x, pdf), set=S.Naturals)
|
||||
>>> dist.pdf(x)
|
||||
2**(1 - x)/2
|
||||
|
||||
3. If you want to create a Finite Random Variable:
|
||||
|
||||
>>> from sympy.stats import FiniteRV, P, E
|
||||
>>> from sympy import Rational, Eq
|
||||
>>> pmf = {1: Rational(1, 3), 2: Rational(1, 6), 3: Rational(1, 4), 4: Rational(1, 4)}
|
||||
>>> X = FiniteRV('X', pmf)
|
||||
>>> E(X)
|
||||
29/12
|
||||
>>> P(X > 3)
|
||||
1/4
|
||||
|
||||
3.1 To create an instance of Finite Distribution:
|
||||
|
||||
>>> from sympy.stats import FiniteDistributionHandmade
|
||||
>>> dist = FiniteDistributionHandmade(pmf)
|
||||
>>> dist.pmf(x)
|
||||
Lambda(x, Piecewise((1/3, Eq(x, 1)), (1/6, Eq(x, 2)), (1/4, Eq(x, 3) | Eq(x, 4)), (0, True)))
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'P', 'E', 'H', 'density', 'where', 'given', 'sample', 'cdf','median',
|
||||
'characteristic_function', 'pspace', 'sample_iter', 'variance', 'std',
|
||||
'skewness', 'kurtosis', 'covariance', 'dependent', 'entropy', 'independent',
|
||||
'random_symbols', 'correlation', 'factorial_moment', 'moment', 'cmoment',
|
||||
'sampling_density', 'moment_generating_function', 'smoment', 'quantile',
|
||||
'coskewness', 'sample_stochastic_process',
|
||||
|
||||
'FiniteRV', 'DiscreteUniform', 'Die', 'Bernoulli', 'Coin', 'Binomial',
|
||||
'BetaBinomial', 'Hypergeometric', 'Rademacher', 'IdealSoliton', 'RobustSoliton',
|
||||
'FiniteDistributionHandmade',
|
||||
|
||||
'ContinuousRV', 'Arcsin', 'Benini', 'Beta', 'BetaNoncentral', 'BetaPrime',
|
||||
'BoundedPareto', 'Cauchy', 'Chi', 'ChiNoncentral', 'ChiSquared', 'Dagum', 'Davis', 'Erlang',
|
||||
'ExGaussian', 'Exponential', 'ExponentialPower', 'FDistribution',
|
||||
'FisherZ', 'Frechet', 'Gamma', 'GammaInverse', 'Gompertz', 'Gumbel',
|
||||
'Kumaraswamy', 'Laplace', 'Levy', 'Logistic','LogCauchy', 'LogLogistic', 'LogitNormal', 'LogNormal', 'Lomax',
|
||||
'Moyal', 'Maxwell', 'Nakagami', 'Normal', 'GaussianInverse', 'Pareto', 'PowerFunction',
|
||||
'QuadraticU', 'RaisedCosine', 'Rayleigh','Reciprocal', 'StudentT', 'ShiftedGompertz',
|
||||
'Trapezoidal', 'Triangular', 'Uniform', 'UniformSum', 'VonMises', 'Wald',
|
||||
'Weibull', 'WignerSemicircle', 'ContinuousDistributionHandmade',
|
||||
|
||||
'FlorySchulz', 'Geometric','Hermite', 'Logarithmic', 'NegativeBinomial', 'Poisson', 'Skellam',
|
||||
'YuleSimon', 'Zeta', 'DiscreteRV', 'DiscreteDistributionHandmade',
|
||||
|
||||
'JointRV', 'Dirichlet', 'GeneralizedMultivariateLogGamma',
|
||||
'GeneralizedMultivariateLogGammaOmega', 'Multinomial', 'MultivariateBeta',
|
||||
'MultivariateEwens', 'MultivariateT', 'NegativeMultinomial',
|
||||
'NormalGamma', 'MultivariateNormal', 'MultivariateLaplace', 'marginal_distribution',
|
||||
|
||||
'StochasticProcess', 'DiscreteTimeStochasticProcess',
|
||||
'DiscreteMarkovChain', 'TransitionMatrixOf', 'StochasticStateSpaceOf',
|
||||
'GeneratorMatrixOf', 'ContinuousMarkovChain', 'BernoulliProcess',
|
||||
'PoissonProcess', 'WienerProcess', 'GammaProcess',
|
||||
|
||||
'CircularEnsemble', 'CircularUnitaryEnsemble',
|
||||
'CircularOrthogonalEnsemble', 'CircularSymplecticEnsemble',
|
||||
'GaussianEnsemble', 'GaussianUnitaryEnsemble',
|
||||
'GaussianOrthogonalEnsemble', 'GaussianSymplecticEnsemble',
|
||||
'joint_eigen_distribution', 'JointEigenDistribution',
|
||||
'level_spacing_distribution',
|
||||
|
||||
'MatrixGamma', 'Wishart', 'MatrixNormal', 'MatrixStudentT',
|
||||
|
||||
'Probability', 'Expectation', 'Variance', 'Covariance', 'Moment',
|
||||
'CentralMoment',
|
||||
|
||||
'ExpectationMatrix', 'VarianceMatrix', 'CrossCovarianceMatrix'
|
||||
|
||||
]
|
||||
from .rv_interface import (P, E, H, density, where, given, sample, cdf, median,
|
||||
characteristic_function, pspace, sample_iter, variance, std, skewness,
|
||||
kurtosis, covariance, dependent, entropy, independent, random_symbols,
|
||||
correlation, factorial_moment, moment, cmoment, sampling_density,
|
||||
moment_generating_function, smoment, quantile, coskewness,
|
||||
sample_stochastic_process)
|
||||
|
||||
from .frv_types import (FiniteRV, DiscreteUniform, Die, Bernoulli, Coin,
|
||||
Binomial, BetaBinomial, Hypergeometric, Rademacher,
|
||||
FiniteDistributionHandmade, IdealSoliton, RobustSoliton)
|
||||
|
||||
from .crv_types import (ContinuousRV, Arcsin, Benini, Beta, BetaNoncentral,
|
||||
BetaPrime, BoundedPareto, Cauchy, Chi, ChiNoncentral, ChiSquared,
|
||||
Dagum, Davis, Erlang, ExGaussian, Exponential, ExponentialPower,
|
||||
FDistribution, FisherZ, Frechet, Gamma, GammaInverse, GaussianInverse,
|
||||
Gompertz, Gumbel, Kumaraswamy, Laplace, Levy, Logistic, LogCauchy,
|
||||
LogLogistic, LogitNormal, LogNormal, Lomax, Maxwell, Moyal, Nakagami,
|
||||
Normal, Pareto, QuadraticU, RaisedCosine, Rayleigh, Reciprocal,
|
||||
StudentT, PowerFunction, ShiftedGompertz, Trapezoidal, Triangular,
|
||||
Uniform, UniformSum, VonMises, Wald, Weibull, WignerSemicircle,
|
||||
ContinuousDistributionHandmade)
|
||||
|
||||
from .drv_types import (FlorySchulz, Geometric, Hermite, Logarithmic, NegativeBinomial, Poisson,
|
||||
Skellam, YuleSimon, Zeta, DiscreteRV, DiscreteDistributionHandmade)
|
||||
|
||||
from .joint_rv_types import (JointRV, Dirichlet,
|
||||
GeneralizedMultivariateLogGamma, GeneralizedMultivariateLogGammaOmega,
|
||||
Multinomial, MultivariateBeta, MultivariateEwens, MultivariateT,
|
||||
NegativeMultinomial, NormalGamma, MultivariateNormal, MultivariateLaplace,
|
||||
marginal_distribution)
|
||||
|
||||
from .stochastic_process_types import (StochasticProcess,
|
||||
DiscreteTimeStochasticProcess, DiscreteMarkovChain,
|
||||
TransitionMatrixOf, StochasticStateSpaceOf, GeneratorMatrixOf,
|
||||
ContinuousMarkovChain, BernoulliProcess, PoissonProcess, WienerProcess,
|
||||
GammaProcess)
|
||||
|
||||
from .random_matrix_models import (CircularEnsemble, CircularUnitaryEnsemble,
|
||||
CircularOrthogonalEnsemble, CircularSymplecticEnsemble,
|
||||
GaussianEnsemble, GaussianUnitaryEnsemble, GaussianOrthogonalEnsemble,
|
||||
GaussianSymplecticEnsemble, joint_eigen_distribution,
|
||||
JointEigenDistribution, level_spacing_distribution)
|
||||
|
||||
from .matrix_distributions import MatrixGamma, Wishart, MatrixNormal, MatrixStudentT
|
||||
|
||||
from .symbolic_probability import (Probability, Expectation, Variance,
|
||||
Covariance, Moment, CentralMoment)
|
||||
|
||||
from .symbolic_multivariate_probability import (ExpectationMatrix, VarianceMatrix,
|
||||
CrossCovarianceMatrix)
|
||||
@@ -0,0 +1,223 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.stats.rv import (NamedArgsMixin, random_symbols, _symbol_converter,
|
||||
PSpace, RandomSymbol, is_random, Distribution)
|
||||
from sympy.stats.crv import ContinuousDistribution, SingleContinuousPSpace
|
||||
from sympy.stats.drv import DiscreteDistribution, SingleDiscretePSpace
|
||||
from sympy.stats.frv import SingleFiniteDistribution, SingleFinitePSpace
|
||||
from sympy.stats.crv_types import ContinuousDistributionHandmade
|
||||
from sympy.stats.drv_types import DiscreteDistributionHandmade
|
||||
from sympy.stats.frv_types import FiniteDistributionHandmade
|
||||
|
||||
|
||||
class CompoundPSpace(PSpace):
|
||||
"""
|
||||
A temporary Probability Space for the Compound Distribution. After
|
||||
Marginalization, this returns the corresponding Probability Space of the
|
||||
parent distribution.
|
||||
"""
|
||||
|
||||
def __new__(cls, s, distribution):
|
||||
s = _symbol_converter(s)
|
||||
if isinstance(distribution, ContinuousDistribution):
|
||||
return SingleContinuousPSpace(s, distribution)
|
||||
if isinstance(distribution, DiscreteDistribution):
|
||||
return SingleDiscretePSpace(s, distribution)
|
||||
if isinstance(distribution, SingleFiniteDistribution):
|
||||
return SingleFinitePSpace(s, distribution)
|
||||
if not isinstance(distribution, CompoundDistribution):
|
||||
raise ValueError("%s should be an isinstance of "
|
||||
"CompoundDistribution"%(distribution))
|
||||
return Basic.__new__(cls, s, distribution)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return RandomSymbol(self.symbol, self)
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def is_Continuous(self):
|
||||
return self.distribution.is_Continuous
|
||||
|
||||
@property
|
||||
def is_Finite(self):
|
||||
return self.distribution.is_Finite
|
||||
|
||||
@property
|
||||
def is_Discrete(self):
|
||||
return self.distribution.is_Discrete
|
||||
|
||||
@property
|
||||
def distribution(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def pdf(self):
|
||||
return self.distribution.pdf(self.symbol)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.distribution.set
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return self._get_newpspace().domain
|
||||
|
||||
def _get_newpspace(self, evaluate=False):
|
||||
x = Dummy('x')
|
||||
parent_dist = self.distribution.args[0]
|
||||
func = Lambda(x, self.distribution.pdf(x, evaluate))
|
||||
new_pspace = self._transform_pspace(self.symbol, parent_dist, func)
|
||||
if new_pspace is not None:
|
||||
return new_pspace
|
||||
message = ("Compound Distribution for %s is not implemented yet" % str(parent_dist))
|
||||
raise NotImplementedError(message)
|
||||
|
||||
def _transform_pspace(self, sym, dist, pdf):
|
||||
"""
|
||||
This function returns the new pspace of the distribution using handmade
|
||||
Distributions and their corresponding pspace.
|
||||
"""
|
||||
pdf = Lambda(sym, pdf(sym))
|
||||
_set = dist.set
|
||||
if isinstance(dist, ContinuousDistribution):
|
||||
return SingleContinuousPSpace(sym, ContinuousDistributionHandmade(pdf, _set))
|
||||
elif isinstance(dist, DiscreteDistribution):
|
||||
return SingleDiscretePSpace(sym, DiscreteDistributionHandmade(pdf, _set))
|
||||
elif isinstance(dist, SingleFiniteDistribution):
|
||||
dens = {k: pdf(k) for k in _set}
|
||||
return SingleFinitePSpace(sym, FiniteDistributionHandmade(dens))
|
||||
|
||||
def compute_density(self, expr, *, compound_evaluate=True, **kwargs):
|
||||
new_pspace = self._get_newpspace(compound_evaluate)
|
||||
expr = expr.subs({self.value: new_pspace.value})
|
||||
return new_pspace.compute_density(expr, **kwargs)
|
||||
|
||||
def compute_cdf(self, expr, *, compound_evaluate=True, **kwargs):
|
||||
new_pspace = self._get_newpspace(compound_evaluate)
|
||||
expr = expr.subs({self.value: new_pspace.value})
|
||||
return new_pspace.compute_cdf(expr, **kwargs)
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs):
|
||||
new_pspace = self._get_newpspace(evaluate)
|
||||
expr = expr.subs({self.value: new_pspace.value})
|
||||
if rvs:
|
||||
rvs = rvs.subs({self.value: new_pspace.value})
|
||||
if isinstance(new_pspace, SingleFinitePSpace):
|
||||
return new_pspace.compute_expectation(expr, rvs, **kwargs)
|
||||
return new_pspace.compute_expectation(expr, rvs, evaluate, **kwargs)
|
||||
|
||||
def probability(self, condition, *, compound_evaluate=True, **kwargs):
|
||||
new_pspace = self._get_newpspace(compound_evaluate)
|
||||
condition = condition.subs({self.value: new_pspace.value})
|
||||
return new_pspace.probability(condition)
|
||||
|
||||
def conditional_space(self, condition, *, compound_evaluate=True, **kwargs):
|
||||
new_pspace = self._get_newpspace(compound_evaluate)
|
||||
condition = condition.subs({self.value: new_pspace.value})
|
||||
return new_pspace.conditional_space(condition)
|
||||
|
||||
|
||||
class CompoundDistribution(Distribution, NamedArgsMixin):
|
||||
"""
|
||||
Class for Compound Distributions.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
dist : Distribution
|
||||
Distribution must contain a random parameter
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats.compound_rv import CompoundDistribution
|
||||
>>> from sympy.stats.crv_types import NormalDistribution
|
||||
>>> from sympy.stats import Normal
|
||||
>>> from sympy.abc import x
|
||||
>>> X = Normal('X', 2, 4)
|
||||
>>> N = NormalDistribution(X, 4)
|
||||
>>> C = CompoundDistribution(N)
|
||||
>>> C.set
|
||||
Interval(-oo, oo)
|
||||
>>> C.pdf(x, evaluate=True).simplify()
|
||||
exp(-x**2/64 + x/16 - 1/16)/(8*sqrt(pi))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Compound_probability_distribution
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, dist):
|
||||
if not isinstance(dist, (ContinuousDistribution,
|
||||
SingleFiniteDistribution, DiscreteDistribution)):
|
||||
message = "Compound Distribution for %s is not implemented yet" % str(dist)
|
||||
raise NotImplementedError(message)
|
||||
if not cls._compound_check(dist):
|
||||
return dist
|
||||
return Basic.__new__(cls, dist)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[0].set
|
||||
|
||||
@property
|
||||
def is_Continuous(self):
|
||||
return isinstance(self.args[0], ContinuousDistribution)
|
||||
|
||||
@property
|
||||
def is_Finite(self):
|
||||
return isinstance(self.args[0], SingleFiniteDistribution)
|
||||
|
||||
@property
|
||||
def is_Discrete(self):
|
||||
return isinstance(self.args[0], DiscreteDistribution)
|
||||
|
||||
def pdf(self, x, evaluate=False):
|
||||
dist = self.args[0]
|
||||
randoms = [rv for rv in dist.args if is_random(rv)]
|
||||
if isinstance(dist, SingleFiniteDistribution):
|
||||
y = Dummy('y', integer=True, negative=False)
|
||||
expr = dist.pmf(y)
|
||||
else:
|
||||
y = Dummy('y')
|
||||
expr = dist.pdf(y)
|
||||
for rv in randoms:
|
||||
expr = self._marginalise(expr, rv, evaluate)
|
||||
return Lambda(y, expr)(x)
|
||||
|
||||
def _marginalise(self, expr, rv, evaluate):
|
||||
if isinstance(rv.pspace.distribution, SingleFiniteDistribution):
|
||||
rv_dens = rv.pspace.distribution.pmf(rv)
|
||||
else:
|
||||
rv_dens = rv.pspace.distribution.pdf(rv)
|
||||
rv_dom = rv.pspace.domain.set
|
||||
if rv.pspace.is_Discrete or rv.pspace.is_Finite:
|
||||
expr = Sum(expr*rv_dens, (rv, rv_dom._inf,
|
||||
rv_dom._sup))
|
||||
else:
|
||||
expr = Integral(expr*rv_dens, (rv, rv_dom._inf,
|
||||
rv_dom._sup))
|
||||
if evaluate:
|
||||
return expr.doit()
|
||||
return expr
|
||||
|
||||
@classmethod
|
||||
def _compound_check(self, dist):
|
||||
"""
|
||||
Checks if the given distribution contains random parameters.
|
||||
"""
|
||||
randoms = []
|
||||
for arg in dist.args:
|
||||
randoms.extend(random_symbols(arg))
|
||||
if len(randoms) == 0:
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,570 @@
|
||||
"""
|
||||
Continuous Random Variables Module
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.stats.crv_types
|
||||
sympy.stats.rv
|
||||
sympy.stats.frv
|
||||
"""
|
||||
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.function import Lambda, PoleError
|
||||
from sympy.core.numbers import (I, nan, oo)
|
||||
from sympy.core.relational import (Eq, Ne)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.core.sympify import _sympify, sympify
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.delta_functions import DiracDelta
|
||||
from sympy.integrals.integrals import (Integral, integrate)
|
||||
from sympy.logic.boolalg import (And, Or)
|
||||
from sympy.polys.polyerrors import PolynomialError
|
||||
from sympy.polys.polytools import poly
|
||||
from sympy.series.series import series
|
||||
from sympy.sets.sets import (FiniteSet, Intersection, Interval, Union)
|
||||
from sympy.solvers.solveset import solveset
|
||||
from sympy.solvers.inequalities import reduce_rational_inequalities
|
||||
from sympy.stats.rv import (RandomDomain, SingleDomain, ConditionalDomain, is_random,
|
||||
ProductDomain, PSpace, SinglePSpace, random_symbols, NamedArgsMixin, Distribution)
|
||||
|
||||
|
||||
class ContinuousDomain(RandomDomain):
|
||||
"""
|
||||
A domain with continuous support
|
||||
|
||||
Represented using symbols and Intervals.
|
||||
"""
|
||||
is_Continuous = True
|
||||
|
||||
def as_boolean(self):
|
||||
raise NotImplementedError("Not Implemented for generic Domains")
|
||||
|
||||
|
||||
class SingleContinuousDomain(ContinuousDomain, SingleDomain):
|
||||
"""
|
||||
A univariate domain with continuous support
|
||||
|
||||
Represented using a single symbol and interval.
|
||||
"""
|
||||
def compute_expectation(self, expr, variables=None, **kwargs):
|
||||
if variables is None:
|
||||
variables = self.symbols
|
||||
if not variables:
|
||||
return expr
|
||||
if frozenset(variables) != frozenset(self.symbols):
|
||||
raise ValueError("Values should be equal")
|
||||
# assumes only intervals
|
||||
return Integral(expr, (self.symbol, self.set), **kwargs)
|
||||
|
||||
def as_boolean(self):
|
||||
return self.set.as_relational(self.symbol)
|
||||
|
||||
|
||||
class ProductContinuousDomain(ProductDomain, ContinuousDomain):
|
||||
"""
|
||||
A collection of independent domains with continuous support
|
||||
"""
|
||||
|
||||
def compute_expectation(self, expr, variables=None, **kwargs):
|
||||
if variables is None:
|
||||
variables = self.symbols
|
||||
for domain in self.domains:
|
||||
domain_vars = frozenset(variables) & frozenset(domain.symbols)
|
||||
if domain_vars:
|
||||
expr = domain.compute_expectation(expr, domain_vars, **kwargs)
|
||||
return expr
|
||||
|
||||
def as_boolean(self):
|
||||
return And(*[domain.as_boolean() for domain in self.domains])
|
||||
|
||||
|
||||
class ConditionalContinuousDomain(ContinuousDomain, ConditionalDomain):
|
||||
"""
|
||||
A domain with continuous support that has been further restricted by a
|
||||
condition such as $x > 3$.
|
||||
"""
|
||||
|
||||
def compute_expectation(self, expr, variables=None, **kwargs):
|
||||
if variables is None:
|
||||
variables = self.symbols
|
||||
if not variables:
|
||||
return expr
|
||||
# Extract the full integral
|
||||
fullintgrl = self.fulldomain.compute_expectation(expr, variables)
|
||||
# separate into integrand and limits
|
||||
integrand, limits = fullintgrl.function, list(fullintgrl.limits)
|
||||
|
||||
conditions = [self.condition]
|
||||
while conditions:
|
||||
cond = conditions.pop()
|
||||
if cond.is_Boolean:
|
||||
if isinstance(cond, And):
|
||||
conditions.extend(cond.args)
|
||||
elif isinstance(cond, Or):
|
||||
raise NotImplementedError("Or not implemented here")
|
||||
elif cond.is_Relational:
|
||||
if cond.is_Equality:
|
||||
# Add the appropriate Delta to the integrand
|
||||
integrand *= DiracDelta(cond.lhs - cond.rhs)
|
||||
else:
|
||||
symbols = cond.free_symbols & set(self.symbols)
|
||||
if len(symbols) != 1: # Can't handle x > y
|
||||
raise NotImplementedError(
|
||||
"Multivariate Inequalities not yet implemented")
|
||||
# Can handle x > 0
|
||||
symbol = symbols.pop()
|
||||
# Find the limit with x, such as (x, -oo, oo)
|
||||
for i, limit in enumerate(limits):
|
||||
if limit[0] == symbol:
|
||||
# Make condition into an Interval like [0, oo]
|
||||
cintvl = reduce_rational_inequalities_wrap(
|
||||
cond, symbol)
|
||||
# Make limit into an Interval like [-oo, oo]
|
||||
lintvl = Interval(limit[1], limit[2])
|
||||
# Intersect them to get [0, oo]
|
||||
intvl = cintvl.intersect(lintvl)
|
||||
# Put back into limits list
|
||||
limits[i] = (symbol, intvl.left, intvl.right)
|
||||
else:
|
||||
raise TypeError(
|
||||
"Condition %s is not a relational or Boolean" % cond)
|
||||
|
||||
return Integral(integrand, *limits, **kwargs)
|
||||
|
||||
def as_boolean(self):
|
||||
return And(self.fulldomain.as_boolean(), self.condition)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if len(self.symbols) == 1:
|
||||
return (self.fulldomain.set & reduce_rational_inequalities_wrap(
|
||||
self.condition, tuple(self.symbols)[0]))
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Set of Conditional Domain not Implemented")
|
||||
|
||||
|
||||
class ContinuousDistribution(Distribution):
|
||||
def __call__(self, *args):
|
||||
return self.pdf(*args)
|
||||
|
||||
|
||||
class SingleContinuousDistribution(ContinuousDistribution, NamedArgsMixin):
|
||||
""" Continuous distribution of a single variable.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Serves as superclass for Normal/Exponential/UniformDistribution etc....
|
||||
|
||||
Represented by parameters for each of the specific classes. E.g
|
||||
NormalDistribution is represented by a mean and standard deviation.
|
||||
|
||||
Provides methods for pdf, cdf, and sampling.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.stats.crv_types.*
|
||||
"""
|
||||
|
||||
set = Interval(-oo, oo)
|
||||
|
||||
def __new__(cls, *args):
|
||||
args = list(map(sympify, args))
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
@staticmethod
|
||||
def check(*args):
|
||||
pass
|
||||
|
||||
@cacheit
|
||||
def compute_cdf(self, **kwargs):
|
||||
""" Compute the CDF from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x, z = symbols('x, z', real=True, cls=Dummy)
|
||||
left_bound = self.set.start
|
||||
|
||||
# CDF is integral of PDF from left bound to z
|
||||
pdf = self.pdf(x)
|
||||
cdf = integrate(pdf.doit(), (x, left_bound, z), **kwargs)
|
||||
# CDF Ensure that CDF left of left_bound is zero
|
||||
cdf = Piecewise((cdf, z >= left_bound), (0, True))
|
||||
return Lambda(z, cdf)
|
||||
|
||||
def _cdf(self, x):
|
||||
return None
|
||||
|
||||
def cdf(self, x, **kwargs):
|
||||
""" Cumulative density function """
|
||||
if len(kwargs) == 0:
|
||||
cdf = self._cdf(x)
|
||||
if cdf is not None:
|
||||
return cdf
|
||||
return self.compute_cdf(**kwargs)(x)
|
||||
|
||||
@cacheit
|
||||
def compute_characteristic_function(self, **kwargs):
|
||||
""" Compute the characteristic function from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x, t = symbols('x, t', real=True, cls=Dummy)
|
||||
pdf = self.pdf(x)
|
||||
cf = integrate(exp(I*t*x)*pdf, (x, self.set))
|
||||
return Lambda(t, cf)
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
return None
|
||||
|
||||
def characteristic_function(self, t, **kwargs):
|
||||
""" Characteristic function """
|
||||
if len(kwargs) == 0:
|
||||
cf = self._characteristic_function(t)
|
||||
if cf is not None:
|
||||
return cf
|
||||
return self.compute_characteristic_function(**kwargs)(t)
|
||||
|
||||
@cacheit
|
||||
def compute_moment_generating_function(self, **kwargs):
|
||||
""" Compute the moment generating function from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x, t = symbols('x, t', real=True, cls=Dummy)
|
||||
pdf = self.pdf(x)
|
||||
mgf = integrate(exp(t * x) * pdf, (x, self.set))
|
||||
return Lambda(t, mgf)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
return None
|
||||
|
||||
def moment_generating_function(self, t, **kwargs):
|
||||
""" Moment generating function """
|
||||
if not kwargs:
|
||||
mgf = self._moment_generating_function(t)
|
||||
if mgf is not None:
|
||||
return mgf
|
||||
return self.compute_moment_generating_function(**kwargs)(t)
|
||||
|
||||
def expectation(self, expr, var, evaluate=True, **kwargs):
|
||||
""" Expectation of expression over distribution """
|
||||
if evaluate:
|
||||
try:
|
||||
p = poly(expr, var)
|
||||
if p.is_zero:
|
||||
return S.Zero
|
||||
t = Dummy('t', real=True)
|
||||
mgf = self._moment_generating_function(t)
|
||||
if mgf is None:
|
||||
return integrate(expr * self.pdf(var), (var, self.set), **kwargs)
|
||||
deg = p.degree()
|
||||
taylor = poly(series(mgf, t, 0, deg + 1).removeO(), t)
|
||||
result = 0
|
||||
for k in range(deg+1):
|
||||
result += p.coeff_monomial(var ** k) * taylor.coeff_monomial(t ** k) * factorial(k)
|
||||
return result
|
||||
except PolynomialError:
|
||||
return integrate(expr * self.pdf(var), (var, self.set), **kwargs)
|
||||
else:
|
||||
return Integral(expr * self.pdf(var), (var, self.set), **kwargs)
|
||||
|
||||
@cacheit
|
||||
def compute_quantile(self, **kwargs):
|
||||
""" Compute the Quantile from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x, p = symbols('x, p', real=True, cls=Dummy)
|
||||
left_bound = self.set.start
|
||||
|
||||
pdf = self.pdf(x)
|
||||
cdf = integrate(pdf, (x, left_bound, x), **kwargs)
|
||||
quantile = solveset(cdf - p, x, self.set)
|
||||
return Lambda(p, Piecewise((quantile, (p >= 0) & (p <= 1) ), (nan, True)))
|
||||
|
||||
def _quantile(self, x):
|
||||
return None
|
||||
|
||||
def quantile(self, x, **kwargs):
|
||||
""" Cumulative density function """
|
||||
if len(kwargs) == 0:
|
||||
quantile = self._quantile(x)
|
||||
if quantile is not None:
|
||||
return quantile
|
||||
return self.compute_quantile(**kwargs)(x)
|
||||
|
||||
|
||||
class ContinuousPSpace(PSpace):
|
||||
""" Continuous Probability Space
|
||||
|
||||
Represents the likelihood of an event space defined over a continuum.
|
||||
|
||||
Represented with a ContinuousDomain and a PDF (Lambda-Like)
|
||||
"""
|
||||
|
||||
is_Continuous = True
|
||||
is_real = True
|
||||
|
||||
@property
|
||||
def pdf(self):
|
||||
return self.density(*self.domain.symbols)
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs):
|
||||
if rvs is None:
|
||||
rvs = self.values
|
||||
else:
|
||||
rvs = frozenset(rvs)
|
||||
|
||||
expr = expr.xreplace({rv: rv.symbol for rv in rvs})
|
||||
|
||||
domain_symbols = frozenset(rv.symbol for rv in rvs)
|
||||
|
||||
return self.domain.compute_expectation(self.pdf * expr,
|
||||
domain_symbols, **kwargs)
|
||||
|
||||
def compute_density(self, expr, **kwargs):
|
||||
# Common case Density(X) where X in self.values
|
||||
if expr in self.values:
|
||||
# Marginalize all other random symbols out of the density
|
||||
randomsymbols = tuple(set(self.values) - frozenset([expr]))
|
||||
symbols = tuple(rs.symbol for rs in randomsymbols)
|
||||
pdf = self.domain.compute_expectation(self.pdf, symbols, **kwargs)
|
||||
return Lambda(expr.symbol, pdf)
|
||||
|
||||
z = Dummy('z', real=True)
|
||||
return Lambda(z, self.compute_expectation(DiracDelta(expr - z), **kwargs))
|
||||
|
||||
@cacheit
|
||||
def compute_cdf(self, expr, **kwargs):
|
||||
if not self.domain.set.is_Interval:
|
||||
raise ValueError(
|
||||
"CDF not well defined on multivariate expressions")
|
||||
|
||||
d = self.compute_density(expr, **kwargs)
|
||||
x, z = symbols('x, z', real=True, cls=Dummy)
|
||||
left_bound = self.domain.set.start
|
||||
|
||||
# CDF is integral of PDF from left bound to z
|
||||
cdf = integrate(d(x), (x, left_bound, z), **kwargs)
|
||||
# CDF Ensure that CDF left of left_bound is zero
|
||||
cdf = Piecewise((cdf, z >= left_bound), (0, True))
|
||||
return Lambda(z, cdf)
|
||||
|
||||
@cacheit
|
||||
def compute_characteristic_function(self, expr, **kwargs):
|
||||
if not self.domain.set.is_Interval:
|
||||
raise NotImplementedError("Characteristic function of multivariate expressions not implemented")
|
||||
|
||||
d = self.compute_density(expr, **kwargs)
|
||||
x, t = symbols('x, t', real=True, cls=Dummy)
|
||||
cf = integrate(exp(I*t*x)*d(x), (x, -oo, oo), **kwargs)
|
||||
return Lambda(t, cf)
|
||||
|
||||
@cacheit
|
||||
def compute_moment_generating_function(self, expr, **kwargs):
|
||||
if not self.domain.set.is_Interval:
|
||||
raise NotImplementedError("Moment generating function of multivariate expressions not implemented")
|
||||
|
||||
d = self.compute_density(expr, **kwargs)
|
||||
x, t = symbols('x, t', real=True, cls=Dummy)
|
||||
mgf = integrate(exp(t * x) * d(x), (x, -oo, oo), **kwargs)
|
||||
return Lambda(t, mgf)
|
||||
|
||||
@cacheit
|
||||
def compute_quantile(self, expr, **kwargs):
|
||||
if not self.domain.set.is_Interval:
|
||||
raise ValueError(
|
||||
"Quantile not well defined on multivariate expressions")
|
||||
|
||||
d = self.compute_cdf(expr, **kwargs)
|
||||
x = Dummy('x', real=True)
|
||||
p = Dummy('p', positive=True)
|
||||
|
||||
quantile = solveset(d(x) - p, x, self.set)
|
||||
|
||||
return Lambda(p, quantile)
|
||||
|
||||
def probability(self, condition, **kwargs):
|
||||
z = Dummy('z', real=True)
|
||||
cond_inv = False
|
||||
if isinstance(condition, Ne):
|
||||
condition = Eq(condition.args[0], condition.args[1])
|
||||
cond_inv = True
|
||||
# Univariate case can be handled by where
|
||||
try:
|
||||
domain = self.where(condition)
|
||||
rv = [rv for rv in self.values if rv.symbol == domain.symbol][0]
|
||||
# Integrate out all other random variables
|
||||
pdf = self.compute_density(rv, **kwargs)
|
||||
# return S.Zero if `domain` is empty set
|
||||
if domain.set is S.EmptySet or isinstance(domain.set, FiniteSet):
|
||||
return S.Zero if not cond_inv else S.One
|
||||
if isinstance(domain.set, Union):
|
||||
return sum(
|
||||
Integral(pdf(z), (z, subset), **kwargs) for subset in
|
||||
domain.set.args if isinstance(subset, Interval))
|
||||
# Integrate out the last variable over the special domain
|
||||
return Integral(pdf(z), (z, domain.set), **kwargs)
|
||||
|
||||
# Other cases can be turned into univariate case
|
||||
# by computing a density handled by density computation
|
||||
except NotImplementedError:
|
||||
from sympy.stats.rv import density
|
||||
expr = condition.lhs - condition.rhs
|
||||
if not is_random(expr):
|
||||
dens = self.density
|
||||
comp = condition.rhs
|
||||
else:
|
||||
dens = density(expr, **kwargs)
|
||||
comp = 0
|
||||
if not isinstance(dens, ContinuousDistribution):
|
||||
from sympy.stats.crv_types import ContinuousDistributionHandmade
|
||||
dens = ContinuousDistributionHandmade(dens, set=self.domain.set)
|
||||
# Turn problem into univariate case
|
||||
space = SingleContinuousPSpace(z, dens)
|
||||
result = space.probability(condition.__class__(space.value, comp))
|
||||
return result if not cond_inv else S.One - result
|
||||
|
||||
def where(self, condition):
|
||||
rvs = frozenset(random_symbols(condition))
|
||||
if not (len(rvs) == 1 and rvs.issubset(self.values)):
|
||||
raise NotImplementedError(
|
||||
"Multiple continuous random variables not supported")
|
||||
rv = tuple(rvs)[0]
|
||||
interval = reduce_rational_inequalities_wrap(condition, rv)
|
||||
interval = interval.intersect(self.domain.set)
|
||||
return SingleContinuousDomain(rv.symbol, interval)
|
||||
|
||||
def conditional_space(self, condition, normalize=True, **kwargs):
|
||||
condition = condition.xreplace({rv: rv.symbol for rv in self.values})
|
||||
domain = ConditionalContinuousDomain(self.domain, condition)
|
||||
if normalize:
|
||||
# create a clone of the variable to
|
||||
# make sure that variables in nested integrals are different
|
||||
# from the variables outside the integral
|
||||
# this makes sure that they are evaluated separately
|
||||
# and in the correct order
|
||||
replacement = {rv: Dummy(str(rv)) for rv in self.symbols}
|
||||
norm = domain.compute_expectation(self.pdf, **kwargs)
|
||||
pdf = self.pdf / norm.xreplace(replacement)
|
||||
# XXX: Converting set to tuple. The order matters to Lambda though
|
||||
# so we shouldn't be starting with a set here...
|
||||
density = Lambda(tuple(domain.symbols), pdf)
|
||||
|
||||
return ContinuousPSpace(domain, density)
|
||||
|
||||
|
||||
class SingleContinuousPSpace(ContinuousPSpace, SinglePSpace):
|
||||
"""
|
||||
A continuous probability space over a single univariate variable.
|
||||
|
||||
These consist of a Symbol and a SingleContinuousDistribution
|
||||
|
||||
This class is normally accessed through the various random variable
|
||||
functions, Normal, Exponential, Uniform, etc....
|
||||
"""
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.distribution.set
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return SingleContinuousDomain(sympify(self.symbol), self.set)
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method.
|
||||
|
||||
Returns dictionary mapping RandomSymbol to realization value.
|
||||
"""
|
||||
return {self.value: self.distribution.sample(size, library=library, seed=seed)}
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs):
|
||||
rvs = rvs or (self.value,)
|
||||
if self.value not in rvs:
|
||||
return expr
|
||||
|
||||
expr = _sympify(expr)
|
||||
expr = expr.xreplace({rv: rv.symbol for rv in rvs})
|
||||
|
||||
x = self.value.symbol
|
||||
try:
|
||||
return self.distribution.expectation(expr, x, evaluate=evaluate, **kwargs)
|
||||
except PoleError:
|
||||
return Integral(expr * self.pdf, (x, self.set), **kwargs)
|
||||
|
||||
def compute_cdf(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
z = Dummy("z", real=True)
|
||||
return Lambda(z, self.distribution.cdf(z, **kwargs))
|
||||
else:
|
||||
return ContinuousPSpace.compute_cdf(self, expr, **kwargs)
|
||||
|
||||
def compute_characteristic_function(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
t = Dummy("t", real=True)
|
||||
return Lambda(t, self.distribution.characteristic_function(t, **kwargs))
|
||||
else:
|
||||
return ContinuousPSpace.compute_characteristic_function(self, expr, **kwargs)
|
||||
|
||||
def compute_moment_generating_function(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
t = Dummy("t", real=True)
|
||||
return Lambda(t, self.distribution.moment_generating_function(t, **kwargs))
|
||||
else:
|
||||
return ContinuousPSpace.compute_moment_generating_function(self, expr, **kwargs)
|
||||
|
||||
def compute_density(self, expr, **kwargs):
|
||||
# https://en.wikipedia.org/wiki/Random_variable#Functions_of_random_variables
|
||||
if expr == self.value:
|
||||
return self.density
|
||||
y = Dummy('y', real=True)
|
||||
|
||||
gs = solveset(expr - y, self.value, S.Reals)
|
||||
|
||||
if isinstance(gs, Intersection):
|
||||
if len(gs.args) == 2 and gs.args[0] is S.Reals:
|
||||
gs = gs.args[1]
|
||||
if not gs.is_FiniteSet:
|
||||
raise ValueError("Can not solve %s for %s" % (expr, self.value))
|
||||
fx = self.compute_density(self.value)
|
||||
fy = sum(fx(g) * abs(g.diff(y)) for g in gs)
|
||||
return Lambda(y, fy)
|
||||
|
||||
def compute_quantile(self, expr, **kwargs):
|
||||
|
||||
if expr == self.value:
|
||||
p = Dummy("p", real=True)
|
||||
return Lambda(p, self.distribution.quantile(p, **kwargs))
|
||||
else:
|
||||
return ContinuousPSpace.compute_quantile(self, expr, **kwargs)
|
||||
|
||||
def _reduce_inequalities(conditions, var, **kwargs):
|
||||
try:
|
||||
return reduce_rational_inequalities(conditions, var, **kwargs)
|
||||
except PolynomialError:
|
||||
raise ValueError("Reduction of condition failed %s\n" % conditions[0])
|
||||
|
||||
|
||||
def reduce_rational_inequalities_wrap(condition, var):
|
||||
if condition.is_Relational:
|
||||
return _reduce_inequalities([[condition]], var, relational=False)
|
||||
if isinstance(condition, Or):
|
||||
return Union(*[_reduce_inequalities([[arg]], var, relational=False)
|
||||
for arg in condition.args])
|
||||
if isinstance(condition, And):
|
||||
intervals = [_reduce_inequalities([[arg]], var, relational=False)
|
||||
for arg in condition.args]
|
||||
I = intervals[0]
|
||||
for i in intervals:
|
||||
I = I.intersect(i)
|
||||
return I
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,350 @@
|
||||
from sympy.concrete.summations import (Sum, summation)
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.relational import (Eq, Ne)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.logic.boolalg import And
|
||||
from sympy.polys.polytools import poly
|
||||
from sympy.series.series import series
|
||||
|
||||
from sympy.polys.polyerrors import PolynomialError
|
||||
from sympy.stats.crv import reduce_rational_inequalities_wrap
|
||||
from sympy.stats.rv import (NamedArgsMixin, SinglePSpace, SingleDomain,
|
||||
random_symbols, PSpace, ConditionalDomain, RandomDomain,
|
||||
ProductDomain, Distribution)
|
||||
from sympy.stats.symbolic_probability import Probability
|
||||
from sympy.sets.fancysets import Range, FiniteSet
|
||||
from sympy.sets.sets import Union
|
||||
from sympy.sets.contains import Contains
|
||||
from sympy.utilities import filldedent
|
||||
from sympy.core.sympify import _sympify
|
||||
|
||||
|
||||
class DiscreteDistribution(Distribution):
|
||||
def __call__(self, *args):
|
||||
return self.pdf(*args)
|
||||
|
||||
|
||||
class SingleDiscreteDistribution(DiscreteDistribution, NamedArgsMixin):
|
||||
""" Discrete distribution of a single variable.
|
||||
|
||||
Serves as superclass for PoissonDistribution etc....
|
||||
|
||||
Provides methods for pdf, cdf, and sampling
|
||||
|
||||
See Also:
|
||||
sympy.stats.crv_types.*
|
||||
"""
|
||||
|
||||
set = S.Integers
|
||||
|
||||
def __new__(cls, *args):
|
||||
args = list(map(sympify, args))
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
@staticmethod
|
||||
def check(*args):
|
||||
pass
|
||||
|
||||
@cacheit
|
||||
def compute_cdf(self, **kwargs):
|
||||
""" Compute the CDF from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x = symbols('x', integer=True, cls=Dummy)
|
||||
z = symbols('z', real=True, cls=Dummy)
|
||||
left_bound = self.set.inf
|
||||
|
||||
# CDF is integral of PDF from left bound to z
|
||||
pdf = self.pdf(x)
|
||||
cdf = summation(pdf, (x, left_bound, floor(z)), **kwargs)
|
||||
# CDF Ensure that CDF left of left_bound is zero
|
||||
cdf = Piecewise((cdf, z >= left_bound), (0, True))
|
||||
return Lambda(z, cdf)
|
||||
|
||||
def _cdf(self, x):
|
||||
return None
|
||||
|
||||
def cdf(self, x, **kwargs):
|
||||
""" Cumulative density function """
|
||||
if not kwargs:
|
||||
cdf = self._cdf(x)
|
||||
if cdf is not None:
|
||||
return cdf
|
||||
return self.compute_cdf(**kwargs)(x)
|
||||
|
||||
@cacheit
|
||||
def compute_characteristic_function(self, **kwargs):
|
||||
""" Compute the characteristic function from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x, t = symbols('x, t', real=True, cls=Dummy)
|
||||
pdf = self.pdf(x)
|
||||
cf = summation(exp(I*t*x)*pdf, (x, self.set.inf, self.set.sup))
|
||||
return Lambda(t, cf)
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
return None
|
||||
|
||||
def characteristic_function(self, t, **kwargs):
|
||||
""" Characteristic function """
|
||||
if not kwargs:
|
||||
cf = self._characteristic_function(t)
|
||||
if cf is not None:
|
||||
return cf
|
||||
return self.compute_characteristic_function(**kwargs)(t)
|
||||
|
||||
@cacheit
|
||||
def compute_moment_generating_function(self, **kwargs):
|
||||
t = Dummy('t', real=True)
|
||||
x = Dummy('x', integer=True)
|
||||
pdf = self.pdf(x)
|
||||
mgf = summation(exp(t*x)*pdf, (x, self.set.inf, self.set.sup))
|
||||
return Lambda(t, mgf)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
return None
|
||||
|
||||
def moment_generating_function(self, t, **kwargs):
|
||||
if not kwargs:
|
||||
mgf = self._moment_generating_function(t)
|
||||
if mgf is not None:
|
||||
return mgf
|
||||
return self.compute_moment_generating_function(**kwargs)(t)
|
||||
|
||||
@cacheit
|
||||
def compute_quantile(self, **kwargs):
|
||||
""" Compute the Quantile from the PDF.
|
||||
|
||||
Returns a Lambda.
|
||||
"""
|
||||
x = Dummy('x', integer=True)
|
||||
p = Dummy('p', real=True)
|
||||
left_bound = self.set.inf
|
||||
pdf = self.pdf(x)
|
||||
cdf = summation(pdf, (x, left_bound, x), **kwargs)
|
||||
set = ((x, p <= cdf), )
|
||||
return Lambda(p, Piecewise(*set))
|
||||
|
||||
def _quantile(self, x):
|
||||
return None
|
||||
|
||||
def quantile(self, x, **kwargs):
|
||||
""" Cumulative density function """
|
||||
if not kwargs:
|
||||
quantile = self._quantile(x)
|
||||
if quantile is not None:
|
||||
return quantile
|
||||
return self.compute_quantile(**kwargs)(x)
|
||||
|
||||
def expectation(self, expr, var, evaluate=True, **kwargs):
|
||||
""" Expectation of expression over distribution """
|
||||
# TODO: support discrete sets with non integer stepsizes
|
||||
|
||||
if evaluate:
|
||||
try:
|
||||
p = poly(expr, var)
|
||||
|
||||
t = Dummy('t', real=True)
|
||||
|
||||
mgf = self.moment_generating_function(t)
|
||||
deg = p.degree()
|
||||
taylor = poly(series(mgf, t, 0, deg + 1).removeO(), t)
|
||||
result = 0
|
||||
for k in range(deg+1):
|
||||
result += p.coeff_monomial(var ** k) * taylor.coeff_monomial(t ** k) * factorial(k)
|
||||
|
||||
return result
|
||||
|
||||
except PolynomialError:
|
||||
return summation(expr * self.pdf(var),
|
||||
(var, self.set.inf, self.set.sup), **kwargs)
|
||||
|
||||
else:
|
||||
return Sum(expr * self.pdf(var),
|
||||
(var, self.set.inf, self.set.sup), **kwargs)
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.pdf(*args)
|
||||
|
||||
|
||||
class DiscreteDomain(RandomDomain):
|
||||
"""
|
||||
A domain with discrete support with step size one.
|
||||
Represented using symbols and Range.
|
||||
"""
|
||||
is_Discrete = True
|
||||
|
||||
class SingleDiscreteDomain(DiscreteDomain, SingleDomain):
|
||||
def as_boolean(self):
|
||||
return Contains(self.symbol, self.set)
|
||||
|
||||
|
||||
class ConditionalDiscreteDomain(DiscreteDomain, ConditionalDomain):
|
||||
"""
|
||||
Domain with discrete support of step size one, that is restricted by
|
||||
some condition.
|
||||
"""
|
||||
@property
|
||||
def set(self):
|
||||
rv = self.symbols
|
||||
if len(self.symbols) > 1:
|
||||
raise NotImplementedError(filldedent('''
|
||||
Multivariate conditional domains are not yet implemented.'''))
|
||||
rv = list(rv)[0]
|
||||
return reduce_rational_inequalities_wrap(self.condition,
|
||||
rv).intersect(self.fulldomain.set)
|
||||
|
||||
|
||||
class DiscretePSpace(PSpace):
|
||||
is_real = True
|
||||
is_Discrete = True
|
||||
|
||||
@property
|
||||
def pdf(self):
|
||||
return self.density(*self.symbols)
|
||||
|
||||
def where(self, condition):
|
||||
rvs = random_symbols(condition)
|
||||
assert all(r.symbol in self.symbols for r in rvs)
|
||||
if len(rvs) > 1:
|
||||
raise NotImplementedError(filldedent('''Multivariate discrete
|
||||
random variables are not yet supported.'''))
|
||||
conditional_domain = reduce_rational_inequalities_wrap(condition,
|
||||
rvs[0])
|
||||
conditional_domain = conditional_domain.intersect(self.domain.set)
|
||||
return SingleDiscreteDomain(rvs[0].symbol, conditional_domain)
|
||||
|
||||
def probability(self, condition):
|
||||
complement = isinstance(condition, Ne)
|
||||
if complement:
|
||||
condition = Eq(condition.args[0], condition.args[1])
|
||||
try:
|
||||
_domain = self.where(condition).set
|
||||
if condition == False or _domain is S.EmptySet:
|
||||
return S.Zero
|
||||
if condition == True or _domain == self.domain.set:
|
||||
return S.One
|
||||
prob = self.eval_prob(_domain)
|
||||
except NotImplementedError:
|
||||
from sympy.stats.rv import density
|
||||
expr = condition.lhs - condition.rhs
|
||||
dens = density(expr)
|
||||
if not isinstance(dens, DiscreteDistribution):
|
||||
from sympy.stats.drv_types import DiscreteDistributionHandmade
|
||||
dens = DiscreteDistributionHandmade(dens)
|
||||
z = Dummy('z', real=True)
|
||||
space = SingleDiscretePSpace(z, dens)
|
||||
prob = space.probability(condition.__class__(space.value, 0))
|
||||
if prob is None:
|
||||
prob = Probability(condition)
|
||||
return prob if not complement else S.One - prob
|
||||
|
||||
def eval_prob(self, _domain):
|
||||
sym = list(self.symbols)[0]
|
||||
if isinstance(_domain, Range):
|
||||
n = symbols('n', integer=True)
|
||||
inf, sup, step = (r for r in _domain.args)
|
||||
summand = ((self.pdf).replace(
|
||||
sym, n*step))
|
||||
rv = summation(summand,
|
||||
(n, inf/step, (sup)/step - 1)).doit()
|
||||
return rv
|
||||
elif isinstance(_domain, FiniteSet):
|
||||
pdf = Lambda(sym, self.pdf)
|
||||
rv = sum(pdf(x) for x in _domain)
|
||||
return rv
|
||||
elif isinstance(_domain, Union):
|
||||
rv = sum(self.eval_prob(x) for x in _domain.args)
|
||||
return rv
|
||||
|
||||
def conditional_space(self, condition):
|
||||
# XXX: Converting from set to tuple. The order matters to Lambda
|
||||
# though so we should be starting with a set...
|
||||
density = Lambda(tuple(self.symbols), self.pdf/self.probability(condition))
|
||||
condition = condition.xreplace({rv: rv.symbol for rv in self.values})
|
||||
domain = ConditionalDiscreteDomain(self.domain, condition)
|
||||
return DiscretePSpace(domain, density)
|
||||
|
||||
class ProductDiscreteDomain(ProductDomain, DiscreteDomain):
|
||||
def as_boolean(self):
|
||||
return And(*[domain.as_boolean for domain in self.domains])
|
||||
|
||||
class SingleDiscretePSpace(DiscretePSpace, SinglePSpace):
|
||||
""" Discrete probability space over a single univariate variable """
|
||||
is_real = True
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.distribution.set
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return SingleDiscreteDomain(self.symbol, self.set)
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method.
|
||||
|
||||
Returns dictionary mapping RandomSymbol to realization value.
|
||||
"""
|
||||
return {self.value: self.distribution.sample(size, library=library, seed=seed)}
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, evaluate=True, **kwargs):
|
||||
rvs = rvs or (self.value,)
|
||||
if self.value not in rvs:
|
||||
return expr
|
||||
|
||||
expr = _sympify(expr)
|
||||
expr = expr.xreplace({rv: rv.symbol for rv in rvs})
|
||||
|
||||
x = self.value.symbol
|
||||
try:
|
||||
return self.distribution.expectation(expr, x, evaluate=evaluate,
|
||||
**kwargs)
|
||||
except NotImplementedError:
|
||||
return Sum(expr * self.pdf, (x, self.set.inf, self.set.sup),
|
||||
**kwargs)
|
||||
|
||||
def compute_cdf(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
x = Dummy("x", real=True)
|
||||
return Lambda(x, self.distribution.cdf(x, **kwargs))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_density(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
return self.distribution
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_characteristic_function(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
t = Dummy("t", real=True)
|
||||
return Lambda(t, self.distribution.characteristic_function(t, **kwargs))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_moment_generating_function(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
t = Dummy("t", real=True)
|
||||
return Lambda(t, self.distribution.moment_generating_function(t, **kwargs))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_quantile(self, expr, **kwargs):
|
||||
if expr == self.value:
|
||||
p = Dummy("p", real=True)
|
||||
return Lambda(p, self.distribution.quantile(p, **kwargs))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,849 @@
|
||||
"""
|
||||
|
||||
Contains
|
||||
========
|
||||
FlorySchulz
|
||||
Geometric
|
||||
Hermite
|
||||
Logarithmic
|
||||
NegativeBinomial
|
||||
Poisson
|
||||
Skellam
|
||||
YuleSimon
|
||||
Zeta
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.combinatorial.factorials import (binomial, factorial, FallingFactorial)
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.bessel import besseli
|
||||
from sympy.functions.special.beta_functions import beta
|
||||
from sympy.functions.special.hyper import hyper
|
||||
from sympy.functions.special.zeta_functions import (polylog, zeta)
|
||||
from sympy.stats.drv import SingleDiscreteDistribution, SingleDiscretePSpace
|
||||
from sympy.stats.rv import _value_check, is_random
|
||||
|
||||
|
||||
__all__ = ['FlorySchulz',
|
||||
'Geometric',
|
||||
'Hermite',
|
||||
'Logarithmic',
|
||||
'NegativeBinomial',
|
||||
'Poisson',
|
||||
'Skellam',
|
||||
'YuleSimon',
|
||||
'Zeta'
|
||||
]
|
||||
|
||||
|
||||
def rv(symbol, cls, *args, **kwargs):
|
||||
args = list(map(sympify, args))
|
||||
dist = cls(*args)
|
||||
if kwargs.pop('check', True):
|
||||
dist.check(*args)
|
||||
pspace = SingleDiscretePSpace(symbol, dist)
|
||||
if any(is_random(arg) for arg in args):
|
||||
from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution
|
||||
pspace = CompoundPSpace(symbol, CompoundDistribution(dist))
|
||||
return pspace.value
|
||||
|
||||
|
||||
class DiscreteDistributionHandmade(SingleDiscreteDistribution):
|
||||
_argnames = ('pdf',)
|
||||
|
||||
def __new__(cls, pdf, set=S.Integers):
|
||||
return Basic.__new__(cls, pdf, set)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[1]
|
||||
|
||||
@staticmethod
|
||||
def check(pdf, set):
|
||||
x = Dummy('x')
|
||||
val = Sum(pdf(x), (x, set._inf, set._sup)).doit()
|
||||
_value_check(Eq(val, 1) != S.false, "The pdf is incorrect on the given set.")
|
||||
|
||||
|
||||
|
||||
def DiscreteRV(symbol, density, set=S.Integers, **kwargs):
|
||||
"""
|
||||
Create a Discrete Random Variable given the following:
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
symbol : Symbol
|
||||
Represents name of the random variable.
|
||||
density : Expression containing symbol
|
||||
Represents probability density function.
|
||||
set : set
|
||||
Represents the region where the pdf is valid, by default is real line.
|
||||
check : bool
|
||||
If True, it will check whether the given density
|
||||
integrates to 1 over the given set. If False, it
|
||||
will not perform this check. Default is False.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import DiscreteRV, P, E
|
||||
>>> from sympy import Rational, Symbol
|
||||
>>> x = Symbol('x')
|
||||
>>> n = 10
|
||||
>>> density = Rational(1, 10)
|
||||
>>> X = DiscreteRV(x, density, set=set(range(n)))
|
||||
>>> E(X)
|
||||
9/2
|
||||
>>> P(X>3)
|
||||
3/5
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
"""
|
||||
set = sympify(set)
|
||||
pdf = Piecewise((density, set.as_relational(symbol)), (0, True))
|
||||
pdf = Lambda(symbol, pdf)
|
||||
# have a default of False while `rv` should have a default of True
|
||||
kwargs['check'] = kwargs.pop('check', False)
|
||||
return rv(symbol.name, DiscreteDistributionHandmade, pdf, set, **kwargs)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Flory-Schulz distribution ------------------------------------------------------------
|
||||
|
||||
class FlorySchulzDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('a',)
|
||||
set = S.Naturals
|
||||
|
||||
@staticmethod
|
||||
def check(a):
|
||||
_value_check((0 < a, a < 1), "a must be between 0 and 1")
|
||||
|
||||
def pdf(self, k):
|
||||
a = self.a
|
||||
return (a**2 * k * (1 - a)**(k - 1))
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
a = self.a
|
||||
return a**2*exp(I*t)/((1 + (a - 1)*exp(I*t))**2)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
a = self.a
|
||||
return a**2*exp(t)/((1 + (a - 1)*exp(t))**2)
|
||||
|
||||
|
||||
def FlorySchulz(name, a):
|
||||
r"""
|
||||
Create a discrete random variable with a FlorySchulz distribution.
|
||||
|
||||
The density of the FlorySchulz distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := (a^2) k (1 - a)^{k-1}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
a : A real number between 0 and 1
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, E, variance, FlorySchulz
|
||||
>>> from sympy import Symbol, S
|
||||
|
||||
>>> a = S.One / 5
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = FlorySchulz("x", a)
|
||||
|
||||
>>> density(X)(z)
|
||||
(4/5)**(z - 1)*z/25
|
||||
|
||||
>>> E(X)
|
||||
9
|
||||
|
||||
>>> variance(X)
|
||||
40
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
https://en.wikipedia.org/wiki/Flory%E2%80%93Schulz_distribution
|
||||
"""
|
||||
return rv(name, FlorySchulzDistribution, a)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Geometric distribution ------------------------------------------------------------
|
||||
|
||||
class GeometricDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('p',)
|
||||
set = S.Naturals
|
||||
|
||||
@staticmethod
|
||||
def check(p):
|
||||
_value_check((0 < p, p <= 1), "p must be between 0 and 1")
|
||||
|
||||
def pdf(self, k):
|
||||
return (1 - self.p)**(k - 1) * self.p
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
p = self.p
|
||||
return p * exp(I*t) / (1 - (1 - p)*exp(I*t))
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
p = self.p
|
||||
return p * exp(t) / (1 - (1 - p) * exp(t))
|
||||
|
||||
|
||||
def Geometric(name, p):
|
||||
r"""
|
||||
Create a discrete random variable with a Geometric distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Geometric distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := p (1 - p)^{k - 1}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
p : A probability between 0 and 1
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Geometric, density, E, variance
|
||||
>>> from sympy import Symbol, S
|
||||
|
||||
>>> p = S.One / 5
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = Geometric("x", p)
|
||||
|
||||
>>> density(X)(z)
|
||||
(4/5)**(z - 1)/5
|
||||
|
||||
>>> E(X)
|
||||
5
|
||||
|
||||
>>> variance(X)
|
||||
20
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Geometric_distribution
|
||||
.. [2] https://mathworld.wolfram.com/GeometricDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, GeometricDistribution, p)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Hermite distribution ---------------------------------------------------------
|
||||
|
||||
|
||||
class HermiteDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('a1', 'a2')
|
||||
set = S.Naturals0
|
||||
|
||||
@staticmethod
|
||||
def check(a1, a2):
|
||||
_value_check(a1.is_nonnegative, 'Parameter a1 must be >= 0.')
|
||||
_value_check(a2.is_nonnegative, 'Parameter a2 must be >= 0.')
|
||||
|
||||
def pdf(self, k):
|
||||
a1, a2 = self.a1, self.a2
|
||||
term1 = exp(-(a1 + a2))
|
||||
j = Dummy("j", integer=True)
|
||||
num = a1**(k - 2*j) * a2**j
|
||||
den = factorial(k - 2*j) * factorial(j)
|
||||
return term1 * Sum(num/den, (j, 0, k//2)).doit()
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
a1, a2 = self.a1, self.a2
|
||||
term1 = a1 * (exp(t) - 1)
|
||||
term2 = a2 * (exp(2*t) - 1)
|
||||
return exp(term1 + term2)
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
a1, a2 = self.a1, self.a2
|
||||
term1 = a1 * (exp(I*t) - 1)
|
||||
term2 = a2 * (exp(2*I*t) - 1)
|
||||
return exp(term1 + term2)
|
||||
|
||||
def Hermite(name, a1, a2):
|
||||
r"""
|
||||
Create a discrete random variable with a Hermite distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Hermite distribution is given by
|
||||
|
||||
.. math::
|
||||
f(x):= e^{-a_1 -a_2}\sum_{j=0}^{\left \lfloor x/2 \right \rfloor}
|
||||
\frac{a_{1}^{x-2j}a_{2}^{j}}{(x-2j)!j!}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
a1 : A Positive number greater than equal to 0.
|
||||
a2 : A Positive number greater than equal to 0.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Hermite, density, E, variance
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> a1 = Symbol("a1", positive=True)
|
||||
>>> a2 = Symbol("a2", positive=True)
|
||||
>>> x = Symbol("x")
|
||||
|
||||
>>> H = Hermite("H", a1=5, a2=4)
|
||||
|
||||
>>> density(H)(2)
|
||||
33*exp(-9)/2
|
||||
|
||||
>>> E(H)
|
||||
13
|
||||
|
||||
>>> variance(H)
|
||||
21
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Hermite_distribution
|
||||
|
||||
"""
|
||||
|
||||
return rv(name, HermiteDistribution, a1, a2)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Logarithmic distribution ------------------------------------------------------------
|
||||
|
||||
class LogarithmicDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('p',)
|
||||
|
||||
set = S.Naturals
|
||||
|
||||
@staticmethod
|
||||
def check(p):
|
||||
_value_check((p > 0, p < 1), "p should be between 0 and 1")
|
||||
|
||||
def pdf(self, k):
|
||||
p = self.p
|
||||
return (-1) * p**k / (k * log(1 - p))
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
p = self.p
|
||||
return log(1 - p * exp(I*t)) / log(1 - p)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
p = self.p
|
||||
return log(1 - p * exp(t)) / log(1 - p)
|
||||
|
||||
|
||||
def Logarithmic(name, p):
|
||||
r"""
|
||||
Create a discrete random variable with a Logarithmic distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Logarithmic distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := \frac{-p^k}{k \ln{(1 - p)}}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
p : A value between 0 and 1
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Logarithmic, density, E, variance
|
||||
>>> from sympy import Symbol, S
|
||||
|
||||
>>> p = S.One / 5
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = Logarithmic("x", p)
|
||||
|
||||
>>> density(X)(z)
|
||||
-1/(5**z*z*log(4/5))
|
||||
|
||||
>>> E(X)
|
||||
-1/(-4*log(5) + 8*log(2))
|
||||
|
||||
>>> variance(X)
|
||||
-1/((-4*log(5) + 8*log(2))*(-2*log(5) + 4*log(2))) + 1/(-64*log(2)*log(5) + 64*log(2)**2 + 16*log(5)**2) - 10/(-32*log(5) + 64*log(2))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Logarithmic_distribution
|
||||
.. [2] https://mathworld.wolfram.com/LogarithmicDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, LogarithmicDistribution, p)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Negative binomial distribution ------------------------------------------------------------
|
||||
|
||||
class NegativeBinomialDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('r', 'p')
|
||||
set = S.Naturals0
|
||||
|
||||
@staticmethod
|
||||
def check(r, p):
|
||||
_value_check(r > 0, 'r should be positive')
|
||||
_value_check((p > 0, p < 1), 'p should be between 0 and 1')
|
||||
|
||||
def pdf(self, k):
|
||||
r = self.r
|
||||
p = self.p
|
||||
|
||||
return binomial(k + r - 1, k) * (1 - p)**k * p**r
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
r = self.r
|
||||
p = self.p
|
||||
|
||||
return (p / (1 - (1 - p) * exp(I*t)))**r
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
r = self.r
|
||||
p = self.p
|
||||
|
||||
return (p / (1 - (1 - p) * exp(t)))**r
|
||||
|
||||
def NegativeBinomial(name, r, p):
|
||||
r"""
|
||||
Create a discrete random variable with a Negative Binomial distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Negative Binomial distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := \binom{k + r - 1}{k} (1 - p)^k p^r
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
r : A positive value
|
||||
Number of successes until the experiment is stopped.
|
||||
p : A value between 0 and 1
|
||||
Probability of success.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import NegativeBinomial, density, E, variance
|
||||
>>> from sympy import Symbol, S
|
||||
|
||||
>>> r = 5
|
||||
>>> p = S.One / 3
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = NegativeBinomial("x", r, p)
|
||||
|
||||
>>> density(X)(z)
|
||||
(2/3)**z*binomial(z + 4, z)/243
|
||||
|
||||
>>> E(X)
|
||||
10
|
||||
|
||||
>>> variance(X)
|
||||
30
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Negative_binomial_distribution
|
||||
.. [2] https://mathworld.wolfram.com/NegativeBinomialDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, NegativeBinomialDistribution, r, p)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Poisson distribution ------------------------------------------------------------
|
||||
|
||||
class PoissonDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('lamda',)
|
||||
|
||||
set = S.Naturals0
|
||||
|
||||
@staticmethod
|
||||
def check(lamda):
|
||||
_value_check(lamda > 0, "Lambda must be positive")
|
||||
|
||||
def pdf(self, k):
|
||||
return self.lamda**k / factorial(k) * exp(-self.lamda)
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
return exp(self.lamda * (exp(I*t) - 1))
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
return exp(self.lamda * (exp(t) - 1))
|
||||
|
||||
def expectation(self, expr, var, evaluate=True, **kwargs):
|
||||
if evaluate:
|
||||
if expr == var:
|
||||
return self.lamda
|
||||
if (
|
||||
isinstance(expr, FallingFactorial)
|
||||
and expr.args[1].is_integer
|
||||
and expr.args[1].is_positive
|
||||
and expr.args[0] == var
|
||||
):
|
||||
return self.lamda ** expr.args[1]
|
||||
return super().expectation(expr, var, evaluate, **kwargs)
|
||||
|
||||
def Poisson(name, lamda):
|
||||
r"""
|
||||
Create a discrete random variable with a Poisson distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Poisson distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := \frac{\lambda^{k} e^{- \lambda}}{k!}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
lamda : Positive number, a rate
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Poisson, density, E, variance
|
||||
>>> from sympy import Symbol, simplify
|
||||
|
||||
>>> rate = Symbol("lambda", positive=True)
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = Poisson("x", rate)
|
||||
|
||||
>>> density(X)(z)
|
||||
lambda**z*exp(-lambda)/factorial(z)
|
||||
|
||||
>>> E(X)
|
||||
lambda
|
||||
|
||||
>>> simplify(variance(X))
|
||||
lambda
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Poisson_distribution
|
||||
.. [2] https://mathworld.wolfram.com/PoissonDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, PoissonDistribution, lamda)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Skellam distribution --------------------------------------------------------
|
||||
|
||||
|
||||
class SkellamDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('mu1', 'mu2')
|
||||
set = S.Integers
|
||||
|
||||
@staticmethod
|
||||
def check(mu1, mu2):
|
||||
_value_check(mu1 >= 0, 'Parameter mu1 must be >= 0')
|
||||
_value_check(mu2 >= 0, 'Parameter mu2 must be >= 0')
|
||||
|
||||
def pdf(self, k):
|
||||
(mu1, mu2) = (self.mu1, self.mu2)
|
||||
term1 = exp(-(mu1 + mu2)) * (mu1 / mu2) ** (k / 2)
|
||||
term2 = besseli(k, 2 * sqrt(mu1 * mu2))
|
||||
return term1 * term2
|
||||
|
||||
def _cdf(self, x):
|
||||
raise NotImplementedError(
|
||||
"Skellam doesn't have closed form for the CDF.")
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
(mu1, mu2) = (self.mu1, self.mu2)
|
||||
return exp(-(mu1 + mu2) + mu1 * exp(I * t) + mu2 * exp(-I * t))
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
(mu1, mu2) = (self.mu1, self.mu2)
|
||||
return exp(-(mu1 + mu2) + mu1 * exp(t) + mu2 * exp(-t))
|
||||
|
||||
|
||||
def Skellam(name, mu1, mu2):
|
||||
r"""
|
||||
Create a discrete random variable with a Skellam distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The Skellam is the distribution of the difference N1 - N2
|
||||
of two statistically independent random variables N1 and N2
|
||||
each Poisson-distributed with respective expected values mu1 and mu2.
|
||||
|
||||
The density of the Skellam distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := e^{-(\mu_1+\mu_2)}(\frac{\mu_1}{\mu_2})^{k/2}I_k(2\sqrt{\mu_1\mu_2})
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mu1 : A non-negative value
|
||||
mu2 : A non-negative value
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Skellam, density, E, variance
|
||||
>>> from sympy import Symbol, pprint
|
||||
|
||||
>>> z = Symbol("z", integer=True)
|
||||
>>> mu1 = Symbol("mu1", positive=True)
|
||||
>>> mu2 = Symbol("mu2", positive=True)
|
||||
>>> X = Skellam("x", mu1, mu2)
|
||||
|
||||
>>> pprint(density(X)(z), use_unicode=False)
|
||||
z
|
||||
-
|
||||
2
|
||||
/mu1\ -mu1 - mu2 / _____ _____\
|
||||
|---| *e *besseli\z, 2*\/ mu1 *\/ mu2 /
|
||||
\mu2/
|
||||
>>> E(X)
|
||||
mu1 - mu2
|
||||
>>> variance(X).expand()
|
||||
mu1 + mu2
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Skellam_distribution
|
||||
|
||||
"""
|
||||
return rv(name, SkellamDistribution, mu1, mu2)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Yule-Simon distribution ------------------------------------------------------------
|
||||
|
||||
class YuleSimonDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('rho',)
|
||||
set = S.Naturals
|
||||
|
||||
@staticmethod
|
||||
def check(rho):
|
||||
_value_check(rho > 0, 'rho should be positive')
|
||||
|
||||
def pdf(self, k):
|
||||
rho = self.rho
|
||||
return rho * beta(k, rho + 1)
|
||||
|
||||
def _cdf(self, x):
|
||||
return Piecewise((1 - floor(x) * beta(floor(x), self.rho + 1), x >= 1), (0, True))
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
rho = self.rho
|
||||
return rho * hyper((1, 1), (rho + 2,), exp(I*t)) * exp(I*t) / (rho + 1)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
rho = self.rho
|
||||
return rho * hyper((1, 1), (rho + 2,), exp(t)) * exp(t) / (rho + 1)
|
||||
|
||||
|
||||
def YuleSimon(name, rho):
|
||||
r"""
|
||||
Create a discrete random variable with a Yule-Simon distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Yule-Simon distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := \rho B(k, \rho + 1)
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rho : A positive value
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import YuleSimon, density, E, variance
|
||||
>>> from sympy import Symbol, simplify
|
||||
|
||||
>>> p = 5
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = YuleSimon("x", p)
|
||||
|
||||
>>> density(X)(z)
|
||||
5*beta(z, 6)
|
||||
|
||||
>>> simplify(E(X))
|
||||
5/4
|
||||
|
||||
>>> simplify(variance(X))
|
||||
25/48
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Yule%E2%80%93Simon_distribution
|
||||
|
||||
"""
|
||||
return rv(name, YuleSimonDistribution, rho)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Zeta distribution ------------------------------------------------------------
|
||||
|
||||
class ZetaDistribution(SingleDiscreteDistribution):
|
||||
_argnames = ('s',)
|
||||
set = S.Naturals
|
||||
|
||||
@staticmethod
|
||||
def check(s):
|
||||
_value_check(s > 1, 's should be greater than 1')
|
||||
|
||||
def pdf(self, k):
|
||||
s = self.s
|
||||
return 1 / (k**s * zeta(s))
|
||||
|
||||
def _characteristic_function(self, t):
|
||||
return polylog(self.s, exp(I*t)) / zeta(self.s)
|
||||
|
||||
def _moment_generating_function(self, t):
|
||||
return polylog(self.s, exp(t)) / zeta(self.s)
|
||||
|
||||
|
||||
def Zeta(name, s):
|
||||
r"""
|
||||
Create a discrete random variable with a Zeta distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The density of the Zeta distribution is given by
|
||||
|
||||
.. math::
|
||||
f(k) := \frac{1}{k^s \zeta{(s)}}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
s : A value greater than 1
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Zeta, density, E, variance
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> s = 5
|
||||
>>> z = Symbol("z")
|
||||
|
||||
>>> X = Zeta("x", s)
|
||||
|
||||
>>> density(X)(z)
|
||||
1/(z**5*zeta(5))
|
||||
|
||||
>>> E(X)
|
||||
pi**4/(90*zeta(5))
|
||||
|
||||
>>> variance(X)
|
||||
-pi**8/(8100*zeta(5)**2) + zeta(3)/zeta(5)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Zeta_distribution
|
||||
|
||||
"""
|
||||
return rv(name, ZetaDistribution, s)
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Tools for arithmetic error propagation."""
|
||||
|
||||
from itertools import repeat, combinations
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.stats.symbolic_probability import RandomSymbol, Variance, Covariance
|
||||
from sympy.stats.rv import is_random
|
||||
|
||||
_arg0_or_var = lambda var: var.args[0] if len(var.args) > 0 else var
|
||||
|
||||
|
||||
def variance_prop(expr, consts=(), include_covar=False):
|
||||
r"""Symbolically propagates variance (`\sigma^2`) for expressions.
|
||||
This is computed as as seen in [1]_.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expr : Expr
|
||||
A SymPy expression to compute the variance for.
|
||||
consts : sequence of Symbols, optional
|
||||
Represents symbols that are known constants in the expr,
|
||||
and thus have zero variance. All symbols not in consts are
|
||||
assumed to be variant.
|
||||
include_covar : bool, optional
|
||||
Flag for whether or not to include covariances, default=False.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
var_expr : Expr
|
||||
An expression for the total variance of the expr.
|
||||
The variance for the original symbols (e.g. x) are represented
|
||||
via instance of the Variance symbol (e.g. Variance(x)).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, exp
|
||||
>>> from sympy.stats.error_prop import variance_prop
|
||||
>>> x, y = symbols('x y')
|
||||
|
||||
>>> variance_prop(x + y)
|
||||
Variance(x) + Variance(y)
|
||||
|
||||
>>> variance_prop(x * y)
|
||||
x**2*Variance(y) + y**2*Variance(x)
|
||||
|
||||
>>> variance_prop(exp(2*x))
|
||||
4*exp(4*x)*Variance(x)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Propagation_of_uncertainty
|
||||
|
||||
"""
|
||||
args = expr.args
|
||||
if len(args) == 0:
|
||||
if expr in consts:
|
||||
return S.Zero
|
||||
elif is_random(expr):
|
||||
return Variance(expr).doit()
|
||||
elif isinstance(expr, Symbol):
|
||||
return Variance(RandomSymbol(expr)).doit()
|
||||
else:
|
||||
return S.Zero
|
||||
nargs = len(args)
|
||||
var_args = list(map(variance_prop, args, repeat(consts, nargs),
|
||||
repeat(include_covar, nargs)))
|
||||
if isinstance(expr, Add):
|
||||
var_expr = Add(*var_args)
|
||||
if include_covar:
|
||||
terms = [2 * Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand() \
|
||||
for x, y in combinations(var_args, 2)]
|
||||
var_expr += Add(*terms)
|
||||
elif isinstance(expr, Mul):
|
||||
terms = [v/a**2 for a, v in zip(args, var_args)]
|
||||
var_expr = simplify(expr**2 * Add(*terms))
|
||||
if include_covar:
|
||||
terms = [2*Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand()/(a*b) \
|
||||
for (a, b), (x, y) in zip(combinations(args, 2),
|
||||
combinations(var_args, 2))]
|
||||
var_expr += Add(*terms)
|
||||
elif isinstance(expr, Pow):
|
||||
b = args[1]
|
||||
v = var_args[0] * (expr * b / args[0])**2
|
||||
var_expr = simplify(v)
|
||||
elif isinstance(expr, exp):
|
||||
var_expr = simplify(var_args[0] * expr**2)
|
||||
else:
|
||||
# unknown how to proceed, return variance of whole expr.
|
||||
var_expr = Variance(expr)
|
||||
return var_expr
|
||||
@@ -0,0 +1,512 @@
|
||||
"""
|
||||
Finite Discrete Random Variables Module
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.stats.frv_types
|
||||
sympy.stats.rv
|
||||
sympy.stats.crv
|
||||
"""
|
||||
from itertools import product
|
||||
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (I, nan)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, Symbol)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.logic.boolalg import (And, Or)
|
||||
from sympy.sets.sets import Intersection
|
||||
from sympy.core.containers import Dict
|
||||
from sympy.core.logic import Logic
|
||||
from sympy.core.relational import Relational
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.sets.sets import FiniteSet
|
||||
from sympy.stats.rv import (RandomDomain, ProductDomain, ConditionalDomain,
|
||||
PSpace, IndependentProductPSpace, SinglePSpace, random_symbols,
|
||||
sumsets, rv_subs, NamedArgsMixin, Density, Distribution)
|
||||
|
||||
|
||||
class FiniteDensity(dict):
|
||||
"""
|
||||
A domain with Finite Density.
|
||||
"""
|
||||
def __call__(self, item):
|
||||
"""
|
||||
Make instance of a class callable.
|
||||
|
||||
If item belongs to current instance of a class, return it.
|
||||
|
||||
Otherwise, return 0.
|
||||
"""
|
||||
item = sympify(item)
|
||||
if item in self:
|
||||
return self[item]
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
"""
|
||||
Return item as dictionary.
|
||||
"""
|
||||
return dict(self)
|
||||
|
||||
class FiniteDomain(RandomDomain):
|
||||
"""
|
||||
A domain with discrete finite support
|
||||
|
||||
Represented using a FiniteSet.
|
||||
"""
|
||||
is_Finite = True
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
return FiniteSet(sym for sym, val in self.elements)
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
return FiniteSet(*[Dict(dict(el)) for el in self.elements])
|
||||
|
||||
def __contains__(self, other):
|
||||
return other in self.elements
|
||||
|
||||
def __iter__(self):
|
||||
return self.elements.__iter__()
|
||||
|
||||
def as_boolean(self):
|
||||
return Or(*[And(*[Eq(sym, val) for sym, val in item]) for item in self])
|
||||
|
||||
|
||||
class SingleFiniteDomain(FiniteDomain):
|
||||
"""
|
||||
A FiniteDomain over a single symbol/set
|
||||
|
||||
Example: The possibilities of a *single* die roll.
|
||||
"""
|
||||
|
||||
def __new__(cls, symbol, set):
|
||||
if not isinstance(set, FiniteSet) and \
|
||||
not isinstance(set, Intersection):
|
||||
set = FiniteSet(*set)
|
||||
return Basic.__new__(cls, symbol, set)
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
return FiniteSet(self.symbol)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
return FiniteSet(*[frozenset(((self.symbol, elem), )) for elem in self.set])
|
||||
|
||||
def __iter__(self):
|
||||
return (frozenset(((self.symbol, elem),)) for elem in self.set)
|
||||
|
||||
def __contains__(self, other):
|
||||
sym, val = tuple(other)[0]
|
||||
return sym == self.symbol and val in self.set
|
||||
|
||||
|
||||
class ProductFiniteDomain(ProductDomain, FiniteDomain):
|
||||
"""
|
||||
A Finite domain consisting of several other FiniteDomains
|
||||
|
||||
Example: The possibilities of the rolls of three independent dice
|
||||
"""
|
||||
|
||||
def __iter__(self):
|
||||
proditer = product(*self.domains)
|
||||
return (sumsets(items) for items in proditer)
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
return FiniteSet(*self)
|
||||
|
||||
|
||||
class ConditionalFiniteDomain(ConditionalDomain, ProductFiniteDomain):
|
||||
"""
|
||||
A FiniteDomain that has been restricted by a condition
|
||||
|
||||
Example: The possibilities of a die roll under the condition that the
|
||||
roll is even.
|
||||
"""
|
||||
|
||||
def __new__(cls, domain, condition):
|
||||
"""
|
||||
Create a new instance of ConditionalFiniteDomain class
|
||||
"""
|
||||
if condition is True:
|
||||
return domain
|
||||
cond = rv_subs(condition)
|
||||
return Basic.__new__(cls, domain, cond)
|
||||
|
||||
def _test(self, elem):
|
||||
"""
|
||||
Test the value. If value is boolean, return it. If value is equality
|
||||
relational (two objects are equal), return it with left-hand side
|
||||
being equal to right-hand side. Otherwise, raise ValueError exception.
|
||||
"""
|
||||
val = self.condition.xreplace(dict(elem))
|
||||
if val in [True, False]:
|
||||
return val
|
||||
elif val.is_Equality:
|
||||
return val.lhs == val.rhs
|
||||
raise ValueError("Undecidable if %s" % str(val))
|
||||
|
||||
def __contains__(self, other):
|
||||
return other in self.fulldomain and self._test(other)
|
||||
|
||||
def __iter__(self):
|
||||
return (elem for elem in self.fulldomain if self._test(elem))
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if isinstance(self.fulldomain, SingleFiniteDomain):
|
||||
return FiniteSet(*[elem for elem in self.fulldomain.set
|
||||
if frozenset(((self.fulldomain.symbol, elem),)) in self])
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Not implemented on multi-dimensional conditional domain")
|
||||
|
||||
def as_boolean(self):
|
||||
return FiniteDomain.as_boolean(self)
|
||||
|
||||
|
||||
class SingleFiniteDistribution(Distribution, NamedArgsMixin):
|
||||
def __new__(cls, *args):
|
||||
args = list(map(sympify, args))
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
@staticmethod
|
||||
def check(*args):
|
||||
pass
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def dict(self):
|
||||
if self.is_symbolic:
|
||||
return Density(self)
|
||||
return {k: self.pmf(k) for k in self.set}
|
||||
|
||||
def pmf(self, *args): # to be overridden by specific distribution
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def set(self): # to be overridden by specific distribution
|
||||
raise NotImplementedError()
|
||||
|
||||
values = property(lambda self: self.dict.values)
|
||||
items = property(lambda self: self.dict.items)
|
||||
is_symbolic = property(lambda self: False)
|
||||
__iter__ = property(lambda self: self.dict.__iter__)
|
||||
__getitem__ = property(lambda self: self.dict.__getitem__)
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.pmf(*args)
|
||||
|
||||
def __contains__(self, other):
|
||||
return other in self.set
|
||||
|
||||
|
||||
#=============================================
|
||||
#========= Probability Space ===============
|
||||
#=============================================
|
||||
|
||||
|
||||
class FinitePSpace(PSpace):
|
||||
"""
|
||||
A Finite Probability Space
|
||||
|
||||
Represents the probabilities of a finite number of events.
|
||||
"""
|
||||
is_Finite = True
|
||||
|
||||
def __new__(cls, domain, density):
|
||||
density = {sympify(key): sympify(val)
|
||||
for key, val in density.items()}
|
||||
public_density = Dict(density)
|
||||
|
||||
obj = PSpace.__new__(cls, domain, public_density)
|
||||
obj._density = density
|
||||
return obj
|
||||
|
||||
def prob_of(self, elem):
|
||||
elem = sympify(elem)
|
||||
density = self._density
|
||||
if isinstance(list(density.keys())[0], FiniteSet):
|
||||
return density.get(elem, S.Zero)
|
||||
return density.get(tuple(elem)[0][1], S.Zero)
|
||||
|
||||
def where(self, condition):
|
||||
assert all(r.symbol in self.symbols for r in random_symbols(condition))
|
||||
return ConditionalFiniteDomain(self.domain, condition)
|
||||
|
||||
def compute_density(self, expr):
|
||||
expr = rv_subs(expr, self.values)
|
||||
d = FiniteDensity()
|
||||
for elem in self.domain:
|
||||
val = expr.xreplace(dict(elem))
|
||||
prob = self.prob_of(elem)
|
||||
d[val] = d.get(val, S.Zero) + prob
|
||||
return d
|
||||
|
||||
@cacheit
|
||||
def compute_cdf(self, expr):
|
||||
d = self.compute_density(expr)
|
||||
cum_prob = S.Zero
|
||||
cdf = []
|
||||
for key in sorted(d):
|
||||
prob = d[key]
|
||||
cum_prob += prob
|
||||
cdf.append((key, cum_prob))
|
||||
|
||||
return dict(cdf)
|
||||
|
||||
@cacheit
|
||||
def sorted_cdf(self, expr, python_float=False):
|
||||
cdf = self.compute_cdf(expr)
|
||||
items = list(cdf.items())
|
||||
sorted_items = sorted(items, key=lambda val_cumprob: val_cumprob[1])
|
||||
if python_float:
|
||||
sorted_items = [(v, float(cum_prob))
|
||||
for v, cum_prob in sorted_items]
|
||||
return sorted_items
|
||||
|
||||
@cacheit
|
||||
def compute_characteristic_function(self, expr):
|
||||
d = self.compute_density(expr)
|
||||
t = Dummy('t', real=True)
|
||||
|
||||
return Lambda(t, sum(exp(I*k*t)*v for k,v in d.items()))
|
||||
|
||||
@cacheit
|
||||
def compute_moment_generating_function(self, expr):
|
||||
d = self.compute_density(expr)
|
||||
t = Dummy('t', real=True)
|
||||
|
||||
return Lambda(t, sum(exp(k*t)*v for k,v in d.items()))
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, **kwargs):
|
||||
rvs = rvs or self.values
|
||||
expr = rv_subs(expr, rvs)
|
||||
probs = [self.prob_of(elem) for elem in self.domain]
|
||||
if isinstance(expr, (Logic, Relational)):
|
||||
parse_domain = [tuple(elem)[0][1] for elem in self.domain]
|
||||
bools = [expr.xreplace(dict(elem)) for elem in self.domain]
|
||||
else:
|
||||
parse_domain = [expr.xreplace(dict(elem)) for elem in self.domain]
|
||||
bools = [True for elem in self.domain]
|
||||
return sum(Piecewise((prob * elem, blv), (S.Zero, True))
|
||||
for prob, elem, blv in zip(probs, parse_domain, bools))
|
||||
|
||||
def compute_quantile(self, expr):
|
||||
cdf = self.compute_cdf(expr)
|
||||
p = Dummy('p', real=True)
|
||||
set = ((nan, (p < 0) | (p > 1)),)
|
||||
for key, value in cdf.items():
|
||||
set = set + ((key, p <= value), )
|
||||
return Lambda(p, Piecewise(*set))
|
||||
|
||||
def probability(self, condition):
|
||||
cond_symbols = frozenset(rs.symbol for rs in random_symbols(condition))
|
||||
cond = rv_subs(condition)
|
||||
if not cond_symbols.issubset(self.symbols):
|
||||
raise ValueError("Cannot compare foreign random symbols, %s"
|
||||
%(str(cond_symbols - self.symbols)))
|
||||
if isinstance(condition, Relational) and \
|
||||
(not cond.free_symbols.issubset(self.domain.free_symbols)):
|
||||
rv = condition.lhs if isinstance(condition.rhs, Symbol) else condition.rhs
|
||||
return sum(Piecewise(
|
||||
(self.prob_of(elem), condition.subs(rv, list(elem)[0][1])),
|
||||
(S.Zero, True)) for elem in self.domain)
|
||||
return sympify(sum(self.prob_of(elem) for elem in self.where(condition)))
|
||||
|
||||
def conditional_space(self, condition):
|
||||
domain = self.where(condition)
|
||||
prob = self.probability(condition)
|
||||
density = {key: val / prob
|
||||
for key, val in self._density.items() if domain._test(key)}
|
||||
return FinitePSpace(domain, density)
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method
|
||||
|
||||
Returns dictionary mapping RandomSymbol to realization value.
|
||||
"""
|
||||
return {self.value: self.distribution.sample(size, library, seed)}
|
||||
|
||||
|
||||
class SingleFinitePSpace(SinglePSpace, FinitePSpace):
|
||||
"""
|
||||
A single finite probability space
|
||||
|
||||
Represents the probabilities of a set of random events that can be
|
||||
attributed to a single variable/symbol.
|
||||
|
||||
This class is implemented by many of the standard FiniteRV types such as
|
||||
Die, Bernoulli, Coin, etc....
|
||||
"""
|
||||
@property
|
||||
def domain(self):
|
||||
return SingleFiniteDomain(self.symbol, self.distribution.set)
|
||||
|
||||
@property
|
||||
def _is_symbolic(self):
|
||||
"""
|
||||
Helper property to check if the distribution
|
||||
of the random variable is having symbolic
|
||||
dimension.
|
||||
"""
|
||||
return self.distribution.is_symbolic
|
||||
|
||||
@property
|
||||
def distribution(self):
|
||||
return self.args[1]
|
||||
|
||||
def pmf(self, expr):
|
||||
return self.distribution.pmf(expr)
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def _density(self):
|
||||
return {FiniteSet((self.symbol, val)): prob
|
||||
for val, prob in self.distribution.dict.items()}
|
||||
|
||||
@cacheit
|
||||
def compute_characteristic_function(self, expr):
|
||||
if self._is_symbolic:
|
||||
d = self.compute_density(expr)
|
||||
t = Dummy('t', real=True)
|
||||
ki = Dummy('ki')
|
||||
return Lambda(t, Sum(d(ki)*exp(I*ki*t), (ki, self.args[1].low, self.args[1].high)))
|
||||
expr = rv_subs(expr, self.values)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_characteristic_function(expr)
|
||||
|
||||
@cacheit
|
||||
def compute_moment_generating_function(self, expr):
|
||||
if self._is_symbolic:
|
||||
d = self.compute_density(expr)
|
||||
t = Dummy('t', real=True)
|
||||
ki = Dummy('ki')
|
||||
return Lambda(t, Sum(d(ki)*exp(ki*t), (ki, self.args[1].low, self.args[1].high)))
|
||||
expr = rv_subs(expr, self.values)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_moment_generating_function(expr)
|
||||
|
||||
def compute_quantile(self, expr):
|
||||
if self._is_symbolic:
|
||||
raise NotImplementedError("Computing quantile for random variables "
|
||||
"with symbolic dimension because the bounds of searching the required "
|
||||
"value is undetermined.")
|
||||
expr = rv_subs(expr, self.values)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_quantile(expr)
|
||||
|
||||
def compute_density(self, expr):
|
||||
if self._is_symbolic:
|
||||
rv = list(random_symbols(expr))[0]
|
||||
k = Dummy('k', integer=True)
|
||||
cond = True if not isinstance(expr, (Relational, Logic)) \
|
||||
else expr.subs(rv, k)
|
||||
return Lambda(k,
|
||||
Piecewise((self.pmf(k), And(k >= self.args[1].low,
|
||||
k <= self.args[1].high, cond)), (S.Zero, True)))
|
||||
expr = rv_subs(expr, self.values)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_density(expr)
|
||||
|
||||
def compute_cdf(self, expr):
|
||||
if self._is_symbolic:
|
||||
d = self.compute_density(expr)
|
||||
k = Dummy('k')
|
||||
ki = Dummy('ki')
|
||||
return Lambda(k, Sum(d(ki), (ki, self.args[1].low, k)))
|
||||
expr = rv_subs(expr, self.values)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_cdf(expr)
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, **kwargs):
|
||||
if self._is_symbolic:
|
||||
rv = random_symbols(expr)[0]
|
||||
k = Dummy('k', integer=True)
|
||||
expr = expr.subs(rv, k)
|
||||
cond = True if not isinstance(expr, (Relational, Logic)) \
|
||||
else expr
|
||||
func = self.pmf(k) * k if cond != True else self.pmf(k) * expr
|
||||
return Sum(Piecewise((func, cond), (S.Zero, True)),
|
||||
(k, self.distribution.low, self.distribution.high)).doit()
|
||||
|
||||
expr = _sympify(expr)
|
||||
expr = rv_subs(expr, rvs)
|
||||
return FinitePSpace(self.domain, self.distribution).compute_expectation(expr, rvs, **kwargs)
|
||||
|
||||
def probability(self, condition):
|
||||
if self._is_symbolic:
|
||||
#TODO: Implement the mechanism for handling queries for symbolic sized distributions.
|
||||
raise NotImplementedError("Currently, probability queries are not "
|
||||
"supported for random variables with symbolic sized distributions.")
|
||||
condition = rv_subs(condition)
|
||||
return FinitePSpace(self.domain, self.distribution).probability(condition)
|
||||
|
||||
def conditional_space(self, condition):
|
||||
"""
|
||||
This method is used for transferring the
|
||||
computation to probability method because
|
||||
conditional space of random variables with
|
||||
symbolic dimensions is currently not possible.
|
||||
"""
|
||||
if self._is_symbolic:
|
||||
self
|
||||
domain = self.where(condition)
|
||||
prob = self.probability(condition)
|
||||
density = {key: val / prob
|
||||
for key, val in self._density.items() if domain._test(key)}
|
||||
return FinitePSpace(domain, density)
|
||||
|
||||
|
||||
class ProductFinitePSpace(IndependentProductPSpace, FinitePSpace):
|
||||
"""
|
||||
A collection of several independent finite probability spaces
|
||||
"""
|
||||
@property
|
||||
def domain(self):
|
||||
return ProductFiniteDomain(*[space.domain for space in self.spaces])
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def _density(self):
|
||||
proditer = product(*[iter(space._density.items())
|
||||
for space in self.spaces])
|
||||
d = {}
|
||||
for items in proditer:
|
||||
elems, probs = list(zip(*items))
|
||||
elem = sumsets(elems)
|
||||
prob = Mul(*probs)
|
||||
d[elem] = d.get(elem, S.Zero) + prob
|
||||
return Dict(d)
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def density(self):
|
||||
return Dict(self._density)
|
||||
|
||||
def probability(self, condition):
|
||||
return FinitePSpace.probability(self, condition)
|
||||
|
||||
def compute_density(self, expr):
|
||||
return FinitePSpace.compute_density(self, expr)
|
||||
@@ -0,0 +1,873 @@
|
||||
"""
|
||||
Finite Discrete Random Variables - Prebuilt variable types
|
||||
|
||||
Contains
|
||||
========
|
||||
FiniteRV
|
||||
DiscreteUniform
|
||||
Die
|
||||
Bernoulli
|
||||
Coin
|
||||
Binomial
|
||||
BetaBinomial
|
||||
Hypergeometric
|
||||
Rademacher
|
||||
IdealSoliton
|
||||
RobustSoliton
|
||||
"""
|
||||
|
||||
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import (Integer, Rational)
|
||||
from sympy.core.relational import (Eq, Ge, Gt, Le, Lt)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, Symbol)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.combinatorial.factorials import binomial
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.logic.boolalg import Or
|
||||
from sympy.sets.contains import Contains
|
||||
from sympy.sets.fancysets import Range
|
||||
from sympy.sets.sets import (Intersection, Interval)
|
||||
from sympy.functions.special.beta_functions import beta as beta_fn
|
||||
from sympy.stats.frv import (SingleFiniteDistribution,
|
||||
SingleFinitePSpace)
|
||||
from sympy.stats.rv import _value_check, Density, is_random
|
||||
from sympy.utilities.iterables import multiset
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
|
||||
__all__ = ['FiniteRV',
|
||||
'DiscreteUniform',
|
||||
'Die',
|
||||
'Bernoulli',
|
||||
'Coin',
|
||||
'Binomial',
|
||||
'BetaBinomial',
|
||||
'Hypergeometric',
|
||||
'Rademacher',
|
||||
'IdealSoliton',
|
||||
'RobustSoliton',
|
||||
]
|
||||
|
||||
def rv(name, cls, *args, **kwargs):
|
||||
args = list(map(sympify, args))
|
||||
dist = cls(*args)
|
||||
if kwargs.pop('check', True):
|
||||
dist.check(*args)
|
||||
pspace = SingleFinitePSpace(name, dist)
|
||||
if any(is_random(arg) for arg in args):
|
||||
from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution
|
||||
pspace = CompoundPSpace(name, CompoundDistribution(dist))
|
||||
return pspace.value
|
||||
|
||||
class FiniteDistributionHandmade(SingleFiniteDistribution):
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
return self.args[0]
|
||||
|
||||
def pmf(self, x):
|
||||
x = Symbol('x')
|
||||
return Lambda(x, Piecewise(*(
|
||||
[(v, Eq(k, x)) for k, v in self.dict.items()] + [(S.Zero, True)])))
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return set(self.dict.keys())
|
||||
|
||||
@staticmethod
|
||||
def check(density):
|
||||
for p in density.values():
|
||||
_value_check((p >= 0, p <= 1),
|
||||
"Probability at a point must be between 0 and 1.")
|
||||
val = sum(density.values())
|
||||
_value_check(Eq(val, 1) != S.false, "Total Probability must be 1.")
|
||||
|
||||
def FiniteRV(name, density, **kwargs):
|
||||
r"""
|
||||
Create a Finite Random Variable given a dict representing the density.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : Symbol
|
||||
Represents name of the random variable.
|
||||
density : dict
|
||||
Dictionary containing the pdf of finite distribution
|
||||
check : bool
|
||||
If True, it will check whether the given density
|
||||
integrates to 1 over the given set. If False, it
|
||||
will not perform this check. Default is False.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import FiniteRV, P, E
|
||||
|
||||
>>> density = {0: .1, 1: .2, 2: .3, 3: .4}
|
||||
>>> X = FiniteRV('X', density)
|
||||
|
||||
>>> E(X)
|
||||
2.00000000000000
|
||||
>>> P(X >= 2)
|
||||
0.700000000000000
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
"""
|
||||
# have a default of False while `rv` should have a default of True
|
||||
kwargs['check'] = kwargs.pop('check', False)
|
||||
return rv(name, FiniteDistributionHandmade, density, **kwargs)
|
||||
|
||||
class DiscreteUniformDistribution(SingleFiniteDistribution):
|
||||
|
||||
@staticmethod
|
||||
def check(*args):
|
||||
# not using _value_check since there is a
|
||||
# suggestion for the user
|
||||
if len(set(args)) != len(args):
|
||||
weights = multiset(args)
|
||||
n = Integer(len(args))
|
||||
for k in weights:
|
||||
weights[k] /= n
|
||||
raise ValueError(filldedent("""
|
||||
Repeated args detected but set expected. For a
|
||||
distribution having different weights for each
|
||||
item use the following:""") + (
|
||||
'\nS("FiniteRV(%s, %s)")' % ("'X'", weights)))
|
||||
|
||||
@property
|
||||
def p(self):
|
||||
return Rational(1, len(self.args))
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def dict(self):
|
||||
return dict.fromkeys(self.set, self.p)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return set(self.args)
|
||||
|
||||
def pmf(self, x):
|
||||
if x in self.args:
|
||||
return self.p
|
||||
else:
|
||||
return S.Zero
|
||||
|
||||
|
||||
def DiscreteUniform(name, items):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a uniform distribution over
|
||||
the input set.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
items : list/tuple
|
||||
Items over which Uniform distribution is to be made
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import DiscreteUniform, density
|
||||
>>> from sympy import symbols
|
||||
|
||||
>>> X = DiscreteUniform('X', symbols('a b c')) # equally likely over a, b, c
|
||||
>>> density(X).dict
|
||||
{a: 1/3, b: 1/3, c: 1/3}
|
||||
|
||||
>>> Y = DiscreteUniform('Y', list(range(5))) # distribution over a range
|
||||
>>> density(Y).dict
|
||||
{0: 1/5, 1: 1/5, 2: 1/5, 3: 1/5, 4: 1/5}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Discrete_uniform_distribution
|
||||
.. [2] https://mathworld.wolfram.com/DiscreteUniformDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, DiscreteUniformDistribution, *items)
|
||||
|
||||
|
||||
class DieDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('sides',)
|
||||
|
||||
@staticmethod
|
||||
def check(sides):
|
||||
_value_check((sides.is_positive, sides.is_integer),
|
||||
"number of sides must be a positive integer.")
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not self.sides.is_number
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return self.sides
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return S.One
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if self.is_symbolic:
|
||||
return Intersection(S.Naturals0, Interval(0, self.sides))
|
||||
return set(map(Integer, range(1, self.sides + 1)))
|
||||
|
||||
def pmf(self, x):
|
||||
x = sympify(x)
|
||||
if not (x.is_number or x.is_Symbol or is_random(x)):
|
||||
raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or "
|
||||
"'RandomSymbol' not %s" % (type(x)))
|
||||
cond = Ge(x, 1) & Le(x, self.sides) & Contains(x, S.Integers)
|
||||
return Piecewise((S.One/self.sides, cond), (S.Zero, True))
|
||||
|
||||
def Die(name, sides=6):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a fair die.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
sides : Integer
|
||||
Represents the number of sides of the Die, by default is 6
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Die, density
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> D6 = Die('D6', 6) # Six sided Die
|
||||
>>> density(D6).dict
|
||||
{1: 1/6, 2: 1/6, 3: 1/6, 4: 1/6, 5: 1/6, 6: 1/6}
|
||||
|
||||
>>> D4 = Die('D4', 4) # Four sided Die
|
||||
>>> density(D4).dict
|
||||
{1: 1/4, 2: 1/4, 3: 1/4, 4: 1/4}
|
||||
|
||||
>>> n = Symbol('n', positive=True, integer=True)
|
||||
>>> Dn = Die('Dn', n) # n sided Die
|
||||
>>> density(Dn).dict
|
||||
Density(DieDistribution(n))
|
||||
>>> density(Dn).dict.subs(n, 4).doit()
|
||||
{1: 1/4, 2: 1/4, 3: 1/4, 4: 1/4}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
"""
|
||||
|
||||
return rv(name, DieDistribution, sides)
|
||||
|
||||
|
||||
class BernoulliDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('p', 'succ', 'fail')
|
||||
|
||||
@staticmethod
|
||||
def check(p, succ, fail):
|
||||
_value_check((p >= 0, p <= 1),
|
||||
"p should be in range [0, 1].")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return {self.succ, self.fail}
|
||||
|
||||
def pmf(self, x):
|
||||
if isinstance(self.succ, Symbol) and isinstance(self.fail, Symbol):
|
||||
return Piecewise((self.p, x == self.succ),
|
||||
(1 - self.p, x == self.fail),
|
||||
(S.Zero, True))
|
||||
return Piecewise((self.p, Eq(x, self.succ)),
|
||||
(1 - self.p, Eq(x, self.fail)),
|
||||
(S.Zero, True))
|
||||
|
||||
|
||||
def Bernoulli(name, p, succ=1, fail=0):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a Bernoulli process.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
p : Rational number between 0 and 1
|
||||
Represents probability of success
|
||||
succ : Integer/symbol/string
|
||||
Represents event of success
|
||||
fail : Integer/symbol/string
|
||||
Represents event of failure
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Bernoulli, density
|
||||
>>> from sympy import S
|
||||
|
||||
>>> X = Bernoulli('X', S(3)/4) # 1-0 Bernoulli variable, probability = 3/4
|
||||
>>> density(X).dict
|
||||
{0: 1/4, 1: 3/4}
|
||||
|
||||
>>> X = Bernoulli('X', S.Half, 'Heads', 'Tails') # A fair coin toss
|
||||
>>> density(X).dict
|
||||
{Heads: 1/2, Tails: 1/2}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Bernoulli_distribution
|
||||
.. [2] https://mathworld.wolfram.com/BernoulliDistribution.html
|
||||
|
||||
"""
|
||||
|
||||
return rv(name, BernoulliDistribution, p, succ, fail)
|
||||
|
||||
|
||||
def Coin(name, p=S.Half):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a Coin toss.
|
||||
|
||||
This is an equivalent of a Bernoulli random variable with
|
||||
"H" and "T" as success and failure events respectively.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
p : Rational Number between 0 and 1
|
||||
Represents probability of getting "Heads", by default is Half
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Coin, density
|
||||
>>> from sympy import Rational
|
||||
|
||||
>>> C = Coin('C') # A fair coin toss
|
||||
>>> density(C).dict
|
||||
{H: 1/2, T: 1/2}
|
||||
|
||||
>>> C2 = Coin('C2', Rational(3, 5)) # An unfair coin
|
||||
>>> density(C2).dict
|
||||
{H: 3/5, T: 2/5}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.stats.Binomial
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Coin_flipping
|
||||
|
||||
"""
|
||||
return rv(name, BernoulliDistribution, p, 'H', 'T')
|
||||
|
||||
|
||||
class BinomialDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('n', 'p', 'succ', 'fail')
|
||||
|
||||
@staticmethod
|
||||
def check(n, p, succ, fail):
|
||||
_value_check((n.is_integer, n.is_nonnegative),
|
||||
"'n' must be nonnegative integer.")
|
||||
_value_check((p <= 1, p >= 0),
|
||||
"p should be in range [0, 1].")
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return self.n
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return S.Zero
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not self.n.is_number
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if self.is_symbolic:
|
||||
return Intersection(S.Naturals0, Interval(0, self.n))
|
||||
return set(self.dict.keys())
|
||||
|
||||
def pmf(self, x):
|
||||
n, p = self.n, self.p
|
||||
x = sympify(x)
|
||||
if not (x.is_number or x.is_Symbol or is_random(x)):
|
||||
raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or "
|
||||
"'RandomSymbol' not %s" % (type(x)))
|
||||
cond = Ge(x, 0) & Le(x, n) & Contains(x, S.Integers)
|
||||
return Piecewise((binomial(n, x) * p**x * (1 - p)**(n - x), cond), (S.Zero, True))
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def dict(self):
|
||||
if self.is_symbolic:
|
||||
return Density(self)
|
||||
return {k*self.succ + (self.n-k)*self.fail: self.pmf(k)
|
||||
for k in range(0, self.n + 1)}
|
||||
|
||||
|
||||
def Binomial(name, n, p, succ=1, fail=0):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a binomial distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Positive Integer
|
||||
Represents number of trials
|
||||
p : Rational Number between 0 and 1
|
||||
Represents probability of success
|
||||
succ : Integer/symbol/string
|
||||
Represents event of success, by default is 1
|
||||
fail : Integer/symbol/string
|
||||
Represents event of failure, by default is 0
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Binomial, density
|
||||
>>> from sympy import S, Symbol
|
||||
|
||||
>>> X = Binomial('X', 4, S.Half) # Four "coin flips"
|
||||
>>> density(X).dict
|
||||
{0: 1/16, 1: 1/4, 2: 3/8, 3: 1/4, 4: 1/16}
|
||||
|
||||
>>> n = Symbol('n', positive=True, integer=True)
|
||||
>>> p = Symbol('p', positive=True)
|
||||
>>> X = Binomial('X', n, S.Half) # n "coin flips"
|
||||
>>> density(X).dict
|
||||
Density(BinomialDistribution(n, 1/2, 1, 0))
|
||||
>>> density(X).dict.subs(n, 4).doit()
|
||||
{0: 1/16, 1: 1/4, 2: 3/8, 3: 1/4, 4: 1/16}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Binomial_distribution
|
||||
.. [2] https://mathworld.wolfram.com/BinomialDistribution.html
|
||||
|
||||
"""
|
||||
|
||||
return rv(name, BinomialDistribution, n, p, succ, fail)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Beta-binomial distribution ----------------------------------------------------------
|
||||
|
||||
class BetaBinomialDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('n', 'alpha', 'beta')
|
||||
|
||||
@staticmethod
|
||||
def check(n, alpha, beta):
|
||||
_value_check((n.is_integer, n.is_nonnegative),
|
||||
"'n' must be nonnegative integer. n = %s." % str(n))
|
||||
_value_check((alpha > 0),
|
||||
"'alpha' must be: alpha > 0 . alpha = %s" % str(alpha))
|
||||
_value_check((beta > 0),
|
||||
"'beta' must be: beta > 0 . beta = %s" % str(beta))
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return self.n
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return S.Zero
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not self.n.is_number
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if self.is_symbolic:
|
||||
return Intersection(S.Naturals0, Interval(0, self.n))
|
||||
return set(map(Integer, range(self.n + 1)))
|
||||
|
||||
def pmf(self, k):
|
||||
n, a, b = self.n, self.alpha, self.beta
|
||||
return binomial(n, k) * beta_fn(k + a, n - k + b) / beta_fn(a, b)
|
||||
|
||||
|
||||
def BetaBinomial(name, n, alpha, beta):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a Beta-binomial distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Positive Integer
|
||||
Represents number of trials
|
||||
alpha : Real positive number
|
||||
beta : Real positive number
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import BetaBinomial, density
|
||||
|
||||
>>> X = BetaBinomial('X', 2, 1, 1)
|
||||
>>> density(X).dict
|
||||
{0: 1/3, 1: 2*beta(2, 2), 2: 1/3}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Beta-binomial_distribution
|
||||
.. [2] https://mathworld.wolfram.com/BetaBinomialDistribution.html
|
||||
|
||||
"""
|
||||
|
||||
return rv(name, BetaBinomialDistribution, n, alpha, beta)
|
||||
|
||||
|
||||
class HypergeometricDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('N', 'm', 'n')
|
||||
|
||||
@staticmethod
|
||||
def check(n, N, m):
|
||||
_value_check((N.is_integer, N.is_nonnegative),
|
||||
"'N' must be nonnegative integer. N = %s." % str(N))
|
||||
_value_check((n.is_integer, n.is_nonnegative),
|
||||
"'n' must be nonnegative integer. n = %s." % str(n))
|
||||
_value_check((m.is_integer, m.is_nonnegative),
|
||||
"'m' must be nonnegative integer. m = %s." % str(m))
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not all(x.is_number for x in (self.N, self.m, self.n))
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return Piecewise((self.n, Lt(self.n, self.m) != False), (self.m, True))
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return Piecewise((0, Gt(0, self.n + self.m - self.N) != False), (self.n + self.m - self.N, True))
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
N, m, n = self.N, self.m, self.n
|
||||
if self.is_symbolic:
|
||||
return Intersection(S.Naturals0, Interval(self.low, self.high))
|
||||
return set(range(max(0, n + m - N), min(n, m) + 1))
|
||||
|
||||
def pmf(self, k):
|
||||
N, m, n = self.N, self.m, self.n
|
||||
return S(binomial(m, k) * binomial(N - m, n - k))/binomial(N, n)
|
||||
|
||||
|
||||
def Hypergeometric(name, N, m, n):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a hypergeometric distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
N : Positive Integer
|
||||
Represents finite population of size N.
|
||||
m : Positive Integer
|
||||
Represents number of trials with required feature.
|
||||
n : Positive Integer
|
||||
Represents numbers of draws.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Hypergeometric, density
|
||||
|
||||
>>> X = Hypergeometric('X', 10, 5, 3) # 10 marbles, 5 white (success), 3 draws
|
||||
>>> density(X).dict
|
||||
{0: 1/12, 1: 5/12, 2: 5/12, 3: 1/12}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Hypergeometric_distribution
|
||||
.. [2] https://mathworld.wolfram.com/HypergeometricDistribution.html
|
||||
|
||||
"""
|
||||
return rv(name, HypergeometricDistribution, N, m, n)
|
||||
|
||||
|
||||
class RademacherDistribution(SingleFiniteDistribution):
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return {-1, 1}
|
||||
|
||||
@property
|
||||
def pmf(self):
|
||||
k = Dummy('k')
|
||||
return Lambda(k, Piecewise((S.Half, Or(Eq(k, -1), Eq(k, 1))), (S.Zero, True)))
|
||||
|
||||
def Rademacher(name):
|
||||
r"""
|
||||
Create a Finite Random Variable representing a Rademacher distribution.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Rademacher, density
|
||||
|
||||
>>> X = Rademacher('X')
|
||||
>>> density(X).dict
|
||||
{-1: 1/2, 1: 1/2}
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.stats.Bernoulli
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Rademacher_distribution
|
||||
|
||||
"""
|
||||
return rv(name, RademacherDistribution)
|
||||
|
||||
class IdealSolitonDistribution(SingleFiniteDistribution):
|
||||
_argnames = ('k',)
|
||||
|
||||
@staticmethod
|
||||
def check(k):
|
||||
_value_check(k.is_integer and k.is_positive,
|
||||
"'k' must be a positive integer.")
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return S.One
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return self.k
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return set(map(Integer, range(1, self.k + 1)))
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def dict(self):
|
||||
if self.k.is_Symbol:
|
||||
return Density(self)
|
||||
d = {1: Rational(1, self.k)}
|
||||
d.update({i: Rational(1, i*(i - 1)) for i in range(2, self.k + 1)})
|
||||
return d
|
||||
|
||||
def pmf(self, x):
|
||||
x = sympify(x)
|
||||
if not (x.is_number or x.is_Symbol or is_random(x)):
|
||||
raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or "
|
||||
"'RandomSymbol' not %s" % (type(x)))
|
||||
cond1 = Eq(x, 1) & x.is_integer
|
||||
cond2 = Ge(x, 1) & Le(x, self.k) & x.is_integer
|
||||
return Piecewise((1/self.k, cond1), (1/(x*(x - 1)), cond2), (S.Zero, True))
|
||||
|
||||
def IdealSoliton(name, k):
|
||||
r"""
|
||||
Create a Finite Random Variable of Ideal Soliton Distribution
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
k : Positive Integer
|
||||
Represents the number of input symbols in an LT (Luby Transform) code.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import IdealSoliton, density, P, E
|
||||
>>> sol = IdealSoliton('sol', 5)
|
||||
>>> density(sol).dict
|
||||
{1: 1/5, 2: 1/2, 3: 1/6, 4: 1/12, 5: 1/20}
|
||||
>>> density(sol).set
|
||||
{1, 2, 3, 4, 5}
|
||||
|
||||
>>> from sympy import Symbol
|
||||
>>> k = Symbol('k', positive=True, integer=True)
|
||||
>>> sol = IdealSoliton('sol', k)
|
||||
>>> density(sol).dict
|
||||
Density(IdealSolitonDistribution(k))
|
||||
>>> density(sol).dict.subs(k, 10).doit()
|
||||
{1: 1/10, 2: 1/2, 3: 1/6, 4: 1/12, 5: 1/20, 6: 1/30, 7: 1/42, 8: 1/56, 9: 1/72, 10: 1/90}
|
||||
|
||||
>>> E(sol.subs(k, 10))
|
||||
7381/2520
|
||||
|
||||
>>> P(sol.subs(k, 4) > 2)
|
||||
1/4
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Soliton_distribution#Ideal_distribution
|
||||
.. [2] https://pages.cs.wisc.edu/~suman/courses/740/papers/luby02lt.pdf
|
||||
|
||||
"""
|
||||
return rv(name, IdealSolitonDistribution, k)
|
||||
|
||||
class RobustSolitonDistribution(SingleFiniteDistribution):
|
||||
_argnames= ('k', 'delta', 'c')
|
||||
|
||||
@staticmethod
|
||||
def check(k, delta, c):
|
||||
_value_check(k.is_integer and k.is_positive,
|
||||
"'k' must be a positive integer")
|
||||
_value_check(Gt(delta, 0) and Le(delta, 1),
|
||||
"'delta' must be a real number in the interval (0,1)")
|
||||
_value_check(c.is_positive,
|
||||
"'c' must be a positive real number.")
|
||||
|
||||
@property
|
||||
def R(self):
|
||||
return self.c * log(self.k/self.delta) * self.k**0.5
|
||||
|
||||
@property
|
||||
def Z(self):
|
||||
z = 0
|
||||
for i in Range(1, round(self.k/self.R)):
|
||||
z += (1/i)
|
||||
z += log(self.R/self.delta)
|
||||
return 1 + z * self.R/self.k
|
||||
|
||||
@property
|
||||
def low(self):
|
||||
return S.One
|
||||
|
||||
@property
|
||||
def high(self):
|
||||
return self.k
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return set(map(Integer, range(1, self.k + 1)))
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not (self.k.is_number and self.c.is_number and self.delta.is_number)
|
||||
|
||||
def pmf(self, x):
|
||||
x = sympify(x)
|
||||
if not (x.is_number or x.is_Symbol or is_random(x)):
|
||||
raise ValueError("'x' expected as an argument of type 'number', 'Symbol', or "
|
||||
"'RandomSymbol' not %s" % (type(x)))
|
||||
|
||||
cond1 = Eq(x, 1) & x.is_integer
|
||||
cond2 = Ge(x, 1) & Le(x, self.k) & x.is_integer
|
||||
rho = Piecewise((Rational(1, self.k), cond1), (Rational(1, x*(x-1)), cond2), (S.Zero, True))
|
||||
|
||||
cond1 = Ge(x, 1) & Le(x, round(self.k/self.R)-1)
|
||||
cond2 = Eq(x, round(self.k/self.R))
|
||||
tau = Piecewise((self.R/(self.k * x), cond1), (self.R * log(self.R/self.delta)/self.k, cond2), (S.Zero, True))
|
||||
|
||||
return (rho + tau)/self.Z
|
||||
|
||||
def RobustSoliton(name, k, delta, c):
|
||||
r'''
|
||||
Create a Finite Random Variable of Robust Soliton Distribution
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
k : Positive Integer
|
||||
Represents the number of input symbols in an LT (Luby Transform) code.
|
||||
delta : Positive Rational Number
|
||||
Represents the failure probability. Must be in the interval (0,1).
|
||||
c : Positive Rational Number
|
||||
Constant of proportionality. Values close to 1 are recommended
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import RobustSoliton, density, P, E
|
||||
>>> robSol = RobustSoliton('robSol', 5, 0.5, 0.01)
|
||||
>>> density(robSol).dict
|
||||
{1: 0.204253668152708, 2: 0.490631107897393, 3: 0.165210624506162, 4: 0.0834387731899302, 5: 0.0505633404760675}
|
||||
>>> density(robSol).set
|
||||
{1, 2, 3, 4, 5}
|
||||
|
||||
>>> from sympy import Symbol
|
||||
>>> k = Symbol('k', positive=True, integer=True)
|
||||
>>> c = Symbol('c', positive=True)
|
||||
>>> robSol = RobustSoliton('robSol', k, 0.5, c)
|
||||
>>> density(robSol).dict
|
||||
Density(RobustSolitonDistribution(k, 0.5, c))
|
||||
>>> density(robSol).dict.subs(k, 10).subs(c, 0.03).doit()
|
||||
{1: 0.116641095387194, 2: 0.467045731687165, 3: 0.159984123349381, 4: 0.0821431680681869, 5: 0.0505765646770100,
|
||||
6: 0.0345781523420719, 7: 0.0253132820710503, 8: 0.0194459129233227, 9: 0.0154831166726115, 10: 0.0126733075238887}
|
||||
|
||||
>>> E(robSol.subs(k, 10).subs(c, 0.05))
|
||||
2.91358846104106
|
||||
|
||||
>>> P(robSol.subs(k, 4).subs(c, 0.1) > 2)
|
||||
0.243650614389834
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Soliton_distribution#Robust_distribution
|
||||
.. [2] https://www.inference.org.uk/mackay/itprnn/ps/588.596.pdf
|
||||
.. [3] https://pages.cs.wisc.edu/~suman/courses/740/papers/luby02lt.pdf
|
||||
|
||||
'''
|
||||
return rv(name, RobustSolitonDistribution, k, delta, c)
|
||||
@@ -0,0 +1,426 @@
|
||||
"""
|
||||
Joint Random Variables Module
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.stats.rv
|
||||
sympy.stats.frv
|
||||
sympy.stats.crv
|
||||
sympy.stats.drv
|
||||
"""
|
||||
from math import prod
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, Symbol)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.sets.sets import ProductSet
|
||||
from sympy.tensor.indexed import Indexed
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.concrete.summations import Sum, summation
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.integrals.integrals import Integral, integrate
|
||||
from sympy.matrices import ImmutableMatrix, matrix2numpy, list2numpy
|
||||
from sympy.stats.crv import SingleContinuousDistribution, SingleContinuousPSpace
|
||||
from sympy.stats.drv import SingleDiscreteDistribution, SingleDiscretePSpace
|
||||
from sympy.stats.rv import (ProductPSpace, NamedArgsMixin, Distribution,
|
||||
ProductDomain, RandomSymbol, random_symbols,
|
||||
SingleDomain, _symbol_converter)
|
||||
from sympy.utilities.iterables import iterable
|
||||
from sympy.utilities.misc import filldedent
|
||||
from sympy.external import import_module
|
||||
|
||||
# __all__ = ['marginal_distribution']
|
||||
|
||||
class JointPSpace(ProductPSpace):
|
||||
"""
|
||||
Represents a joint probability space. Represented using symbols for
|
||||
each component and a distribution.
|
||||
"""
|
||||
def __new__(cls, sym, dist):
|
||||
if isinstance(dist, SingleContinuousDistribution):
|
||||
return SingleContinuousPSpace(sym, dist)
|
||||
if isinstance(dist, SingleDiscreteDistribution):
|
||||
return SingleDiscretePSpace(sym, dist)
|
||||
sym = _symbol_converter(sym)
|
||||
return Basic.__new__(cls, sym, dist)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.domain.set
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def distribution(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return JointRandomSymbol(self.symbol, self)
|
||||
|
||||
@property
|
||||
def component_count(self):
|
||||
_set = self.distribution.set
|
||||
if isinstance(_set, ProductSet):
|
||||
return S(len(_set.args))
|
||||
elif isinstance(_set, Product):
|
||||
return _set.limits[0][-1]
|
||||
return S.One
|
||||
|
||||
@property
|
||||
def pdf(self):
|
||||
sym = [Indexed(self.symbol, i) for i in range(self.component_count)]
|
||||
return self.distribution(*sym)
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
rvs = random_symbols(self.distribution)
|
||||
if not rvs:
|
||||
return SingleDomain(self.symbol, self.distribution.set)
|
||||
return ProductDomain(*[rv.pspace.domain for rv in rvs])
|
||||
|
||||
def component_domain(self, index):
|
||||
return self.set.args[index]
|
||||
|
||||
def marginal_distribution(self, *indices):
|
||||
count = self.component_count
|
||||
if count.atoms(Symbol):
|
||||
raise ValueError("Marginal distributions cannot be computed "
|
||||
"for symbolic dimensions. It is a work under progress.")
|
||||
orig = [Indexed(self.symbol, i) for i in range(count)]
|
||||
all_syms = [Symbol(str(i)) for i in orig]
|
||||
replace_dict = dict(zip(all_syms, orig))
|
||||
sym = tuple(Symbol(str(Indexed(self.symbol, i))) for i in indices)
|
||||
limits = [[i,] for i in all_syms if i not in sym]
|
||||
index = 0
|
||||
for i in range(count):
|
||||
if i not in indices:
|
||||
limits[index].append(self.distribution.set.args[i])
|
||||
limits[index] = tuple(limits[index])
|
||||
index += 1
|
||||
if self.distribution.is_Continuous:
|
||||
f = Lambda(sym, integrate(self.distribution(*all_syms), *limits))
|
||||
elif self.distribution.is_Discrete:
|
||||
f = Lambda(sym, summation(self.distribution(*all_syms), *limits))
|
||||
return f.xreplace(replace_dict)
|
||||
|
||||
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs):
|
||||
syms = tuple(self.value[i] for i in range(self.component_count))
|
||||
rvs = rvs or syms
|
||||
if not any(i in rvs for i in syms):
|
||||
return expr
|
||||
expr = expr*self.pdf
|
||||
for rv in rvs:
|
||||
if isinstance(rv, Indexed):
|
||||
expr = expr.xreplace({rv: Indexed(str(rv.base), rv.args[1])})
|
||||
elif isinstance(rv, RandomSymbol):
|
||||
expr = expr.xreplace({rv: rv.symbol})
|
||||
if self.value in random_symbols(expr):
|
||||
raise NotImplementedError(filldedent('''
|
||||
Expectations of expression with unindexed joint random symbols
|
||||
cannot be calculated yet.'''))
|
||||
limits = tuple((Indexed(str(rv.base),rv.args[1]),
|
||||
self.distribution.set.args[rv.args[1]]) for rv in syms)
|
||||
return Integral(expr, *limits)
|
||||
|
||||
def where(self, condition):
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_density(self, expr):
|
||||
raise NotImplementedError()
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method
|
||||
|
||||
Returns dictionary mapping RandomSymbol to realization value.
|
||||
"""
|
||||
return {RandomSymbol(self.symbol, self): self.distribution.sample(size,
|
||||
library=library, seed=seed)}
|
||||
|
||||
def probability(self, condition):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SampleJointScipy:
|
||||
"""Returns the sample from scipy of the given distribution"""
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_scipy(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_scipy(cls, dist, size, seed):
|
||||
"""Sample from SciPy."""
|
||||
|
||||
import numpy
|
||||
if seed is None or isinstance(seed, int):
|
||||
rand_state = numpy.random.default_rng(seed=seed)
|
||||
else:
|
||||
rand_state = seed
|
||||
from scipy import stats as scipy_stats
|
||||
scipy_rv_map = {
|
||||
'MultivariateNormalDistribution': lambda dist, size: scipy_stats.multivariate_normal.rvs(
|
||||
mean=matrix2numpy(dist.mu).flatten(),
|
||||
cov=matrix2numpy(dist.sigma), size=size, random_state=rand_state),
|
||||
'MultivariateBetaDistribution': lambda dist, size: scipy_stats.dirichlet.rvs(
|
||||
alpha=list2numpy(dist.alpha, float).flatten(), size=size, random_state=rand_state),
|
||||
'MultinomialDistribution': lambda dist, size: scipy_stats.multinomial.rvs(
|
||||
n=int(dist.n), p=list2numpy(dist.p, float).flatten(), size=size, random_state=rand_state)
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape,
|
||||
'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape,
|
||||
'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape
|
||||
}
|
||||
|
||||
dist_list = scipy_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
|
||||
samples = scipy_rv_map[dist.__class__.__name__](dist, size)
|
||||
return samples.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
class SampleJointNumpy:
|
||||
"""Returns the sample from numpy of the given distribution"""
|
||||
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_numpy(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_numpy(cls, dist, size, seed):
|
||||
"""Sample from NumPy."""
|
||||
|
||||
import numpy
|
||||
if seed is None or isinstance(seed, int):
|
||||
rand_state = numpy.random.default_rng(seed=seed)
|
||||
else:
|
||||
rand_state = seed
|
||||
numpy_rv_map = {
|
||||
'MultivariateNormalDistribution': lambda dist, size: rand_state.multivariate_normal(
|
||||
mean=matrix2numpy(dist.mu, float).flatten(),
|
||||
cov=matrix2numpy(dist.sigma, float), size=size),
|
||||
'MultivariateBetaDistribution': lambda dist, size: rand_state.dirichlet(
|
||||
alpha=list2numpy(dist.alpha, float).flatten(), size=size),
|
||||
'MultinomialDistribution': lambda dist, size: rand_state.multinomial(
|
||||
n=int(dist.n), pvals=list2numpy(dist.p, float).flatten(), size=size)
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape,
|
||||
'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape,
|
||||
'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape
|
||||
}
|
||||
|
||||
dist_list = numpy_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
|
||||
samples = numpy_rv_map[dist.__class__.__name__](dist, prod(size))
|
||||
return samples.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
class SampleJointPymc:
|
||||
"""Returns the sample from pymc of the given distribution"""
|
||||
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_pymc(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_pymc(cls, dist, size, seed):
|
||||
"""Sample from PyMC."""
|
||||
|
||||
try:
|
||||
import pymc
|
||||
except ImportError:
|
||||
import pymc3 as pymc
|
||||
pymc_rv_map = {
|
||||
'MultivariateNormalDistribution': lambda dist:
|
||||
pymc.MvNormal('X', mu=matrix2numpy(dist.mu, float).flatten(),
|
||||
cov=matrix2numpy(dist.sigma, float), shape=(1, dist.mu.shape[0])),
|
||||
'MultivariateBetaDistribution': lambda dist:
|
||||
pymc.Dirichlet('X', a=list2numpy(dist.alpha, float).flatten()),
|
||||
'MultinomialDistribution': lambda dist:
|
||||
pymc.Multinomial('X', n=int(dist.n),
|
||||
p=list2numpy(dist.p, float).flatten(), shape=(1, len(dist.p)))
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
'MultivariateNormalDistribution': lambda dist: matrix2numpy(dist.mu).flatten().shape,
|
||||
'MultivariateBetaDistribution': lambda dist: list2numpy(dist.alpha).flatten().shape,
|
||||
'MultinomialDistribution': lambda dist: list2numpy(dist.p).flatten().shape
|
||||
}
|
||||
|
||||
dist_list = pymc_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
|
||||
import logging
|
||||
logging.getLogger("pymc3").setLevel(logging.ERROR)
|
||||
with pymc.Model():
|
||||
pymc_rv_map[dist.__class__.__name__](dist)
|
||||
samples = pymc.sample(draws=prod(size), chains=1, progressbar=False, random_seed=seed, return_inferencedata=False, compute_convergence_checks=False)[:]['X']
|
||||
return samples.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
|
||||
_get_sample_class_jrv = {
|
||||
'scipy': SampleJointScipy,
|
||||
'pymc3': SampleJointPymc,
|
||||
'pymc': SampleJointPymc,
|
||||
'numpy': SampleJointNumpy
|
||||
}
|
||||
|
||||
class JointDistribution(Distribution, NamedArgsMixin):
|
||||
"""
|
||||
Represented by the random variables part of the joint distribution.
|
||||
Contains methods for PDF, CDF, sampling, marginal densities, etc.
|
||||
"""
|
||||
|
||||
_argnames = ('pdf', )
|
||||
|
||||
def __new__(cls, *args):
|
||||
args = list(map(sympify, args))
|
||||
for i in range(len(args)):
|
||||
if isinstance(args[i], list):
|
||||
args[i] = ImmutableMatrix(args[i])
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return ProductDomain(self.symbols)
|
||||
|
||||
@property
|
||||
def pdf(self):
|
||||
return self.density.args[1]
|
||||
|
||||
def cdf(self, other):
|
||||
if not isinstance(other, dict):
|
||||
raise ValueError("%s should be of type dict, got %s"%(other, type(other)))
|
||||
rvs = other.keys()
|
||||
_set = self.domain.set.sets
|
||||
expr = self.pdf(tuple(i.args[0] for i in self.symbols))
|
||||
for i in range(len(other)):
|
||||
if rvs[i].is_Continuous:
|
||||
density = Integral(expr, (rvs[i], _set[i].inf,
|
||||
other[rvs[i]]))
|
||||
elif rvs[i].is_Discrete:
|
||||
density = Sum(expr, (rvs[i], _set[i].inf,
|
||||
other[rvs[i]]))
|
||||
return density
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
""" A random realization from the distribution """
|
||||
|
||||
libraries = ('scipy', 'numpy', 'pymc3', 'pymc')
|
||||
if library not in libraries:
|
||||
raise NotImplementedError("Sampling from %s is not supported yet."
|
||||
% str(library))
|
||||
if not import_module(library):
|
||||
raise ValueError("Failed to import %s" % library)
|
||||
|
||||
samps = _get_sample_class_jrv[library](self, size, seed=seed)
|
||||
|
||||
if samps is not None:
|
||||
return samps
|
||||
raise NotImplementedError(
|
||||
"Sampling for %s is not currently implemented from %s"
|
||||
% (self.__class__.__name__, library)
|
||||
)
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.pdf(*args)
|
||||
|
||||
class JointRandomSymbol(RandomSymbol):
|
||||
"""
|
||||
Representation of random symbols with joint probability distributions
|
||||
to allow indexing."
|
||||
"""
|
||||
def __getitem__(self, key):
|
||||
if isinstance(self.pspace, JointPSpace):
|
||||
if (self.pspace.component_count <= key) == True:
|
||||
raise ValueError("Index keys for %s can only up to %s." %
|
||||
(self.name, self.pspace.component_count - 1))
|
||||
return Indexed(self, key)
|
||||
|
||||
|
||||
|
||||
class MarginalDistribution(Distribution):
|
||||
"""
|
||||
Represents the marginal distribution of a joint probability space.
|
||||
|
||||
Initialised using a probability distribution and random variables(or
|
||||
their indexed components) which should be a part of the resultant
|
||||
distribution.
|
||||
"""
|
||||
|
||||
def __new__(cls, dist, *rvs):
|
||||
if len(rvs) == 1 and iterable(rvs[0]):
|
||||
rvs = tuple(rvs[0])
|
||||
if not all(isinstance(rv, (Indexed, RandomSymbol)) for rv in rvs):
|
||||
raise ValueError(filldedent('''Marginal distribution can be
|
||||
intitialised only in terms of random variables or indexed random
|
||||
variables'''))
|
||||
rvs = Tuple.fromiter(rv for rv in rvs)
|
||||
if not isinstance(dist, JointDistribution) and len(random_symbols(dist)) == 0:
|
||||
return dist
|
||||
return Basic.__new__(cls, dist, rvs)
|
||||
|
||||
def check(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
rvs = [i for i in self.args[1] if isinstance(i, RandomSymbol)]
|
||||
return ProductSet(*[rv.pspace.set for rv in rvs])
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
rvs = self.args[1]
|
||||
return {rv.pspace.symbol for rv in rvs}
|
||||
|
||||
def pdf(self, *x):
|
||||
expr, rvs = self.args[0], self.args[1]
|
||||
marginalise_out = [i for i in random_symbols(expr) if i not in rvs]
|
||||
if isinstance(expr, JointDistribution):
|
||||
count = len(expr.domain.args)
|
||||
x = Dummy('x', real=True)
|
||||
syms = tuple(Indexed(x, i) for i in count)
|
||||
expr = expr.pdf(syms)
|
||||
else:
|
||||
syms = tuple(rv.pspace.symbol if isinstance(rv, RandomSymbol) else rv.args[0] for rv in rvs)
|
||||
return Lambda(syms, self.compute_pdf(expr, marginalise_out))(*x)
|
||||
|
||||
def compute_pdf(self, expr, rvs):
|
||||
for rv in rvs:
|
||||
lpdf = 1
|
||||
if isinstance(rv, RandomSymbol):
|
||||
lpdf = rv.pspace.pdf
|
||||
expr = self.marginalise_out(expr*lpdf, rv)
|
||||
return expr
|
||||
|
||||
def marginalise_out(self, expr, rv):
|
||||
from sympy.concrete.summations import Sum
|
||||
if isinstance(rv, RandomSymbol):
|
||||
dom = rv.pspace.set
|
||||
elif isinstance(rv, Indexed):
|
||||
dom = rv.base.component_domain(
|
||||
rv.pspace.component_domain(rv.args[1]))
|
||||
expr = expr.xreplace({rv: rv.pspace.symbol})
|
||||
if rv.pspace.is_Continuous:
|
||||
#TODO: Modify to support integration
|
||||
#for all kinds of sets.
|
||||
expr = Integral(expr, (rv.pspace.symbol, dom))
|
||||
elif rv.pspace.is_Discrete:
|
||||
#incorporate this into `Sum`/`summation`
|
||||
if dom in (S.Integers, S.Naturals, S.Naturals0):
|
||||
dom = (dom.inf, dom.sup)
|
||||
expr = Sum(expr, (rv.pspace.symbol, dom))
|
||||
return expr
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.pdf(*args)
|
||||
@@ -0,0 +1,945 @@
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (Integer, Rational, pi)
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.combinatorial.factorials import (rf, factorial)
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.bessel import besselk
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.matrices.dense import (Matrix, ones)
|
||||
from sympy.sets.fancysets import Range
|
||||
from sympy.sets.sets import (Intersection, Interval)
|
||||
from sympy.tensor.indexed import (Indexed, IndexedBase)
|
||||
from sympy.matrices import ImmutableMatrix, MatrixSymbol
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.stats.joint_rv import JointDistribution, JointPSpace, MarginalDistribution
|
||||
from sympy.stats.rv import _value_check, random_symbols
|
||||
|
||||
__all__ = ['JointRV',
|
||||
'MultivariateNormal',
|
||||
'MultivariateLaplace',
|
||||
'Dirichlet',
|
||||
'GeneralizedMultivariateLogGamma',
|
||||
'GeneralizedMultivariateLogGammaOmega',
|
||||
'Multinomial',
|
||||
'MultivariateBeta',
|
||||
'MultivariateEwens',
|
||||
'MultivariateT',
|
||||
'NegativeMultinomial',
|
||||
'NormalGamma'
|
||||
]
|
||||
|
||||
def multivariate_rv(cls, sym, *args):
|
||||
args = list(map(sympify, args))
|
||||
dist = cls(*args)
|
||||
args = dist.args
|
||||
dist.check(*args)
|
||||
return JointPSpace(sym, dist).value
|
||||
|
||||
|
||||
def marginal_distribution(rv, *indices):
|
||||
"""
|
||||
Marginal distribution function of a joint random variable.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rv : A random variable with a joint probability distribution.
|
||||
indices : Component indices or the indexed random symbol
|
||||
for which the joint distribution is to be calculated
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
A Lambda expression in `sym`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import MultivariateNormal, marginal_distribution
|
||||
>>> m = MultivariateNormal('X', [1, 2], [[2, 1], [1, 2]])
|
||||
>>> marginal_distribution(m, m[0])(1)
|
||||
1/(2*sqrt(pi))
|
||||
|
||||
"""
|
||||
indices = list(indices)
|
||||
for i in range(len(indices)):
|
||||
if isinstance(indices[i], Indexed):
|
||||
indices[i] = indices[i].args[1]
|
||||
prob_space = rv.pspace
|
||||
if not indices:
|
||||
raise ValueError(
|
||||
"At least one component for marginal density is needed.")
|
||||
if hasattr(prob_space.distribution, '_marginal_distribution'):
|
||||
return prob_space.distribution._marginal_distribution(indices, rv.symbol)
|
||||
return prob_space.marginal_distribution(*indices)
|
||||
|
||||
|
||||
class JointDistributionHandmade(JointDistribution):
|
||||
|
||||
_argnames = ('pdf',)
|
||||
is_Continuous = True
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[1]
|
||||
|
||||
|
||||
def JointRV(symbol, pdf, _set=None):
|
||||
"""
|
||||
Create a Joint Random Variable where each of its component is continuous,
|
||||
given the following:
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
symbol : Symbol
|
||||
Represents name of the random variable.
|
||||
pdf : A PDF in terms of indexed symbols of the symbol given
|
||||
as the first argument
|
||||
|
||||
NOTE
|
||||
====
|
||||
|
||||
As of now, the set for each component for a ``JointRV`` is
|
||||
equal to the set of all integers, which cannot be changed.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import exp, pi, Indexed, S
|
||||
>>> from sympy.stats import density, JointRV
|
||||
>>> x1, x2 = (Indexed('x', i) for i in (1, 2))
|
||||
>>> pdf = exp(-x1**2/2 + x1 - x2**2/2 - S(1)/2)/(2*pi)
|
||||
>>> N1 = JointRV('x', pdf) #Multivariate Normal distribution
|
||||
>>> density(N1)(1, 2)
|
||||
exp(-2)/(2*pi)
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
"""
|
||||
#TODO: Add support for sets provided by the user
|
||||
symbol = sympify(symbol)
|
||||
syms = [i for i in pdf.free_symbols if isinstance(i, Indexed)
|
||||
and i.base == IndexedBase(symbol)]
|
||||
syms = tuple(sorted(syms, key = lambda index: index.args[1]))
|
||||
_set = S.Reals**len(syms)
|
||||
pdf = Lambda(syms, pdf)
|
||||
dist = JointDistributionHandmade(pdf, _set)
|
||||
jrv = JointPSpace(symbol, dist).value
|
||||
rvs = random_symbols(pdf)
|
||||
if len(rvs) != 0:
|
||||
dist = MarginalDistribution(dist, (jrv,))
|
||||
return JointPSpace(symbol, dist).value
|
||||
return jrv
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate Normal distribution ---------------------------------------------
|
||||
|
||||
class MultivariateNormalDistribution(JointDistribution):
|
||||
_argnames = ('mu', 'sigma')
|
||||
|
||||
is_Continuous=True
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = self.mu.shape[0]
|
||||
return S.Reals**k
|
||||
|
||||
@staticmethod
|
||||
def check(mu, sigma):
|
||||
_value_check(mu.shape[0] == sigma.shape[0],
|
||||
"Size of the mean vector and covariance matrix are incorrect.")
|
||||
#check if covariance matrix is positive semi definite or not.
|
||||
if not isinstance(sigma, MatrixSymbol):
|
||||
_value_check(sigma.is_positive_semidefinite,
|
||||
"The covariance matrix must be positive semi definite. ")
|
||||
|
||||
def pdf(self, *args):
|
||||
mu, sigma = self.mu, self.sigma
|
||||
k = mu.shape[0]
|
||||
if len(args) == 1 and args[0].is_Matrix:
|
||||
args = args[0]
|
||||
else:
|
||||
args = ImmutableMatrix(args)
|
||||
x = args - mu
|
||||
density = S.One/sqrt((2*pi)**(k)*det(sigma))*exp(
|
||||
Rational(-1, 2)*x.transpose()*(sigma.inv()*x))
|
||||
return MatrixElement(density, 0, 0)
|
||||
|
||||
def _marginal_distribution(self, indices, sym):
|
||||
sym = ImmutableMatrix([Indexed(sym, i) for i in indices])
|
||||
_mu, _sigma = self.mu, self.sigma
|
||||
k = self.mu.shape[0]
|
||||
for i in range(k):
|
||||
if i not in indices:
|
||||
_mu = _mu.row_del(i)
|
||||
_sigma = _sigma.col_del(i)
|
||||
_sigma = _sigma.row_del(i)
|
||||
return Lambda(tuple(sym), S.One/sqrt((2*pi)**(len(_mu))*det(_sigma))*exp(
|
||||
Rational(-1, 2)*(_mu - sym).transpose()*(_sigma.inv()*\
|
||||
(_mu - sym)))[0])
|
||||
|
||||
def MultivariateNormal(name, mu, sigma):
|
||||
r"""
|
||||
Creates a continuous random variable with Multivariate Normal
|
||||
Distribution.
|
||||
|
||||
The density of the multivariate normal distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mu : List representing the mean or the mean vector
|
||||
sigma : Positive semidefinite square matrix
|
||||
Represents covariance Matrix.
|
||||
If `\sigma` is noninvertible then only sampling is supported currently
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import MultivariateNormal, density, marginal_distribution
|
||||
>>> from sympy import symbols, MatrixSymbol
|
||||
>>> X = MultivariateNormal('X', [3, 4], [[2, 1], [1, 2]])
|
||||
>>> y, z = symbols('y z')
|
||||
>>> density(X)(y, z)
|
||||
sqrt(3)*exp(-y**2/3 + y*z/3 + 2*y/3 - z**2/3 + 5*z/3 - 13/3)/(6*pi)
|
||||
>>> density(X)(1, 2)
|
||||
sqrt(3)*exp(-4/3)/(6*pi)
|
||||
>>> marginal_distribution(X, X[1])(y)
|
||||
exp(-(y - 4)**2/4)/(2*sqrt(pi))
|
||||
>>> marginal_distribution(X, X[0])(y)
|
||||
exp(-(y - 3)**2/4)/(2*sqrt(pi))
|
||||
|
||||
The example below shows that it is also possible to use
|
||||
symbolic parameters to define the MultivariateNormal class.
|
||||
|
||||
>>> n = symbols('n', integer=True, positive=True)
|
||||
>>> Sg = MatrixSymbol('Sg', n, n)
|
||||
>>> mu = MatrixSymbol('mu', n, 1)
|
||||
>>> obs = MatrixSymbol('obs', n, 1)
|
||||
>>> X = MultivariateNormal('X', mu, Sg)
|
||||
|
||||
The density of a multivariate normal can be
|
||||
calculated using a matrix argument, as shown below.
|
||||
|
||||
>>> density(X)(obs)
|
||||
(exp(((1/2)*mu.T - (1/2)*obs.T)*Sg**(-1)*(-mu + obs))/sqrt((2*pi)**n*Determinant(Sg)))[0, 0]
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Multivariate_normal_distribution
|
||||
|
||||
"""
|
||||
return multivariate_rv(MultivariateNormalDistribution, name, mu, sigma)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate Laplace distribution --------------------------------------------
|
||||
|
||||
class MultivariateLaplaceDistribution(JointDistribution):
|
||||
_argnames = ('mu', 'sigma')
|
||||
is_Continuous=True
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = self.mu.shape[0]
|
||||
return S.Reals**k
|
||||
|
||||
@staticmethod
|
||||
def check(mu, sigma):
|
||||
_value_check(mu.shape[0] == sigma.shape[0],
|
||||
"Size of the mean vector and covariance matrix are incorrect.")
|
||||
# check if covariance matrix is positive definite or not.
|
||||
if not isinstance(sigma, MatrixSymbol):
|
||||
_value_check(sigma.is_positive_definite,
|
||||
"The covariance matrix must be positive definite. ")
|
||||
|
||||
def pdf(self, *args):
|
||||
mu, sigma = self.mu, self.sigma
|
||||
mu_T = mu.transpose()
|
||||
k = S(mu.shape[0])
|
||||
sigma_inv = sigma.inv()
|
||||
args = ImmutableMatrix(args)
|
||||
args_T = args.transpose()
|
||||
x = (mu_T*sigma_inv*mu)[0]
|
||||
y = (args_T*sigma_inv*args)[0]
|
||||
v = 1 - k/2
|
||||
return (2 * (y/(2 + x))**(v/2) * besselk(v, sqrt((2 + x)*y)) *
|
||||
exp((args_T * sigma_inv * mu)[0]) /
|
||||
((2 * pi)**(k/2) * sqrt(det(sigma))))
|
||||
|
||||
|
||||
def MultivariateLaplace(name, mu, sigma):
|
||||
"""
|
||||
Creates a continuous random variable with Multivariate Laplace
|
||||
Distribution.
|
||||
|
||||
The density of the multivariate Laplace distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mu : List representing the mean or the mean vector
|
||||
sigma : Positive definite square matrix
|
||||
Represents covariance Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import MultivariateLaplace, density
|
||||
>>> from sympy import symbols
|
||||
>>> y, z = symbols('y z')
|
||||
>>> X = MultivariateLaplace('X', [2, 4], [[3, 1], [1, 3]])
|
||||
>>> density(X)(y, z)
|
||||
sqrt(2)*exp(y/4 + 5*z/4)*besselk(0, sqrt(15*y*(3*y/8 - z/8)/2 + 15*z*(-y/8 + 3*z/8)/2))/(4*pi)
|
||||
>>> density(X)(1, 2)
|
||||
sqrt(2)*exp(11/4)*besselk(0, sqrt(165)/4)/(4*pi)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Multivariate_Laplace_distribution
|
||||
|
||||
"""
|
||||
return multivariate_rv(MultivariateLaplaceDistribution, name, mu, sigma)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate StudentT distribution -------------------------------------------
|
||||
|
||||
class MultivariateTDistribution(JointDistribution):
|
||||
_argnames = ('mu', 'shape_mat', 'dof')
|
||||
is_Continuous=True
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = self.mu.shape[0]
|
||||
return S.Reals**k
|
||||
|
||||
@staticmethod
|
||||
def check(mu, sigma, v):
|
||||
_value_check(mu.shape[0] == sigma.shape[0],
|
||||
"Size of the location vector and shape matrix are incorrect.")
|
||||
# check if covariance matrix is positive definite or not.
|
||||
if not isinstance(sigma, MatrixSymbol):
|
||||
_value_check(sigma.is_positive_definite,
|
||||
"The shape matrix must be positive definite. ")
|
||||
|
||||
def pdf(self, *args):
|
||||
mu, sigma = self.mu, self.shape_mat
|
||||
v = S(self.dof)
|
||||
k = S(mu.shape[0])
|
||||
sigma_inv = sigma.inv()
|
||||
args = ImmutableMatrix(args)
|
||||
x = args - mu
|
||||
return gamma((k + v)/2)/(gamma(v/2)*(v*pi)**(k/2)*sqrt(det(sigma)))\
|
||||
*(1 + 1/v*(x.transpose()*sigma_inv*x)[0])**((-v - k)/2)
|
||||
|
||||
def MultivariateT(syms, mu, sigma, v):
|
||||
"""
|
||||
Creates a joint random variable with multivariate T-distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
syms : A symbol/str
|
||||
For identifying the random variable.
|
||||
mu : A list/matrix
|
||||
Representing the location vector
|
||||
sigma : The shape matrix for the distribution
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, MultivariateT
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> x = Symbol("x")
|
||||
>>> X = MultivariateT("x", [1, 1], [[1, 0], [0, 1]], 2)
|
||||
|
||||
>>> density(X)(1, 2)
|
||||
2/(9*pi)
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
"""
|
||||
return multivariate_rv(MultivariateTDistribution, syms, mu, sigma, v)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate Normal Gamma distribution ---------------------------------------
|
||||
|
||||
class NormalGammaDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('mu', 'lamda', 'alpha', 'beta')
|
||||
is_Continuous=True
|
||||
|
||||
@staticmethod
|
||||
def check(mu, lamda, alpha, beta):
|
||||
_value_check(mu.is_real, "Location must be real.")
|
||||
_value_check(lamda > 0, "Lambda must be positive")
|
||||
_value_check(alpha > 0, "alpha must be positive")
|
||||
_value_check(beta > 0, "beta must be positive")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return S.Reals*Interval(0, S.Infinity)
|
||||
|
||||
def pdf(self, x, tau):
|
||||
beta, alpha, lamda = self.beta, self.alpha, self.lamda
|
||||
mu = self.mu
|
||||
|
||||
return beta**alpha*sqrt(lamda)/(gamma(alpha)*sqrt(2*pi))*\
|
||||
tau**(alpha - S.Half)*exp(-1*beta*tau)*\
|
||||
exp(-1*(lamda*tau*(x - mu)**2)/S(2))
|
||||
|
||||
def _marginal_distribution(self, indices, *sym):
|
||||
if len(indices) == 2:
|
||||
return self.pdf(*sym)
|
||||
if indices[0] == 0:
|
||||
#For marginal over `x`, return non-standardized Student-T's
|
||||
#distribution
|
||||
x = sym[0]
|
||||
v, mu, sigma = self.alpha - S.Half, self.mu, \
|
||||
S(self.beta)/(self.lamda * self.alpha)
|
||||
return Lambda(sym, gamma((v + 1)/2)/(gamma(v/2)*sqrt(pi*v)*sigma)*\
|
||||
(1 + 1/v*((x - mu)/sigma)**2)**((-v -1)/2))
|
||||
#For marginal over `tau`, return Gamma distribution as per construction
|
||||
from sympy.stats.crv_types import GammaDistribution
|
||||
return Lambda(sym, GammaDistribution(self.alpha, self.beta)(sym[0]))
|
||||
|
||||
def NormalGamma(sym, mu, lamda, alpha, beta):
|
||||
"""
|
||||
Creates a bivariate joint random variable with multivariate Normal gamma
|
||||
distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
sym : A symbol/str
|
||||
For identifying the random variable.
|
||||
mu : A real number
|
||||
The mean of the normal distribution
|
||||
lamda : A positive integer
|
||||
Parameter of joint distribution
|
||||
alpha : A positive integer
|
||||
Parameter of joint distribution
|
||||
beta : A positive integer
|
||||
Parameter of joint distribution
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, NormalGamma
|
||||
>>> from sympy import symbols
|
||||
|
||||
>>> X = NormalGamma('x', 0, 1, 2, 3)
|
||||
>>> y, z = symbols('y z')
|
||||
|
||||
>>> density(X)(y, z)
|
||||
9*sqrt(2)*z**(3/2)*exp(-3*z)*exp(-y**2*z/2)/(2*sqrt(pi))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Normal-gamma_distribution
|
||||
|
||||
"""
|
||||
return multivariate_rv(NormalGammaDistribution, sym, mu, lamda, alpha, beta)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate Beta/Dirichlet distribution -------------------------------------
|
||||
|
||||
class MultivariateBetaDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('alpha',)
|
||||
is_Continuous = True
|
||||
|
||||
@staticmethod
|
||||
def check(alpha):
|
||||
_value_check(len(alpha) >= 2, "At least two categories should be passed.")
|
||||
for a_k in alpha:
|
||||
_value_check((a_k > 0) != False, "Each concentration parameter"
|
||||
" should be positive.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = len(self.alpha)
|
||||
return Interval(0, 1)**k
|
||||
|
||||
def pdf(self, *syms):
|
||||
alpha = self.alpha
|
||||
B = Mul.fromiter(map(gamma, alpha))/gamma(Add(*alpha))
|
||||
return Mul.fromiter(sym**(a_k - 1) for a_k, sym in zip(alpha, syms))/B
|
||||
|
||||
def MultivariateBeta(syms, *alpha):
|
||||
"""
|
||||
Creates a continuous random variable with Dirichlet/Multivariate Beta
|
||||
Distribution.
|
||||
|
||||
The density of the Dirichlet distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
alpha : Positive real numbers
|
||||
Signifies concentration numbers.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, MultivariateBeta, marginal_distribution
|
||||
>>> from sympy import Symbol
|
||||
>>> a1 = Symbol('a1', positive=True)
|
||||
>>> a2 = Symbol('a2', positive=True)
|
||||
>>> B = MultivariateBeta('B', [a1, a2])
|
||||
>>> C = MultivariateBeta('C', a1, a2)
|
||||
>>> x = Symbol('x')
|
||||
>>> y = Symbol('y')
|
||||
>>> density(B)(x, y)
|
||||
x**(a1 - 1)*y**(a2 - 1)*gamma(a1 + a2)/(gamma(a1)*gamma(a2))
|
||||
>>> marginal_distribution(C, C[0])(x)
|
||||
x**(a1 - 1)*gamma(a1 + a2)/(a2*gamma(a1)*gamma(a2))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Dirichlet_distribution
|
||||
.. [2] https://mathworld.wolfram.com/DirichletDistribution.html
|
||||
|
||||
"""
|
||||
if not isinstance(alpha[0], list):
|
||||
alpha = (list(alpha),)
|
||||
return multivariate_rv(MultivariateBetaDistribution, syms, alpha[0])
|
||||
|
||||
Dirichlet = MultivariateBeta
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multivariate Ewens distribution ----------------------------------------------
|
||||
|
||||
class MultivariateEwensDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('n', 'theta')
|
||||
is_Discrete = True
|
||||
is_Continuous = False
|
||||
|
||||
@staticmethod
|
||||
def check(n, theta):
|
||||
_value_check((n > 0),
|
||||
"sample size should be positive integer.")
|
||||
_value_check(theta.is_positive, "mutation rate should be positive.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if not isinstance(self.n, Integer):
|
||||
i = Symbol('i', integer=True, positive=True)
|
||||
return Product(Intersection(S.Naturals0, Interval(0, self.n//i)),
|
||||
(i, 1, self.n))
|
||||
prod_set = Range(0, self.n + 1)
|
||||
for i in range(2, self.n + 1):
|
||||
prod_set *= Range(0, self.n//i + 1)
|
||||
return prod_set.flatten()
|
||||
|
||||
def pdf(self, *syms):
|
||||
n, theta = self.n, self.theta
|
||||
condi = isinstance(self.n, Integer)
|
||||
if not (isinstance(syms[0], IndexedBase) or condi):
|
||||
raise ValueError("Please use IndexedBase object for syms as "
|
||||
"the dimension is symbolic")
|
||||
term_1 = factorial(n)/rf(theta, n)
|
||||
if condi:
|
||||
term_2 = Mul.fromiter(theta**syms[j]/((j+1)**syms[j]*factorial(syms[j]))
|
||||
for j in range(n))
|
||||
cond = Eq(sum((k + 1)*syms[k] for k in range(n)), n)
|
||||
return Piecewise((term_1 * term_2, cond), (0, True))
|
||||
syms = syms[0]
|
||||
j, k = symbols('j, k', positive=True, integer=True)
|
||||
term_2 = Product(theta**syms[j]/((j+1)**syms[j]*factorial(syms[j])),
|
||||
(j, 0, n - 1))
|
||||
cond = Eq(Sum((k + 1)*syms[k], (k, 0, n - 1)), n)
|
||||
return Piecewise((term_1 * term_2, cond), (0, True))
|
||||
|
||||
|
||||
def MultivariateEwens(syms, n, theta):
|
||||
"""
|
||||
Creates a discrete random variable with Multivariate Ewens
|
||||
Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Positive integer
|
||||
Size of the sample or the integer whose partitions are considered
|
||||
theta : Positive real number
|
||||
Denotes Mutation rate
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, marginal_distribution, MultivariateEwens
|
||||
>>> from sympy import Symbol
|
||||
>>> a1 = Symbol('a1', positive=True)
|
||||
>>> a2 = Symbol('a2', positive=True)
|
||||
>>> ed = MultivariateEwens('E', 2, 1)
|
||||
>>> density(ed)(a1, a2)
|
||||
Piecewise((1/(2**a2*factorial(a1)*factorial(a2)), Eq(a1 + 2*a2, 2)), (0, True))
|
||||
>>> marginal_distribution(ed, ed[0])(a1)
|
||||
Piecewise((1/factorial(a1), Eq(a1, 2)), (0, True))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Ewens%27s_sampling_formula
|
||||
.. [2] https://www.jstor.org/stable/24780825
|
||||
"""
|
||||
return multivariate_rv(MultivariateEwensDistribution, syms, n, theta)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Generalized Multivariate Log Gamma distribution ------------------------------
|
||||
|
||||
class GeneralizedMultivariateLogGammaDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('delta', 'v', 'lamda', 'mu')
|
||||
is_Continuous=True
|
||||
|
||||
def check(self, delta, v, l, mu):
|
||||
_value_check((delta >= 0, delta <= 1), "delta must be in range [0, 1].")
|
||||
_value_check((v > 0), "v must be positive")
|
||||
for lk in l:
|
||||
_value_check((lk > 0), "lamda must be a positive vector.")
|
||||
for muk in mu:
|
||||
_value_check((muk > 0), "mu must be a positive vector.")
|
||||
_value_check(len(l) > 1,"the distribution should have at least"
|
||||
" two random variables.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return S.Reals**len(self.lamda)
|
||||
|
||||
def pdf(self, *y):
|
||||
d, v, l, mu = self.delta, self.v, self.lamda, self.mu
|
||||
n = Symbol('n', negative=False, integer=True)
|
||||
k = len(l)
|
||||
sterm1 = Pow((1 - d), n)/\
|
||||
((gamma(v + n)**(k - 1))*gamma(v)*gamma(n + 1))
|
||||
sterm2 = Mul.fromiter(mui*li**(-v - n) for mui, li in zip(mu, l))
|
||||
term1 = sterm1 * sterm2
|
||||
sterm3 = (v + n) * sum(mui * yi for mui, yi in zip(mu, y))
|
||||
sterm4 = sum(exp(mui * yi)/li for (mui, yi, li) in zip(mu, y, l))
|
||||
term2 = exp(sterm3 - sterm4)
|
||||
return Pow(d, v) * Sum(term1 * term2, (n, 0, S.Infinity))
|
||||
|
||||
def GeneralizedMultivariateLogGamma(syms, delta, v, lamda, mu):
|
||||
"""
|
||||
Creates a joint random variable with generalized multivariate log gamma
|
||||
distribution.
|
||||
|
||||
The joint pdf can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
syms : list/tuple/set of symbols for identifying each component
|
||||
delta : A constant in range $[0, 1]$
|
||||
v : Positive real number
|
||||
lamda : List of positive real numbers
|
||||
mu : List of positive real numbers
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density
|
||||
>>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGamma
|
||||
>>> from sympy import symbols, S
|
||||
>>> v = 1
|
||||
>>> l, mu = [1, 1, 1], [1, 1, 1]
|
||||
>>> d = S.Half
|
||||
>>> y = symbols('y_1:4', positive=True)
|
||||
>>> Gd = GeneralizedMultivariateLogGamma('G', d, v, l, mu)
|
||||
>>> density(Gd)(y[0], y[1], y[2])
|
||||
Sum(exp((n + 1)*(y_1 + y_2 + y_3) - exp(y_1) - exp(y_2) -
|
||||
exp(y_3))/(2**n*gamma(n + 1)**3), (n, 0, oo))/2
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Generalized_multivariate_log-gamma_distribution
|
||||
.. [2] https://www.researchgate.net/publication/234137346_On_a_multivariate_log-gamma_distribution_and_the_use_of_the_distribution_in_the_Bayesian_analysis
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
If the GeneralizedMultivariateLogGamma is too long to type use,
|
||||
|
||||
>>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGamma as GMVLG
|
||||
>>> Gd = GMVLG('G', d, v, l, mu)
|
||||
|
||||
If you want to pass the matrix omega instead of the constant delta, then use
|
||||
``GeneralizedMultivariateLogGammaOmega``.
|
||||
|
||||
"""
|
||||
return multivariate_rv(GeneralizedMultivariateLogGammaDistribution,
|
||||
syms, delta, v, lamda, mu)
|
||||
|
||||
def GeneralizedMultivariateLogGammaOmega(syms, omega, v, lamda, mu):
|
||||
"""
|
||||
Extends GeneralizedMultivariateLogGamma.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
syms : list/tuple/set of symbols
|
||||
For identifying each component
|
||||
omega : A square matrix
|
||||
Every element of square matrix must be absolute value of
|
||||
square root of correlation coefficient
|
||||
v : Positive real number
|
||||
lamda : List of positive real numbers
|
||||
mu : List of positive real numbers
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density
|
||||
>>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGammaOmega
|
||||
>>> from sympy import Matrix, symbols, S
|
||||
>>> omega = Matrix([[1, S.Half, S.Half], [S.Half, 1, S.Half], [S.Half, S.Half, 1]])
|
||||
>>> v = 1
|
||||
>>> l, mu = [1, 1, 1], [1, 1, 1]
|
||||
>>> G = GeneralizedMultivariateLogGammaOmega('G', omega, v, l, mu)
|
||||
>>> y = symbols('y_1:4', positive=True)
|
||||
>>> density(G)(y[0], y[1], y[2])
|
||||
sqrt(2)*Sum((1 - sqrt(2)/2)**n*exp((n + 1)*(y_1 + y_2 + y_3) - exp(y_1) -
|
||||
exp(y_2) - exp(y_3))/gamma(n + 1)**3, (n, 0, oo))/2
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Generalized_multivariate_log-gamma_distribution
|
||||
.. [2] https://www.researchgate.net/publication/234137346_On_a_multivariate_log-gamma_distribution_and_the_use_of_the_distribution_in_the_Bayesian_analysis
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
If the GeneralizedMultivariateLogGammaOmega is too long to type use,
|
||||
|
||||
>>> from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGammaOmega as GMVLGO
|
||||
>>> G = GMVLGO('G', omega, v, l, mu)
|
||||
|
||||
"""
|
||||
_value_check((omega.is_square, isinstance(omega, Matrix)), "omega must be a"
|
||||
" square matrix")
|
||||
for val in omega.values():
|
||||
_value_check((val >= 0, val <= 1),
|
||||
"all values in matrix must be between 0 and 1(both inclusive).")
|
||||
_value_check(omega.diagonal().equals(ones(1, omega.shape[0])),
|
||||
"all the elements of diagonal should be 1.")
|
||||
_value_check((omega.shape[0] == len(lamda), len(lamda) == len(mu)),
|
||||
"lamda, mu should be of same length and omega should "
|
||||
" be of shape (length of lamda, length of mu)")
|
||||
_value_check(len(lamda) > 1,"the distribution should have at least"
|
||||
" two random variables.")
|
||||
delta = Pow(Rational(omega.det()), Rational(1, len(lamda) - 1))
|
||||
return GeneralizedMultivariateLogGamma(syms, delta, v, lamda, mu)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Multinomial distribution -----------------------------------------------------
|
||||
|
||||
class MultinomialDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('n', 'p')
|
||||
is_Continuous=False
|
||||
is_Discrete = True
|
||||
|
||||
@staticmethod
|
||||
def check(n, p):
|
||||
_value_check(n > 0,
|
||||
"number of trials must be a positive integer")
|
||||
for p_k in p:
|
||||
_value_check((p_k >= 0, p_k <= 1),
|
||||
"probability must be in range [0, 1]")
|
||||
_value_check(Eq(sum(p), 1),
|
||||
"probabilities must sum to 1")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return Intersection(S.Naturals0, Interval(0, self.n))**len(self.p)
|
||||
|
||||
def pdf(self, *x):
|
||||
n, p = self.n, self.p
|
||||
term_1 = factorial(n)/Mul.fromiter(factorial(x_k) for x_k in x)
|
||||
term_2 = Mul.fromiter(p_k**x_k for p_k, x_k in zip(p, x))
|
||||
return Piecewise((term_1 * term_2, Eq(sum(x), n)), (0, True))
|
||||
|
||||
def Multinomial(syms, n, *p):
|
||||
"""
|
||||
Creates a discrete random variable with Multinomial Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Positive integer
|
||||
Represents number of trials
|
||||
p : List of event probabilities
|
||||
Must be in the range of $[0, 1]$.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, Multinomial, marginal_distribution
|
||||
>>> from sympy import symbols
|
||||
>>> x1, x2, x3 = symbols('x1, x2, x3', nonnegative=True, integer=True)
|
||||
>>> p1, p2, p3 = symbols('p1, p2, p3', positive=True)
|
||||
>>> M = Multinomial('M', 3, p1, p2, p3)
|
||||
>>> density(M)(x1, x2, x3)
|
||||
Piecewise((6*p1**x1*p2**x2*p3**x3/(factorial(x1)*factorial(x2)*factorial(x3)),
|
||||
Eq(x1 + x2 + x3, 3)), (0, True))
|
||||
>>> marginal_distribution(M, M[0])(x1).subs(x1, 1)
|
||||
3*p1*p2**2 + 6*p1*p2*p3 + 3*p1*p3**2
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Multinomial_distribution
|
||||
.. [2] https://mathworld.wolfram.com/MultinomialDistribution.html
|
||||
|
||||
"""
|
||||
if not isinstance(p[0], list):
|
||||
p = (list(p), )
|
||||
return multivariate_rv(MultinomialDistribution, syms, n, p[0])
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Negative Multinomial Distribution --------------------------------------------
|
||||
|
||||
class NegativeMultinomialDistribution(JointDistribution):
|
||||
|
||||
_argnames = ('k0', 'p')
|
||||
is_Continuous=False
|
||||
is_Discrete = True
|
||||
|
||||
@staticmethod
|
||||
def check(k0, p):
|
||||
_value_check(k0 > 0,
|
||||
"number of failures must be a positive integer")
|
||||
for p_k in p:
|
||||
_value_check((p_k >= 0, p_k <= 1),
|
||||
"probability must be in range [0, 1].")
|
||||
_value_check(sum(p) <= 1,
|
||||
"success probabilities must not be greater than 1.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return Range(0, S.Infinity)**len(self.p)
|
||||
|
||||
def pdf(self, *k):
|
||||
k0, p = self.k0, self.p
|
||||
term_1 = (gamma(k0 + sum(k))*(1 - sum(p))**k0)/gamma(k0)
|
||||
term_2 = Mul.fromiter(pi**ki/factorial(ki) for pi, ki in zip(p, k))
|
||||
return term_1 * term_2
|
||||
|
||||
def NegativeMultinomial(syms, k0, *p):
|
||||
"""
|
||||
Creates a discrete random variable with Negative Multinomial Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
k0 : positive integer
|
||||
Represents number of failures before the experiment is stopped
|
||||
p : List of event probabilities
|
||||
Must be in the range of $[0, 1]$
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, NegativeMultinomial, marginal_distribution
|
||||
>>> from sympy import symbols
|
||||
>>> x1, x2, x3 = symbols('x1, x2, x3', nonnegative=True, integer=True)
|
||||
>>> p1, p2, p3 = symbols('p1, p2, p3', positive=True)
|
||||
>>> N = NegativeMultinomial('M', 3, p1, p2, p3)
|
||||
>>> N_c = NegativeMultinomial('M', 3, 0.1, 0.1, 0.1)
|
||||
>>> density(N)(x1, x2, x3)
|
||||
p1**x1*p2**x2*p3**x3*(-p1 - p2 - p3 + 1)**3*gamma(x1 + x2 +
|
||||
x3 + 3)/(2*factorial(x1)*factorial(x2)*factorial(x3))
|
||||
>>> marginal_distribution(N_c, N_c[0])(1).evalf().round(2)
|
||||
0.25
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Negative_multinomial_distribution
|
||||
.. [2] https://mathworld.wolfram.com/NegativeBinomialDistribution.html
|
||||
|
||||
"""
|
||||
if not isinstance(p[0], list):
|
||||
p = (list(p), )
|
||||
return multivariate_rv(NegativeMultinomialDistribution, syms, k0, p[0])
|
||||
@@ -0,0 +1,610 @@
|
||||
from math import prod
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.special.gamma_functions import multigamma
|
||||
from sympy.core.sympify import sympify, _sympify
|
||||
from sympy.matrices import (ImmutableMatrix, Inverse, Trace, Determinant,
|
||||
MatrixSymbol, MatrixBase, Transpose, MatrixSet,
|
||||
matrix2numpy)
|
||||
from sympy.stats.rv import (_value_check, RandomMatrixSymbol, NamedArgsMixin, PSpace,
|
||||
_symbol_converter, MatrixDomain, Distribution)
|
||||
from sympy.external import import_module
|
||||
|
||||
|
||||
################################################################################
|
||||
#------------------------Matrix Probability Space------------------------------#
|
||||
################################################################################
|
||||
class MatrixPSpace(PSpace):
|
||||
"""
|
||||
Represents probability space for
|
||||
Matrix Distributions.
|
||||
"""
|
||||
def __new__(cls, sym, distribution, dim_n, dim_m):
|
||||
sym = _symbol_converter(sym)
|
||||
dim_n, dim_m = _sympify(dim_n), _sympify(dim_m)
|
||||
if not (dim_n.is_integer and dim_m.is_integer):
|
||||
raise ValueError("Dimensions should be integers")
|
||||
return Basic.__new__(cls, sym, distribution, dim_n, dim_m)
|
||||
|
||||
distribution = property(lambda self: self.args[1])
|
||||
symbol = property(lambda self: self.args[0])
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return MatrixDomain(self.symbol, self.distribution.set)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return RandomMatrixSymbol(self.symbol, self.args[2], self.args[3], self)
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
return {self.value}
|
||||
|
||||
def compute_density(self, expr, *args):
|
||||
rms = expr.atoms(RandomMatrixSymbol)
|
||||
if len(rms) > 1 or (not isinstance(expr, RandomMatrixSymbol)):
|
||||
raise NotImplementedError("Currently, no algorithm has been "
|
||||
"implemented to handle general expressions containing "
|
||||
"multiple matrix distributions.")
|
||||
return self.distribution.pdf(expr)
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method
|
||||
|
||||
Returns dictionary mapping RandomMatrixSymbol to realization value.
|
||||
"""
|
||||
return {self.value: self.distribution.sample(size, library=library, seed=seed)}
|
||||
|
||||
|
||||
def rv(symbol, cls, args):
|
||||
args = list(map(sympify, args))
|
||||
dist = cls(*args)
|
||||
dist.check(*args)
|
||||
dim = dist.dimension
|
||||
pspace = MatrixPSpace(symbol, dist, dim[0], dim[1])
|
||||
return pspace.value
|
||||
|
||||
|
||||
class SampleMatrixScipy:
|
||||
"""Returns the sample from scipy of the given distribution"""
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_scipy(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_scipy(cls, dist, size, seed):
|
||||
"""Sample from SciPy."""
|
||||
|
||||
from scipy import stats as scipy_stats
|
||||
import numpy
|
||||
scipy_rv_map = {
|
||||
'WishartDistribution': lambda dist, size, rand_state: scipy_stats.wishart.rvs(
|
||||
df=int(dist.n), scale=matrix2numpy(dist.scale_matrix, float), size=size),
|
||||
'MatrixNormalDistribution': lambda dist, size, rand_state: scipy_stats.matrix_normal.rvs(
|
||||
mean=matrix2numpy(dist.location_matrix, float),
|
||||
rowcov=matrix2numpy(dist.scale_matrix_1, float),
|
||||
colcov=matrix2numpy(dist.scale_matrix_2, float), size=size, random_state=rand_state)
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
'WishartDistribution': lambda dist: dist.scale_matrix.shape,
|
||||
'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape
|
||||
}
|
||||
|
||||
dist_list = scipy_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
|
||||
if seed is None or isinstance(seed, int):
|
||||
rand_state = numpy.random.default_rng(seed=seed)
|
||||
else:
|
||||
rand_state = seed
|
||||
samp = scipy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state)
|
||||
return samp.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
|
||||
class SampleMatrixNumpy:
|
||||
"""Returns the sample from numpy of the given distribution"""
|
||||
|
||||
### TODO: Add tests after adding matrix distributions in numpy_rv_map
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_numpy(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_numpy(cls, dist, size, seed):
|
||||
"""Sample from NumPy."""
|
||||
|
||||
numpy_rv_map = {
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
}
|
||||
|
||||
dist_list = numpy_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
|
||||
import numpy
|
||||
if seed is None or isinstance(seed, int):
|
||||
rand_state = numpy.random.default_rng(seed=seed)
|
||||
else:
|
||||
rand_state = seed
|
||||
samp = numpy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state)
|
||||
return samp.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
|
||||
class SampleMatrixPymc:
|
||||
"""Returns the sample from pymc of the given distribution"""
|
||||
|
||||
def __new__(cls, dist, size, seed=None):
|
||||
return cls._sample_pymc(dist, size, seed)
|
||||
|
||||
@classmethod
|
||||
def _sample_pymc(cls, dist, size, seed):
|
||||
"""Sample from PyMC."""
|
||||
|
||||
try:
|
||||
import pymc
|
||||
except ImportError:
|
||||
import pymc3 as pymc
|
||||
pymc_rv_map = {
|
||||
'MatrixNormalDistribution': lambda dist: pymc.MatrixNormal('X',
|
||||
mu=matrix2numpy(dist.location_matrix, float),
|
||||
rowcov=matrix2numpy(dist.scale_matrix_1, float),
|
||||
colcov=matrix2numpy(dist.scale_matrix_2, float),
|
||||
shape=dist.location_matrix.shape),
|
||||
'WishartDistribution': lambda dist: pymc.WishartBartlett('X',
|
||||
nu=int(dist.n), S=matrix2numpy(dist.scale_matrix, float))
|
||||
}
|
||||
|
||||
sample_shape = {
|
||||
'WishartDistribution': lambda dist: dist.scale_matrix.shape,
|
||||
'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape
|
||||
}
|
||||
|
||||
dist_list = pymc_rv_map.keys()
|
||||
|
||||
if dist.__class__.__name__ not in dist_list:
|
||||
return None
|
||||
import logging
|
||||
logging.getLogger("pymc").setLevel(logging.ERROR)
|
||||
with pymc.Model():
|
||||
pymc_rv_map[dist.__class__.__name__](dist)
|
||||
samps = pymc.sample(draws=prod(size), chains=1, progressbar=False, random_seed=seed, return_inferencedata=False, compute_convergence_checks=False)['X']
|
||||
return samps.reshape(size + sample_shape[dist.__class__.__name__](dist))
|
||||
|
||||
_get_sample_class_matrixrv = {
|
||||
'scipy': SampleMatrixScipy,
|
||||
'pymc3': SampleMatrixPymc,
|
||||
'pymc': SampleMatrixPymc,
|
||||
'numpy': SampleMatrixNumpy
|
||||
}
|
||||
|
||||
################################################################################
|
||||
#-------------------------Matrix Distribution----------------------------------#
|
||||
################################################################################
|
||||
|
||||
class MatrixDistribution(Distribution, NamedArgsMixin):
|
||||
"""
|
||||
Abstract class for Matrix Distribution.
|
||||
"""
|
||||
def __new__(cls, *args):
|
||||
args = [ImmutableMatrix(arg) if isinstance(arg, list)
|
||||
else _sympify(arg) for arg in args]
|
||||
return Basic.__new__(cls, *args)
|
||||
|
||||
@staticmethod
|
||||
def check(*args):
|
||||
pass
|
||||
|
||||
def __call__(self, expr):
|
||||
if isinstance(expr, list):
|
||||
expr = ImmutableMatrix(expr)
|
||||
return self.pdf(expr)
|
||||
|
||||
def sample(self, size=(), library='scipy', seed=None):
|
||||
"""
|
||||
Internal sample method
|
||||
|
||||
Returns dictionary mapping RandomSymbol to realization value.
|
||||
"""
|
||||
|
||||
libraries = ['scipy', 'numpy', 'pymc3', 'pymc']
|
||||
if library not in libraries:
|
||||
raise NotImplementedError("Sampling from %s is not supported yet."
|
||||
% str(library))
|
||||
if not import_module(library):
|
||||
raise ValueError("Failed to import %s" % library)
|
||||
|
||||
samps = _get_sample_class_matrixrv[library](self, size, seed)
|
||||
|
||||
if samps is not None:
|
||||
return samps
|
||||
raise NotImplementedError(
|
||||
"Sampling for %s is not currently implemented from %s"
|
||||
% (self.__class__.__name__, library)
|
||||
)
|
||||
|
||||
################################################################################
|
||||
#------------------------Matrix Distribution Types-----------------------------#
|
||||
################################################################################
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Matrix Gamma distribution ----------------------------------------------------
|
||||
|
||||
class MatrixGammaDistribution(MatrixDistribution):
|
||||
|
||||
_argnames = ('alpha', 'beta', 'scale_matrix')
|
||||
|
||||
@staticmethod
|
||||
def check(alpha, beta, scale_matrix):
|
||||
if not isinstance(scale_matrix, MatrixSymbol):
|
||||
_value_check(scale_matrix.is_positive_definite, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
_value_check(scale_matrix.is_square, "Should "
|
||||
"be square matrix")
|
||||
_value_check(alpha.is_positive, "Shape parameter should be positive.")
|
||||
_value_check(beta.is_positive, "Scale parameter should be positive.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = self.scale_matrix.shape[0]
|
||||
return MatrixSet(k, k, S.Reals)
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return self.scale_matrix.shape
|
||||
|
||||
def pdf(self, x):
|
||||
alpha, beta, scale_matrix = self.alpha, self.beta, self.scale_matrix
|
||||
p = scale_matrix.shape[0]
|
||||
if isinstance(x, list):
|
||||
x = ImmutableMatrix(x)
|
||||
if not isinstance(x, (MatrixBase, MatrixSymbol)):
|
||||
raise ValueError("%s should be an isinstance of Matrix "
|
||||
"or MatrixSymbol" % str(x))
|
||||
sigma_inv_x = - Inverse(scale_matrix)*x / beta
|
||||
term1 = exp(Trace(sigma_inv_x))/((beta**(p*alpha)) * multigamma(alpha, p))
|
||||
term2 = (Determinant(scale_matrix))**(-alpha)
|
||||
term3 = (Determinant(x))**(alpha - S(p + 1)/2)
|
||||
return term1 * term2 * term3
|
||||
|
||||
def MatrixGamma(symbol, alpha, beta, scale_matrix):
|
||||
"""
|
||||
Creates a random variable with Matrix Gamma Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
alpha: Positive Real number
|
||||
Shape Parameter
|
||||
beta: Positive Real number
|
||||
Scale Parameter
|
||||
scale_matrix: Positive definite real square matrix
|
||||
Scale Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, MatrixGamma
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> a, b = symbols('a b', positive=True)
|
||||
>>> M = MatrixGamma('M', a, b, [[2, 1], [1, 2]])
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> density(M)(X).doit()
|
||||
exp(Trace(Matrix([
|
||||
[-2/3, 1/3],
|
||||
[ 1/3, -2/3]])*X)/b)*Determinant(X)**(a - 3/2)/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2))
|
||||
>>> density(M)([[1, 0], [0, 1]]).doit()
|
||||
exp(-4/(3*b))/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2))
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Matrix_gamma_distribution
|
||||
|
||||
"""
|
||||
if isinstance(scale_matrix, list):
|
||||
scale_matrix = ImmutableMatrix(scale_matrix)
|
||||
return rv(symbol, MatrixGammaDistribution, (alpha, beta, scale_matrix))
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Wishart Distribution ---------------------------------------------------------
|
||||
|
||||
class WishartDistribution(MatrixDistribution):
|
||||
|
||||
_argnames = ('n', 'scale_matrix')
|
||||
|
||||
@staticmethod
|
||||
def check(n, scale_matrix):
|
||||
if not isinstance(scale_matrix, MatrixSymbol):
|
||||
_value_check(scale_matrix.is_positive_definite, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
_value_check(scale_matrix.is_square, "Should "
|
||||
"be square matrix")
|
||||
_value_check(n.is_positive, "Shape parameter should be positive.")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
k = self.scale_matrix.shape[0]
|
||||
return MatrixSet(k, k, S.Reals)
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return self.scale_matrix.shape
|
||||
|
||||
def pdf(self, x):
|
||||
n, scale_matrix = self.n, self.scale_matrix
|
||||
p = scale_matrix.shape[0]
|
||||
if isinstance(x, list):
|
||||
x = ImmutableMatrix(x)
|
||||
if not isinstance(x, (MatrixBase, MatrixSymbol)):
|
||||
raise ValueError("%s should be an isinstance of Matrix "
|
||||
"or MatrixSymbol" % str(x))
|
||||
sigma_inv_x = - Inverse(scale_matrix)*x / S(2)
|
||||
term1 = exp(Trace(sigma_inv_x))/((2**(p*n/S(2))) * multigamma(n/S(2), p))
|
||||
term2 = (Determinant(scale_matrix))**(-n/S(2))
|
||||
term3 = (Determinant(x))**(S(n - p - 1)/2)
|
||||
return term1 * term2 * term3
|
||||
|
||||
def Wishart(symbol, n, scale_matrix):
|
||||
"""
|
||||
Creates a random variable with Wishart Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n: Positive Real number
|
||||
Represents degrees of freedom
|
||||
scale_matrix: Positive definite real square matrix
|
||||
Scale Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import density, Wishart
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> n = symbols('n', positive=True)
|
||||
>>> W = Wishart('W', n, [[2, 1], [1, 2]])
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> density(W)(X).doit()
|
||||
exp(Trace(Matrix([
|
||||
[-1/3, 1/6],
|
||||
[ 1/6, -1/3]])*X))*Determinant(X)**(n/2 - 3/2)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2))
|
||||
>>> density(W)([[1, 0], [0, 1]]).doit()
|
||||
exp(-2/3)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Wishart_distribution
|
||||
|
||||
"""
|
||||
if isinstance(scale_matrix, list):
|
||||
scale_matrix = ImmutableMatrix(scale_matrix)
|
||||
return rv(symbol, WishartDistribution, (n, scale_matrix))
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Matrix Normal distribution ---------------------------------------------------
|
||||
|
||||
class MatrixNormalDistribution(MatrixDistribution):
|
||||
|
||||
_argnames = ('location_matrix', 'scale_matrix_1', 'scale_matrix_2')
|
||||
|
||||
@staticmethod
|
||||
def check(location_matrix, scale_matrix_1, scale_matrix_2):
|
||||
if not isinstance(scale_matrix_1, MatrixSymbol):
|
||||
_value_check(scale_matrix_1.is_positive_definite, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
if not isinstance(scale_matrix_2, MatrixSymbol):
|
||||
_value_check(scale_matrix_2.is_positive_definite, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
_value_check(scale_matrix_1.is_square, "Scale matrix 1 should be "
|
||||
"be square matrix")
|
||||
_value_check(scale_matrix_2.is_square, "Scale matrix 2 should be "
|
||||
"be square matrix")
|
||||
n = location_matrix.shape[0]
|
||||
p = location_matrix.shape[1]
|
||||
_value_check(scale_matrix_1.shape[0] == n, "Scale matrix 1 should be"
|
||||
" of shape %s x %s"% (str(n), str(n)))
|
||||
_value_check(scale_matrix_2.shape[0] == p, "Scale matrix 2 should be"
|
||||
" of shape %s x %s"% (str(p), str(p)))
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
n, p = self.location_matrix.shape
|
||||
return MatrixSet(n, p, S.Reals)
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return self.location_matrix.shape
|
||||
|
||||
def pdf(self, x):
|
||||
M, U, V = self.location_matrix, self.scale_matrix_1, self.scale_matrix_2
|
||||
n, p = M.shape
|
||||
if isinstance(x, list):
|
||||
x = ImmutableMatrix(x)
|
||||
if not isinstance(x, (MatrixBase, MatrixSymbol)):
|
||||
raise ValueError("%s should be an isinstance of Matrix "
|
||||
"or MatrixSymbol" % str(x))
|
||||
term1 = Inverse(V)*Transpose(x - M)*Inverse(U)*(x - M)
|
||||
num = exp(-Trace(term1)/S(2))
|
||||
den = (2*pi)**(S(n*p)/2) * Determinant(U)**(S(p)/2) * Determinant(V)**(S(n)/2)
|
||||
return num/den
|
||||
|
||||
def MatrixNormal(symbol, location_matrix, scale_matrix_1, scale_matrix_2):
|
||||
"""
|
||||
Creates a random variable with Matrix Normal Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
location_matrix: Real ``n x p`` matrix
|
||||
Represents degrees of freedom
|
||||
scale_matrix_1: Positive definite matrix
|
||||
Scale Matrix of shape ``n x n``
|
||||
scale_matrix_2: Positive definite matrix
|
||||
Scale Matrix of shape ``p x p``
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> from sympy.stats import density, MatrixNormal
|
||||
>>> M = MatrixNormal('M', [[1, 2]], [1], [[1, 0], [0, 1]])
|
||||
>>> X = MatrixSymbol('X', 1, 2)
|
||||
>>> density(M)(X).doit()
|
||||
exp(-Trace((Matrix([
|
||||
[-1],
|
||||
[-2]]) + X.T)*(Matrix([[-1, -2]]) + X))/2)/(2*pi)
|
||||
>>> density(M)([[3, 4]]).doit()
|
||||
exp(-4)/(2*pi)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Matrix_normal_distribution
|
||||
|
||||
"""
|
||||
if isinstance(location_matrix, list):
|
||||
location_matrix = ImmutableMatrix(location_matrix)
|
||||
if isinstance(scale_matrix_1, list):
|
||||
scale_matrix_1 = ImmutableMatrix(scale_matrix_1)
|
||||
if isinstance(scale_matrix_2, list):
|
||||
scale_matrix_2 = ImmutableMatrix(scale_matrix_2)
|
||||
args = (location_matrix, scale_matrix_1, scale_matrix_2)
|
||||
return rv(symbol, MatrixNormalDistribution, args)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Matrix Student's T distribution ---------------------------------------------------
|
||||
|
||||
class MatrixStudentTDistribution(MatrixDistribution):
|
||||
|
||||
_argnames = ('nu', 'location_matrix', 'scale_matrix_1', 'scale_matrix_2')
|
||||
|
||||
@staticmethod
|
||||
def check(nu, location_matrix, scale_matrix_1, scale_matrix_2):
|
||||
if not isinstance(scale_matrix_1, MatrixSymbol):
|
||||
_value_check(scale_matrix_1.is_positive_definite != False, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
if not isinstance(scale_matrix_2, MatrixSymbol):
|
||||
_value_check(scale_matrix_2.is_positive_definite != False, "The shape "
|
||||
"matrix must be positive definite.")
|
||||
_value_check(scale_matrix_1.is_square != False, "Scale matrix 1 should be "
|
||||
"be square matrix")
|
||||
_value_check(scale_matrix_2.is_square != False, "Scale matrix 2 should be "
|
||||
"be square matrix")
|
||||
n = location_matrix.shape[0]
|
||||
p = location_matrix.shape[1]
|
||||
_value_check(scale_matrix_1.shape[0] == p, "Scale matrix 1 should be"
|
||||
" of shape %s x %s" % (str(p), str(p)))
|
||||
_value_check(scale_matrix_2.shape[0] == n, "Scale matrix 2 should be"
|
||||
" of shape %s x %s" % (str(n), str(n)))
|
||||
_value_check(nu.is_positive != False, "Degrees of freedom must be positive")
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
n, p = self.location_matrix.shape
|
||||
return MatrixSet(n, p, S.Reals)
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return self.location_matrix.shape
|
||||
|
||||
def pdf(self, x):
|
||||
from sympy.matrices.dense import eye
|
||||
if isinstance(x, list):
|
||||
x = ImmutableMatrix(x)
|
||||
if not isinstance(x, (MatrixBase, MatrixSymbol)):
|
||||
raise ValueError("%s should be an isinstance of Matrix "
|
||||
"or MatrixSymbol" % str(x))
|
||||
nu, M, Omega, Sigma = self.nu, self.location_matrix, self.scale_matrix_1, self.scale_matrix_2
|
||||
n, p = M.shape
|
||||
|
||||
K = multigamma((nu + n + p - 1)/2, p) * Determinant(Omega)**(-n/2) * Determinant(Sigma)**(-p/2) \
|
||||
/ ((pi)**(n*p/2) * multigamma((nu + p - 1)/2, p))
|
||||
return K * (Determinant(eye(n) + Inverse(Sigma)*(x - M)*Inverse(Omega)*Transpose(x - M))) \
|
||||
**(-(nu + n + p -1)/2)
|
||||
|
||||
|
||||
|
||||
def MatrixStudentT(symbol, nu, location_matrix, scale_matrix_1, scale_matrix_2):
|
||||
"""
|
||||
Creates a random variable with Matrix Gamma Distribution.
|
||||
|
||||
The density of the said distribution can be found at [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
nu: Positive Real number
|
||||
degrees of freedom
|
||||
location_matrix: Positive definite real square matrix
|
||||
Location Matrix of shape ``n x p``
|
||||
scale_matrix_1: Positive definite real square matrix
|
||||
Scale Matrix of shape ``p x p``
|
||||
scale_matrix_2: Positive definite real square matrix
|
||||
Scale Matrix of shape ``n x n``
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
RandomSymbol
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol,symbols
|
||||
>>> from sympy.stats import density, MatrixStudentT
|
||||
>>> v = symbols('v',positive=True)
|
||||
>>> M = MatrixStudentT('M', v, [[1, 2]], [[1, 0], [0, 1]], [1])
|
||||
>>> X = MatrixSymbol('X', 1, 2)
|
||||
>>> density(M)(X)
|
||||
gamma(v/2 + 1)*Determinant((Matrix([[-1, -2]]) + X)*(Matrix([
|
||||
[-1],
|
||||
[-2]]) + X.T) + Matrix([[1]]))**(-v/2 - 1)/(pi**1.0*gamma(v/2)*Determinant(Matrix([[1]]))**1.0*Determinant(Matrix([
|
||||
[1, 0],
|
||||
[0, 1]]))**0.5)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Matrix_t-distribution
|
||||
|
||||
"""
|
||||
if isinstance(location_matrix, list):
|
||||
location_matrix = ImmutableMatrix(location_matrix)
|
||||
if isinstance(scale_matrix_1, list):
|
||||
scale_matrix_1 = ImmutableMatrix(scale_matrix_1)
|
||||
if isinstance(scale_matrix_2, list):
|
||||
scale_matrix_2 = ImmutableMatrix(scale_matrix_2)
|
||||
args = (nu, location_matrix, scale_matrix_1, scale_matrix_2)
|
||||
return rv(symbol, MatrixStudentTDistribution, args)
|
||||
@@ -0,0 +1,30 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.stats.rv import PSpace, _symbol_converter, RandomMatrixSymbol
|
||||
|
||||
class RandomMatrixPSpace(PSpace):
|
||||
"""
|
||||
Represents probability space for
|
||||
random matrices. It contains the mechanics
|
||||
for handling the API calls for random matrices.
|
||||
"""
|
||||
def __new__(cls, sym, model=None):
|
||||
sym = _symbol_converter(sym)
|
||||
if model:
|
||||
return Basic.__new__(cls, sym, model)
|
||||
else:
|
||||
return Basic.__new__(cls, sym)
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
try:
|
||||
return self.args[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def compute_density(self, expr, *args):
|
||||
rms = expr.atoms(RandomMatrixSymbol)
|
||||
if len(rms) > 2 or (not isinstance(expr, RandomMatrixSymbol)):
|
||||
raise NotImplementedError("Currently, no algorithm has been "
|
||||
"implemented to handle general expressions containing "
|
||||
"multiple random matrices.")
|
||||
return self.model.density(expr)
|
||||
@@ -0,0 +1,457 @@
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import (I, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.tensor.indexed import IndexedBase
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.stats.rv import _symbol_converter, Density, RandomMatrixSymbol, is_random
|
||||
from sympy.stats.joint_rv_types import JointDistributionHandmade
|
||||
from sympy.stats.random_matrix import RandomMatrixPSpace
|
||||
from sympy.tensor.array import ArrayComprehension
|
||||
|
||||
__all__ = [
|
||||
'CircularEnsemble',
|
||||
'CircularUnitaryEnsemble',
|
||||
'CircularOrthogonalEnsemble',
|
||||
'CircularSymplecticEnsemble',
|
||||
'GaussianEnsemble',
|
||||
'GaussianUnitaryEnsemble',
|
||||
'GaussianOrthogonalEnsemble',
|
||||
'GaussianSymplecticEnsemble',
|
||||
'joint_eigen_distribution',
|
||||
'JointEigenDistribution',
|
||||
'level_spacing_distribution'
|
||||
]
|
||||
|
||||
@is_random.register(RandomMatrixSymbol)
|
||||
def _(x):
|
||||
return True
|
||||
|
||||
|
||||
class RandomMatrixEnsembleModel(Basic):
|
||||
"""
|
||||
Base class for random matrix ensembles.
|
||||
It acts as an umbrella and contains
|
||||
the methods common to all the ensembles
|
||||
defined in sympy.stats.random_matrix_models.
|
||||
"""
|
||||
def __new__(cls, sym, dim=None):
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
if dim.is_integer == False:
|
||||
raise ValueError("Dimension of the random matrices must be "
|
||||
"integers, received %s instead."%(dim))
|
||||
return Basic.__new__(cls, sym, dim)
|
||||
|
||||
symbol = property(lambda self: self.args[0])
|
||||
dimension = property(lambda self: self.args[1])
|
||||
|
||||
def density(self, expr):
|
||||
return Density(expr)
|
||||
|
||||
def __call__(self, expr):
|
||||
return self.density(expr)
|
||||
|
||||
class GaussianEnsembleModel(RandomMatrixEnsembleModel):
|
||||
"""
|
||||
Abstract class for Gaussian ensembles.
|
||||
Contains the properties common to all the
|
||||
gaussian ensembles.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Random_matrix#Gaussian_ensembles
|
||||
.. [2] https://arxiv.org/pdf/1712.07903.pdf
|
||||
"""
|
||||
def _compute_normalization_constant(self, beta, n):
|
||||
"""
|
||||
Helper function for computing normalization
|
||||
constant for joint probability density of eigen
|
||||
values of Gaussian ensembles.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Selberg_integral#Mehta's_integral
|
||||
"""
|
||||
n = S(n)
|
||||
prod_term = lambda j: gamma(1 + beta*S(j)/2)/gamma(S.One + beta/S(2))
|
||||
j = Dummy('j', integer=True, positive=True)
|
||||
term1 = Product(prod_term(j), (j, 1, n)).doit()
|
||||
term2 = (2/(beta*n))**(beta*n*(n - 1)/4 + n/2)
|
||||
term3 = (2*pi)**(n/2)
|
||||
return term1 * term2 * term3
|
||||
|
||||
def _compute_joint_eigen_distribution(self, beta):
|
||||
"""
|
||||
Helper function for computing the joint
|
||||
probability distribution of eigen values
|
||||
of the random matrix.
|
||||
"""
|
||||
n = self.dimension
|
||||
Zbn = self._compute_normalization_constant(beta, n)
|
||||
l = IndexedBase('l')
|
||||
i = Dummy('i', integer=True, positive=True)
|
||||
j = Dummy('j', integer=True, positive=True)
|
||||
k = Dummy('k', integer=True, positive=True)
|
||||
term1 = exp((-S(n)/2) * Sum(l[k]**2, (k, 1, n)).doit())
|
||||
sub_term = Lambda(i, Product(Abs(l[j] - l[i])**beta, (j, i + 1, n)))
|
||||
term2 = Product(sub_term(i).doit(), (i, 1, n - 1)).doit()
|
||||
syms = ArrayComprehension(l[k], (k, 1, n)).doit()
|
||||
return Lambda(tuple(syms), (term1 * term2)/Zbn)
|
||||
|
||||
class GaussianUnitaryEnsembleModel(GaussianEnsembleModel):
|
||||
@property
|
||||
def normalization_constant(self):
|
||||
n = self.dimension
|
||||
return 2**(S(n)/2) * pi**(S(n**2)/2)
|
||||
|
||||
def density(self, expr):
|
||||
n, ZGUE = self.dimension, self.normalization_constant
|
||||
h_pspace = RandomMatrixPSpace('P', model=self)
|
||||
H = RandomMatrixSymbol('H', n, n, pspace=h_pspace)
|
||||
return Lambda(H, exp(-S(n)/2 * Trace(H**2))/ZGUE)(expr)
|
||||
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S(2))
|
||||
|
||||
def level_spacing_distribution(self):
|
||||
s = Dummy('s')
|
||||
f = (32/pi**2)*(s**2)*exp((-4/pi)*s**2)
|
||||
return Lambda(s, f)
|
||||
|
||||
class GaussianOrthogonalEnsembleModel(GaussianEnsembleModel):
|
||||
@property
|
||||
def normalization_constant(self):
|
||||
n = self.dimension
|
||||
_H = MatrixSymbol('_H', n, n)
|
||||
return Integral(exp(-S(n)/4 * Trace(_H**2)))
|
||||
|
||||
def density(self, expr):
|
||||
n, ZGOE = self.dimension, self.normalization_constant
|
||||
h_pspace = RandomMatrixPSpace('P', model=self)
|
||||
H = RandomMatrixSymbol('H', n, n, pspace=h_pspace)
|
||||
return Lambda(H, exp(-S(n)/4 * Trace(H**2))/ZGOE)(expr)
|
||||
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S.One)
|
||||
|
||||
def level_spacing_distribution(self):
|
||||
s = Dummy('s')
|
||||
f = (pi/2)*s*exp((-pi/4)*s**2)
|
||||
return Lambda(s, f)
|
||||
|
||||
class GaussianSymplecticEnsembleModel(GaussianEnsembleModel):
|
||||
@property
|
||||
def normalization_constant(self):
|
||||
n = self.dimension
|
||||
_H = MatrixSymbol('_H', n, n)
|
||||
return Integral(exp(-S(n) * Trace(_H**2)))
|
||||
|
||||
def density(self, expr):
|
||||
n, ZGSE = self.dimension, self.normalization_constant
|
||||
h_pspace = RandomMatrixPSpace('P', model=self)
|
||||
H = RandomMatrixSymbol('H', n, n, pspace=h_pspace)
|
||||
return Lambda(H, exp(-S(n) * Trace(H**2))/ZGSE)(expr)
|
||||
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S(4))
|
||||
|
||||
def level_spacing_distribution(self):
|
||||
s = Dummy('s')
|
||||
f = ((S(2)**18)/((S(3)**6)*(pi**3)))*(s**4)*exp((-64/(9*pi))*s**2)
|
||||
return Lambda(s, f)
|
||||
|
||||
def GaussianEnsemble(sym, dim):
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = GaussianEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def GaussianUnitaryEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Gaussian Unitary Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import GaussianUnitaryEnsemble as GUE, density
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> G = GUE('U', 2)
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> density(G)(X)
|
||||
exp(-Trace(X**2))/(2*pi**2)
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = GaussianUnitaryEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def GaussianOrthogonalEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Gaussian Orthogonal Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import GaussianOrthogonalEnsemble as GOE, density
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> G = GOE('U', 2)
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> density(G)(X)
|
||||
exp(-Trace(X**2)/2)/Integral(exp(-Trace(_H**2)/2), _H)
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = GaussianOrthogonalEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def GaussianSymplecticEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Gaussian Symplectic Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import GaussianSymplecticEnsemble as GSE, density
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> G = GSE('U', 2)
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> density(G)(X)
|
||||
exp(-2*Trace(X**2))/Integral(exp(-2*Trace(_H**2)), _H)
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = GaussianSymplecticEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
class CircularEnsembleModel(RandomMatrixEnsembleModel):
|
||||
"""
|
||||
Abstract class for Circular ensembles.
|
||||
Contains the properties and methods
|
||||
common to all the circular ensembles.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Circular_ensemble
|
||||
"""
|
||||
def density(self, expr):
|
||||
# TODO : Add support for Lie groups(as extensions of sympy.diffgeom)
|
||||
# and define measures on them
|
||||
raise NotImplementedError("Support for Haar measure hasn't been "
|
||||
"implemented yet, therefore the density of "
|
||||
"%s cannot be computed."%(self))
|
||||
|
||||
def _compute_joint_eigen_distribution(self, beta):
|
||||
"""
|
||||
Helper function to compute the joint distribution of phases
|
||||
of the complex eigen values of matrices belonging to any
|
||||
circular ensembles.
|
||||
"""
|
||||
n = self.dimension
|
||||
Zbn = ((2*pi)**n)*(gamma(beta*n/2 + 1)/S(gamma(beta/2 + 1))**n)
|
||||
t = IndexedBase('t')
|
||||
i, j, k = (Dummy('i', integer=True), Dummy('j', integer=True),
|
||||
Dummy('k', integer=True))
|
||||
syms = ArrayComprehension(t[i], (i, 1, n)).doit()
|
||||
f = Product(Product(Abs(exp(I*t[k]) - exp(I*t[j]))**beta, (j, k + 1, n)).doit(),
|
||||
(k, 1, n - 1)).doit()
|
||||
return Lambda(tuple(syms), f/Zbn)
|
||||
|
||||
class CircularUnitaryEnsembleModel(CircularEnsembleModel):
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S(2))
|
||||
|
||||
class CircularOrthogonalEnsembleModel(CircularEnsembleModel):
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S.One)
|
||||
|
||||
class CircularSymplecticEnsembleModel(CircularEnsembleModel):
|
||||
def joint_eigen_distribution(self):
|
||||
return self._compute_joint_eigen_distribution(S(4))
|
||||
|
||||
def CircularEnsemble(sym, dim):
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = CircularEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def CircularUnitaryEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Circular Unitary Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import CircularUnitaryEnsemble as CUE
|
||||
>>> from sympy.stats import joint_eigen_distribution
|
||||
>>> C = CUE('U', 1)
|
||||
>>> joint_eigen_distribution(C)
|
||||
Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k]))**2, (_j, _k + 1, 1), (_k, 1, 0))/(2*pi))
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
As can be seen above in the example, density of CiruclarUnitaryEnsemble
|
||||
is not evaluated because the exact definition is based on haar measure of
|
||||
unitary group which is not unique.
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = CircularUnitaryEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def CircularOrthogonalEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Circular Orthogonal Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import CircularOrthogonalEnsemble as COE
|
||||
>>> from sympy.stats import joint_eigen_distribution
|
||||
>>> C = COE('O', 1)
|
||||
>>> joint_eigen_distribution(C)
|
||||
Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k])), (_j, _k + 1, 1), (_k, 1, 0))/(2*pi))
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
As can be seen above in the example, density of CiruclarOrthogonalEnsemble
|
||||
is not evaluated because the exact definition is based on haar measure of
|
||||
unitary group which is not unique.
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = CircularOrthogonalEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def CircularSymplecticEnsemble(sym, dim):
|
||||
"""
|
||||
Represents Circular Symplectic Ensembles.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import CircularSymplecticEnsemble as CSE
|
||||
>>> from sympy.stats import joint_eigen_distribution
|
||||
>>> C = CSE('S', 1)
|
||||
>>> joint_eigen_distribution(C)
|
||||
Lambda(t[1], Product(Abs(exp(I*t[_j]) - exp(I*t[_k]))**4, (_j, _k + 1, 1), (_k, 1, 0))/(2*pi))
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
As can be seen above in the example, density of CiruclarSymplecticEnsemble
|
||||
is not evaluated because the exact definition is based on haar measure of
|
||||
unitary group which is not unique.
|
||||
"""
|
||||
sym, dim = _symbol_converter(sym), _sympify(dim)
|
||||
model = CircularSymplecticEnsembleModel(sym, dim)
|
||||
rmp = RandomMatrixPSpace(sym, model=model)
|
||||
return RandomMatrixSymbol(sym, dim, dim, pspace=rmp)
|
||||
|
||||
def joint_eigen_distribution(mat):
|
||||
"""
|
||||
For obtaining joint probability distribution
|
||||
of eigen values of random matrix.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat: RandomMatrixSymbol
|
||||
The matrix symbol whose eigen values are to be considered.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Lambda
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import GaussianUnitaryEnsemble as GUE
|
||||
>>> from sympy.stats import joint_eigen_distribution
|
||||
>>> U = GUE('U', 2)
|
||||
>>> joint_eigen_distribution(U)
|
||||
Lambda((l[1], l[2]), exp(-l[1]**2 - l[2]**2)*Product(Abs(l[_i] - l[_j])**2, (_j, _i + 1, 2), (_i, 1, 1))/pi)
|
||||
"""
|
||||
if not isinstance(mat, RandomMatrixSymbol):
|
||||
raise ValueError("%s is not of type, RandomMatrixSymbol."%(mat))
|
||||
return mat.pspace.model.joint_eigen_distribution()
|
||||
|
||||
def JointEigenDistribution(mat):
|
||||
"""
|
||||
Creates joint distribution of eigen values of matrices with random
|
||||
expressions.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat: Matrix
|
||||
The matrix under consideration.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
JointDistributionHandmade
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Normal, JointEigenDistribution
|
||||
>>> from sympy import Matrix
|
||||
>>> A = [[Normal('A00', 0, 1), Normal('A01', 0, 1)],
|
||||
... [Normal('A10', 0, 1), Normal('A11', 0, 1)]]
|
||||
>>> JointEigenDistribution(Matrix(A))
|
||||
JointDistributionHandmade(-sqrt(A00**2 - 2*A00*A11 + 4*A01*A10 + A11**2)/2
|
||||
+ A00/2 + A11/2, sqrt(A00**2 - 2*A00*A11 + 4*A01*A10 + A11**2)/2 + A00/2 + A11/2)
|
||||
|
||||
"""
|
||||
eigenvals = mat.eigenvals(multiple=True)
|
||||
if not all(is_random(eigenval) for eigenval in set(eigenvals)):
|
||||
raise ValueError("Eigen values do not have any random expression, "
|
||||
"joint distribution cannot be generated.")
|
||||
return JointDistributionHandmade(*eigenvals)
|
||||
|
||||
def level_spacing_distribution(mat):
|
||||
"""
|
||||
For obtaining distribution of level spacings.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat: RandomMatrixSymbol
|
||||
The random matrix symbol whose eigen values are
|
||||
to be considered for finding the level spacings.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Lambda
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import GaussianUnitaryEnsemble as GUE
|
||||
>>> from sympy.stats import level_spacing_distribution
|
||||
>>> U = GUE('U', 2)
|
||||
>>> level_spacing_distribution(U)
|
||||
Lambda(_s, 32*_s**2*exp(-4*_s**2/pi)/pi**2)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Random_matrix#Distribution_of_level_spacings
|
||||
"""
|
||||
return mat.pspace.model.level_spacing_distribution()
|
||||
1798
backend_service/venv/lib/python3.13/site-packages/sympy/stats/rv.py
Normal file
1798
backend_service/venv/lib/python3.13/site-packages/sympy/stats/rv.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,519 @@
|
||||
from sympy.sets import FiniteSet
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions.combinatorial.factorials import FallingFactorial
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import piecewise_fold
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.solvers.solveset import solveset
|
||||
from .rv import (probability, expectation, density, where, given, pspace, cdf, PSpace,
|
||||
characteristic_function, sample, sample_iter, random_symbols, independent, dependent,
|
||||
sampling_density, moment_generating_function, quantile, is_random,
|
||||
sample_stochastic_process)
|
||||
|
||||
|
||||
__all__ = ['P', 'E', 'H', 'density', 'where', 'given', 'sample', 'cdf',
|
||||
'characteristic_function', 'pspace', 'sample_iter', 'variance', 'std',
|
||||
'skewness', 'kurtosis', 'covariance', 'dependent', 'entropy', 'median',
|
||||
'independent', 'random_symbols', 'correlation', 'factorial_moment',
|
||||
'moment', 'cmoment', 'sampling_density', 'moment_generating_function',
|
||||
'smoment', 'quantile', 'sample_stochastic_process']
|
||||
|
||||
|
||||
|
||||
def moment(X, n, c=0, condition=None, *, evaluate=True, **kwargs):
|
||||
"""
|
||||
Return the nth moment of a random expression about c.
|
||||
|
||||
.. math::
|
||||
moment(X, c, n) = E((X-c)^{n})
|
||||
|
||||
Default value of c is 0.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Die, moment, E
|
||||
>>> X = Die('X', 6)
|
||||
>>> moment(X, 1, 6)
|
||||
-5/2
|
||||
>>> moment(X, 2)
|
||||
91/6
|
||||
>>> moment(X, 1) == E(X)
|
||||
True
|
||||
"""
|
||||
from sympy.stats.symbolic_probability import Moment
|
||||
if evaluate:
|
||||
return Moment(X, n, c, condition).doit()
|
||||
return Moment(X, n, c, condition).rewrite(Integral)
|
||||
|
||||
|
||||
def variance(X, condition=None, **kwargs):
|
||||
"""
|
||||
Variance of a random expression.
|
||||
|
||||
.. math::
|
||||
variance(X) = E((X-E(X))^{2})
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Die, Bernoulli, variance
|
||||
>>> from sympy import simplify, Symbol
|
||||
|
||||
>>> X = Die('X', 6)
|
||||
>>> p = Symbol('p')
|
||||
>>> B = Bernoulli('B', p, 1, 0)
|
||||
|
||||
>>> variance(2*X)
|
||||
35/3
|
||||
|
||||
>>> simplify(variance(B))
|
||||
p*(1 - p)
|
||||
"""
|
||||
if is_random(X) and pspace(X) == PSpace():
|
||||
from sympy.stats.symbolic_probability import Variance
|
||||
return Variance(X, condition)
|
||||
|
||||
return cmoment(X, 2, condition, **kwargs)
|
||||
|
||||
|
||||
def standard_deviation(X, condition=None, **kwargs):
|
||||
r"""
|
||||
Standard Deviation of a random expression
|
||||
|
||||
.. math::
|
||||
std(X) = \sqrt(E((X-E(X))^{2}))
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Bernoulli, std
|
||||
>>> from sympy import Symbol, simplify
|
||||
|
||||
>>> p = Symbol('p')
|
||||
>>> B = Bernoulli('B', p, 1, 0)
|
||||
|
||||
>>> simplify(std(B))
|
||||
sqrt(p*(1 - p))
|
||||
"""
|
||||
return sqrt(variance(X, condition, **kwargs))
|
||||
std = standard_deviation
|
||||
|
||||
def entropy(expr, condition=None, **kwargs):
|
||||
"""
|
||||
Calculates entropy of a probability distribution.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expression : the random expression whose entropy is to be calculated
|
||||
condition : optional, to specify conditions on random expression
|
||||
b: base of the logarithm, optional
|
||||
By default, it is taken as Euler's number
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
result : Entropy of the expression, a constant
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Normal, Die, entropy
|
||||
>>> X = Normal('X', 0, 1)
|
||||
>>> entropy(X)
|
||||
log(2)/2 + 1/2 + log(pi)/2
|
||||
|
||||
>>> D = Die('D', 4)
|
||||
>>> entropy(D)
|
||||
log(4)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Entropy_%28information_theory%29
|
||||
.. [2] https://www.crmarsh.com/static/pdf/Charles_Marsh_Continuous_Entropy.pdf
|
||||
.. [3] https://kconrad.math.uconn.edu/blurbs/analysis/entropypost.pdf
|
||||
"""
|
||||
pdf = density(expr, condition, **kwargs)
|
||||
base = kwargs.get('b', exp(1))
|
||||
if isinstance(pdf, dict):
|
||||
return sum(-prob*log(prob, base) for prob in pdf.values())
|
||||
return expectation(-log(pdf(expr), base))
|
||||
|
||||
def covariance(X, Y, condition=None, **kwargs):
|
||||
"""
|
||||
Covariance of two random expressions.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The expectation that the two variables will rise and fall together
|
||||
|
||||
.. math::
|
||||
covariance(X,Y) = E((X-E(X)) (Y-E(Y)))
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Exponential, covariance
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> rate = Symbol('lambda', positive=True, real=True)
|
||||
>>> X = Exponential('X', rate)
|
||||
>>> Y = Exponential('Y', rate)
|
||||
|
||||
>>> covariance(X, X)
|
||||
lambda**(-2)
|
||||
>>> covariance(X, Y)
|
||||
0
|
||||
>>> covariance(X, Y + rate*X)
|
||||
1/lambda
|
||||
"""
|
||||
if (is_random(X) and pspace(X) == PSpace()) or (is_random(Y) and pspace(Y) == PSpace()):
|
||||
from sympy.stats.symbolic_probability import Covariance
|
||||
return Covariance(X, Y, condition)
|
||||
|
||||
return expectation(
|
||||
(X - expectation(X, condition, **kwargs)) *
|
||||
(Y - expectation(Y, condition, **kwargs)),
|
||||
condition, **kwargs)
|
||||
|
||||
|
||||
def correlation(X, Y, condition=None, **kwargs):
|
||||
r"""
|
||||
Correlation of two random expressions, also known as correlation
|
||||
coefficient or Pearson's correlation.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The normalized expectation that the two variables will rise
|
||||
and fall together
|
||||
|
||||
.. math::
|
||||
correlation(X,Y) = E((X-E(X))(Y-E(Y)) / (\sigma_x \sigma_y))
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Exponential, correlation
|
||||
>>> from sympy import Symbol
|
||||
|
||||
>>> rate = Symbol('lambda', positive=True, real=True)
|
||||
>>> X = Exponential('X', rate)
|
||||
>>> Y = Exponential('Y', rate)
|
||||
|
||||
>>> correlation(X, X)
|
||||
1
|
||||
>>> correlation(X, Y)
|
||||
0
|
||||
>>> correlation(X, Y + rate*X)
|
||||
1/sqrt(1 + lambda**(-2))
|
||||
"""
|
||||
return covariance(X, Y, condition, **kwargs)/(std(X, condition, **kwargs)
|
||||
* std(Y, condition, **kwargs))
|
||||
|
||||
|
||||
def cmoment(X, n, condition=None, *, evaluate=True, **kwargs):
|
||||
"""
|
||||
Return the nth central moment of a random expression about its mean.
|
||||
|
||||
.. math::
|
||||
cmoment(X, n) = E((X - E(X))^{n})
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Die, cmoment, variance
|
||||
>>> X = Die('X', 6)
|
||||
>>> cmoment(X, 3)
|
||||
0
|
||||
>>> cmoment(X, 2)
|
||||
35/12
|
||||
>>> cmoment(X, 2) == variance(X)
|
||||
True
|
||||
"""
|
||||
from sympy.stats.symbolic_probability import CentralMoment
|
||||
if evaluate:
|
||||
return CentralMoment(X, n, condition).doit()
|
||||
return CentralMoment(X, n, condition).rewrite(Integral)
|
||||
|
||||
|
||||
def smoment(X, n, condition=None, **kwargs):
|
||||
r"""
|
||||
Return the nth Standardized moment of a random expression.
|
||||
|
||||
.. math::
|
||||
smoment(X, n) = E(((X - \mu)/\sigma_X)^{n})
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import skewness, Exponential, smoment
|
||||
>>> from sympy import Symbol
|
||||
>>> rate = Symbol('lambda', positive=True, real=True)
|
||||
>>> Y = Exponential('Y', rate)
|
||||
>>> smoment(Y, 4)
|
||||
9
|
||||
>>> smoment(Y, 4) == smoment(3*Y, 4)
|
||||
True
|
||||
>>> smoment(Y, 3) == skewness(Y)
|
||||
True
|
||||
"""
|
||||
sigma = std(X, condition, **kwargs)
|
||||
return (1/sigma)**n*cmoment(X, n, condition, **kwargs)
|
||||
|
||||
def skewness(X, condition=None, **kwargs):
|
||||
r"""
|
||||
Measure of the asymmetry of the probability distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Positive skew indicates that most of the values lie to the right of
|
||||
the mean.
|
||||
|
||||
.. math::
|
||||
skewness(X) = E(((X - E(X))/\sigma_X)^{3})
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
condition : Expr containing RandomSymbols
|
||||
A conditional expression. skewness(X, X>0) is skewness of X given X > 0
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import skewness, Exponential, Normal
|
||||
>>> from sympy import Symbol
|
||||
>>> X = Normal('X', 0, 1)
|
||||
>>> skewness(X)
|
||||
0
|
||||
>>> skewness(X, X > 0) # find skewness given X > 0
|
||||
(-sqrt(2)/sqrt(pi) + 4*sqrt(2)/pi**(3/2))/(1 - 2/pi)**(3/2)
|
||||
|
||||
>>> rate = Symbol('lambda', positive=True, real=True)
|
||||
>>> Y = Exponential('Y', rate)
|
||||
>>> skewness(Y)
|
||||
2
|
||||
"""
|
||||
return smoment(X, 3, condition=condition, **kwargs)
|
||||
|
||||
def kurtosis(X, condition=None, **kwargs):
|
||||
r"""
|
||||
Characterizes the tails/outliers of a probability distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Kurtosis of any univariate normal distribution is 3. Kurtosis less than
|
||||
3 means that the distribution produces fewer and less extreme outliers
|
||||
than the normal distribution.
|
||||
|
||||
.. math::
|
||||
kurtosis(X) = E(((X - E(X))/\sigma_X)^{4})
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
condition : Expr containing RandomSymbols
|
||||
A conditional expression. kurtosis(X, X>0) is kurtosis of X given X > 0
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import kurtosis, Exponential, Normal
|
||||
>>> from sympy import Symbol
|
||||
>>> X = Normal('X', 0, 1)
|
||||
>>> kurtosis(X)
|
||||
3
|
||||
>>> kurtosis(X, X > 0) # find kurtosis given X > 0
|
||||
(-4/pi - 12/pi**2 + 3)/(1 - 2/pi)**2
|
||||
|
||||
>>> rate = Symbol('lamda', positive=True, real=True)
|
||||
>>> Y = Exponential('Y', rate)
|
||||
>>> kurtosis(Y)
|
||||
9
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Kurtosis
|
||||
.. [2] https://mathworld.wolfram.com/Kurtosis.html
|
||||
"""
|
||||
return smoment(X, 4, condition=condition, **kwargs)
|
||||
|
||||
|
||||
def factorial_moment(X, n, condition=None, **kwargs):
|
||||
"""
|
||||
The factorial moment is a mathematical quantity defined as the expectation
|
||||
or average of the falling factorial of a random variable.
|
||||
|
||||
.. math::
|
||||
factorial-moment(X, n) = E(X(X - 1)(X - 2)...(X - n + 1))
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n: A natural number, n-th factorial moment.
|
||||
|
||||
condition : Expr containing RandomSymbols
|
||||
A conditional expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import factorial_moment, Poisson, Binomial
|
||||
>>> from sympy import Symbol, S
|
||||
>>> lamda = Symbol('lamda')
|
||||
>>> X = Poisson('X', lamda)
|
||||
>>> factorial_moment(X, 2)
|
||||
lamda**2
|
||||
>>> Y = Binomial('Y', 2, S.Half)
|
||||
>>> factorial_moment(Y, 2)
|
||||
1/2
|
||||
>>> factorial_moment(Y, 2, Y > 1) # find factorial moment for Y > 1
|
||||
2
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Factorial_moment
|
||||
.. [2] https://mathworld.wolfram.com/FactorialMoment.html
|
||||
"""
|
||||
return expectation(FallingFactorial(X, n), condition=condition, **kwargs)
|
||||
|
||||
def median(X, evaluate=True, **kwargs):
|
||||
r"""
|
||||
Calculates the median of the probability distribution.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Mathematically, median of Probability distribution is defined as all those
|
||||
values of `m` for which the following condition is satisfied
|
||||
|
||||
.. math::
|
||||
P(X\leq m) \geq \frac{1}{2} \text{ and} \text{ } P(X\geq m)\geq \frac{1}{2}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
X: The random expression whose median is to be calculated.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
The FiniteSet or an Interval which contains the median of the
|
||||
random expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Normal, Die, median
|
||||
>>> N = Normal('N', 3, 1)
|
||||
>>> median(N)
|
||||
{3}
|
||||
>>> D = Die('D')
|
||||
>>> median(D)
|
||||
{3, 4}
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Median#Probability_distributions
|
||||
|
||||
"""
|
||||
if not is_random(X):
|
||||
return X
|
||||
|
||||
from sympy.stats.crv import ContinuousPSpace
|
||||
from sympy.stats.drv import DiscretePSpace
|
||||
from sympy.stats.frv import FinitePSpace
|
||||
|
||||
if isinstance(pspace(X), FinitePSpace):
|
||||
cdf = pspace(X).compute_cdf(X)
|
||||
result = []
|
||||
for key, value in cdf.items():
|
||||
if value>= Rational(1, 2) and (1 - value) + \
|
||||
pspace(X).probability(Eq(X, key)) >= Rational(1, 2):
|
||||
result.append(key)
|
||||
return FiniteSet(*result)
|
||||
if isinstance(pspace(X), (ContinuousPSpace, DiscretePSpace)):
|
||||
cdf = pspace(X).compute_cdf(X)
|
||||
x = Dummy('x')
|
||||
result = solveset(piecewise_fold(cdf(x) - Rational(1, 2)), x, pspace(X).set)
|
||||
return result
|
||||
raise NotImplementedError("The median of %s is not implemented."%str(pspace(X)))
|
||||
|
||||
|
||||
def coskewness(X, Y, Z, condition=None, **kwargs):
|
||||
r"""
|
||||
Calculates the co-skewness of three random variables.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Mathematically Coskewness is defined as
|
||||
|
||||
.. math::
|
||||
coskewness(X,Y,Z)=\frac{E[(X-E[X]) * (Y-E[Y]) * (Z-E[Z])]} {\sigma_{X}\sigma_{Y}\sigma_{Z}}
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
X : RandomSymbol
|
||||
Random Variable used to calculate coskewness
|
||||
Y : RandomSymbol
|
||||
Random Variable used to calculate coskewness
|
||||
Z : RandomSymbol
|
||||
Random Variable used to calculate coskewness
|
||||
condition : Expr containing RandomSymbols
|
||||
A conditional expression
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import coskewness, Exponential, skewness
|
||||
>>> from sympy import symbols
|
||||
>>> p = symbols('p', positive=True)
|
||||
>>> X = Exponential('X', p)
|
||||
>>> Y = Exponential('Y', 2*p)
|
||||
>>> coskewness(X, Y, Y)
|
||||
0
|
||||
>>> coskewness(X, Y + X, Y + 2*X)
|
||||
16*sqrt(85)/85
|
||||
>>> coskewness(X + 2*Y, Y + X, Y + 2*X, X > 3)
|
||||
9*sqrt(170)/85
|
||||
>>> coskewness(Y, Y, Y) == skewness(Y)
|
||||
True
|
||||
>>> coskewness(X, Y + p*X, Y + 2*p*X)
|
||||
4/(sqrt(1 + 1/(4*p**2))*sqrt(4 + 1/(4*p**2)))
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
coskewness : The coskewness of the three random variables
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Coskewness
|
||||
|
||||
"""
|
||||
num = expectation((X - expectation(X, condition, **kwargs)) \
|
||||
* (Y - expectation(Y, condition, **kwargs)) \
|
||||
* (Z - expectation(Z, condition, **kwargs)), condition, **kwargs)
|
||||
den = std(X, condition, **kwargs) * std(Y, condition, **kwargs) \
|
||||
* std(Z, condition, **kwargs)
|
||||
return num/den
|
||||
|
||||
|
||||
P = probability
|
||||
E = expectation
|
||||
H = entropy
|
||||
@@ -0,0 +1,105 @@
|
||||
from functools import singledispatch
|
||||
|
||||
from sympy.external import import_module
|
||||
from sympy.stats.crv_types import BetaDistribution, ChiSquaredDistribution, ExponentialDistribution, GammaDistribution, \
|
||||
LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, FDistributionDistribution, GumbelDistribution, LaplaceDistribution, \
|
||||
LogisticDistribution, RayleighDistribution, TriangularDistribution
|
||||
from sympy.stats.drv_types import GeometricDistribution, PoissonDistribution, ZetaDistribution
|
||||
from sympy.stats.frv_types import BinomialDistribution, HypergeometricDistribution
|
||||
|
||||
|
||||
numpy = import_module('numpy')
|
||||
|
||||
|
||||
@singledispatch
|
||||
def do_sample_numpy(dist, size, rand_state):
|
||||
return None
|
||||
|
||||
|
||||
# CRV:
|
||||
|
||||
@do_sample_numpy.register(BetaDistribution)
|
||||
def _(dist: BetaDistribution, size, rand_state):
|
||||
return rand_state.beta(a=float(dist.alpha), b=float(dist.beta), size=size)
|
||||
|
||||
|
||||
@do_sample_numpy.register(ChiSquaredDistribution)
|
||||
def _(dist: ChiSquaredDistribution, size, rand_state):
|
||||
return rand_state.chisquare(df=float(dist.k), size=size)
|
||||
|
||||
|
||||
@do_sample_numpy.register(ExponentialDistribution)
|
||||
def _(dist: ExponentialDistribution, size, rand_state):
|
||||
return rand_state.exponential(1 / float(dist.rate), size=size)
|
||||
|
||||
@do_sample_numpy.register(FDistributionDistribution)
|
||||
def _(dist: FDistributionDistribution, size, rand_state):
|
||||
return rand_state.f(dfnum = float(dist.d1), dfden = float(dist.d2), size=size)
|
||||
|
||||
@do_sample_numpy.register(GammaDistribution)
|
||||
def _(dist: GammaDistribution, size, rand_state):
|
||||
return rand_state.gamma(shape = float(dist.k), scale = float(dist.theta), size=size)
|
||||
|
||||
@do_sample_numpy.register(GumbelDistribution)
|
||||
def _(dist: GumbelDistribution, size, rand_state):
|
||||
return rand_state.gumbel(loc = float(dist.mu), scale = float(dist.beta), size=size)
|
||||
|
||||
@do_sample_numpy.register(LaplaceDistribution)
|
||||
def _(dist: LaplaceDistribution, size, rand_state):
|
||||
return rand_state.laplace(loc = float(dist.mu), scale = float(dist.b), size=size)
|
||||
|
||||
@do_sample_numpy.register(LogisticDistribution)
|
||||
def _(dist: LogisticDistribution, size, rand_state):
|
||||
return rand_state.logistic(loc = float(dist.mu), scale = float(dist.s), size=size)
|
||||
|
||||
@do_sample_numpy.register(LogNormalDistribution)
|
||||
def _(dist: LogNormalDistribution, size, rand_state):
|
||||
return rand_state.lognormal(mean = float(dist.mean), sigma = float(dist.std), size=size)
|
||||
|
||||
@do_sample_numpy.register(NormalDistribution)
|
||||
def _(dist: NormalDistribution, size, rand_state):
|
||||
return rand_state.normal(loc = float(dist.mean), scale = float(dist.std), size=size)
|
||||
|
||||
@do_sample_numpy.register(RayleighDistribution)
|
||||
def _(dist: RayleighDistribution, size, rand_state):
|
||||
return rand_state.rayleigh(scale = float(dist.sigma), size=size)
|
||||
|
||||
@do_sample_numpy.register(ParetoDistribution)
|
||||
def _(dist: ParetoDistribution, size, rand_state):
|
||||
return (numpy.random.pareto(a=float(dist.alpha), size=size) + 1) * float(dist.xm)
|
||||
|
||||
@do_sample_numpy.register(TriangularDistribution)
|
||||
def _(dist: TriangularDistribution, size, rand_state):
|
||||
return rand_state.triangular(left = float(dist.a), mode = float(dist.b), right = float(dist.c), size=size)
|
||||
|
||||
@do_sample_numpy.register(UniformDistribution)
|
||||
def _(dist: UniformDistribution, size, rand_state):
|
||||
return rand_state.uniform(low=float(dist.left), high=float(dist.right), size=size)
|
||||
|
||||
|
||||
# DRV:
|
||||
|
||||
@do_sample_numpy.register(GeometricDistribution)
|
||||
def _(dist: GeometricDistribution, size, rand_state):
|
||||
return rand_state.geometric(p=float(dist.p), size=size)
|
||||
|
||||
|
||||
@do_sample_numpy.register(PoissonDistribution)
|
||||
def _(dist: PoissonDistribution, size, rand_state):
|
||||
return rand_state.poisson(lam=float(dist.lamda), size=size)
|
||||
|
||||
|
||||
@do_sample_numpy.register(ZetaDistribution)
|
||||
def _(dist: ZetaDistribution, size, rand_state):
|
||||
return rand_state.zipf(a=float(dist.s), size=size)
|
||||
|
||||
|
||||
# FRV:
|
||||
|
||||
@do_sample_numpy.register(BinomialDistribution)
|
||||
def _(dist: BinomialDistribution, size, rand_state):
|
||||
return rand_state.binomial(n=int(dist.n), p=float(dist.p), size=size)
|
||||
|
||||
@do_sample_numpy.register(HypergeometricDistribution)
|
||||
def _(dist: HypergeometricDistribution, size, rand_state):
|
||||
return rand_state.hypergeometric(ngood = int(dist.N), nbad = int(dist.m), nsample = int(dist.n), size=size)
|
||||
@@ -0,0 +1,99 @@
|
||||
from functools import singledispatch
|
||||
from sympy.external import import_module
|
||||
from sympy.stats.crv_types import BetaDistribution, CauchyDistribution, ChiSquaredDistribution, ExponentialDistribution, \
|
||||
GammaDistribution, LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, \
|
||||
GaussianInverseDistribution
|
||||
from sympy.stats.drv_types import PoissonDistribution, GeometricDistribution, NegativeBinomialDistribution
|
||||
from sympy.stats.frv_types import BinomialDistribution, BernoulliDistribution
|
||||
|
||||
|
||||
try:
|
||||
import pymc
|
||||
except ImportError:
|
||||
pymc = import_module('pymc3')
|
||||
|
||||
@singledispatch
|
||||
def do_sample_pymc(dist):
|
||||
return None
|
||||
|
||||
|
||||
# CRV:
|
||||
|
||||
@do_sample_pymc.register(BetaDistribution)
|
||||
def _(dist: BetaDistribution):
|
||||
return pymc.Beta('X', alpha=float(dist.alpha), beta=float(dist.beta))
|
||||
|
||||
|
||||
@do_sample_pymc.register(CauchyDistribution)
|
||||
def _(dist: CauchyDistribution):
|
||||
return pymc.Cauchy('X', alpha=float(dist.x0), beta=float(dist.gamma))
|
||||
|
||||
|
||||
@do_sample_pymc.register(ChiSquaredDistribution)
|
||||
def _(dist: ChiSquaredDistribution):
|
||||
return pymc.ChiSquared('X', nu=float(dist.k))
|
||||
|
||||
|
||||
@do_sample_pymc.register(ExponentialDistribution)
|
||||
def _(dist: ExponentialDistribution):
|
||||
return pymc.Exponential('X', lam=float(dist.rate))
|
||||
|
||||
|
||||
@do_sample_pymc.register(GammaDistribution)
|
||||
def _(dist: GammaDistribution):
|
||||
return pymc.Gamma('X', alpha=float(dist.k), beta=1 / float(dist.theta))
|
||||
|
||||
|
||||
@do_sample_pymc.register(LogNormalDistribution)
|
||||
def _(dist: LogNormalDistribution):
|
||||
return pymc.Lognormal('X', mu=float(dist.mean), sigma=float(dist.std))
|
||||
|
||||
|
||||
@do_sample_pymc.register(NormalDistribution)
|
||||
def _(dist: NormalDistribution):
|
||||
return pymc.Normal('X', float(dist.mean), float(dist.std))
|
||||
|
||||
|
||||
@do_sample_pymc.register(GaussianInverseDistribution)
|
||||
def _(dist: GaussianInverseDistribution):
|
||||
return pymc.Wald('X', mu=float(dist.mean), lam=float(dist.shape))
|
||||
|
||||
|
||||
@do_sample_pymc.register(ParetoDistribution)
|
||||
def _(dist: ParetoDistribution):
|
||||
return pymc.Pareto('X', alpha=float(dist.alpha), m=float(dist.xm))
|
||||
|
||||
|
||||
@do_sample_pymc.register(UniformDistribution)
|
||||
def _(dist: UniformDistribution):
|
||||
return pymc.Uniform('X', lower=float(dist.left), upper=float(dist.right))
|
||||
|
||||
|
||||
# DRV:
|
||||
|
||||
@do_sample_pymc.register(GeometricDistribution)
|
||||
def _(dist: GeometricDistribution):
|
||||
return pymc.Geometric('X', p=float(dist.p))
|
||||
|
||||
|
||||
@do_sample_pymc.register(NegativeBinomialDistribution)
|
||||
def _(dist: NegativeBinomialDistribution):
|
||||
return pymc.NegativeBinomial('X', mu=float((dist.p * dist.r) / (1 - dist.p)),
|
||||
alpha=float(dist.r))
|
||||
|
||||
|
||||
@do_sample_pymc.register(PoissonDistribution)
|
||||
def _(dist: PoissonDistribution):
|
||||
return pymc.Poisson('X', mu=float(dist.lamda))
|
||||
|
||||
|
||||
# FRV:
|
||||
|
||||
@do_sample_pymc.register(BernoulliDistribution)
|
||||
def _(dist: BernoulliDistribution):
|
||||
return pymc.Bernoulli('X', p=float(dist.p))
|
||||
|
||||
|
||||
@do_sample_pymc.register(BinomialDistribution)
|
||||
def _(dist: BinomialDistribution):
|
||||
return pymc.Binomial('X', n=int(dist.n), p=float(dist.p))
|
||||
@@ -0,0 +1,167 @@
|
||||
from functools import singledispatch
|
||||
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.utilities.lambdify import lambdify
|
||||
from sympy.external import import_module
|
||||
from sympy.stats import DiscreteDistributionHandmade
|
||||
from sympy.stats.crv import SingleContinuousDistribution
|
||||
from sympy.stats.crv_types import ChiSquaredDistribution, ExponentialDistribution, GammaDistribution, \
|
||||
LogNormalDistribution, NormalDistribution, ParetoDistribution, UniformDistribution, BetaDistribution, \
|
||||
StudentTDistribution, CauchyDistribution
|
||||
from sympy.stats.drv_types import GeometricDistribution, LogarithmicDistribution, NegativeBinomialDistribution, \
|
||||
PoissonDistribution, SkellamDistribution, YuleSimonDistribution, ZetaDistribution
|
||||
from sympy.stats.frv import SingleFiniteDistribution
|
||||
|
||||
|
||||
scipy = import_module("scipy", import_kwargs={'fromlist':['stats']})
|
||||
|
||||
|
||||
@singledispatch
|
||||
def do_sample_scipy(dist, size, seed):
|
||||
return None
|
||||
|
||||
|
||||
# CRV
|
||||
|
||||
@do_sample_scipy.register(SingleContinuousDistribution)
|
||||
def _(dist: SingleContinuousDistribution, size, seed):
|
||||
# if we don't need to make a handmade pdf, we won't
|
||||
import scipy.stats
|
||||
|
||||
z = Dummy('z')
|
||||
handmade_pdf = lambdify(z, dist.pdf(z), ['numpy', 'scipy'])
|
||||
|
||||
class scipy_pdf(scipy.stats.rv_continuous):
|
||||
def _pdf(dist, x):
|
||||
return handmade_pdf(x)
|
||||
|
||||
scipy_rv = scipy_pdf(a=float(dist.set._inf),
|
||||
b=float(dist.set._sup), name='scipy_pdf')
|
||||
return scipy_rv.rvs(size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(ChiSquaredDistribution)
|
||||
def _(dist: ChiSquaredDistribution, size, seed):
|
||||
# same parametrisation
|
||||
return scipy.stats.chi2.rvs(df=float(dist.k), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(ExponentialDistribution)
|
||||
def _(dist: ExponentialDistribution, size, seed):
|
||||
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.expon.html#scipy.stats.expon
|
||||
return scipy.stats.expon.rvs(scale=1 / float(dist.rate), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(GammaDistribution)
|
||||
def _(dist: GammaDistribution, size, seed):
|
||||
# https://stackoverflow.com/questions/42150965/how-to-plot-gamma-distribution-with-alpha-and-beta-parameters-in-python
|
||||
return scipy.stats.gamma.rvs(a=float(dist.k), scale=float(dist.theta), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(LogNormalDistribution)
|
||||
def _(dist: LogNormalDistribution, size, seed):
|
||||
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.lognorm.html
|
||||
return scipy.stats.lognorm.rvs(scale=float(exp(dist.mean)), s=float(dist.std), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(NormalDistribution)
|
||||
def _(dist: NormalDistribution, size, seed):
|
||||
return scipy.stats.norm.rvs(loc=float(dist.mean), scale=float(dist.std), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(ParetoDistribution)
|
||||
def _(dist: ParetoDistribution, size, seed):
|
||||
# https://stackoverflow.com/questions/42260519/defining-pareto-distribution-in-python-scipy
|
||||
return scipy.stats.pareto.rvs(b=float(dist.alpha), scale=float(dist.xm), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(StudentTDistribution)
|
||||
def _(dist: StudentTDistribution, size, seed):
|
||||
return scipy.stats.t.rvs(df=float(dist.nu), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(UniformDistribution)
|
||||
def _(dist: UniformDistribution, size, seed):
|
||||
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.uniform.html
|
||||
return scipy.stats.uniform.rvs(loc=float(dist.left), scale=float(dist.right - dist.left), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(BetaDistribution)
|
||||
def _(dist: BetaDistribution, size, seed):
|
||||
# same parametrisation
|
||||
return scipy.stats.beta.rvs(a=float(dist.alpha), b=float(dist.beta), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(CauchyDistribution)
|
||||
def _(dist: CauchyDistribution, size, seed):
|
||||
return scipy.stats.cauchy.rvs(loc=float(dist.x0), scale=float(dist.gamma), size=size, random_state=seed)
|
||||
|
||||
|
||||
# DRV:
|
||||
|
||||
@do_sample_scipy.register(DiscreteDistributionHandmade)
|
||||
def _(dist: DiscreteDistributionHandmade, size, seed):
|
||||
from scipy.stats import rv_discrete
|
||||
|
||||
z = Dummy('z')
|
||||
handmade_pmf = lambdify(z, dist.pdf(z), ['numpy', 'scipy'])
|
||||
|
||||
class scipy_pmf(rv_discrete):
|
||||
def _pmf(dist, x):
|
||||
return handmade_pmf(x)
|
||||
|
||||
scipy_rv = scipy_pmf(a=float(dist.set._inf), b=float(dist.set._sup),
|
||||
name='scipy_pmf')
|
||||
return scipy_rv.rvs(size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(GeometricDistribution)
|
||||
def _(dist: GeometricDistribution, size, seed):
|
||||
return scipy.stats.geom.rvs(p=float(dist.p), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(LogarithmicDistribution)
|
||||
def _(dist: LogarithmicDistribution, size, seed):
|
||||
return scipy.stats.logser.rvs(p=float(dist.p), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(NegativeBinomialDistribution)
|
||||
def _(dist: NegativeBinomialDistribution, size, seed):
|
||||
return scipy.stats.nbinom.rvs(n=float(dist.r), p=float(dist.p), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(PoissonDistribution)
|
||||
def _(dist: PoissonDistribution, size, seed):
|
||||
return scipy.stats.poisson.rvs(mu=float(dist.lamda), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(SkellamDistribution)
|
||||
def _(dist: SkellamDistribution, size, seed):
|
||||
return scipy.stats.skellam.rvs(mu1=float(dist.mu1), mu2=float(dist.mu2), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(YuleSimonDistribution)
|
||||
def _(dist: YuleSimonDistribution, size, seed):
|
||||
return scipy.stats.yulesimon.rvs(alpha=float(dist.rho), size=size, random_state=seed)
|
||||
|
||||
|
||||
@do_sample_scipy.register(ZetaDistribution)
|
||||
def _(dist: ZetaDistribution, size, seed):
|
||||
return scipy.stats.zipf.rvs(a=float(dist.s), size=size, random_state=seed)
|
||||
|
||||
|
||||
# FRV:
|
||||
|
||||
@do_sample_scipy.register(SingleFiniteDistribution)
|
||||
def _(dist: SingleFiniteDistribution, size, seed):
|
||||
# scipy can handle with custom distributions
|
||||
|
||||
from scipy.stats import rv_discrete
|
||||
density_ = dist.dict
|
||||
x, y = [], []
|
||||
for k, v in density_.items():
|
||||
x.append(int(k))
|
||||
y.append(float(v))
|
||||
scipy_rv = rv_discrete(name='scipy_rv', values=(x, y))
|
||||
return scipy_rv.rvs(size=size, random_state=seed)
|
||||
@@ -0,0 +1,181 @@
|
||||
from sympy.core.numbers import oo
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.external import import_module
|
||||
from sympy.stats import Beta, Chi, Normal, Gamma, Exponential, LogNormal, Pareto, ChiSquared, Uniform, sample, \
|
||||
BetaPrime, Cauchy, GammaInverse, GaussianInverse, StudentT, Weibull, density, ContinuousRV, FDistribution, \
|
||||
Gumbel, Laplace, Logistic, Rayleigh, Triangular
|
||||
from sympy.testing.pytest import skip, raises
|
||||
|
||||
|
||||
def test_sample_numpy():
|
||||
distribs_numpy = [
|
||||
Beta("B", 1, 1),
|
||||
Normal("N", 0, 1),
|
||||
Gamma("G", 2, 7),
|
||||
Exponential("E", 2),
|
||||
LogNormal("LN", 0, 1),
|
||||
Pareto("P", 1, 1),
|
||||
ChiSquared("CS", 2),
|
||||
Uniform("U", 0, 1),
|
||||
FDistribution("FD", 1, 2),
|
||||
Gumbel("GB", 1, 2),
|
||||
Laplace("L", 1, 2),
|
||||
Logistic("LO", 1, 2),
|
||||
Rayleigh("R", 1),
|
||||
Triangular("T", 1, 2, 2),
|
||||
]
|
||||
size = 3
|
||||
numpy = import_module('numpy')
|
||||
if not numpy:
|
||||
skip('Numpy is not installed. Abort tests for _sample_numpy.')
|
||||
else:
|
||||
for X in distribs_numpy:
|
||||
samps = sample(X, size=size, library='numpy')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: sample(Chi("C", 1), library='numpy'))
|
||||
raises(NotImplementedError,
|
||||
lambda: Chi("C", 1).pspace.distribution.sample(library='tensorflow'))
|
||||
|
||||
|
||||
def test_sample_scipy():
|
||||
distribs_scipy = [
|
||||
Beta("B", 1, 1),
|
||||
BetaPrime("BP", 1, 1),
|
||||
Cauchy("C", 1, 1),
|
||||
Chi("C", 1),
|
||||
Normal("N", 0, 1),
|
||||
Gamma("G", 2, 7),
|
||||
GammaInverse("GI", 1, 1),
|
||||
GaussianInverse("GUI", 1, 1),
|
||||
Exponential("E", 2),
|
||||
LogNormal("LN", 0, 1),
|
||||
Pareto("P", 1, 1),
|
||||
StudentT("S", 2),
|
||||
ChiSquared("CS", 2),
|
||||
Uniform("U", 0, 1)
|
||||
]
|
||||
size = 3
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests for _sample_scipy.')
|
||||
else:
|
||||
for X in distribs_scipy:
|
||||
samps = sample(X, size=size, library='scipy')
|
||||
samps2 = sample(X, size=(2, 2), library='scipy')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
for i in range(2):
|
||||
for j in range(2):
|
||||
assert samps2[i][j] in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_sample_pymc():
|
||||
distribs_pymc = [
|
||||
Beta("B", 1, 1),
|
||||
Cauchy("C", 1, 1),
|
||||
Normal("N", 0, 1),
|
||||
Gamma("G", 2, 7),
|
||||
GaussianInverse("GI", 1, 1),
|
||||
Exponential("E", 2),
|
||||
LogNormal("LN", 0, 1),
|
||||
Pareto("P", 1, 1),
|
||||
ChiSquared("CS", 2),
|
||||
Uniform("U", 0, 1)
|
||||
]
|
||||
size = 3
|
||||
pymc = import_module('pymc')
|
||||
if not pymc:
|
||||
skip('PyMC is not installed. Abort tests for _sample_pymc.')
|
||||
else:
|
||||
for X in distribs_pymc:
|
||||
samps = sample(X, size=size, library='pymc')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: sample(Chi("C", 1), library='pymc'))
|
||||
|
||||
|
||||
def test_sampling_gamma_inverse():
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests for sampling of gamma inverse.')
|
||||
X = GammaInverse("x", 1, 1)
|
||||
assert sample(X) in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_lognormal_sampling():
|
||||
# Right now, only density function and sampling works
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
for i in range(3):
|
||||
X = LogNormal('x', i, 1)
|
||||
assert sample(X) in X.pspace.domain.set
|
||||
|
||||
size = 5
|
||||
samps = sample(X, size=size)
|
||||
for samp in samps:
|
||||
assert samp in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_sampling_gaussian_inverse():
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests for sampling of Gaussian inverse.')
|
||||
X = GaussianInverse("x", 1, 1)
|
||||
assert sample(X, library='scipy') in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_prefab_sampling():
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
N = Normal('X', 0, 1)
|
||||
L = LogNormal('L', 0, 1)
|
||||
E = Exponential('Ex', 1)
|
||||
P = Pareto('P', 1, 3)
|
||||
W = Weibull('W', 1, 1)
|
||||
U = Uniform('U', 0, 1)
|
||||
B = Beta('B', 2, 5)
|
||||
G = Gamma('G', 1, 3)
|
||||
|
||||
variables = [N, L, E, P, W, U, B, G]
|
||||
niter = 10
|
||||
size = 5
|
||||
for var in variables:
|
||||
for _ in range(niter):
|
||||
assert sample(var) in var.pspace.domain.set
|
||||
samps = sample(var, size=size)
|
||||
for samp in samps:
|
||||
assert samp in var.pspace.domain.set
|
||||
|
||||
|
||||
def test_sample_continuous():
|
||||
z = Symbol('z')
|
||||
Z = ContinuousRV(z, exp(-z), set=Interval(0, oo))
|
||||
assert density(Z)(-1) == 0
|
||||
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
assert sample(Z) in Z.pspace.domain.set
|
||||
sym, val = list(Z.pspace.sample().items())[0]
|
||||
assert sym == Z and val in Interval(0, oo)
|
||||
|
||||
libraries = ['scipy', 'numpy', 'pymc']
|
||||
for lib in libraries:
|
||||
try:
|
||||
imported_lib = import_module(lib)
|
||||
if imported_lib:
|
||||
s0, s1, s2 = [], [], []
|
||||
s0 = sample(Z, size=10, library=lib, seed=0)
|
||||
s1 = sample(Z, size=10, library=lib, seed=0)
|
||||
s2 = sample(Z, size=10, library=lib, seed=1)
|
||||
assert all(s0 == s1)
|
||||
assert all(s1 != s2)
|
||||
except NotImplementedError:
|
||||
continue
|
||||
@@ -0,0 +1,109 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.external import import_module
|
||||
from sympy.stats import (
|
||||
Geometric,
|
||||
Poisson,
|
||||
Zeta,
|
||||
sample,
|
||||
Skellam,
|
||||
Logarithmic,
|
||||
NegativeBinomial,
|
||||
YuleSimon,
|
||||
DiscreteRV,
|
||||
)
|
||||
from sympy.testing.pytest import skip, raises, slow
|
||||
|
||||
|
||||
def test_sample_numpy():
|
||||
distribs_numpy = [
|
||||
Geometric('G', 0.5),
|
||||
Poisson('P', 1),
|
||||
Zeta('Z', 2)
|
||||
]
|
||||
size = 3
|
||||
numpy = import_module('numpy')
|
||||
if not numpy:
|
||||
skip('Numpy is not installed. Abort tests for _sample_numpy.')
|
||||
else:
|
||||
for X in distribs_numpy:
|
||||
samps = sample(X, size=size, library='numpy')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: sample(Skellam('S', 1, 1), library='numpy'))
|
||||
raises(NotImplementedError,
|
||||
lambda: Skellam('S', 1, 1).pspace.distribution.sample(library='tensorflow'))
|
||||
|
||||
|
||||
def test_sample_scipy():
|
||||
p = S(2)/3
|
||||
x = Symbol('x', integer=True, positive=True)
|
||||
pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution
|
||||
distribs_scipy = [
|
||||
DiscreteRV(x, pdf, set=S.Naturals),
|
||||
Geometric('G', 0.5),
|
||||
Logarithmic('L', 0.5),
|
||||
NegativeBinomial('N', 5, 0.4),
|
||||
Poisson('P', 1),
|
||||
Skellam('S', 1, 1),
|
||||
YuleSimon('Y', 1),
|
||||
Zeta('Z', 2)
|
||||
]
|
||||
size = 3
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests for _sample_scipy.')
|
||||
else:
|
||||
for X in distribs_scipy:
|
||||
samps = sample(X, size=size, library='scipy')
|
||||
samps2 = sample(X, size=(2, 2), library='scipy')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
for i in range(2):
|
||||
for j in range(2):
|
||||
assert samps2[i][j] in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_sample_pymc():
|
||||
distribs_pymc = [
|
||||
Geometric('G', 0.5),
|
||||
Poisson('P', 1),
|
||||
NegativeBinomial('N', 5, 0.4)
|
||||
]
|
||||
size = 3
|
||||
pymc = import_module('pymc')
|
||||
if not pymc:
|
||||
skip('PyMC is not installed. Abort tests for _sample_pymc.')
|
||||
else:
|
||||
for X in distribs_pymc:
|
||||
samps = sample(X, size=size, library='pymc')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: sample(Skellam('S', 1, 1), library='pymc'))
|
||||
|
||||
@slow
|
||||
def test_sample_discrete():
|
||||
X = Geometric('X', S.Half)
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests')
|
||||
assert sample(X) in X.pspace.domain.set
|
||||
samps = sample(X, size=2) # This takes long time if ran without scipy
|
||||
for samp in samps:
|
||||
assert samp in X.pspace.domain.set
|
||||
|
||||
libraries = ['scipy', 'numpy', 'pymc']
|
||||
for lib in libraries:
|
||||
try:
|
||||
imported_lib = import_module(lib)
|
||||
if imported_lib:
|
||||
s0, s1, s2 = [], [], []
|
||||
s0 = sample(X, size=10, library=lib, seed=0)
|
||||
s1 = sample(X, size=10, library=lib, seed=0)
|
||||
s2 = sample(X, size=10, library=lib, seed=1)
|
||||
assert all(s0 == s1)
|
||||
assert not all(s1 == s2)
|
||||
except NotImplementedError:
|
||||
continue
|
||||
@@ -0,0 +1,94 @@
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.external import import_module
|
||||
from sympy.stats import Binomial, sample, Die, FiniteRV, DiscreteUniform, Bernoulli, BetaBinomial, Hypergeometric, \
|
||||
Rademacher
|
||||
from sympy.testing.pytest import skip, raises
|
||||
|
||||
def test_given_sample():
|
||||
X = Die('X', 6)
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
assert sample(X, X > 5) == 6
|
||||
|
||||
def test_sample_numpy():
|
||||
distribs_numpy = [
|
||||
Binomial("B", 5, 0.4),
|
||||
Hypergeometric("H", 2, 1, 1)
|
||||
]
|
||||
size = 3
|
||||
numpy = import_module('numpy')
|
||||
if not numpy:
|
||||
skip('Numpy is not installed. Abort tests for _sample_numpy.')
|
||||
else:
|
||||
for X in distribs_numpy:
|
||||
samps = sample(X, size=size, library='numpy')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: sample(Die("D"), library='numpy'))
|
||||
raises(NotImplementedError,
|
||||
lambda: Die("D").pspace.sample(library='tensorflow'))
|
||||
|
||||
|
||||
def test_sample_scipy():
|
||||
distribs_scipy = [
|
||||
FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)}),
|
||||
DiscreteUniform("Y", list(range(5))),
|
||||
Die("D"),
|
||||
Bernoulli("Be", 0.3),
|
||||
Binomial("Bi", 5, 0.4),
|
||||
BetaBinomial("Bb", 2, 1, 1),
|
||||
Hypergeometric("H", 1, 1, 1),
|
||||
Rademacher("R")
|
||||
]
|
||||
|
||||
size = 3
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests for _sample_scipy.')
|
||||
else:
|
||||
for X in distribs_scipy:
|
||||
samps = sample(X, size=size)
|
||||
samps2 = sample(X, size=(2, 2))
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
for i in range(2):
|
||||
for j in range(2):
|
||||
assert samps2[i][j] in X.pspace.domain.set
|
||||
|
||||
|
||||
def test_sample_pymc():
|
||||
distribs_pymc = [
|
||||
Bernoulli('B', 0.2),
|
||||
Binomial('N', 5, 0.4)
|
||||
]
|
||||
size = 3
|
||||
pymc = import_module('pymc')
|
||||
if not pymc:
|
||||
skip('PyMC is not installed. Abort tests for _sample_pymc.')
|
||||
else:
|
||||
for X in distribs_pymc:
|
||||
samps = sample(X, size=size, library='pymc')
|
||||
for sam in samps:
|
||||
assert sam in X.pspace.domain.set
|
||||
raises(NotImplementedError,
|
||||
lambda: (sample(Die("D"), library='pymc')))
|
||||
|
||||
|
||||
def test_sample_seed():
|
||||
F = FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)})
|
||||
size = 10
|
||||
libraries = ['scipy', 'numpy', 'pymc']
|
||||
for lib in libraries:
|
||||
try:
|
||||
imported_lib = import_module(lib)
|
||||
if imported_lib:
|
||||
s0 = sample(F, size=size, library=lib, seed=0)
|
||||
s1 = sample(F, size=size, library=lib, seed=0)
|
||||
s2 = sample(F, size=size, library=lib, seed=1)
|
||||
assert all(s0 == s1)
|
||||
assert not all(s1 == s2)
|
||||
except NotImplementedError:
|
||||
continue
|
||||
@@ -0,0 +1,66 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.stats.joint_rv import ProductPSpace
|
||||
from sympy.stats.rv import ProductDomain, _symbol_converter, Distribution
|
||||
|
||||
|
||||
class StochasticPSpace(ProductPSpace):
|
||||
"""
|
||||
Represents probability space of stochastic processes
|
||||
and their random variables. Contains mechanics to do
|
||||
computations for queries of stochastic processes.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Initialized by symbol, the specific process and
|
||||
distribution(optional) if the random indexed symbols
|
||||
of the process follows any specific distribution, like,
|
||||
in Bernoulli Process, each random indexed symbol follows
|
||||
Bernoulli distribution. For processes with memory, this
|
||||
parameter should not be passed.
|
||||
"""
|
||||
|
||||
def __new__(cls, sym, process, distribution=None):
|
||||
sym = _symbol_converter(sym)
|
||||
from sympy.stats.stochastic_process_types import StochasticProcess
|
||||
if not isinstance(process, StochasticProcess):
|
||||
raise TypeError("`process` must be an instance of StochasticProcess.")
|
||||
if distribution is None:
|
||||
distribution = Distribution()
|
||||
return Basic.__new__(cls, sym, process, distribution)
|
||||
|
||||
@property
|
||||
def process(self):
|
||||
"""
|
||||
The associated stochastic process.
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return ProductDomain(self.process.index_set,
|
||||
self.process.state_space)
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def distribution(self):
|
||||
return self.args[2]
|
||||
|
||||
def probability(self, condition, given_condition=None, evaluate=True, **kwargs):
|
||||
"""
|
||||
Transfers the task of handling queries to the specific stochastic
|
||||
process because every process has their own logic of handling such
|
||||
queries.
|
||||
"""
|
||||
return self.process.probability(condition, given_condition, evaluate, **kwargs)
|
||||
|
||||
def compute_expectation(self, expr, condition=None, evaluate=True, **kwargs):
|
||||
"""
|
||||
Transfers the task of handling queries to the specific stochastic
|
||||
process because every process has their own logic of handling such
|
||||
queries.
|
||||
"""
|
||||
return self.process.expectation(expr, condition, evaluate, **kwargs)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,308 @@
|
||||
import itertools
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.function import expand as _expand
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.singleton import S
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.stats.rv import RandomSymbol, is_random
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.stats.symbolic_probability import Variance, Covariance, Expectation
|
||||
|
||||
|
||||
class ExpectationMatrix(Expectation, MatrixExpr):
|
||||
"""
|
||||
Expectation of a random matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import ExpectationMatrix, Normal
|
||||
>>> from sympy.stats.rv import RandomMatrixSymbol
|
||||
>>> from sympy import symbols, MatrixSymbol, Matrix
|
||||
>>> k = symbols("k")
|
||||
>>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k)
|
||||
>>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1)
|
||||
>>> ExpectationMatrix(X)
|
||||
ExpectationMatrix(X)
|
||||
>>> ExpectationMatrix(A*X).shape
|
||||
(k, 1)
|
||||
|
||||
To expand the expectation in its expression, use ``expand()``:
|
||||
|
||||
>>> ExpectationMatrix(A*X + B*Y).expand()
|
||||
A*ExpectationMatrix(X) + B*ExpectationMatrix(Y)
|
||||
>>> ExpectationMatrix((X + Y)*(X - Y).T).expand()
|
||||
ExpectationMatrix(X*X.T) - ExpectationMatrix(X*Y.T) + ExpectationMatrix(Y*X.T) - ExpectationMatrix(Y*Y.T)
|
||||
|
||||
To evaluate the ``ExpectationMatrix``, use ``doit()``:
|
||||
|
||||
>>> N11, N12 = Normal('N11', 11, 1), Normal('N12', 12, 1)
|
||||
>>> N21, N22 = Normal('N21', 21, 1), Normal('N22', 22, 1)
|
||||
>>> M11, M12 = Normal('M11', 1, 1), Normal('M12', 2, 1)
|
||||
>>> M21, M22 = Normal('M21', 3, 1), Normal('M22', 4, 1)
|
||||
>>> x1 = Matrix([[N11, N12], [N21, N22]])
|
||||
>>> x2 = Matrix([[M11, M12], [M21, M22]])
|
||||
>>> ExpectationMatrix(x1 + x2).doit()
|
||||
Matrix([
|
||||
[12, 14],
|
||||
[24, 26]])
|
||||
|
||||
"""
|
||||
def __new__(cls, expr, condition=None):
|
||||
expr = _sympify(expr)
|
||||
if condition is None:
|
||||
if not is_random(expr):
|
||||
return expr
|
||||
obj = Expr.__new__(cls, expr)
|
||||
else:
|
||||
condition = _sympify(condition)
|
||||
obj = Expr.__new__(cls, expr, condition)
|
||||
|
||||
obj._shape = expr.shape
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def expand(self, **hints):
|
||||
expr = self.args[0]
|
||||
condition = self._condition
|
||||
if not is_random(expr):
|
||||
return expr
|
||||
|
||||
if isinstance(expr, Add):
|
||||
return Add.fromiter(Expectation(a, condition=condition).expand()
|
||||
for a in expr.args)
|
||||
|
||||
expand_expr = _expand(expr)
|
||||
if isinstance(expand_expr, Add):
|
||||
return Add.fromiter(Expectation(a, condition=condition).expand()
|
||||
for a in expand_expr.args)
|
||||
|
||||
elif isinstance(expr, (Mul, MatMul)):
|
||||
rv = []
|
||||
nonrv = []
|
||||
postnon = []
|
||||
|
||||
for a in expr.args:
|
||||
if is_random(a):
|
||||
if rv:
|
||||
rv.extend(postnon)
|
||||
else:
|
||||
nonrv.extend(postnon)
|
||||
postnon = []
|
||||
rv.append(a)
|
||||
elif a.is_Matrix:
|
||||
postnon.append(a)
|
||||
else:
|
||||
nonrv.append(a)
|
||||
|
||||
# In order to avoid infinite-looping (MatMul may call .doit() again),
|
||||
# do not rebuild
|
||||
if len(nonrv) == 0:
|
||||
return self
|
||||
return Mul.fromiter(nonrv)*Expectation(Mul.fromiter(rv),
|
||||
condition=condition)*Mul.fromiter(postnon)
|
||||
|
||||
return self
|
||||
|
||||
class VarianceMatrix(Variance, MatrixExpr):
|
||||
"""
|
||||
Variance of a random matrix probability expression. Also known as
|
||||
Covariance matrix, auto-covariance matrix, dispersion matrix,
|
||||
or variance-covariance matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import VarianceMatrix
|
||||
>>> from sympy.stats.rv import RandomMatrixSymbol
|
||||
>>> from sympy import symbols, MatrixSymbol
|
||||
>>> k = symbols("k")
|
||||
>>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k)
|
||||
>>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1)
|
||||
>>> VarianceMatrix(X)
|
||||
VarianceMatrix(X)
|
||||
>>> VarianceMatrix(X).shape
|
||||
(k, k)
|
||||
|
||||
To expand the variance in its expression, use ``expand()``:
|
||||
|
||||
>>> VarianceMatrix(A*X).expand()
|
||||
A*VarianceMatrix(X)*A.T
|
||||
>>> VarianceMatrix(A*X + B*Y).expand()
|
||||
2*A*CrossCovarianceMatrix(X, Y)*B.T + A*VarianceMatrix(X)*A.T + B*VarianceMatrix(Y)*B.T
|
||||
"""
|
||||
def __new__(cls, arg, condition=None):
|
||||
arg = _sympify(arg)
|
||||
|
||||
if 1 not in arg.shape:
|
||||
raise ShapeError("Expression is not a vector")
|
||||
|
||||
shape = (arg.shape[0], arg.shape[0]) if arg.shape[1] == 1 else (arg.shape[1], arg.shape[1])
|
||||
|
||||
if condition:
|
||||
obj = Expr.__new__(cls, arg, condition)
|
||||
else:
|
||||
obj = Expr.__new__(cls, arg)
|
||||
|
||||
obj._shape = shape
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def expand(self, **hints):
|
||||
arg = self.args[0]
|
||||
condition = self._condition
|
||||
|
||||
if not is_random(arg):
|
||||
return ZeroMatrix(*self.shape)
|
||||
|
||||
if isinstance(arg, RandomSymbol):
|
||||
return self
|
||||
elif isinstance(arg, Add):
|
||||
rv = []
|
||||
for a in arg.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
variances = Add(*(Variance(xv, condition).expand() for xv in rv))
|
||||
map_to_covar = lambda x: 2*Covariance(*x, condition=condition).expand()
|
||||
covariances = Add(*map(map_to_covar, itertools.combinations(rv, 2)))
|
||||
return variances + covariances
|
||||
elif isinstance(arg, (Mul, MatMul)):
|
||||
nonrv = []
|
||||
rv = []
|
||||
for a in arg.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
else:
|
||||
nonrv.append(a)
|
||||
if len(rv) == 0:
|
||||
return ZeroMatrix(*self.shape)
|
||||
# Avoid possible infinite loops with MatMul:
|
||||
if len(nonrv) == 0:
|
||||
return self
|
||||
# Variance of many multiple matrix products is not implemented:
|
||||
if len(rv) > 1:
|
||||
return self
|
||||
return Mul.fromiter(nonrv)*Variance(Mul.fromiter(rv),
|
||||
condition)*(Mul.fromiter(nonrv)).transpose()
|
||||
|
||||
# this expression contains a RandomSymbol somehow:
|
||||
return self
|
||||
|
||||
class CrossCovarianceMatrix(Covariance, MatrixExpr):
|
||||
"""
|
||||
Covariance of a random matrix probability expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import CrossCovarianceMatrix
|
||||
>>> from sympy.stats.rv import RandomMatrixSymbol
|
||||
>>> from sympy import symbols, MatrixSymbol
|
||||
>>> k = symbols("k")
|
||||
>>> A, B = MatrixSymbol("A", k, k), MatrixSymbol("B", k, k)
|
||||
>>> C, D = MatrixSymbol("C", k, k), MatrixSymbol("D", k, k)
|
||||
>>> X, Y = RandomMatrixSymbol("X", k, 1), RandomMatrixSymbol("Y", k, 1)
|
||||
>>> Z, W = RandomMatrixSymbol("Z", k, 1), RandomMatrixSymbol("W", k, 1)
|
||||
>>> CrossCovarianceMatrix(X, Y)
|
||||
CrossCovarianceMatrix(X, Y)
|
||||
>>> CrossCovarianceMatrix(X, Y).shape
|
||||
(k, k)
|
||||
|
||||
To expand the covariance in its expression, use ``expand()``:
|
||||
|
||||
>>> CrossCovarianceMatrix(X + Y, Z).expand()
|
||||
CrossCovarianceMatrix(X, Z) + CrossCovarianceMatrix(Y, Z)
|
||||
>>> CrossCovarianceMatrix(A*X, Y).expand()
|
||||
A*CrossCovarianceMatrix(X, Y)
|
||||
>>> CrossCovarianceMatrix(A*X, B.T*Y).expand()
|
||||
A*CrossCovarianceMatrix(X, Y)*B
|
||||
>>> CrossCovarianceMatrix(A*X + B*Y, C.T*Z + D.T*W).expand()
|
||||
A*CrossCovarianceMatrix(X, W)*D + A*CrossCovarianceMatrix(X, Z)*C + B*CrossCovarianceMatrix(Y, W)*D + B*CrossCovarianceMatrix(Y, Z)*C
|
||||
|
||||
"""
|
||||
def __new__(cls, arg1, arg2, condition=None):
|
||||
arg1 = _sympify(arg1)
|
||||
arg2 = _sympify(arg2)
|
||||
|
||||
if (1 not in arg1.shape) or (1 not in arg2.shape) or (arg1.shape[1] != arg2.shape[1]):
|
||||
raise ShapeError("Expression is not a vector")
|
||||
|
||||
shape = (arg1.shape[0], arg2.shape[0]) if arg1.shape[1] == 1 and arg2.shape[1] == 1 \
|
||||
else (1, 1)
|
||||
|
||||
if condition:
|
||||
obj = Expr.__new__(cls, arg1, arg2, condition)
|
||||
else:
|
||||
obj = Expr.__new__(cls, arg1, arg2)
|
||||
|
||||
obj._shape = shape
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def expand(self, **hints):
|
||||
arg1 = self.args[0]
|
||||
arg2 = self.args[1]
|
||||
condition = self._condition
|
||||
|
||||
if arg1 == arg2:
|
||||
return VarianceMatrix(arg1, condition).expand()
|
||||
|
||||
if not is_random(arg1) or not is_random(arg2):
|
||||
return ZeroMatrix(*self.shape)
|
||||
|
||||
if isinstance(arg1, RandomSymbol) and isinstance(arg2, RandomSymbol):
|
||||
return CrossCovarianceMatrix(arg1, arg2, condition)
|
||||
|
||||
coeff_rv_list1 = self._expand_single_argument(arg1.expand())
|
||||
coeff_rv_list2 = self._expand_single_argument(arg2.expand())
|
||||
|
||||
addends = [a*CrossCovarianceMatrix(r1, r2, condition=condition)*b.transpose()
|
||||
for (a, r1) in coeff_rv_list1 for (b, r2) in coeff_rv_list2]
|
||||
return Add.fromiter(addends)
|
||||
|
||||
@classmethod
|
||||
def _expand_single_argument(cls, expr):
|
||||
# return (coefficient, random_symbol) pairs:
|
||||
if isinstance(expr, RandomSymbol):
|
||||
return [(S.One, expr)]
|
||||
elif isinstance(expr, Add):
|
||||
outval = []
|
||||
for a in expr.args:
|
||||
if isinstance(a, (Mul, MatMul)):
|
||||
outval.append(cls._get_mul_nonrv_rv_tuple(a))
|
||||
elif is_random(a):
|
||||
outval.append((S.One, a))
|
||||
|
||||
return outval
|
||||
elif isinstance(expr, (Mul, MatMul)):
|
||||
return [cls._get_mul_nonrv_rv_tuple(expr)]
|
||||
elif is_random(expr):
|
||||
return [(S.One, expr)]
|
||||
|
||||
@classmethod
|
||||
def _get_mul_nonrv_rv_tuple(cls, m):
|
||||
rv = []
|
||||
nonrv = []
|
||||
for a in m.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
else:
|
||||
nonrv.append(a)
|
||||
return (Mul.fromiter(nonrv), Mul.fromiter(rv))
|
||||
@@ -0,0 +1,698 @@
|
||||
import itertools
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.function import expand as _expand
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.logic.boolalg import Not
|
||||
from sympy.core.parameters import global_parameters
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.core.relational import Relational
|
||||
from sympy.logic.boolalg import Boolean
|
||||
from sympy.stats import variance, covariance
|
||||
from sympy.stats.rv import (RandomSymbol, pspace, dependent,
|
||||
given, sampling_E, RandomIndexedSymbol, is_random,
|
||||
PSpace, sampling_P, random_symbols)
|
||||
|
||||
__all__ = ['Probability', 'Expectation', 'Variance', 'Covariance']
|
||||
|
||||
|
||||
@is_random.register(Expr)
|
||||
def _(x):
|
||||
atoms = x.free_symbols
|
||||
if len(atoms) == 1 and next(iter(atoms)) == x:
|
||||
return False
|
||||
return any(is_random(i) for i in atoms)
|
||||
|
||||
@is_random.register(RandomSymbol) # type: ignore
|
||||
def _(x):
|
||||
return True
|
||||
|
||||
|
||||
class Probability(Expr):
|
||||
"""
|
||||
Symbolic expression for the probability.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Probability, Normal
|
||||
>>> from sympy import Integral
|
||||
>>> X = Normal("X", 0, 1)
|
||||
>>> prob = Probability(X > 1)
|
||||
>>> prob
|
||||
Probability(X > 1)
|
||||
|
||||
Integral representation:
|
||||
|
||||
>>> prob.rewrite(Integral)
|
||||
Integral(sqrt(2)*exp(-_z**2/2)/(2*sqrt(pi)), (_z, 1, oo))
|
||||
|
||||
Evaluation of the integral:
|
||||
|
||||
>>> prob.evaluate_integral()
|
||||
sqrt(2)*(-sqrt(2)*sqrt(pi)*erf(sqrt(2)/2) + sqrt(2)*sqrt(pi))/(4*sqrt(pi))
|
||||
"""
|
||||
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, prob, condition=None, **kwargs):
|
||||
prob = _sympify(prob)
|
||||
if condition is None:
|
||||
obj = Expr.__new__(cls, prob)
|
||||
else:
|
||||
condition = _sympify(condition)
|
||||
obj = Expr.__new__(cls, prob, condition)
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
def doit(self, **hints):
|
||||
condition = self.args[0]
|
||||
given_condition = self._condition
|
||||
numsamples = hints.get('numsamples', False)
|
||||
evaluate = hints.get('evaluate', True)
|
||||
|
||||
if isinstance(condition, Not):
|
||||
return S.One - self.func(condition.args[0], given_condition,
|
||||
evaluate=evaluate).doit(**hints)
|
||||
|
||||
if condition.has(RandomIndexedSymbol):
|
||||
return pspace(condition).probability(condition, given_condition,
|
||||
evaluate=evaluate)
|
||||
|
||||
if isinstance(given_condition, RandomSymbol):
|
||||
condrv = random_symbols(condition)
|
||||
if len(condrv) == 1 and condrv[0] == given_condition:
|
||||
from sympy.stats.frv_types import BernoulliDistribution
|
||||
return BernoulliDistribution(self.func(condition).doit(**hints), 0, 1)
|
||||
if any(dependent(rv, given_condition) for rv in condrv):
|
||||
return Probability(condition, given_condition)
|
||||
else:
|
||||
return Probability(condition).doit()
|
||||
|
||||
if given_condition is not None and \
|
||||
not isinstance(given_condition, (Relational, Boolean)):
|
||||
raise ValueError("%s is not a relational or combination of relationals"
|
||||
% (given_condition))
|
||||
|
||||
if given_condition == False or condition is S.false:
|
||||
return S.Zero
|
||||
if not isinstance(condition, (Relational, Boolean)):
|
||||
raise ValueError("%s is not a relational or combination of relationals"
|
||||
% (condition))
|
||||
if condition is S.true:
|
||||
return S.One
|
||||
|
||||
if numsamples:
|
||||
return sampling_P(condition, given_condition, numsamples=numsamples)
|
||||
if given_condition is not None: # If there is a condition
|
||||
# Recompute on new conditional expr
|
||||
return Probability(given(condition, given_condition)).doit()
|
||||
|
||||
# Otherwise pass work off to the ProbabilitySpace
|
||||
if pspace(condition) == PSpace():
|
||||
return Probability(condition, given_condition)
|
||||
|
||||
result = pspace(condition).probability(condition)
|
||||
if hasattr(result, 'doit') and evaluate:
|
||||
return result.doit()
|
||||
else:
|
||||
return result
|
||||
|
||||
def _eval_rewrite_as_Integral(self, arg, condition=None, **kwargs):
|
||||
return self.func(arg, condition=condition).doit(evaluate=False)
|
||||
|
||||
_eval_rewrite_as_Sum = _eval_rewrite_as_Integral
|
||||
|
||||
def evaluate_integral(self):
|
||||
return self.rewrite(Integral).doit()
|
||||
|
||||
|
||||
class Expectation(Expr):
|
||||
"""
|
||||
Symbolic expression for the expectation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Expectation, Normal, Probability, Poisson
|
||||
>>> from sympy import symbols, Integral, Sum
|
||||
>>> mu = symbols("mu")
|
||||
>>> sigma = symbols("sigma", positive=True)
|
||||
>>> X = Normal("X", mu, sigma)
|
||||
>>> Expectation(X)
|
||||
Expectation(X)
|
||||
>>> Expectation(X).evaluate_integral().simplify()
|
||||
mu
|
||||
|
||||
To get the integral expression of the expectation:
|
||||
|
||||
>>> Expectation(X).rewrite(Integral)
|
||||
Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo))
|
||||
|
||||
The same integral expression, in more abstract terms:
|
||||
|
||||
>>> Expectation(X).rewrite(Probability)
|
||||
Integral(x*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
|
||||
To get the Summation expression of the expectation for discrete random variables:
|
||||
|
||||
>>> lamda = symbols('lamda', positive=True)
|
||||
>>> Z = Poisson('Z', lamda)
|
||||
>>> Expectation(Z).rewrite(Sum)
|
||||
Sum(Z*lamda**Z*exp(-lamda)/factorial(Z), (Z, 0, oo))
|
||||
|
||||
This class is aware of some properties of the expectation:
|
||||
|
||||
>>> from sympy.abc import a
|
||||
>>> Expectation(a*X)
|
||||
Expectation(a*X)
|
||||
>>> Y = Normal("Y", 1, 2)
|
||||
>>> Expectation(X + Y)
|
||||
Expectation(X + Y)
|
||||
|
||||
To expand the ``Expectation`` into its expression, use ``expand()``:
|
||||
|
||||
>>> Expectation(X + Y).expand()
|
||||
Expectation(X) + Expectation(Y)
|
||||
>>> Expectation(a*X + Y).expand()
|
||||
a*Expectation(X) + Expectation(Y)
|
||||
>>> Expectation(a*X + Y)
|
||||
Expectation(a*X + Y)
|
||||
>>> Expectation((X + Y)*(X - Y)).expand()
|
||||
Expectation(X**2) - Expectation(Y**2)
|
||||
|
||||
To evaluate the ``Expectation``, use ``doit()``:
|
||||
|
||||
>>> Expectation(X + Y).doit()
|
||||
mu + 1
|
||||
>>> Expectation(X + Expectation(Y + Expectation(2*X))).doit()
|
||||
3*mu + 1
|
||||
|
||||
To prevent evaluating nested ``Expectation``, use ``doit(deep=False)``
|
||||
|
||||
>>> Expectation(X + Expectation(Y)).doit(deep=False)
|
||||
mu + Expectation(Expectation(Y))
|
||||
>>> Expectation(X + Expectation(Y + Expectation(2*X))).doit(deep=False)
|
||||
mu + Expectation(Expectation(Expectation(2*X) + Y))
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, expr, condition=None, **kwargs):
|
||||
expr = _sympify(expr)
|
||||
if expr.is_Matrix:
|
||||
from sympy.stats.symbolic_multivariate_probability import ExpectationMatrix
|
||||
return ExpectationMatrix(expr, condition)
|
||||
if condition is None:
|
||||
if not is_random(expr):
|
||||
return expr
|
||||
obj = Expr.__new__(cls, expr)
|
||||
else:
|
||||
condition = _sympify(condition)
|
||||
obj = Expr.__new__(cls, expr, condition)
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
def _eval_is_commutative(self):
|
||||
return(self.args[0].is_commutative)
|
||||
|
||||
def expand(self, **hints):
|
||||
expr = self.args[0]
|
||||
condition = self._condition
|
||||
|
||||
if not is_random(expr):
|
||||
return expr
|
||||
|
||||
if isinstance(expr, Add):
|
||||
return Add.fromiter(Expectation(a, condition=condition).expand()
|
||||
for a in expr.args)
|
||||
|
||||
expand_expr = _expand(expr)
|
||||
if isinstance(expand_expr, Add):
|
||||
return Add.fromiter(Expectation(a, condition=condition).expand()
|
||||
for a in expand_expr.args)
|
||||
|
||||
elif isinstance(expr, Mul):
|
||||
rv = []
|
||||
nonrv = []
|
||||
for a in expr.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
else:
|
||||
nonrv.append(a)
|
||||
return Mul.fromiter(nonrv)*Expectation(Mul.fromiter(rv), condition=condition)
|
||||
|
||||
return self
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
condition = self._condition
|
||||
expr = self.args[0]
|
||||
numsamples = hints.get('numsamples', False)
|
||||
evaluate = hints.get('evaluate', True)
|
||||
|
||||
if deep:
|
||||
expr = expr.doit(**hints)
|
||||
|
||||
if not is_random(expr) or isinstance(expr, Expectation): # expr isn't random?
|
||||
return expr
|
||||
if numsamples: # Computing by monte carlo sampling?
|
||||
evalf = hints.get('evalf', True)
|
||||
return sampling_E(expr, condition, numsamples=numsamples, evalf=evalf)
|
||||
|
||||
if expr.has(RandomIndexedSymbol):
|
||||
return pspace(expr).compute_expectation(expr, condition)
|
||||
|
||||
# Create new expr and recompute E
|
||||
if condition is not None: # If there is a condition
|
||||
return self.func(given(expr, condition)).doit(**hints)
|
||||
|
||||
# A few known statements for efficiency
|
||||
|
||||
if expr.is_Add: # We know that E is Linear
|
||||
return Add(*[self.func(arg, condition).doit(**hints)
|
||||
if not isinstance(arg, Expectation) else self.func(arg, condition)
|
||||
for arg in expr.args])
|
||||
if expr.is_Mul:
|
||||
if expr.atoms(Expectation):
|
||||
return expr
|
||||
|
||||
if pspace(expr) == PSpace():
|
||||
return self.func(expr)
|
||||
# Otherwise case is simple, pass work off to the ProbabilitySpace
|
||||
result = pspace(expr).compute_expectation(expr, evaluate=evaluate)
|
||||
if hasattr(result, 'doit') and evaluate:
|
||||
return result.doit(**hints)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def _eval_rewrite_as_Probability(self, arg, condition=None, **kwargs):
|
||||
rvs = arg.atoms(RandomSymbol)
|
||||
if len(rvs) > 1:
|
||||
raise NotImplementedError()
|
||||
if len(rvs) == 0:
|
||||
return arg
|
||||
|
||||
rv = rvs.pop()
|
||||
if rv.pspace is None:
|
||||
raise ValueError("Probability space not known")
|
||||
|
||||
symbol = rv.symbol
|
||||
if symbol.name[0].isupper():
|
||||
symbol = Symbol(symbol.name.lower())
|
||||
else :
|
||||
symbol = Symbol(symbol.name + "_1")
|
||||
|
||||
if rv.pspace.is_Continuous:
|
||||
return Integral(arg.replace(rv, symbol)*Probability(Eq(rv, symbol), condition), (symbol, rv.pspace.domain.set.inf, rv.pspace.domain.set.sup))
|
||||
else:
|
||||
if rv.pspace.is_Finite:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
return Sum(arg.replace(rv, symbol)*Probability(Eq(rv, symbol), condition), (symbol, rv.pspace.domain.set.inf, rv.pspace.set.sup))
|
||||
|
||||
def _eval_rewrite_as_Integral(self, arg, condition=None, evaluate=False, **kwargs):
|
||||
return self.func(arg, condition=condition).doit(deep=False, evaluate=evaluate)
|
||||
|
||||
_eval_rewrite_as_Sum = _eval_rewrite_as_Integral # For discrete this will be Sum
|
||||
|
||||
def evaluate_integral(self):
|
||||
return self.rewrite(Integral).doit()
|
||||
|
||||
evaluate_sum = evaluate_integral
|
||||
|
||||
class Variance(Expr):
|
||||
"""
|
||||
Symbolic expression for the variance.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, Integral
|
||||
>>> from sympy.stats import Normal, Expectation, Variance, Probability
|
||||
>>> mu = symbols("mu", positive=True)
|
||||
>>> sigma = symbols("sigma", positive=True)
|
||||
>>> X = Normal("X", mu, sigma)
|
||||
>>> Variance(X)
|
||||
Variance(X)
|
||||
>>> Variance(X).evaluate_integral()
|
||||
sigma**2
|
||||
|
||||
Integral representation of the underlying calculations:
|
||||
|
||||
>>> Variance(X).rewrite(Integral)
|
||||
Integral(sqrt(2)*(X - Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)))**2*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo))
|
||||
|
||||
Integral representation, without expanding the PDF:
|
||||
|
||||
>>> Variance(X).rewrite(Probability)
|
||||
-Integral(x*Probability(Eq(X, x)), (x, -oo, oo))**2 + Integral(x**2*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
|
||||
Rewrite the variance in terms of the expectation
|
||||
|
||||
>>> Variance(X).rewrite(Expectation)
|
||||
-Expectation(X)**2 + Expectation(X**2)
|
||||
|
||||
Some transformations based on the properties of the variance may happen:
|
||||
|
||||
>>> from sympy.abc import a
|
||||
>>> Y = Normal("Y", 0, 1)
|
||||
>>> Variance(a*X)
|
||||
Variance(a*X)
|
||||
|
||||
To expand the variance in its expression, use ``expand()``:
|
||||
|
||||
>>> Variance(a*X).expand()
|
||||
a**2*Variance(X)
|
||||
>>> Variance(X + Y)
|
||||
Variance(X + Y)
|
||||
>>> Variance(X + Y).expand()
|
||||
2*Covariance(X, Y) + Variance(X) + Variance(Y)
|
||||
|
||||
"""
|
||||
def __new__(cls, arg, condition=None, **kwargs):
|
||||
arg = _sympify(arg)
|
||||
|
||||
if arg.is_Matrix:
|
||||
from sympy.stats.symbolic_multivariate_probability import VarianceMatrix
|
||||
return VarianceMatrix(arg, condition)
|
||||
if condition is None:
|
||||
obj = Expr.__new__(cls, arg)
|
||||
else:
|
||||
condition = _sympify(condition)
|
||||
obj = Expr.__new__(cls, arg, condition)
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
def _eval_is_commutative(self):
|
||||
return self.args[0].is_commutative
|
||||
|
||||
def expand(self, **hints):
|
||||
arg = self.args[0]
|
||||
condition = self._condition
|
||||
|
||||
if not is_random(arg):
|
||||
return S.Zero
|
||||
|
||||
if isinstance(arg, RandomSymbol):
|
||||
return self
|
||||
elif isinstance(arg, Add):
|
||||
rv = []
|
||||
for a in arg.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
variances = Add(*(Variance(xv, condition).expand() for xv in rv))
|
||||
map_to_covar = lambda x: 2*Covariance(*x, condition=condition).expand()
|
||||
covariances = Add(*map(map_to_covar, itertools.combinations(rv, 2)))
|
||||
return variances + covariances
|
||||
elif isinstance(arg, Mul):
|
||||
nonrv = []
|
||||
rv = []
|
||||
for a in arg.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
else:
|
||||
nonrv.append(a**2)
|
||||
if len(rv) == 0:
|
||||
return S.Zero
|
||||
return Mul.fromiter(nonrv)*Variance(Mul.fromiter(rv), condition)
|
||||
|
||||
# this expression contains a RandomSymbol somehow:
|
||||
return self
|
||||
|
||||
def _eval_rewrite_as_Expectation(self, arg, condition=None, **kwargs):
|
||||
e1 = Expectation(arg**2, condition)
|
||||
e2 = Expectation(arg, condition)**2
|
||||
return e1 - e2
|
||||
|
||||
def _eval_rewrite_as_Probability(self, arg, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Probability)
|
||||
|
||||
def _eval_rewrite_as_Integral(self, arg, condition=None, **kwargs):
|
||||
return variance(self.args[0], self._condition, evaluate=False)
|
||||
|
||||
_eval_rewrite_as_Sum = _eval_rewrite_as_Integral
|
||||
|
||||
def evaluate_integral(self):
|
||||
return self.rewrite(Integral).doit()
|
||||
|
||||
|
||||
class Covariance(Expr):
|
||||
"""
|
||||
Symbolic expression for the covariance.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.stats import Covariance
|
||||
>>> from sympy.stats import Normal
|
||||
>>> X = Normal("X", 3, 2)
|
||||
>>> Y = Normal("Y", 0, 1)
|
||||
>>> Z = Normal("Z", 0, 1)
|
||||
>>> W = Normal("W", 0, 1)
|
||||
>>> cexpr = Covariance(X, Y)
|
||||
>>> cexpr
|
||||
Covariance(X, Y)
|
||||
|
||||
Evaluate the covariance, `X` and `Y` are independent,
|
||||
therefore zero is the result:
|
||||
|
||||
>>> cexpr.evaluate_integral()
|
||||
0
|
||||
|
||||
Rewrite the covariance expression in terms of expectations:
|
||||
|
||||
>>> from sympy.stats import Expectation
|
||||
>>> cexpr.rewrite(Expectation)
|
||||
Expectation(X*Y) - Expectation(X)*Expectation(Y)
|
||||
|
||||
In order to expand the argument, use ``expand()``:
|
||||
|
||||
>>> from sympy.abc import a, b, c, d
|
||||
>>> Covariance(a*X + b*Y, c*Z + d*W)
|
||||
Covariance(a*X + b*Y, c*Z + d*W)
|
||||
>>> Covariance(a*X + b*Y, c*Z + d*W).expand()
|
||||
a*c*Covariance(X, Z) + a*d*Covariance(W, X) + b*c*Covariance(Y, Z) + b*d*Covariance(W, Y)
|
||||
|
||||
This class is aware of some properties of the covariance:
|
||||
|
||||
>>> Covariance(X, X).expand()
|
||||
Variance(X)
|
||||
>>> Covariance(a*X, b*Y).expand()
|
||||
a*b*Covariance(X, Y)
|
||||
"""
|
||||
|
||||
def __new__(cls, arg1, arg2, condition=None, **kwargs):
|
||||
arg1 = _sympify(arg1)
|
||||
arg2 = _sympify(arg2)
|
||||
|
||||
if arg1.is_Matrix or arg2.is_Matrix:
|
||||
from sympy.stats.symbolic_multivariate_probability import CrossCovarianceMatrix
|
||||
return CrossCovarianceMatrix(arg1, arg2, condition)
|
||||
|
||||
if kwargs.pop('evaluate', global_parameters.evaluate):
|
||||
arg1, arg2 = sorted([arg1, arg2], key=default_sort_key)
|
||||
|
||||
if condition is None:
|
||||
obj = Expr.__new__(cls, arg1, arg2)
|
||||
else:
|
||||
condition = _sympify(condition)
|
||||
obj = Expr.__new__(cls, arg1, arg2, condition)
|
||||
obj._condition = condition
|
||||
return obj
|
||||
|
||||
def _eval_is_commutative(self):
|
||||
return self.args[0].is_commutative
|
||||
|
||||
def expand(self, **hints):
|
||||
arg1 = self.args[0]
|
||||
arg2 = self.args[1]
|
||||
condition = self._condition
|
||||
|
||||
if arg1 == arg2:
|
||||
return Variance(arg1, condition).expand()
|
||||
|
||||
if not is_random(arg1):
|
||||
return S.Zero
|
||||
if not is_random(arg2):
|
||||
return S.Zero
|
||||
|
||||
arg1, arg2 = sorted([arg1, arg2], key=default_sort_key)
|
||||
|
||||
if isinstance(arg1, RandomSymbol) and isinstance(arg2, RandomSymbol):
|
||||
return Covariance(arg1, arg2, condition)
|
||||
|
||||
coeff_rv_list1 = self._expand_single_argument(arg1.expand())
|
||||
coeff_rv_list2 = self._expand_single_argument(arg2.expand())
|
||||
|
||||
addends = [a*b*Covariance(*sorted([r1, r2], key=default_sort_key), condition=condition)
|
||||
for (a, r1) in coeff_rv_list1 for (b, r2) in coeff_rv_list2]
|
||||
return Add.fromiter(addends)
|
||||
|
||||
@classmethod
|
||||
def _expand_single_argument(cls, expr):
|
||||
# return (coefficient, random_symbol) pairs:
|
||||
if isinstance(expr, RandomSymbol):
|
||||
return [(S.One, expr)]
|
||||
elif isinstance(expr, Add):
|
||||
outval = []
|
||||
for a in expr.args:
|
||||
if isinstance(a, Mul):
|
||||
outval.append(cls._get_mul_nonrv_rv_tuple(a))
|
||||
elif is_random(a):
|
||||
outval.append((S.One, a))
|
||||
|
||||
return outval
|
||||
elif isinstance(expr, Mul):
|
||||
return [cls._get_mul_nonrv_rv_tuple(expr)]
|
||||
elif is_random(expr):
|
||||
return [(S.One, expr)]
|
||||
|
||||
@classmethod
|
||||
def _get_mul_nonrv_rv_tuple(cls, m):
|
||||
rv = []
|
||||
nonrv = []
|
||||
for a in m.args:
|
||||
if is_random(a):
|
||||
rv.append(a)
|
||||
else:
|
||||
nonrv.append(a)
|
||||
return (Mul.fromiter(nonrv), Mul.fromiter(rv))
|
||||
|
||||
def _eval_rewrite_as_Expectation(self, arg1, arg2, condition=None, **kwargs):
|
||||
e1 = Expectation(arg1*arg2, condition)
|
||||
e2 = Expectation(arg1, condition)*Expectation(arg2, condition)
|
||||
return e1 - e2
|
||||
|
||||
def _eval_rewrite_as_Probability(self, arg1, arg2, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Probability)
|
||||
|
||||
def _eval_rewrite_as_Integral(self, arg1, arg2, condition=None, **kwargs):
|
||||
return covariance(self.args[0], self.args[1], self._condition, evaluate=False)
|
||||
|
||||
_eval_rewrite_as_Sum = _eval_rewrite_as_Integral
|
||||
|
||||
def evaluate_integral(self):
|
||||
return self.rewrite(Integral).doit()
|
||||
|
||||
|
||||
class Moment(Expr):
|
||||
"""
|
||||
Symbolic class for Moment
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Symbol, Integral
|
||||
>>> from sympy.stats import Normal, Expectation, Probability, Moment
|
||||
>>> mu = Symbol('mu', real=True)
|
||||
>>> sigma = Symbol('sigma', positive=True)
|
||||
>>> X = Normal('X', mu, sigma)
|
||||
>>> M = Moment(X, 3, 1)
|
||||
|
||||
To evaluate the result of Moment use `doit`:
|
||||
|
||||
>>> M.doit()
|
||||
mu**3 - 3*mu**2 + 3*mu*sigma**2 + 3*mu - 3*sigma**2 - 1
|
||||
|
||||
Rewrite the Moment expression in terms of Expectation:
|
||||
|
||||
>>> M.rewrite(Expectation)
|
||||
Expectation((X - 1)**3)
|
||||
|
||||
Rewrite the Moment expression in terms of Probability:
|
||||
|
||||
>>> M.rewrite(Probability)
|
||||
Integral((x - 1)**3*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
|
||||
Rewrite the Moment expression in terms of Integral:
|
||||
|
||||
>>> M.rewrite(Integral)
|
||||
Integral(sqrt(2)*(X - 1)**3*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo))
|
||||
|
||||
"""
|
||||
def __new__(cls, X, n, c=0, condition=None, **kwargs):
|
||||
X = _sympify(X)
|
||||
n = _sympify(n)
|
||||
c = _sympify(c)
|
||||
if condition is not None:
|
||||
condition = _sympify(condition)
|
||||
return super().__new__(cls, X, n, c, condition)
|
||||
else:
|
||||
return super().__new__(cls, X, n, c)
|
||||
|
||||
def doit(self, **hints):
|
||||
return self.rewrite(Expectation).doit(**hints)
|
||||
|
||||
def _eval_rewrite_as_Expectation(self, X, n, c=0, condition=None, **kwargs):
|
||||
return Expectation((X - c)**n, condition)
|
||||
|
||||
def _eval_rewrite_as_Probability(self, X, n, c=0, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Probability)
|
||||
|
||||
def _eval_rewrite_as_Integral(self, X, n, c=0, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Integral)
|
||||
|
||||
|
||||
class CentralMoment(Expr):
|
||||
"""
|
||||
Symbolic class Central Moment
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Symbol, Integral
|
||||
>>> from sympy.stats import Normal, Expectation, Probability, CentralMoment
|
||||
>>> mu = Symbol('mu', real=True)
|
||||
>>> sigma = Symbol('sigma', positive=True)
|
||||
>>> X = Normal('X', mu, sigma)
|
||||
>>> CM = CentralMoment(X, 4)
|
||||
|
||||
To evaluate the result of CentralMoment use `doit`:
|
||||
|
||||
>>> CM.doit().simplify()
|
||||
3*sigma**4
|
||||
|
||||
Rewrite the CentralMoment expression in terms of Expectation:
|
||||
|
||||
>>> CM.rewrite(Expectation)
|
||||
Expectation((-Expectation(X) + X)**4)
|
||||
|
||||
Rewrite the CentralMoment expression in terms of Probability:
|
||||
|
||||
>>> CM.rewrite(Probability)
|
||||
Integral((x - Integral(x*Probability(True), (x, -oo, oo)))**4*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
|
||||
Rewrite the CentralMoment expression in terms of Integral:
|
||||
|
||||
>>> CM.rewrite(Integral)
|
||||
Integral(sqrt(2)*(X - Integral(sqrt(2)*X*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo)))**4*exp(-(X - mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (X, -oo, oo))
|
||||
|
||||
"""
|
||||
def __new__(cls, X, n, condition=None, **kwargs):
|
||||
X = _sympify(X)
|
||||
n = _sympify(n)
|
||||
if condition is not None:
|
||||
condition = _sympify(condition)
|
||||
return super().__new__(cls, X, n, condition)
|
||||
else:
|
||||
return super().__new__(cls, X, n)
|
||||
|
||||
def doit(self, **hints):
|
||||
return self.rewrite(Expectation).doit(**hints)
|
||||
|
||||
def _eval_rewrite_as_Expectation(self, X, n, condition=None, **kwargs):
|
||||
mu = Expectation(X, condition, **kwargs)
|
||||
return Moment(X, n, mu, condition, **kwargs).rewrite(Expectation)
|
||||
|
||||
def _eval_rewrite_as_Probability(self, X, n, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Probability)
|
||||
|
||||
def _eval_rewrite_as_Integral(self, X, n, condition=None, **kwargs):
|
||||
return self.rewrite(Expectation).rewrite(Integral)
|
||||
@@ -0,0 +1,159 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import (oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.beta_functions import beta
|
||||
from sympy.functions.special.error_functions import erf
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.stats import (Normal, P, E, density, Gamma, Poisson, Rayleigh,
|
||||
variance, Bernoulli, Beta, Uniform, cdf)
|
||||
from sympy.stats.compound_rv import CompoundDistribution, CompoundPSpace
|
||||
from sympy.stats.crv_types import NormalDistribution
|
||||
from sympy.stats.drv_types import PoissonDistribution
|
||||
from sympy.stats.frv_types import BernoulliDistribution
|
||||
from sympy.testing.pytest import raises, ignore_warnings
|
||||
from sympy.stats.joint_rv_types import MultivariateNormalDistribution
|
||||
|
||||
from sympy.abc import x
|
||||
|
||||
|
||||
# helpers for testing troublesome unevaluated expressions
|
||||
flat = lambda s: ''.join(str(s).split())
|
||||
streq = lambda *a: len(set(map(flat, a))) == 1
|
||||
assert streq(x, x)
|
||||
assert streq(x, 'x')
|
||||
assert not streq(x, x + 1)
|
||||
|
||||
|
||||
def test_normal_CompoundDist():
|
||||
X = Normal('X', 1, 2)
|
||||
Y = Normal('X', X, 4)
|
||||
assert density(Y)(x).simplify() == sqrt(10)*exp(-x**2/40 + x/20 - S(1)/40)/(20*sqrt(pi))
|
||||
assert E(Y) == 1 # it is always equal to mean of X
|
||||
assert P(Y > 1) == S(1)/2 # as 1 is the mean
|
||||
assert P(Y > 5).simplify() == S(1)/2 - erf(sqrt(10)/5)/2
|
||||
assert variance(Y) == variance(X) + 4**2 # 2**2 + 4**2
|
||||
# https://math.stackexchange.com/questions/1484451/
|
||||
# (Contains proof of E and variance computation)
|
||||
|
||||
|
||||
def test_poisson_CompoundDist():
|
||||
k, t, y = symbols('k t y', positive=True, real=True)
|
||||
G = Gamma('G', k, t)
|
||||
D = Poisson('P', G)
|
||||
assert density(D)(y).simplify() == t**y*(t + 1)**(-k - y)*gamma(k + y)/(gamma(k)*gamma(y + 1))
|
||||
# https://en.wikipedia.org/wiki/Negative_binomial_distribution#Gamma%E2%80%93Poisson_mixture
|
||||
assert E(D).simplify() == k*t # mean of NegativeBinomialDistribution
|
||||
|
||||
|
||||
def test_bernoulli_CompoundDist():
|
||||
X = Beta('X', 1, 2)
|
||||
Y = Bernoulli('Y', X)
|
||||
assert density(Y).dict == {0: S(2)/3, 1: S(1)/3}
|
||||
assert E(Y) == P(Eq(Y, 1)) == S(1)/3
|
||||
assert variance(Y) == S(2)/9
|
||||
assert cdf(Y) == {0: S(2)/3, 1: 1}
|
||||
|
||||
# test issue 8128
|
||||
a = Bernoulli('a', S(1)/2)
|
||||
b = Bernoulli('b', a)
|
||||
assert density(b).dict == {0: S(1)/2, 1: S(1)/2}
|
||||
assert P(b > 0.5) == S(1)/2
|
||||
|
||||
X = Uniform('X', 0, 1)
|
||||
Y = Bernoulli('Y', X)
|
||||
assert E(Y) == S(1)/2
|
||||
assert P(Eq(Y, 1)) == E(Y)
|
||||
|
||||
|
||||
def test_unevaluated_CompoundDist():
|
||||
# these tests need to be removed once they work with evaluation as they are currently not
|
||||
# evaluated completely in sympy.
|
||||
R = Rayleigh('R', 4)
|
||||
X = Normal('X', 3, R)
|
||||
ans = '''
|
||||
Piecewise(((-sqrt(pi)*sinh(x/4 - 3/4) + sqrt(pi)*cosh(x/4 - 3/4))/(
|
||||
8*sqrt(pi)), Abs(arg(x - 3)) <= pi/4), (Integral(sqrt(2)*exp(-(x - 3)
|
||||
**2/(2*R**2))*exp(-R**2/32)/(32*sqrt(pi)), (R, 0, oo)), True))'''
|
||||
assert streq(density(X)(x), ans)
|
||||
|
||||
expre = '''
|
||||
Integral(X*Integral(sqrt(2)*exp(-(X-3)**2/(2*R**2))*exp(-R**2/32)/(32*
|
||||
sqrt(pi)),(R,0,oo)),(X,-oo,oo))'''
|
||||
with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed
|
||||
assert streq(E(X, evaluate=False).rewrite(Integral), expre)
|
||||
|
||||
X = Poisson('X', 1)
|
||||
Y = Poisson('Y', X)
|
||||
Z = Poisson('Z', Y)
|
||||
exprd = Sum(exp(-Y)*Y**x*Sum(exp(-1)*exp(-X)*X**Y/(factorial(X)*factorial(Y)
|
||||
), (X, 0, oo))/factorial(x), (Y, 0, oo))
|
||||
assert density(Z)(x) == exprd
|
||||
|
||||
N = Normal('N', 1, 2)
|
||||
M = Normal('M', 3, 4)
|
||||
D = Normal('D', M, N)
|
||||
exprd = '''
|
||||
Integral(sqrt(2)*exp(-(N-1)**2/8)*Integral(exp(-(x-M)**2/(2*N**2))*exp
|
||||
(-(M-3)**2/32)/(8*pi*N),(M,-oo,oo))/(4*sqrt(pi)),(N,-oo,oo))'''
|
||||
assert streq(density(D, evaluate=False)(x), exprd)
|
||||
|
||||
|
||||
def test_Compound_Distribution():
|
||||
X = Normal('X', 2, 4)
|
||||
N = NormalDistribution(X, 4)
|
||||
C = CompoundDistribution(N)
|
||||
assert C.is_Continuous
|
||||
assert C.set == Interval(-oo, oo)
|
||||
assert C.pdf(x, evaluate=True).simplify() == exp(-x**2/64 + x/16 - S(1)/16)/(8*sqrt(pi))
|
||||
|
||||
assert not isinstance(CompoundDistribution(NormalDistribution(2, 3)),
|
||||
CompoundDistribution)
|
||||
M = MultivariateNormalDistribution([1, 2], [[2, 1], [1, 2]])
|
||||
raises(NotImplementedError, lambda: CompoundDistribution(M))
|
||||
|
||||
X = Beta('X', 2, 4)
|
||||
B = BernoulliDistribution(X, 1, 0)
|
||||
C = CompoundDistribution(B)
|
||||
assert C.is_Finite
|
||||
assert C.set == {0, 1}
|
||||
y = symbols('y', negative=False, integer=True)
|
||||
assert C.pdf(y, evaluate=True) == Piecewise((S(1)/(30*beta(2, 4)), Eq(y, 0)),
|
||||
(S(1)/(60*beta(2, 4)), Eq(y, 1)), (0, True))
|
||||
|
||||
k, t, z = symbols('k t z', positive=True, real=True)
|
||||
G = Gamma('G', k, t)
|
||||
X = PoissonDistribution(G)
|
||||
C = CompoundDistribution(X)
|
||||
assert C.is_Discrete
|
||||
assert C.set == S.Naturals0
|
||||
assert C.pdf(z, evaluate=True).simplify() == t**z*(t + 1)**(-k - z)*gamma(k \
|
||||
+ z)/(gamma(k)*gamma(z + 1))
|
||||
|
||||
|
||||
def test_compound_pspace():
|
||||
X = Normal('X', 2, 4)
|
||||
Y = Normal('Y', 3, 6)
|
||||
assert not isinstance(Y.pspace, CompoundPSpace)
|
||||
N = NormalDistribution(1, 2)
|
||||
D = PoissonDistribution(3)
|
||||
B = BernoulliDistribution(0.2, 1, 0)
|
||||
pspace1 = CompoundPSpace('N', N)
|
||||
pspace2 = CompoundPSpace('D', D)
|
||||
pspace3 = CompoundPSpace('B', B)
|
||||
assert not isinstance(pspace1, CompoundPSpace)
|
||||
assert not isinstance(pspace2, CompoundPSpace)
|
||||
assert not isinstance(pspace3, CompoundPSpace)
|
||||
M = MultivariateNormalDistribution([1, 2], [[2, 1], [1, 2]])
|
||||
raises(ValueError, lambda: CompoundPSpace('M', M))
|
||||
Y = Normal('Y', X, 6)
|
||||
assert isinstance(Y.pspace, CompoundPSpace)
|
||||
assert Y.pspace.distribution == CompoundDistribution(NormalDistribution(X, 6))
|
||||
assert Y.pspace.domain.set == Interval(-oo, oo)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import (I, Rational, oo, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.complexes import (im, re)
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.bessel import besseli
|
||||
from sympy.functions.special.beta_functions import beta
|
||||
from sympy.functions.special.zeta_functions import zeta
|
||||
from sympy.sets.sets import FiniteSet
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.utilities.lambdify import lambdify
|
||||
from sympy.core.relational import Eq, Ne
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.logic.boolalg import Or
|
||||
from sympy.sets.fancysets import Range
|
||||
from sympy.stats import (P, E, variance, density, characteristic_function,
|
||||
where, moment_generating_function, skewness, cdf,
|
||||
kurtosis, coskewness)
|
||||
from sympy.stats.drv_types import (PoissonDistribution, GeometricDistribution,
|
||||
FlorySchulz, Poisson, Geometric, Hermite, Logarithmic,
|
||||
NegativeBinomial, Skellam, YuleSimon, Zeta,
|
||||
DiscreteRV)
|
||||
from sympy.testing.pytest import slow, nocache_fail, raises, skip
|
||||
from sympy.stats.symbolic_probability import Expectation
|
||||
from sympy.functions.combinatorial.factorials import FallingFactorial
|
||||
|
||||
x = Symbol('x')
|
||||
|
||||
|
||||
def test_PoissonDistribution():
|
||||
l = 3
|
||||
p = PoissonDistribution(l)
|
||||
assert abs(p.cdf(10).evalf() - 1) < .001
|
||||
assert abs(p.cdf(10.4).evalf() - 1) < .001
|
||||
assert p.expectation(x, x) == l
|
||||
assert p.expectation(x**2, x) - p.expectation(x, x)**2 == l
|
||||
|
||||
|
||||
def test_Poisson():
|
||||
l = 3
|
||||
x = Poisson('x', l)
|
||||
assert E(x) == l
|
||||
assert E(2*x) == 2*l
|
||||
assert variance(x) == l
|
||||
assert density(x) == PoissonDistribution(l)
|
||||
assert isinstance(E(x, evaluate=False), Expectation)
|
||||
assert isinstance(E(2*x, evaluate=False), Expectation)
|
||||
# issue 8248
|
||||
assert x.pspace.compute_expectation(1) == 1
|
||||
# issue 27344
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
skip("numpy not installed")
|
||||
y = Poisson('y', np.float64(4.72544290380919e-11))
|
||||
assert E(y) == 4.72544290380919e-11
|
||||
y = Poisson('y', np.float64(4.725442903809197e-11))
|
||||
assert E(y) == 4.725442903809197e-11
|
||||
l2 = 5
|
||||
z = Poisson('z', l2)
|
||||
assert E(z) == l2
|
||||
assert E(FallingFactorial(z, 3)) == l2**3
|
||||
assert E(z**2) == l2 + l2**2
|
||||
|
||||
|
||||
def test_FlorySchulz():
|
||||
a = Symbol("a")
|
||||
z = Symbol("z")
|
||||
x = FlorySchulz('x', a)
|
||||
assert E(x) == (2 - a)/a
|
||||
assert (variance(x) - 2*(1 - a)/a**2).simplify() == S(0)
|
||||
assert density(x)(z) == a**2*z*(1 - a)**(z - 1)
|
||||
|
||||
|
||||
@slow
|
||||
def test_GeometricDistribution():
|
||||
p = S.One / 5
|
||||
d = GeometricDistribution(p)
|
||||
assert d.expectation(x, x) == 1/p
|
||||
assert d.expectation(x**2, x) - d.expectation(x, x)**2 == (1-p)/p**2
|
||||
assert abs(d.cdf(20000).evalf() - 1) < .001
|
||||
assert abs(d.cdf(20000.8).evalf() - 1) < .001
|
||||
G = Geometric('G', p=S(1)/4)
|
||||
assert cdf(G)(S(7)/2) == P(G <= S(7)/2)
|
||||
|
||||
X = Geometric('X', Rational(1, 5))
|
||||
Y = Geometric('Y', Rational(3, 10))
|
||||
assert coskewness(X, X + Y, X + 2*Y).simplify() == sqrt(230)*Rational(81, 1150)
|
||||
|
||||
|
||||
def test_Hermite():
|
||||
a1 = Symbol("a1", positive=True)
|
||||
a2 = Symbol("a2", negative=True)
|
||||
raises(ValueError, lambda: Hermite("H", a1, a2))
|
||||
|
||||
a1 = Symbol("a1", negative=True)
|
||||
a2 = Symbol("a2", positive=True)
|
||||
raises(ValueError, lambda: Hermite("H", a1, a2))
|
||||
|
||||
a1 = Symbol("a1", positive=True)
|
||||
x = Symbol("x")
|
||||
H = Hermite("H", a1, a2)
|
||||
assert moment_generating_function(H)(x) == exp(a1*(exp(x) - 1)
|
||||
+ a2*(exp(2*x) - 1))
|
||||
assert characteristic_function(H)(x) == exp(a1*(exp(I*x) - 1)
|
||||
+ a2*(exp(2*I*x) - 1))
|
||||
assert E(H) == a1 + 2*a2
|
||||
|
||||
H = Hermite("H", a1=5, a2=4)
|
||||
assert density(H)(2) == 33*exp(-9)/2
|
||||
assert E(H) == 13
|
||||
assert variance(H) == 21
|
||||
assert kurtosis(H) == Rational(464,147)
|
||||
assert skewness(H) == 37*sqrt(21)/441
|
||||
|
||||
def test_Logarithmic():
|
||||
p = S.Half
|
||||
x = Logarithmic('x', p)
|
||||
assert E(x) == -p / ((1 - p) * log(1 - p))
|
||||
assert variance(x) == -1/log(2)**2 + 2/log(2)
|
||||
assert E(2*x**2 + 3*x + 4) == 4 + 7 / log(2)
|
||||
assert isinstance(E(x, evaluate=False), Expectation)
|
||||
|
||||
|
||||
@nocache_fail
|
||||
def test_negative_binomial():
|
||||
r = 5
|
||||
p = S.One / 3
|
||||
x = NegativeBinomial('x', r, p)
|
||||
assert E(x) == r * (1 - p) / p
|
||||
# This hangs when run with the cache disabled:
|
||||
assert variance(x) == r * (1 - p) / p**2
|
||||
assert E(x**5 + 2*x + 3) == E(x**5) + 2*E(x) + 3 == Rational(796473, 1)
|
||||
assert isinstance(E(x, evaluate=False), Expectation)
|
||||
|
||||
|
||||
def test_skellam():
|
||||
mu1 = Symbol('mu1')
|
||||
mu2 = Symbol('mu2')
|
||||
z = Symbol('z')
|
||||
X = Skellam('x', mu1, mu2)
|
||||
|
||||
assert density(X)(z) == (mu1/mu2)**(z/2) * \
|
||||
exp(-mu1 - mu2)*besseli(z, 2*sqrt(mu1*mu2))
|
||||
assert skewness(X).expand() == mu1/(mu1*sqrt(mu1 + mu2) + mu2 *
|
||||
sqrt(mu1 + mu2)) - mu2/(mu1*sqrt(mu1 + mu2) + mu2*sqrt(mu1 + mu2))
|
||||
assert variance(X).expand() == mu1 + mu2
|
||||
assert E(X) == mu1 - mu2
|
||||
assert characteristic_function(X)(z) == exp(
|
||||
mu1*exp(I*z) - mu1 - mu2 + mu2*exp(-I*z))
|
||||
assert moment_generating_function(X)(z) == exp(
|
||||
mu1*exp(z) - mu1 - mu2 + mu2*exp(-z))
|
||||
|
||||
|
||||
def test_yule_simon():
|
||||
from sympy.core.singleton import S
|
||||
rho = S(3)
|
||||
x = YuleSimon('x', rho)
|
||||
assert simplify(E(x)) == rho / (rho - 1)
|
||||
assert simplify(variance(x)) == rho**2 / ((rho - 1)**2 * (rho - 2))
|
||||
assert isinstance(E(x, evaluate=False), Expectation)
|
||||
# To test the cdf function
|
||||
assert cdf(x)(x) == Piecewise((-beta(floor(x), 4)*floor(x) + 1, x >= 1), (0, True))
|
||||
|
||||
|
||||
def test_zeta():
|
||||
s = S(5)
|
||||
x = Zeta('x', s)
|
||||
assert E(x) == zeta(s-1) / zeta(s)
|
||||
assert simplify(variance(x)) == (
|
||||
zeta(s) * zeta(s-2) - zeta(s-1)**2) / zeta(s)**2
|
||||
|
||||
|
||||
def test_discrete_probability():
|
||||
X = Geometric('X', Rational(1, 5))
|
||||
Y = Poisson('Y', 4)
|
||||
G = Geometric('e', x)
|
||||
assert P(Eq(X, 3)) == Rational(16, 125)
|
||||
assert P(X < 3) == Rational(9, 25)
|
||||
assert P(X > 3) == Rational(64, 125)
|
||||
assert P(X >= 3) == Rational(16, 25)
|
||||
assert P(X <= 3) == Rational(61, 125)
|
||||
assert P(Ne(X, 3)) == Rational(109, 125)
|
||||
assert P(Eq(Y, 3)) == 32*exp(-4)/3
|
||||
assert P(Y < 3) == 13*exp(-4)
|
||||
assert P(Y > 3).equals(32*(Rational(-71, 32) + 3*exp(4)/32)*exp(-4)/3)
|
||||
assert P(Y >= 3).equals(32*(Rational(-39, 32) + 3*exp(4)/32)*exp(-4)/3)
|
||||
assert P(Y <= 3) == 71*exp(-4)/3
|
||||
assert P(Ne(Y, 3)).equals(
|
||||
13*exp(-4) + 32*(Rational(-71, 32) + 3*exp(4)/32)*exp(-4)/3)
|
||||
assert P(X < S.Infinity) is S.One
|
||||
assert P(X > S.Infinity) is S.Zero
|
||||
assert P(G < 3) == x*(2-x)
|
||||
assert P(Eq(G, 3)) == x*(-x + 1)**2
|
||||
|
||||
|
||||
def test_DiscreteRV():
|
||||
p = S(1)/2
|
||||
x = Symbol('x', integer=True, positive=True)
|
||||
pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution
|
||||
D = DiscreteRV(x, pdf, set=S.Naturals, check=True)
|
||||
assert E(D) == E(Geometric('G', S(1)/2)) == 2
|
||||
assert P(D > 3) == S(1)/8
|
||||
assert D.pspace.domain.set == S.Naturals
|
||||
raises(ValueError, lambda: DiscreteRV(x, x, FiniteSet(*range(4)), check=True))
|
||||
|
||||
# purposeful invalid pmf but it should not raise since check=False
|
||||
# see test_drv_types.test_ContinuousRV for explanation
|
||||
X = DiscreteRV(x, 1/x, S.Naturals)
|
||||
assert P(X < 2) == 1
|
||||
assert E(X) == oo
|
||||
|
||||
def test_precomputed_characteristic_functions():
|
||||
import mpmath
|
||||
|
||||
def test_cf(dist, support_lower_limit, support_upper_limit):
|
||||
pdf = density(dist)
|
||||
t = S('t')
|
||||
x = S('x')
|
||||
|
||||
# first function is the hardcoded CF of the distribution
|
||||
cf1 = lambdify([t], characteristic_function(dist)(t), 'mpmath')
|
||||
|
||||
# second function is the Fourier transform of the density function
|
||||
f = lambdify([x, t], pdf(x)*exp(I*x*t), 'mpmath')
|
||||
cf2 = lambda t: mpmath.nsum(lambda x: f(x, t), [
|
||||
support_lower_limit, support_upper_limit], maxdegree=10)
|
||||
|
||||
# compare the two functions at various points
|
||||
for test_point in [2, 5, 8, 11]:
|
||||
n1 = cf1(test_point)
|
||||
n2 = cf2(test_point)
|
||||
|
||||
assert abs(re(n1) - re(n2)) < 1e-12
|
||||
assert abs(im(n1) - im(n2)) < 1e-12
|
||||
|
||||
test_cf(Geometric('g', Rational(1, 3)), 1, mpmath.inf)
|
||||
test_cf(Logarithmic('l', Rational(1, 5)), 1, mpmath.inf)
|
||||
test_cf(NegativeBinomial('n', 5, Rational(1, 7)), 0, mpmath.inf)
|
||||
test_cf(Poisson('p', 5), 0, mpmath.inf)
|
||||
test_cf(YuleSimon('y', 5), 1, mpmath.inf)
|
||||
test_cf(Zeta('z', 5), 1, mpmath.inf)
|
||||
|
||||
|
||||
def test_moment_generating_functions():
|
||||
t = S('t')
|
||||
|
||||
geometric_mgf = moment_generating_function(Geometric('g', S.Half))(t)
|
||||
assert geometric_mgf.diff(t).subs(t, 0) == 2
|
||||
|
||||
logarithmic_mgf = moment_generating_function(Logarithmic('l', S.Half))(t)
|
||||
assert logarithmic_mgf.diff(t).subs(t, 0) == 1/log(2)
|
||||
|
||||
negative_binomial_mgf = moment_generating_function(
|
||||
NegativeBinomial('n', 5, Rational(1, 3)))(t)
|
||||
assert negative_binomial_mgf.diff(t).subs(t, 0) == Rational(10, 1)
|
||||
|
||||
poisson_mgf = moment_generating_function(Poisson('p', 5))(t)
|
||||
assert poisson_mgf.diff(t).subs(t, 0) == 5
|
||||
|
||||
skellam_mgf = moment_generating_function(Skellam('s', 1, 1))(t)
|
||||
assert skellam_mgf.diff(t).subs(
|
||||
t, 2) == (-exp(-2) + exp(2))*exp(-2 + exp(-2) + exp(2))
|
||||
|
||||
yule_simon_mgf = moment_generating_function(YuleSimon('y', 3))(t)
|
||||
assert simplify(yule_simon_mgf.diff(t).subs(t, 0)) == Rational(3, 2)
|
||||
|
||||
zeta_mgf = moment_generating_function(Zeta('z', 5))(t)
|
||||
assert zeta_mgf.diff(t).subs(t, 0) == pi**4/(90*zeta(5))
|
||||
|
||||
|
||||
def test_Or():
|
||||
X = Geometric('X', S.Half)
|
||||
assert P(Or(X < 3, X > 4)) == Rational(13, 16)
|
||||
assert P(Or(X > 2, X > 1)) == P(X > 1)
|
||||
assert P(Or(X >= 3, X < 3)) == 1
|
||||
|
||||
|
||||
def test_where():
|
||||
X = Geometric('X', Rational(1, 5))
|
||||
Y = Poisson('Y', 4)
|
||||
assert where(X**2 > 4).set == Range(3, S.Infinity, 1)
|
||||
assert where(X**2 >= 4).set == Range(2, S.Infinity, 1)
|
||||
assert where(Y**2 < 9).set == Range(0, 3, 1)
|
||||
assert where(Y**2 <= 9).set == Range(0, 4, 1)
|
||||
|
||||
|
||||
def test_conditional():
|
||||
X = Geometric('X', Rational(2, 3))
|
||||
Y = Poisson('Y', 3)
|
||||
assert P(X > 2, X > 3) == 1
|
||||
assert P(X > 3, X > 2) == Rational(1, 3)
|
||||
assert P(Y > 2, Y < 2) == 0
|
||||
assert P(Eq(Y, 3), Y >= 0) == 9*exp(-3)/2
|
||||
assert P(Eq(Y, 3), Eq(Y, 2)) == 0
|
||||
assert P(X < 2, Eq(X, 2)) == 0
|
||||
assert P(X > 2, Eq(X, 3)) == 1
|
||||
|
||||
|
||||
def test_product_spaces():
|
||||
X1 = Geometric('X1', S.Half)
|
||||
X2 = Geometric('X2', Rational(1, 3))
|
||||
assert str(P(X1 + X2 < 3).rewrite(Sum)) == (
|
||||
"Sum(Piecewise((1/(4*2**n), n >= -1), (0, True)), (n, -oo, -1))/3")
|
||||
assert str(P(X1 + X2 > 3).rewrite(Sum)) == (
|
||||
'Sum(Piecewise((2**(X2 - n - 2)*(2/3)**(X2 - 1)/6, '
|
||||
'X2 - n <= 2), (0, True)), (X2, 1, oo), (n, 1, oo))')
|
||||
assert P(Eq(X1 + X2, 3)) == Rational(1, 12)
|
||||
@@ -0,0 +1,60 @@
|
||||
from sympy.core.function import Function
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.stats.error_prop import variance_prop
|
||||
from sympy.stats.symbolic_probability import (RandomSymbol, Variance,
|
||||
Covariance)
|
||||
|
||||
|
||||
def test_variance_prop():
|
||||
x, y, z = symbols('x y z')
|
||||
phi, t = consts = symbols('phi t')
|
||||
a = RandomSymbol(x)
|
||||
var_x = Variance(a)
|
||||
var_y = Variance(RandomSymbol(y))
|
||||
var_z = Variance(RandomSymbol(z))
|
||||
f = Function('f')(x)
|
||||
cases = {
|
||||
x + y: var_x + var_y,
|
||||
a + y: var_x + var_y,
|
||||
x + y + z: var_x + var_y + var_z,
|
||||
2*x: 4*var_x,
|
||||
x*y: var_x*y**2 + var_y*x**2,
|
||||
1/x: var_x/x**4,
|
||||
x/y: (var_x*y**2 + var_y*x**2)/y**4,
|
||||
exp(x): var_x*exp(2*x),
|
||||
exp(2*x): 4*var_x*exp(4*x),
|
||||
exp(-x*t): t**2*var_x*exp(-2*t*x),
|
||||
f: Variance(f),
|
||||
}
|
||||
for inp, out in cases.items():
|
||||
obs = variance_prop(inp, consts=consts)
|
||||
assert out == obs
|
||||
|
||||
def test_variance_prop_with_covar():
|
||||
x, y, z = symbols('x y z')
|
||||
phi, t = consts = symbols('phi t')
|
||||
a = RandomSymbol(x)
|
||||
var_x = Variance(a)
|
||||
b = RandomSymbol(y)
|
||||
var_y = Variance(b)
|
||||
c = RandomSymbol(z)
|
||||
var_z = Variance(c)
|
||||
covar_x_y = Covariance(a, b)
|
||||
covar_x_z = Covariance(a, c)
|
||||
covar_y_z = Covariance(b, c)
|
||||
cases = {
|
||||
x + y: var_x + var_y + 2*covar_x_y,
|
||||
a + y: var_x + var_y + 2*covar_x_y,
|
||||
x + y + z: var_x + var_y + var_z + \
|
||||
2*covar_x_y + 2*covar_x_z + 2*covar_y_z,
|
||||
2*x: 4*var_x,
|
||||
x*y: var_x*y**2 + var_y*x**2 + 2*covar_x_y/(x*y),
|
||||
1/x: var_x/x**4,
|
||||
exp(x): var_x*exp(2*x),
|
||||
exp(2*x): 4*var_x*exp(4*x),
|
||||
exp(-x*t): t**2*var_x*exp(-2*t*x),
|
||||
}
|
||||
for inp, out in cases.items():
|
||||
obs = variance_prop(inp, consts=consts, include_covar=True)
|
||||
assert out == obs
|
||||
@@ -0,0 +1,509 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.containers import (Dict, Tuple)
|
||||
from sympy.core.function import Function
|
||||
from sympy.core.numbers import (I, Rational, nan)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.combinatorial.factorials import binomial
|
||||
from sympy.functions.combinatorial.numbers import harmonic
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.elementary.trigonometric import cos
|
||||
from sympy.functions.special.beta_functions import beta
|
||||
from sympy.logic.boolalg import (And, Or)
|
||||
from sympy.polys.polytools import cancel
|
||||
from sympy.sets.sets import FiniteSet
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.stats import (DiscreteUniform, Die, Bernoulli, Coin, Binomial, BetaBinomial,
|
||||
Hypergeometric, Rademacher, IdealSoliton, RobustSoliton, P, E, variance,
|
||||
covariance, skewness, density, where, FiniteRV, pspace, cdf,
|
||||
correlation, moment, cmoment, smoment, characteristic_function,
|
||||
moment_generating_function, quantile, kurtosis, median, coskewness)
|
||||
from sympy.stats.frv_types import DieDistribution, BinomialDistribution, \
|
||||
HypergeometricDistribution
|
||||
from sympy.stats.rv import Density
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def BayesTest(A, B):
|
||||
assert P(A, B) == P(And(A, B)) / P(B)
|
||||
assert P(A, B) == P(B, A) * P(A) / P(B)
|
||||
|
||||
|
||||
def test_discreteuniform():
|
||||
# Symbolic
|
||||
a, b, c, t = symbols('a b c t')
|
||||
X = DiscreteUniform('X', [a, b, c])
|
||||
|
||||
assert E(X) == (a + b + c)/3
|
||||
assert simplify(variance(X)
|
||||
- ((a**2 + b**2 + c**2)/3 - (a/3 + b/3 + c/3)**2)) == 0
|
||||
assert P(Eq(X, a)) == P(Eq(X, b)) == P(Eq(X, c)) == S('1/3')
|
||||
|
||||
Y = DiscreteUniform('Y', range(-5, 5))
|
||||
|
||||
# Numeric
|
||||
assert E(Y) == S('-1/2')
|
||||
assert variance(Y) == S('33/4')
|
||||
assert median(Y) == FiniteSet(-1, 0)
|
||||
|
||||
for x in range(-5, 5):
|
||||
assert P(Eq(Y, x)) == S('1/10')
|
||||
assert P(Y <= x) == S(x + 6)/10
|
||||
assert P(Y >= x) == S(5 - x)/10
|
||||
|
||||
assert dict(density(Die('D', 6)).items()) == \
|
||||
dict(density(DiscreteUniform('U', range(1, 7))).items())
|
||||
|
||||
assert characteristic_function(X)(t) == exp(I*a*t)/3 + exp(I*b*t)/3 + exp(I*c*t)/3
|
||||
assert moment_generating_function(X)(t) == exp(a*t)/3 + exp(b*t)/3 + exp(c*t)/3
|
||||
# issue 18611
|
||||
raises(ValueError, lambda: DiscreteUniform('Z', [a, a, a, b, b, c]))
|
||||
|
||||
def test_dice():
|
||||
# TODO: Make iid method!
|
||||
X, Y, Z = Die('X', 6), Die('Y', 6), Die('Z', 6)
|
||||
a, b, t, p = symbols('a b t p')
|
||||
|
||||
assert E(X) == 3 + S.Half
|
||||
assert variance(X) == Rational(35, 12)
|
||||
assert E(X + Y) == 7
|
||||
assert E(X + X) == 7
|
||||
assert E(a*X + b) == a*E(X) + b
|
||||
assert variance(X + Y) == variance(X) + variance(Y) == cmoment(X + Y, 2)
|
||||
assert variance(X + X) == 4 * variance(X) == cmoment(X + X, 2)
|
||||
assert cmoment(X, 0) == 1
|
||||
assert cmoment(4*X, 3) == 64*cmoment(X, 3)
|
||||
assert covariance(X, Y) is S.Zero
|
||||
assert covariance(X, X + Y) == variance(X)
|
||||
assert density(Eq(cos(X*S.Pi), 1))[True] == S.Half
|
||||
assert correlation(X, Y) == 0
|
||||
assert correlation(X, Y) == correlation(Y, X)
|
||||
assert smoment(X + Y, 3) == skewness(X + Y)
|
||||
assert smoment(X + Y, 4) == kurtosis(X + Y)
|
||||
assert smoment(X, 0) == 1
|
||||
assert P(X > 3) == S.Half
|
||||
assert P(2*X > 6) == S.Half
|
||||
assert P(X > Y) == Rational(5, 12)
|
||||
assert P(Eq(X, Y)) == P(Eq(X, 1))
|
||||
|
||||
assert E(X, X > 3) == 5 == moment(X, 1, 0, X > 3)
|
||||
assert E(X, Y > 3) == E(X) == moment(X, 1, 0, Y > 3)
|
||||
assert E(X + Y, Eq(X, Y)) == E(2*X)
|
||||
assert moment(X, 0) == 1
|
||||
assert moment(5*X, 2) == 25*moment(X, 2)
|
||||
assert quantile(X)(p) == Piecewise((nan, (p > 1) | (p < 0)),\
|
||||
(S.One, p <= Rational(1, 6)), (S(2), p <= Rational(1, 3)), (S(3), p <= S.Half),\
|
||||
(S(4), p <= Rational(2, 3)), (S(5), p <= Rational(5, 6)), (S(6), p <= 1))
|
||||
|
||||
assert P(X > 3, X > 3) is S.One
|
||||
assert P(X > Y, Eq(Y, 6)) is S.Zero
|
||||
assert P(Eq(X + Y, 12)) == Rational(1, 36)
|
||||
assert P(Eq(X + Y, 12), Eq(X, 6)) == Rational(1, 6)
|
||||
|
||||
assert density(X + Y) == density(Y + Z) != density(X + X)
|
||||
d = density(2*X + Y**Z)
|
||||
assert d[S(22)] == Rational(1, 108) and d[S(4100)] == Rational(1, 216) and S(3130) not in d
|
||||
|
||||
assert pspace(X).domain.as_boolean() == Or(
|
||||
*[Eq(X.symbol, i) for i in [1, 2, 3, 4, 5, 6]])
|
||||
|
||||
assert where(X > 3).set == FiniteSet(4, 5, 6)
|
||||
|
||||
assert characteristic_function(X)(t) == exp(6*I*t)/6 + exp(5*I*t)/6 + exp(4*I*t)/6 + exp(3*I*t)/6 + exp(2*I*t)/6 + exp(I*t)/6
|
||||
assert moment_generating_function(X)(t) == exp(6*t)/6 + exp(5*t)/6 + exp(4*t)/6 + exp(3*t)/6 + exp(2*t)/6 + exp(t)/6
|
||||
assert median(X) == FiniteSet(3, 4)
|
||||
D = Die('D', 7)
|
||||
assert median(D) == FiniteSet(4)
|
||||
# Bayes test for die
|
||||
BayesTest(X > 3, X + Y < 5)
|
||||
BayesTest(Eq(X - Y, Z), Z > Y)
|
||||
BayesTest(X > 3, X > 2)
|
||||
|
||||
# arg test for die
|
||||
raises(ValueError, lambda: Die('X', -1)) # issue 8105: negative sides.
|
||||
raises(ValueError, lambda: Die('X', 0))
|
||||
raises(ValueError, lambda: Die('X', 1.5)) # issue 8103: non integer sides.
|
||||
|
||||
# symbolic test for die
|
||||
n, k = symbols('n, k', positive=True)
|
||||
D = Die('D', n)
|
||||
dens = density(D).dict
|
||||
assert dens == Density(DieDistribution(n))
|
||||
assert set(dens.subs(n, 4).doit().keys()) == {1, 2, 3, 4}
|
||||
assert set(dens.subs(n, 4).doit().values()) == {Rational(1, 4)}
|
||||
k = Dummy('k', integer=True)
|
||||
assert E(D).dummy_eq(
|
||||
Sum(Piecewise((k/n, k <= n), (0, True)), (k, 1, n)))
|
||||
assert variance(D).subs(n, 6).doit() == Rational(35, 12)
|
||||
|
||||
ki = Dummy('ki')
|
||||
cumuf = cdf(D)(k)
|
||||
assert cumuf.dummy_eq(
|
||||
Sum(Piecewise((1/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, k)))
|
||||
assert cumuf.subs({n: 6, k: 2}).doit() == Rational(1, 3)
|
||||
|
||||
t = Dummy('t')
|
||||
cf = characteristic_function(D)(t)
|
||||
assert cf.dummy_eq(
|
||||
Sum(Piecewise((exp(ki*I*t)/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, n)))
|
||||
assert cf.subs(n, 3).doit() == exp(3*I*t)/3 + exp(2*I*t)/3 + exp(I*t)/3
|
||||
mgf = moment_generating_function(D)(t)
|
||||
assert mgf.dummy_eq(
|
||||
Sum(Piecewise((exp(ki*t)/n, (ki >= 1) & (ki <= n)), (0, True)), (ki, 1, n)))
|
||||
assert mgf.subs(n, 3).doit() == exp(3*t)/3 + exp(2*t)/3 + exp(t)/3
|
||||
|
||||
def test_given():
|
||||
X = Die('X', 6)
|
||||
assert density(X, X > 5) == {S(6): S.One}
|
||||
assert where(X > 2, X > 5).as_boolean() == Eq(X.symbol, 6)
|
||||
|
||||
|
||||
def test_domains():
|
||||
X, Y = Die('x', 6), Die('y', 6)
|
||||
x, y = X.symbol, Y.symbol
|
||||
# Domains
|
||||
d = where(X > Y)
|
||||
assert d.condition == (x > y)
|
||||
d = where(And(X > Y, Y > 3))
|
||||
assert d.as_boolean() == Or(And(Eq(x, 5), Eq(y, 4)), And(Eq(x, 6),
|
||||
Eq(y, 5)), And(Eq(x, 6), Eq(y, 4)))
|
||||
assert len(d.elements) == 3
|
||||
|
||||
assert len(pspace(X + Y).domain.elements) == 36
|
||||
|
||||
Z = Die('x', 4)
|
||||
|
||||
raises(ValueError, lambda: P(X > Z)) # Two domains with same internal symbol
|
||||
|
||||
assert pspace(X + Y).domain.set == FiniteSet(1, 2, 3, 4, 5, 6)**2
|
||||
|
||||
assert where(X > 3).set == FiniteSet(4, 5, 6)
|
||||
assert X.pspace.domain.dict == FiniteSet(
|
||||
*[Dict({X.symbol: i}) for i in range(1, 7)])
|
||||
|
||||
assert where(X > Y).dict == FiniteSet(*[Dict({X.symbol: i, Y.symbol: j})
|
||||
for i in range(1, 7) for j in range(1, 7) if i > j])
|
||||
|
||||
def test_bernoulli():
|
||||
p, a, b, t = symbols('p a b t')
|
||||
X = Bernoulli('B', p, a, b)
|
||||
|
||||
assert E(X) == a*p + b*(-p + 1)
|
||||
assert density(X)[a] == p
|
||||
assert density(X)[b] == 1 - p
|
||||
assert characteristic_function(X)(t) == p * exp(I * a * t) + (-p + 1) * exp(I * b * t)
|
||||
assert moment_generating_function(X)(t) == p * exp(a * t) + (-p + 1) * exp(b * t)
|
||||
|
||||
X = Bernoulli('B', p, 1, 0)
|
||||
z = Symbol("z")
|
||||
|
||||
assert E(X) == p
|
||||
assert simplify(variance(X)) == p*(1 - p)
|
||||
assert E(a*X + b) == a*E(X) + b
|
||||
assert simplify(variance(a*X + b)) == simplify(a**2 * variance(X))
|
||||
assert quantile(X)(z) == Piecewise((nan, (z > 1) | (z < 0)), (0, z <= 1 - p), (1, z <= 1))
|
||||
Y = Bernoulli('Y', Rational(1, 2))
|
||||
assert median(Y) == FiniteSet(0, 1)
|
||||
Z = Bernoulli('Z', Rational(2, 3))
|
||||
assert median(Z) == FiniteSet(1)
|
||||
raises(ValueError, lambda: Bernoulli('B', 1.5))
|
||||
raises(ValueError, lambda: Bernoulli('B', -0.5))
|
||||
|
||||
#issue 8248
|
||||
assert X.pspace.compute_expectation(1) == 1
|
||||
|
||||
p = Rational(1, 5)
|
||||
X = Binomial('X', 5, p)
|
||||
Y = Binomial('Y', 7, 2*p)
|
||||
Z = Binomial('Z', 9, 3*p)
|
||||
assert coskewness(Y + Z, X + Y, X + Z).simplify() == 0
|
||||
assert coskewness(Y + 2*X + Z, X + 2*Y + Z, X + 2*Z + Y).simplify() == \
|
||||
sqrt(1529)*Rational(12, 16819)
|
||||
assert coskewness(Y + 2*X + Z, X + 2*Y + Z, X + 2*Z + Y, X < 2).simplify() \
|
||||
== -sqrt(357451121)*Rational(2812, 4646864573)
|
||||
|
||||
def test_cdf():
|
||||
D = Die('D', 6)
|
||||
o = S.One
|
||||
|
||||
assert cdf(
|
||||
D) == sympify({1: o/6, 2: o/3, 3: o/2, 4: 2*o/3, 5: 5*o/6, 6: o})
|
||||
|
||||
|
||||
def test_coins():
|
||||
C, D = Coin('C'), Coin('D')
|
||||
H, T = symbols('H, T')
|
||||
assert P(Eq(C, D)) == S.Half
|
||||
assert density(Tuple(C, D)) == {(H, H): Rational(1, 4), (H, T): Rational(1, 4),
|
||||
(T, H): Rational(1, 4), (T, T): Rational(1, 4)}
|
||||
assert dict(density(C).items()) == {H: S.Half, T: S.Half}
|
||||
|
||||
F = Coin('F', Rational(1, 10))
|
||||
assert P(Eq(F, H)) == Rational(1, 10)
|
||||
|
||||
d = pspace(C).domain
|
||||
|
||||
assert d.as_boolean() == Or(Eq(C.symbol, H), Eq(C.symbol, T))
|
||||
|
||||
raises(ValueError, lambda: P(C > D)) # Can't intelligently compare H to T
|
||||
|
||||
def test_binomial_verify_parameters():
|
||||
raises(ValueError, lambda: Binomial('b', .2, .5))
|
||||
raises(ValueError, lambda: Binomial('b', 3, 1.5))
|
||||
|
||||
def test_binomial_numeric():
|
||||
nvals = range(5)
|
||||
pvals = [0, Rational(1, 4), S.Half, Rational(3, 4), 1]
|
||||
|
||||
for n in nvals:
|
||||
for p in pvals:
|
||||
X = Binomial('X', n, p)
|
||||
assert E(X) == n*p
|
||||
assert variance(X) == n*p*(1 - p)
|
||||
if n > 0 and 0 < p < 1:
|
||||
assert skewness(X) == (1 - 2*p)/sqrt(n*p*(1 - p))
|
||||
assert kurtosis(X) == 3 + (1 - 6*p*(1 - p))/(n*p*(1 - p))
|
||||
for k in range(n + 1):
|
||||
assert P(Eq(X, k)) == binomial(n, k)*p**k*(1 - p)**(n - k)
|
||||
|
||||
def test_binomial_quantile():
|
||||
X = Binomial('X', 50, S.Half)
|
||||
assert quantile(X)(0.95) == S(31)
|
||||
assert median(X) == FiniteSet(25)
|
||||
|
||||
X = Binomial('X', 5, S.Half)
|
||||
p = Symbol("p", positive=True)
|
||||
assert quantile(X)(p) == Piecewise((nan, p > S.One), (S.Zero, p <= Rational(1, 32)),\
|
||||
(S.One, p <= Rational(3, 16)), (S(2), p <= S.Half), (S(3), p <= Rational(13, 16)),\
|
||||
(S(4), p <= Rational(31, 32)), (S(5), p <= S.One))
|
||||
assert median(X) == FiniteSet(2, 3)
|
||||
|
||||
|
||||
def test_binomial_symbolic():
|
||||
n = 2
|
||||
p = symbols('p', positive=True)
|
||||
X = Binomial('X', n, p)
|
||||
t = Symbol('t')
|
||||
|
||||
assert simplify(E(X)) == n*p == simplify(moment(X, 1))
|
||||
assert simplify(variance(X)) == n*p*(1 - p) == simplify(cmoment(X, 2))
|
||||
assert cancel(skewness(X) - (1 - 2*p)/sqrt(n*p*(1 - p))) == 0
|
||||
assert cancel((kurtosis(X)) - (3 + (1 - 6*p*(1 - p))/(n*p*(1 - p)))) == 0
|
||||
assert characteristic_function(X)(t) == p ** 2 * exp(2 * I * t) + 2 * p * (-p + 1) * exp(I * t) + (-p + 1) ** 2
|
||||
assert moment_generating_function(X)(t) == p ** 2 * exp(2 * t) + 2 * p * (-p + 1) * exp(t) + (-p + 1) ** 2
|
||||
|
||||
# Test ability to change success/failure winnings
|
||||
H, T = symbols('H T')
|
||||
Y = Binomial('Y', n, p, succ=H, fail=T)
|
||||
assert simplify(E(Y) - (n*(H*p + T*(1 - p)))) == 0
|
||||
|
||||
# test symbolic dimensions
|
||||
n = symbols('n')
|
||||
B = Binomial('B', n, p)
|
||||
raises(NotImplementedError, lambda: P(B > 2))
|
||||
assert density(B).dict == Density(BinomialDistribution(n, p, 1, 0))
|
||||
assert set(density(B).dict.subs(n, 4).doit().keys()) == \
|
||||
{S.Zero, S.One, S(2), S(3), S(4)}
|
||||
assert set(density(B).dict.subs(n, 4).doit().values()) == \
|
||||
{(1 - p)**4, 4*p*(1 - p)**3, 6*p**2*(1 - p)**2, 4*p**3*(1 - p), p**4}
|
||||
k = Dummy('k', integer=True)
|
||||
assert E(B > 2).dummy_eq(
|
||||
Sum(Piecewise((k*p**k*(1 - p)**(-k + n)*binomial(n, k), (k >= 0)
|
||||
& (k <= n) & (k > 2)), (0, True)), (k, 0, n)))
|
||||
|
||||
def test_beta_binomial():
|
||||
# verify parameters
|
||||
raises(ValueError, lambda: BetaBinomial('b', .2, 1, 2))
|
||||
raises(ValueError, lambda: BetaBinomial('b', 2, -1, 2))
|
||||
raises(ValueError, lambda: BetaBinomial('b', 2, 1, -2))
|
||||
assert BetaBinomial('b', 2, 1, 1)
|
||||
|
||||
# test numeric values
|
||||
nvals = range(1,5)
|
||||
alphavals = [Rational(1, 4), S.Half, Rational(3, 4), 1, 10]
|
||||
betavals = [Rational(1, 4), S.Half, Rational(3, 4), 1, 10]
|
||||
|
||||
for n in nvals:
|
||||
for a in alphavals:
|
||||
for b in betavals:
|
||||
X = BetaBinomial('X', n, a, b)
|
||||
assert E(X) == moment(X, 1)
|
||||
assert variance(X) == cmoment(X, 2)
|
||||
|
||||
# test symbolic
|
||||
n, a, b = symbols('a b n')
|
||||
assert BetaBinomial('x', n, a, b)
|
||||
n = 2 # Because we're using for loops, can't do symbolic n
|
||||
a, b = symbols('a b', positive=True)
|
||||
X = BetaBinomial('X', n, a, b)
|
||||
t = Symbol('t')
|
||||
|
||||
assert E(X).expand() == moment(X, 1).expand()
|
||||
assert variance(X).expand() == cmoment(X, 2).expand()
|
||||
assert skewness(X) == smoment(X, 3)
|
||||
assert characteristic_function(X)(t) == exp(2*I*t)*beta(a + 2, b)/beta(a, b) +\
|
||||
2*exp(I*t)*beta(a + 1, b + 1)/beta(a, b) + beta(a, b + 2)/beta(a, b)
|
||||
assert moment_generating_function(X)(t) == exp(2*t)*beta(a + 2, b)/beta(a, b) +\
|
||||
2*exp(t)*beta(a + 1, b + 1)/beta(a, b) + beta(a, b + 2)/beta(a, b)
|
||||
|
||||
def test_hypergeometric_numeric():
|
||||
for N in range(1, 5):
|
||||
for m in range(0, N + 1):
|
||||
for n in range(1, N + 1):
|
||||
X = Hypergeometric('X', N, m, n)
|
||||
N, m, n = map(sympify, (N, m, n))
|
||||
assert sum(density(X).values()) == 1
|
||||
assert E(X) == n * m / N
|
||||
if N > 1:
|
||||
assert variance(X) == n*(m/N)*(N - m)/N*(N - n)/(N - 1)
|
||||
# Only test for skewness when defined
|
||||
if N > 2 and 0 < m < N and n < N:
|
||||
assert skewness(X) == simplify((N - 2*m)*sqrt(N - 1)*(N - 2*n)
|
||||
/ (sqrt(n*m*(N - m)*(N - n))*(N - 2)))
|
||||
|
||||
def test_hypergeometric_symbolic():
|
||||
N, m, n = symbols('N, m, n')
|
||||
H = Hypergeometric('H', N, m, n)
|
||||
dens = density(H).dict
|
||||
expec = E(H > 2)
|
||||
assert dens == Density(HypergeometricDistribution(N, m, n))
|
||||
assert dens.subs(N, 5).doit() == Density(HypergeometricDistribution(5, m, n))
|
||||
assert set(dens.subs({N: 3, m: 2, n: 1}).doit().keys()) == {S.Zero, S.One}
|
||||
assert set(dens.subs({N: 3, m: 2, n: 1}).doit().values()) == {Rational(1, 3), Rational(2, 3)}
|
||||
k = Dummy('k', integer=True)
|
||||
assert expec.dummy_eq(
|
||||
Sum(Piecewise((k*binomial(m, k)*binomial(N - m, -k + n)
|
||||
/binomial(N, n), k > 2), (0, True)), (k, 0, n)))
|
||||
|
||||
def test_rademacher():
|
||||
X = Rademacher('X')
|
||||
t = Symbol('t')
|
||||
|
||||
assert E(X) == 0
|
||||
assert variance(X) == 1
|
||||
assert density(X)[-1] == S.Half
|
||||
assert density(X)[1] == S.Half
|
||||
assert characteristic_function(X)(t) == exp(I*t)/2 + exp(-I*t)/2
|
||||
assert moment_generating_function(X)(t) == exp(t) / 2 + exp(-t) / 2
|
||||
|
||||
def test_ideal_soliton():
|
||||
raises(ValueError, lambda : IdealSoliton('sol', -12))
|
||||
raises(ValueError, lambda : IdealSoliton('sol', 13.2))
|
||||
raises(ValueError, lambda : IdealSoliton('sol', 0))
|
||||
f = Function('f')
|
||||
raises(ValueError, lambda : density(IdealSoliton('sol', 10)).pmf(f))
|
||||
|
||||
k = Symbol('k', integer=True, positive=True)
|
||||
x = Symbol('x', integer=True, positive=True)
|
||||
t = Symbol('t')
|
||||
sol = IdealSoliton('sol', k)
|
||||
assert density(sol).low == S.One
|
||||
assert density(sol).high == k
|
||||
assert density(sol).dict == Density(density(sol))
|
||||
assert density(sol).pmf(x) == Piecewise((1/k, Eq(x, 1)), (1/(x*(x - 1)), k >= x), (0, True))
|
||||
|
||||
k_vals = [5, 20, 50, 100, 1000]
|
||||
for i in k_vals:
|
||||
assert E(sol.subs(k, i)) == harmonic(i) == moment(sol.subs(k, i), 1)
|
||||
assert variance(sol.subs(k, i)) == (i - 1) + harmonic(i) - harmonic(i)**2 == cmoment(sol.subs(k, i),2)
|
||||
assert skewness(sol.subs(k, i)) == smoment(sol.subs(k, i), 3)
|
||||
assert kurtosis(sol.subs(k, i)) == smoment(sol.subs(k, i), 4)
|
||||
|
||||
assert exp(I*t)/10 + Sum(exp(I*t*x)/(x*x - x), (x, 2, k)).subs(k, 10).doit() == characteristic_function(sol.subs(k, 10))(t)
|
||||
assert exp(t)/10 + Sum(exp(t*x)/(x*x - x), (x, 2, k)).subs(k, 10).doit() == moment_generating_function(sol.subs(k, 10))(t)
|
||||
|
||||
def test_robust_soliton():
|
||||
raises(ValueError, lambda : RobustSoliton('robSol', -12, 0.1, 0.02))
|
||||
raises(ValueError, lambda : RobustSoliton('robSol', 13, 1.89, 0.1))
|
||||
raises(ValueError, lambda : RobustSoliton('robSol', 15, 0.6, -2.31))
|
||||
f = Function('f')
|
||||
raises(ValueError, lambda : density(RobustSoliton('robSol', 15, 0.6, 0.1)).pmf(f))
|
||||
|
||||
k = Symbol('k', integer=True, positive=True)
|
||||
delta = Symbol('delta', positive=True)
|
||||
c = Symbol('c', positive=True)
|
||||
robSol = RobustSoliton('robSol', k, delta, c)
|
||||
assert density(robSol).low == 1
|
||||
assert density(robSol).high == k
|
||||
|
||||
k_vals = [10, 20, 50]
|
||||
delta_vals = [0.2, 0.4, 0.6]
|
||||
c_vals = [0.01, 0.03, 0.05]
|
||||
for x in k_vals:
|
||||
for y in delta_vals:
|
||||
for z in c_vals:
|
||||
assert E(robSol.subs({k: x, delta: y, c: z})) == moment(robSol.subs({k: x, delta: y, c: z}), 1)
|
||||
assert variance(robSol.subs({k: x, delta: y, c: z})) == cmoment(robSol.subs({k: x, delta: y, c: z}), 2)
|
||||
assert skewness(robSol.subs({k: x, delta: y, c: z})) == smoment(robSol.subs({k: x, delta: y, c: z}), 3)
|
||||
assert kurtosis(robSol.subs({k: x, delta: y, c: z})) == smoment(robSol.subs({k: x, delta: y, c: z}), 4)
|
||||
|
||||
def test_FiniteRV():
|
||||
F = FiniteRV('F', {1: S.Half, 2: Rational(1, 4), 3: Rational(1, 4)}, check=True)
|
||||
p = Symbol("p", positive=True)
|
||||
|
||||
assert dict(density(F).items()) == {S.One: S.Half, S(2): Rational(1, 4), S(3): Rational(1, 4)}
|
||||
assert P(F >= 2) == S.Half
|
||||
assert quantile(F)(p) == Piecewise((nan, p > S.One), (S.One, p <= S.Half),\
|
||||
(S(2), p <= Rational(3, 4)),(S(3), True))
|
||||
|
||||
assert pspace(F).domain.as_boolean() == Or(
|
||||
*[Eq(F.symbol, i) for i in [1, 2, 3]])
|
||||
|
||||
assert F.pspace.domain.set == FiniteSet(1, 2, 3)
|
||||
raises(ValueError, lambda: FiniteRV('F', {1: S.Half, 2: S.Half, 3: S.Half}, check=True))
|
||||
raises(ValueError, lambda: FiniteRV('F', {1: S.Half, 2: Rational(-1, 2), 3: S.One}, check=True))
|
||||
raises(ValueError, lambda: FiniteRV('F', {1: S.One, 2: Rational(3, 2), 3: S.Zero,\
|
||||
4: Rational(-1, 2), 5: Rational(-3, 4), 6: Rational(-1, 4)}, check=True))
|
||||
|
||||
# purposeful invalid pmf but it should not raise since check=False
|
||||
# see test_drv_types.test_ContinuousRV for explanation
|
||||
X = FiniteRV('X', {1: 1, 2: 2})
|
||||
assert E(X) == 5
|
||||
assert P(X <= 2) + P(X > 2) != 1
|
||||
|
||||
def test_density_call():
|
||||
from sympy.abc import p
|
||||
x = Bernoulli('x', p)
|
||||
d = density(x)
|
||||
assert d(0) == 1 - p
|
||||
assert d(S.Zero) == 1 - p
|
||||
assert d(5) == 0
|
||||
|
||||
assert 0 in d
|
||||
assert 5 not in d
|
||||
assert d(S.Zero) == d[S.Zero]
|
||||
|
||||
|
||||
def test_DieDistribution():
|
||||
from sympy.abc import x
|
||||
X = DieDistribution(6)
|
||||
assert X.pmf(S.Half) is S.Zero
|
||||
assert X.pmf(x).subs({x: 1}).doit() == Rational(1, 6)
|
||||
assert X.pmf(x).subs({x: 7}).doit() == 0
|
||||
assert X.pmf(x).subs({x: -1}).doit() == 0
|
||||
assert X.pmf(x).subs({x: Rational(1, 3)}).doit() == 0
|
||||
raises(ValueError, lambda: X.pmf(Matrix([0, 0])))
|
||||
raises(ValueError, lambda: X.pmf(x**2 - 1))
|
||||
|
||||
def test_FinitePSpace():
|
||||
X = Die('X', 6)
|
||||
space = pspace(X)
|
||||
assert space.density == DieDistribution(6)
|
||||
|
||||
def test_symbolic_conditions():
|
||||
B = Bernoulli('B', Rational(1, 4))
|
||||
D = Die('D', 4)
|
||||
b, n = symbols('b, n')
|
||||
Y = P(Eq(B, b))
|
||||
Z = E(D > n)
|
||||
assert Y == \
|
||||
Piecewise((Rational(1, 4), Eq(b, 1)), (0, True)) + \
|
||||
Piecewise((Rational(3, 4), Eq(b, 0)), (0, True))
|
||||
assert Z == \
|
||||
Piecewise((Rational(1, 4), n < 1), (0, True)) + Piecewise((S.Half, n < 2), (0, True)) + \
|
||||
Piecewise((Rational(3, 4), n < 3), (0, True)) + Piecewise((S.One, n < 4), (0, True))
|
||||
@@ -0,0 +1,436 @@
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import (Rational, oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial)
|
||||
from sympy.functions.elementary.complexes import polar_lift
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.bessel import besselk
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.matrices.dense import eye
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
from sympy.sets.fancysets import Range
|
||||
from sympy.sets.sets import (Interval, ProductSet)
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.tensor.indexed import (Indexed, IndexedBase)
|
||||
from sympy.core.numbers import comp
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.matrices import Matrix, MatrixSymbol
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.stats import density, median, marginal_distribution, Normal, Laplace, E, sample
|
||||
from sympy.stats.joint_rv_types import (JointRV, MultivariateNormalDistribution,
|
||||
JointDistributionHandmade, MultivariateT, NormalGamma,
|
||||
GeneralizedMultivariateLogGammaOmega as GMVLGO, MultivariateBeta,
|
||||
GeneralizedMultivariateLogGamma as GMVLG, MultivariateEwens,
|
||||
Multinomial, NegativeMultinomial, MultivariateNormal,
|
||||
MultivariateLaplace)
|
||||
from sympy.testing.pytest import raises, XFAIL, skip, slow
|
||||
from sympy.external import import_module
|
||||
|
||||
from sympy.abc import x, y
|
||||
|
||||
|
||||
|
||||
def test_Normal():
|
||||
m = Normal('A', [1, 2], [[1, 0], [0, 1]])
|
||||
A = MultivariateNormal('A', [1, 2], [[1, 0], [0, 1]])
|
||||
assert m == A
|
||||
assert density(m)(1, 2) == 1/(2*pi)
|
||||
assert m.pspace.distribution.set == ProductSet(S.Reals, S.Reals)
|
||||
raises (ValueError, lambda:m[2])
|
||||
n = Normal('B', [1, 2, 3], [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
|
||||
p = Normal('C', Matrix([1, 2]), Matrix([[1, 0], [0, 1]]))
|
||||
assert density(m)(x, y) == density(p)(x, y)
|
||||
assert marginal_distribution(n, 0, 1)(1, 2) == 1/(2*pi)
|
||||
raises(ValueError, lambda: marginal_distribution(m))
|
||||
assert integrate(density(m)(x, y), (x, -oo, oo), (y, -oo, oo)).evalf() == 1.0
|
||||
N = Normal('N', [1, 2], [[x, 0], [0, y]])
|
||||
assert density(N)(0, 0) == exp(-((4*x + y)/(2*x*y)))/(2*pi*sqrt(x*y))
|
||||
|
||||
raises (ValueError, lambda: Normal('M', [1, 2], [[1, 1], [1, -1]]))
|
||||
# symbolic
|
||||
n = symbols('n', integer=True, positive=True)
|
||||
mu = MatrixSymbol('mu', n, 1)
|
||||
sigma = MatrixSymbol('sigma', n, n)
|
||||
X = Normal('X', mu, sigma)
|
||||
assert density(X) == MultivariateNormalDistribution(mu, sigma)
|
||||
raises (NotImplementedError, lambda: median(m))
|
||||
# Below tests should work after issue #17267 is resolved
|
||||
# assert E(X) == mu
|
||||
# assert variance(X) == sigma
|
||||
|
||||
# test symbolic multivariate normal densities
|
||||
n = 3
|
||||
|
||||
Sg = MatrixSymbol('Sg', n, n)
|
||||
mu = MatrixSymbol('mu', n, 1)
|
||||
obs = MatrixSymbol('obs', n, 1)
|
||||
|
||||
X = MultivariateNormal('X', mu, Sg)
|
||||
density_X = density(X)
|
||||
|
||||
eval_a = density_X(obs).subs({Sg: eye(3),
|
||||
mu: Matrix([0, 0, 0]), obs: Matrix([0, 0, 0])}).doit()
|
||||
eval_b = density_X(0, 0, 0).subs({Sg: eye(3), mu: Matrix([0, 0, 0])}).doit()
|
||||
|
||||
assert eval_a == sqrt(2)/(4*pi**Rational(3/2))
|
||||
assert eval_b == sqrt(2)/(4*pi**Rational(3/2))
|
||||
|
||||
n = symbols('n', integer=True, positive=True)
|
||||
|
||||
Sg = MatrixSymbol('Sg', n, n)
|
||||
mu = MatrixSymbol('mu', n, 1)
|
||||
obs = MatrixSymbol('obs', n, 1)
|
||||
|
||||
X = MultivariateNormal('X', mu, Sg)
|
||||
density_X_at_obs = density(X)(obs)
|
||||
|
||||
expected_density = MatrixElement(
|
||||
exp((S(1)/2) * (mu.T - obs.T) * Sg**(-1) * (-mu + obs)) / \
|
||||
sqrt((2*pi)**n * Determinant(Sg)), 0, 0)
|
||||
|
||||
assert density_X_at_obs == expected_density
|
||||
|
||||
|
||||
def test_MultivariateTDist():
|
||||
t1 = MultivariateT('T', [0, 0], [[1, 0], [0, 1]], 2)
|
||||
assert(density(t1))(1, 1) == 1/(8*pi)
|
||||
assert t1.pspace.distribution.set == ProductSet(S.Reals, S.Reals)
|
||||
assert integrate(density(t1)(x, y), (x, -oo, oo), \
|
||||
(y, -oo, oo)).evalf() == 1.0
|
||||
raises(ValueError, lambda: MultivariateT('T', [1, 2], [[1, 1], [1, -1]], 1))
|
||||
t2 = MultivariateT('t2', [1, 2], [[x, 0], [0, y]], 1)
|
||||
assert density(t2)(1, 2) == 1/(2*pi*sqrt(x*y))
|
||||
|
||||
|
||||
def test_multivariate_laplace():
|
||||
raises(ValueError, lambda: Laplace('T', [1, 2], [[1, 2], [2, 1]]))
|
||||
L = Laplace('L', [1, 0], [[1, 0], [0, 1]])
|
||||
L2 = MultivariateLaplace('L2', [1, 0], [[1, 0], [0, 1]])
|
||||
assert density(L)(2, 3) == exp(2)*besselk(0, sqrt(39))/pi
|
||||
L1 = Laplace('L1', [1, 2], [[x, 0], [0, y]])
|
||||
assert density(L1)(0, 1) == \
|
||||
exp(2/y)*besselk(0, sqrt((2 + 4/y + 1/x)/y))/(pi*sqrt(x*y))
|
||||
assert L.pspace.distribution.set == ProductSet(S.Reals, S.Reals)
|
||||
assert L.pspace.distribution == L2.pspace.distribution
|
||||
|
||||
|
||||
def test_NormalGamma():
|
||||
ng = NormalGamma('G', 1, 2, 3, 4)
|
||||
assert density(ng)(1, 1) == 32*exp(-4)/sqrt(pi)
|
||||
assert ng.pspace.distribution.set == ProductSet(S.Reals, Interval(0, oo))
|
||||
raises(ValueError, lambda:NormalGamma('G', 1, 2, 3, -1))
|
||||
assert marginal_distribution(ng, 0)(1) == \
|
||||
3*sqrt(10)*gamma(Rational(7, 4))/(10*sqrt(pi)*gamma(Rational(5, 4)))
|
||||
assert marginal_distribution(ng, y)(1) == exp(Rational(-1, 4))/128
|
||||
assert marginal_distribution(ng,[0,1])(x) == x**2*exp(-x/4)/128
|
||||
|
||||
|
||||
def test_GeneralizedMultivariateLogGammaDistribution():
|
||||
h = S.Half
|
||||
omega = Matrix([[1, h, h, h],
|
||||
[h, 1, h, h],
|
||||
[h, h, 1, h],
|
||||
[h, h, h, 1]])
|
||||
v, l, mu = (4, [1, 2, 3, 4], [1, 2, 3, 4])
|
||||
y_1, y_2, y_3, y_4 = symbols('y_1:5', real=True)
|
||||
delta = symbols('d', positive=True)
|
||||
G = GMVLGO('G', omega, v, l, mu)
|
||||
Gd = GMVLG('Gd', delta, v, l, mu)
|
||||
dend = ("d**4*Sum(4*24**(-n - 4)*(1 - d)**n*exp((n + 4)*(y_1 + 2*y_2 + 3*y_3 "
|
||||
"+ 4*y_4) - exp(y_1) - exp(2*y_2)/2 - exp(3*y_3)/3 - exp(4*y_4)/4)/"
|
||||
"(gamma(n + 1)*gamma(n + 4)**3), (n, 0, oo))")
|
||||
assert str(density(Gd)(y_1, y_2, y_3, y_4)) == dend
|
||||
den = ("5*2**(2/3)*5**(1/3)*Sum(4*24**(-n - 4)*(-2**(2/3)*5**(1/3)/4 + 1)**n*"
|
||||
"exp((n + 4)*(y_1 + 2*y_2 + 3*y_3 + 4*y_4) - exp(y_1) - exp(2*y_2)/2 - "
|
||||
"exp(3*y_3)/3 - exp(4*y_4)/4)/(gamma(n + 1)*gamma(n + 4)**3), (n, 0, oo))/64")
|
||||
assert str(density(G)(y_1, y_2, y_3, y_4)) == den
|
||||
marg = ("5*2**(2/3)*5**(1/3)*exp(4*y_1)*exp(-exp(y_1))*Integral(exp(-exp(4*G[3])"
|
||||
"/4)*exp(16*G[3])*Integral(exp(-exp(3*G[2])/3)*exp(12*G[2])*Integral(exp("
|
||||
"-exp(2*G[1])/2)*exp(8*G[1])*Sum((-1/4)**n*(-4 + 2**(2/3)*5**(1/3"
|
||||
"))**n*exp(n*y_1)*exp(2*n*G[1])*exp(3*n*G[2])*exp(4*n*G[3])/(24**n*gamma(n + 1)"
|
||||
"*gamma(n + 4)**3), (n, 0, oo)), (G[1], -oo, oo)), (G[2], -oo, oo)), (G[3]"
|
||||
", -oo, oo))/5308416")
|
||||
assert str(marginal_distribution(G, G[0])(y_1)) == marg
|
||||
omega_f1 = Matrix([[1, h, h]])
|
||||
omega_f2 = Matrix([[1, h, h, h],
|
||||
[h, 1, 2, h],
|
||||
[h, h, 1, h],
|
||||
[h, h, h, 1]])
|
||||
omega_f3 = Matrix([[6, h, h, h],
|
||||
[h, 1, 2, h],
|
||||
[h, h, 1, h],
|
||||
[h, h, h, 1]])
|
||||
v_f = symbols("v_f", positive=False, real=True)
|
||||
l_f = [1, 2, v_f, 4]
|
||||
m_f = [v_f, 2, 3, 4]
|
||||
omega_f4 = Matrix([[1, h, h, h, h],
|
||||
[h, 1, h, h, h],
|
||||
[h, h, 1, h, h],
|
||||
[h, h, h, 1, h],
|
||||
[h, h, h, h, 1]])
|
||||
l_f1 = [1, 2, 3, 4, 5]
|
||||
omega_f5 = Matrix([[1]])
|
||||
mu_f5 = l_f5 = [1]
|
||||
|
||||
raises(ValueError, lambda: GMVLGO('G', omega_f1, v, l, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega_f2, v, l, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega_f3, v, l, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega, v_f, l, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega, v, l_f, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega, v, l, m_f))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega_f4, v, l, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega, v, l_f1, mu))
|
||||
raises(ValueError, lambda: GMVLGO('G', omega_f5, v, l_f5, mu_f5))
|
||||
raises(ValueError, lambda: GMVLG('G', Rational(3, 2), v, l, mu))
|
||||
|
||||
|
||||
def test_MultivariateBeta():
|
||||
a1, a2 = symbols('a1, a2', positive=True)
|
||||
a1_f, a2_f = symbols('a1, a2', positive=False, real=True)
|
||||
mb = MultivariateBeta('B', [a1, a2])
|
||||
mb_c = MultivariateBeta('C', a1, a2)
|
||||
assert density(mb)(1, 2) == S(2)**(a2 - 1)*gamma(a1 + a2)/\
|
||||
(gamma(a1)*gamma(a2))
|
||||
assert marginal_distribution(mb_c, 0)(3) == S(3)**(a1 - 1)*gamma(a1 + a2)/\
|
||||
(a2*gamma(a1)*gamma(a2))
|
||||
raises(ValueError, lambda: MultivariateBeta('b1', [a1_f, a2]))
|
||||
raises(ValueError, lambda: MultivariateBeta('b2', [a1, a2_f]))
|
||||
raises(ValueError, lambda: MultivariateBeta('b3', [0, 0]))
|
||||
raises(ValueError, lambda: MultivariateBeta('b4', [a1_f, a2_f]))
|
||||
assert mb.pspace.distribution.set == ProductSet(Interval(0, 1), Interval(0, 1))
|
||||
|
||||
|
||||
def test_MultivariateEwens():
|
||||
n, theta, i = symbols('n theta i', positive=True)
|
||||
|
||||
# tests for integer dimensions
|
||||
theta_f = symbols('t_f', negative=True)
|
||||
a = symbols('a_1:4', positive = True, integer = True)
|
||||
ed = MultivariateEwens('E', 3, theta)
|
||||
assert density(ed)(a[0], a[1], a[2]) == Piecewise((6*2**(-a[1])*3**(-a[2])*
|
||||
theta**a[0]*theta**a[1]*theta**a[2]/
|
||||
(theta*(theta + 1)*(theta + 2)*
|
||||
factorial(a[0])*factorial(a[1])*
|
||||
factorial(a[2])), Eq(a[0] + 2*a[1] +
|
||||
3*a[2], 3)), (0, True))
|
||||
assert marginal_distribution(ed, ed[1])(a[1]) == Piecewise((6*2**(-a[1])*
|
||||
theta**a[1]/((theta + 1)*
|
||||
(theta + 2)*factorial(a[1])),
|
||||
Eq(2*a[1] + 1, 3)), (0, True))
|
||||
raises(ValueError, lambda: MultivariateEwens('e1', 5, theta_f))
|
||||
assert ed.pspace.distribution.set == ProductSet(Range(0, 4, 1),
|
||||
Range(0, 2, 1), Range(0, 2, 1))
|
||||
|
||||
# tests for symbolic dimensions
|
||||
eds = MultivariateEwens('E', n, theta)
|
||||
a = IndexedBase('a')
|
||||
j, k = symbols('j, k')
|
||||
den = Piecewise((factorial(n)*Product(theta**a[j]*(j + 1)**(-a[j])/
|
||||
factorial(a[j]), (j, 0, n - 1))/RisingFactorial(theta, n),
|
||||
Eq(n, Sum((k + 1)*a[k], (k, 0, n - 1)))), (0, True))
|
||||
assert density(eds)(a).dummy_eq(den)
|
||||
|
||||
|
||||
def test_Multinomial():
|
||||
n, x1, x2, x3, x4 = symbols('n, x1, x2, x3, x4', nonnegative=True, integer=True)
|
||||
p1, p2, p3, p4 = symbols('p1, p2, p3, p4', positive=True)
|
||||
p1_f, n_f = symbols('p1_f, n_f', negative=True)
|
||||
M = Multinomial('M', n, [p1, p2, p3, p4])
|
||||
C = Multinomial('C', 3, p1, p2, p3)
|
||||
f = factorial
|
||||
assert density(M)(x1, x2, x3, x4) == Piecewise((p1**x1*p2**x2*p3**x3*p4**x4*
|
||||
f(n)/(f(x1)*f(x2)*f(x3)*f(x4)),
|
||||
Eq(n, x1 + x2 + x3 + x4)), (0, True))
|
||||
assert marginal_distribution(C, C[0])(x1).subs(x1, 1) ==\
|
||||
3*p1*p2**2 +\
|
||||
6*p1*p2*p3 +\
|
||||
3*p1*p3**2
|
||||
raises(ValueError, lambda: Multinomial('b1', 5, [p1, p2, p3, p1_f]))
|
||||
raises(ValueError, lambda: Multinomial('b2', n_f, [p1, p2, p3, p4]))
|
||||
raises(ValueError, lambda: Multinomial('b3', n, 0.5, 0.4, 0.3, 0.1))
|
||||
|
||||
|
||||
def test_NegativeMultinomial():
|
||||
k0, x1, x2, x3, x4 = symbols('k0, x1, x2, x3, x4', nonnegative=True, integer=True)
|
||||
p1, p2, p3, p4 = symbols('p1, p2, p3, p4', positive=True)
|
||||
p1_f = symbols('p1_f', negative=True)
|
||||
N = NegativeMultinomial('N', 4, [p1, p2, p3, p4])
|
||||
C = NegativeMultinomial('C', 4, 0.1, 0.2, 0.3)
|
||||
g = gamma
|
||||
f = factorial
|
||||
assert simplify(density(N)(x1, x2, x3, x4) -
|
||||
p1**x1*p2**x2*p3**x3*p4**x4*(-p1 - p2 - p3 - p4 + 1)**4*g(x1 + x2 +
|
||||
x3 + x4 + 4)/(6*f(x1)*f(x2)*f(x3)*f(x4))) is S.Zero
|
||||
assert comp(marginal_distribution(C, C[0])(1).evalf(), 0.33, .01)
|
||||
raises(ValueError, lambda: NegativeMultinomial('b1', 5, [p1, p2, p3, p1_f]))
|
||||
raises(ValueError, lambda: NegativeMultinomial('b2', k0, 0.5, 0.4, 0.3, 0.4))
|
||||
assert N.pspace.distribution.set == ProductSet(Range(0, oo, 1),
|
||||
Range(0, oo, 1), Range(0, oo, 1), Range(0, oo, 1))
|
||||
|
||||
|
||||
@slow
|
||||
def test_JointPSpace_marginal_distribution():
|
||||
T = MultivariateT('T', [0, 0], [[1, 0], [0, 1]], 2)
|
||||
got = marginal_distribution(T, T[1])(x)
|
||||
ans = sqrt(2)*(x**2/2 + 1)/(4*polar_lift(x**2/2 + 1)**(S(5)/2))
|
||||
assert got == ans, got
|
||||
assert integrate(marginal_distribution(T, 1)(x), (x, -oo, oo)) == 1
|
||||
|
||||
t = MultivariateT('T', [0, 0, 0], [[1, 0, 0], [0, 1, 0], [0, 0, 1]], 3)
|
||||
assert comp(marginal_distribution(t, 0)(1).evalf(), 0.2, .01)
|
||||
|
||||
|
||||
def test_JointRV():
|
||||
x1, x2 = (Indexed('x', i) for i in (1, 2))
|
||||
pdf = exp(-x1**2/2 + x1 - x2**2/2 - S.Half)/(2*pi)
|
||||
X = JointRV('x', pdf)
|
||||
assert density(X)(1, 2) == exp(-2)/(2*pi)
|
||||
assert isinstance(X.pspace.distribution, JointDistributionHandmade)
|
||||
assert marginal_distribution(X, 0)(2) == sqrt(2)*exp(Rational(-1, 2))/(2*sqrt(pi))
|
||||
|
||||
|
||||
def test_expectation():
|
||||
m = Normal('A', [x, y], [[1, 0], [0, 1]])
|
||||
assert simplify(E(m[1])) == y
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_joint_vector_expectation():
|
||||
m = Normal('A', [x, y], [[1, 0], [0, 1]])
|
||||
assert E(m) == (x, y)
|
||||
|
||||
|
||||
def test_sample_numpy():
|
||||
distribs_numpy = [
|
||||
MultivariateNormal("M", [3, 4], [[2, 1], [1, 2]]),
|
||||
MultivariateBeta("B", [0.4, 5, 15, 50, 203]),
|
||||
Multinomial("N", 50, [0.3, 0.2, 0.1, 0.25, 0.15])
|
||||
]
|
||||
size = 3
|
||||
numpy = import_module('numpy')
|
||||
if not numpy:
|
||||
skip('Numpy is not installed. Abort tests for _sample_numpy.')
|
||||
else:
|
||||
for X in distribs_numpy:
|
||||
samps = sample(X, size=size, library='numpy')
|
||||
for sam in samps:
|
||||
assert tuple(sam) in X.pspace.distribution.set
|
||||
N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1)
|
||||
raises(NotImplementedError, lambda: sample(N_c, library='numpy'))
|
||||
|
||||
|
||||
def test_sample_scipy():
|
||||
distribs_scipy = [
|
||||
MultivariateNormal("M", [0, 0], [[0.1, 0.025], [0.025, 0.1]]),
|
||||
MultivariateBeta("B", [0.4, 5, 15]),
|
||||
Multinomial("N", 8, [0.3, 0.2, 0.1, 0.4])
|
||||
]
|
||||
|
||||
size = 3
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests for _sample_scipy.')
|
||||
else:
|
||||
for X in distribs_scipy:
|
||||
samps = sample(X, size=size)
|
||||
samps2 = sample(X, size=(2, 2))
|
||||
for sam in samps:
|
||||
assert tuple(sam) in X.pspace.distribution.set
|
||||
for i in range(2):
|
||||
for j in range(2):
|
||||
assert tuple(samps2[i][j]) in X.pspace.distribution.set
|
||||
N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1)
|
||||
raises(NotImplementedError, lambda: sample(N_c))
|
||||
|
||||
|
||||
def test_sample_pymc():
|
||||
distribs_pymc = [
|
||||
MultivariateNormal("M", [5, 2], [[1, 0], [0, 1]]),
|
||||
MultivariateBeta("B", [0.4, 5, 15]),
|
||||
Multinomial("N", 4, [0.3, 0.2, 0.1, 0.4])
|
||||
]
|
||||
size = 3
|
||||
pymc = import_module('pymc')
|
||||
if not pymc:
|
||||
skip('PyMC is not installed. Abort tests for _sample_pymc.')
|
||||
else:
|
||||
for X in distribs_pymc:
|
||||
samps = sample(X, size=size, library='pymc')
|
||||
for sam in samps:
|
||||
assert tuple(sam.flatten()) in X.pspace.distribution.set
|
||||
N_c = NegativeMultinomial('N', 3, 0.1, 0.1, 0.1)
|
||||
raises(NotImplementedError, lambda: sample(N_c, library='pymc'))
|
||||
|
||||
|
||||
def test_sample_seed():
|
||||
x1, x2 = (Indexed('x', i) for i in (1, 2))
|
||||
pdf = exp(-x1**2/2 + x1 - x2**2/2 - S.Half)/(2*pi)
|
||||
X = JointRV('x', pdf)
|
||||
|
||||
libraries = ['scipy', 'numpy', 'pymc']
|
||||
for lib in libraries:
|
||||
try:
|
||||
imported_lib = import_module(lib)
|
||||
if imported_lib:
|
||||
s0, s1, s2 = [], [], []
|
||||
s0 = sample(X, size=10, library=lib, seed=0)
|
||||
s1 = sample(X, size=10, library=lib, seed=0)
|
||||
s2 = sample(X, size=10, library=lib, seed=1)
|
||||
assert all(s0 == s1)
|
||||
assert all(s1 != s2)
|
||||
except NotImplementedError:
|
||||
continue
|
||||
|
||||
#
|
||||
# XXX: This fails for pymc. Previously the test appeared to pass but that is
|
||||
# just because the library argument was not passed so the test always used
|
||||
# scipy.
|
||||
#
|
||||
def test_issue_21057():
|
||||
m = Normal("x", [0, 0], [[0, 0], [0, 0]])
|
||||
n = MultivariateNormal("x", [0, 0], [[0, 0], [0, 0]])
|
||||
p = Normal("x", [0, 0], [[0, 0], [0, 1]])
|
||||
assert m == n
|
||||
libraries = ('scipy', 'numpy') # , 'pymc') # <-- pymc fails
|
||||
for library in libraries:
|
||||
try:
|
||||
imported_lib = import_module(library)
|
||||
if imported_lib:
|
||||
s1 = sample(m, size=8, library=library)
|
||||
s2 = sample(n, size=8, library=library)
|
||||
s3 = sample(p, size=8, library=library)
|
||||
assert tuple(s1.flatten()) == tuple(s2.flatten())
|
||||
for s in s3:
|
||||
assert tuple(s.flatten()) in p.pspace.distribution.set
|
||||
except NotImplementedError:
|
||||
continue
|
||||
|
||||
|
||||
#
|
||||
# When this passes the pymc part can be uncommented in test_issue_21057 above
|
||||
# and this can be deleted.
|
||||
#
|
||||
@XFAIL
|
||||
def test_issue_21057_pymc():
|
||||
m = Normal("x", [0, 0], [[0, 0], [0, 0]])
|
||||
n = MultivariateNormal("x", [0, 0], [[0, 0], [0, 0]])
|
||||
p = Normal("x", [0, 0], [[0, 0], [0, 1]])
|
||||
assert m == n
|
||||
libraries = ('pymc',)
|
||||
for library in libraries:
|
||||
try:
|
||||
imported_lib = import_module(library)
|
||||
if imported_lib:
|
||||
s1 = sample(m, size=8, library=library)
|
||||
s2 = sample(n, size=8, library=library)
|
||||
s3 = sample(p, size=8, library=library)
|
||||
assert tuple(s1.flatten()) == tuple(s2.flatten())
|
||||
for s in s3:
|
||||
assert tuple(s.flatten()) in p.pspace.distribution.set
|
||||
except NotImplementedError:
|
||||
continue
|
||||
@@ -0,0 +1,186 @@
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.matrices import Determinant, Matrix, Trace, MatrixSymbol, MatrixSet
|
||||
from sympy.stats import density, sample
|
||||
from sympy.stats.matrix_distributions import (MatrixGammaDistribution,
|
||||
MatrixGamma, MatrixPSpace, Wishart, MatrixNormal, MatrixStudentT)
|
||||
from sympy.testing.pytest import raises, skip
|
||||
from sympy.external import import_module
|
||||
|
||||
|
||||
def test_MatrixPSpace():
|
||||
M = MatrixGammaDistribution(1, 2, [[2, 1], [1, 2]])
|
||||
MP = MatrixPSpace('M', M, 2, 2)
|
||||
assert MP.distribution == M
|
||||
raises(ValueError, lambda: MatrixPSpace('M', M, 1.2, 2))
|
||||
|
||||
def test_MatrixGamma():
|
||||
M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]])
|
||||
assert M.pspace.distribution.set == MatrixSet(2, 2, S.Reals)
|
||||
assert isinstance(density(M), MatrixGammaDistribution)
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
num = exp(Trace(Matrix([[-S(1)/2, 0], [0, -S(1)/2]])*X))
|
||||
assert density(M)(X).doit() == num/(4*pi*sqrt(Determinant(X)))
|
||||
assert density(M)([[2, 1], [1, 2]]).doit() == sqrt(3)*exp(-2)/(12*pi)
|
||||
X = MatrixSymbol('X', 1, 2)
|
||||
Y = MatrixSymbol('Y', 1, 2)
|
||||
assert density(M)([X, Y]).doit() == exp(-X[0, 0]/2 - Y[0, 1]/2)/(4*pi*sqrt(
|
||||
X[0, 0]*Y[0, 1] - X[0, 1]*Y[0, 0]))
|
||||
# symbolic
|
||||
a, b = symbols('a b', positive=True)
|
||||
d = symbols('d', positive=True, integer=True)
|
||||
Y = MatrixSymbol('Y', d, d)
|
||||
Z = MatrixSymbol('Z', 2, 2)
|
||||
SM = MatrixSymbol('SM', d, d)
|
||||
M2 = MatrixGamma('M2', a, b, SM)
|
||||
M3 = MatrixGamma('M3', 2, 3, [[2, 1], [1, 2]])
|
||||
k = Dummy('k')
|
||||
exprd = pi**(-d*(d - 1)/4)*b**(-a*d)*exp(Trace((-1/b)*SM**(-1)*Y)
|
||||
)*Determinant(SM)**(-a)*Determinant(Y)**(a - d/2 - S(1)/2)/Product(
|
||||
gamma(-k/2 + a + S(1)/2), (k, 1, d))
|
||||
assert density(M2)(Y).dummy_eq(exprd)
|
||||
raises(NotImplementedError, lambda: density(M3 + M)(Z))
|
||||
raises(ValueError, lambda: density(M)(1))
|
||||
raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixGamma('M', -1, -2, [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [2, 1]]))
|
||||
raises(ValueError, lambda: MatrixGamma('M', -1, 2, [[1, 0], [0]]))
|
||||
|
||||
def test_Wishart():
|
||||
W = Wishart('W', 5, [[1, 0], [0, 1]])
|
||||
assert W.pspace.distribution.set == MatrixSet(2, 2, S.Reals)
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
term1 = exp(Trace(Matrix([[-S(1)/2, 0], [0, -S(1)/2]])*X))
|
||||
assert density(W)(X).doit() == term1 * Determinant(X)/(24*pi)
|
||||
assert density(W)([[2, 1], [1, 2]]).doit() == exp(-2)/(8*pi)
|
||||
n = symbols('n', positive=True)
|
||||
d = symbols('d', positive=True, integer=True)
|
||||
Y = MatrixSymbol('Y', d, d)
|
||||
SM = MatrixSymbol('SM', d, d)
|
||||
W = Wishart('W', n, SM)
|
||||
k = Dummy('k')
|
||||
exprd = 2**(-d*n/2)*pi**(-d*(d - 1)/4)*exp(Trace(-(S(1)/2)*SM**(-1)*Y)
|
||||
)*Determinant(SM)**(-n/2)*Determinant(Y)**(
|
||||
-d/2 + n/2 - S(1)/2)/Product(gamma(-k/2 + n/2 + S(1)/2), (k, 1, d))
|
||||
assert density(W)(Y).dummy_eq(exprd)
|
||||
raises(ValueError, lambda: density(W)(1))
|
||||
raises(ValueError, lambda: Wishart('W', -1, [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: Wishart('W', -1, [[1, 0], [2, 1]]))
|
||||
raises(ValueError, lambda: Wishart('W', 2, [[1, 0], [0]]))
|
||||
|
||||
def test_MatrixNormal():
|
||||
M = MatrixNormal('M', [[5, 6]], [4], [[2, 1], [1, 2]])
|
||||
assert M.pspace.distribution.set == MatrixSet(1, 2, S.Reals)
|
||||
X = MatrixSymbol('X', 1, 2)
|
||||
term1 = exp(-Trace(Matrix([[ S(2)/3, -S(1)/3], [-S(1)/3, S(2)/3]])*(
|
||||
Matrix([[-5], [-6]]) + X.T)*Matrix([[S(1)/4]])*(Matrix([[-5, -6]]) + X))/2)
|
||||
assert density(M)(X).doit() == (sqrt(3)) * term1/(24*pi)
|
||||
assert density(M)([[7, 8]]).doit() == sqrt(3)*exp(-S(1)/3)/(24*pi)
|
||||
d, n = symbols('d n', positive=True, integer=True)
|
||||
SM2 = MatrixSymbol('SM2', d, d)
|
||||
SM1 = MatrixSymbol('SM1', n, n)
|
||||
LM = MatrixSymbol('LM', n, d)
|
||||
Y = MatrixSymbol('Y', n, d)
|
||||
M = MatrixNormal('M', LM, SM1, SM2)
|
||||
exprd = (2*pi)**(-d*n/2)*exp(-Trace(SM2**(-1)*(-LM.T + Y.T)*SM1**(-1)*(-LM + Y)
|
||||
)/2)*Determinant(SM1)**(-d/2)*Determinant(SM2)**(-n/2)
|
||||
assert density(M)(Y).doit() == exprd
|
||||
raises(ValueError, lambda: density(M)(1))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [0, 1]], [[1, 0], [2, 1]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2, 1]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [0, 1]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [1, 2], [[1, 0], [2, 1]], [[1, 0], [0]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [[1, 2]], [[1, 0], [0, 1]], [[1, 0]]))
|
||||
raises(ValueError, lambda: MatrixNormal('M', [[1, 2]], [1], [[1, 0]]))
|
||||
|
||||
def test_MatrixStudentT():
|
||||
M = MatrixStudentT('M', 2, [[5, 6]], [[2, 1], [1, 2]], [4])
|
||||
assert M.pspace.distribution.set == MatrixSet(1, 2, S.Reals)
|
||||
X = MatrixSymbol('X', 1, 2)
|
||||
D = pi ** (-1.0) * Determinant(Matrix([[4]])) ** (-1.0) * Determinant(Matrix([[2, 1], [1, 2]])) \
|
||||
** (-0.5) / Determinant(Matrix([[S(1) / 4]]) * (Matrix([[-5, -6]]) + X)
|
||||
* Matrix([[S(2) / 3, -S(1) / 3], [-S(1) / 3, S(2) / 3]]) * (
|
||||
Matrix([[-5], [-6]]) + X.T) + Matrix([[1]])) ** 2
|
||||
assert density(M)(X) == D
|
||||
|
||||
v = symbols('v', positive=True)
|
||||
n, p = 1, 2
|
||||
Omega = MatrixSymbol('Omega', p, p)
|
||||
Sigma = MatrixSymbol('Sigma', n, n)
|
||||
Location = MatrixSymbol('Location', n, p)
|
||||
Y = MatrixSymbol('Y', n, p)
|
||||
M = MatrixStudentT('M', v, Location, Omega, Sigma)
|
||||
|
||||
exprd = gamma(v/2 + 1)*Determinant(Matrix([[1]]) + Sigma**(-1)*(-Location + Y)*Omega**(-1)*(-Location.T + Y.T))**(-v/2 - 1) / \
|
||||
(pi*gamma(v/2)*sqrt(Determinant(Omega))*Determinant(Sigma))
|
||||
|
||||
assert density(M)(Y) == exprd
|
||||
raises(ValueError, lambda: density(M)(1))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [0, 1]], [[1, 0], [2, 1]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2, 1]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [0, 1]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2]], [[1, 0], [0, 1]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [1, 2], [[1, 0], [2, 1]], [[1], [2]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [[1, 2]], [[1, 0], [0, 1]], [[1, 0]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', 1, [[1, 2]], [1], [[1, 0]]))
|
||||
raises(ValueError, lambda: MatrixStudentT('M', -1, [1, 2], [[1, 0], [0, 1]], [4]))
|
||||
|
||||
def test_sample_scipy():
|
||||
distribs_scipy = [
|
||||
MatrixNormal('M', [[5, 6]], [4], [[2, 1], [1, 2]]),
|
||||
Wishart('W', 5, [[1, 0], [0, 1]])
|
||||
]
|
||||
|
||||
size = 5
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy not installed. Abort tests for _sample_scipy.')
|
||||
else:
|
||||
for X in distribs_scipy:
|
||||
samps = sample(X, size=size)
|
||||
for sam in samps:
|
||||
assert Matrix(sam) in X.pspace.distribution.set
|
||||
M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]])
|
||||
raises(NotImplementedError, lambda: sample(M, size=3))
|
||||
|
||||
def test_sample_pymc():
|
||||
distribs_pymc = [
|
||||
MatrixNormal('M', [[5, 6], [3, 4]], [[1, 0], [0, 1]], [[2, 1], [1, 2]]),
|
||||
Wishart('W', 7, [[2, 1], [1, 2]])
|
||||
]
|
||||
size = 3
|
||||
pymc = import_module('pymc')
|
||||
if not pymc:
|
||||
skip('PyMC is not installed. Abort tests for _sample_pymc.')
|
||||
else:
|
||||
for X in distribs_pymc:
|
||||
samps = sample(X, size=size, library='pymc')
|
||||
for sam in samps:
|
||||
assert Matrix(sam) in X.pspace.distribution.set
|
||||
M = MatrixGamma('M', 1, 2, [[1, 0], [0, 1]])
|
||||
raises(NotImplementedError, lambda: sample(M, size=3))
|
||||
|
||||
def test_sample_seed():
|
||||
X = MatrixNormal('M', [[5, 6], [3, 4]], [[1, 0], [0, 1]], [[2, 1], [1, 2]])
|
||||
|
||||
libraries = ['scipy', 'numpy', 'pymc']
|
||||
for lib in libraries:
|
||||
try:
|
||||
imported_lib = import_module(lib)
|
||||
if imported_lib:
|
||||
s0, s1, s2 = [], [], []
|
||||
s0 = sample(X, size=10, library=lib, seed=0)
|
||||
s1 = sample(X, size=10, library=lib, seed=0)
|
||||
s2 = sample(X, size=10, library=lib, seed=1)
|
||||
for i in range(10):
|
||||
assert (s0[i] == s1[i]).all()
|
||||
assert (s1[i] != s2[i]).all()
|
||||
|
||||
except NotImplementedError:
|
||||
continue
|
||||
@@ -0,0 +1,82 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (Integer, oo, pi)
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.relational import (Eq, Ne)
|
||||
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.delta_functions import DiracDelta
|
||||
from sympy.functions.special.gamma_functions import gamma
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.tensor.indexed import (Indexed, IndexedBase)
|
||||
from sympy.functions.elementary.piecewise import ExprCondPair
|
||||
from sympy.stats import (Poisson, Beta, Exponential, P,
|
||||
Multinomial, MultivariateBeta)
|
||||
from sympy.stats.crv_types import Normal
|
||||
from sympy.stats.drv_types import PoissonDistribution
|
||||
from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution
|
||||
from sympy.stats.joint_rv import MarginalDistribution
|
||||
from sympy.stats.rv import pspace, density
|
||||
from sympy.testing.pytest import ignore_warnings
|
||||
|
||||
def test_density():
|
||||
x = Symbol('x')
|
||||
l = Symbol('l', positive=True)
|
||||
rate = Beta(l, 2, 3)
|
||||
X = Poisson(x, rate)
|
||||
assert isinstance(pspace(X), CompoundPSpace)
|
||||
assert density(X, Eq(rate, rate.symbol)) == PoissonDistribution(l)
|
||||
N1 = Normal('N1', 0, 1)
|
||||
N2 = Normal('N2', N1, 2)
|
||||
assert density(N2)(0).doit() == sqrt(10)/(10*sqrt(pi))
|
||||
assert simplify(density(N2, Eq(N1, 1))(x)) == \
|
||||
sqrt(2)*exp(-(x - 1)**2/8)/(4*sqrt(pi))
|
||||
assert simplify(density(N2)(x)) == sqrt(10)*exp(-x**2/10)/(10*sqrt(pi))
|
||||
|
||||
def test_MarginalDistribution():
|
||||
a1, p1, p2 = symbols('a1 p1 p2', positive=True)
|
||||
C = Multinomial('C', 2, p1, p2)
|
||||
B = MultivariateBeta('B', a1, C[0])
|
||||
MGR = MarginalDistribution(B, (C[0],))
|
||||
mgrc = Mul(Symbol('B'), Piecewise(ExprCondPair(Mul(Integer(2),
|
||||
Pow(Symbol('p1', positive=True), Indexed(IndexedBase(Symbol('C')),
|
||||
Integer(0))), Pow(Symbol('p2', positive=True),
|
||||
Indexed(IndexedBase(Symbol('C')), Integer(1))),
|
||||
Pow(factorial(Indexed(IndexedBase(Symbol('C')), Integer(0))), Integer(-1)),
|
||||
Pow(factorial(Indexed(IndexedBase(Symbol('C')), Integer(1))), Integer(-1))),
|
||||
Eq(Add(Indexed(IndexedBase(Symbol('C')), Integer(0)),
|
||||
Indexed(IndexedBase(Symbol('C')), Integer(1))), Integer(2))),
|
||||
ExprCondPair(Integer(0), True)), Pow(gamma(Symbol('a1', positive=True)),
|
||||
Integer(-1)), gamma(Add(Symbol('a1', positive=True),
|
||||
Indexed(IndexedBase(Symbol('C')), Integer(0)))),
|
||||
Pow(gamma(Indexed(IndexedBase(Symbol('C')), Integer(0))), Integer(-1)),
|
||||
Pow(Indexed(IndexedBase(Symbol('B')), Integer(0)),
|
||||
Add(Symbol('a1', positive=True), Integer(-1))),
|
||||
Pow(Indexed(IndexedBase(Symbol('B')), Integer(1)),
|
||||
Add(Indexed(IndexedBase(Symbol('C')), Integer(0)), Integer(-1))))
|
||||
assert MGR(C) == mgrc
|
||||
|
||||
def test_compound_distribution():
|
||||
Y = Poisson('Y', 1)
|
||||
Z = Poisson('Z', Y)
|
||||
assert isinstance(pspace(Z), CompoundPSpace)
|
||||
assert isinstance(pspace(Z).distribution, CompoundDistribution)
|
||||
assert Z.pspace.distribution.pdf(1).doit() == exp(-2)*exp(exp(-1))
|
||||
|
||||
def test_mix_expression():
|
||||
Y, E = Poisson('Y', 1), Exponential('E', 1)
|
||||
k = Dummy('k')
|
||||
expr1 = Integral(Sum(exp(-1)*Integral(exp(-k)*DiracDelta(k - 2), (k, 0, oo)
|
||||
)/factorial(k), (k, 0, oo)), (k, -oo, 0))
|
||||
expr2 = Integral(Sum(exp(-1)*Integral(exp(-k)*DiracDelta(k - 2), (k, 0, oo)
|
||||
)/factorial(k), (k, 0, oo)), (k, 0, oo))
|
||||
assert P(Eq(Y + E, 1)) == 0
|
||||
assert P(Ne(Y + E, 2)) == 1
|
||||
with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed
|
||||
assert P(E + Y < 2, evaluate=False).rewrite(Integral).dummy_eq(expr1)
|
||||
assert P(E + Y > 2, evaluate=False).rewrite(Integral).dummy_eq(expr2)
|
||||
@@ -0,0 +1,135 @@
|
||||
from sympy.concrete.products import Product
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import (I, Rational, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.tensor.indexed import IndexedBase
|
||||
from sympy.stats import (GaussianUnitaryEnsemble as GUE, density,
|
||||
GaussianOrthogonalEnsemble as GOE,
|
||||
GaussianSymplecticEnsemble as GSE,
|
||||
joint_eigen_distribution,
|
||||
CircularUnitaryEnsemble as CUE,
|
||||
CircularOrthogonalEnsemble as COE,
|
||||
CircularSymplecticEnsemble as CSE,
|
||||
JointEigenDistribution,
|
||||
level_spacing_distribution,
|
||||
Normal, Beta)
|
||||
from sympy.stats.joint_rv_types import JointDistributionHandmade
|
||||
from sympy.stats.rv import RandomMatrixSymbol
|
||||
from sympy.stats.random_matrix_models import GaussianEnsemble, RandomMatrixPSpace
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
def test_GaussianEnsemble():
|
||||
G = GaussianEnsemble('G', 3)
|
||||
assert density(G) == G.pspace.model
|
||||
raises(ValueError, lambda: GaussianEnsemble('G', 3.5))
|
||||
|
||||
def test_GaussianUnitaryEnsemble():
|
||||
H = RandomMatrixSymbol('H', 3, 3)
|
||||
G = GUE('U', 3)
|
||||
assert density(G)(H) == sqrt(2)*exp(-3*Trace(H**2)/2)/(4*pi**Rational(9, 2))
|
||||
i, j = (Dummy('i', integer=True, positive=True),
|
||||
Dummy('j', integer=True, positive=True))
|
||||
l = IndexedBase('l')
|
||||
assert joint_eigen_distribution(G).dummy_eq(
|
||||
Lambda((l[1], l[2], l[3]),
|
||||
27*sqrt(6)*exp(-3*(l[1]**2)/2 - 3*(l[2]**2)/2 - 3*(l[3]**2)/2)*
|
||||
Product(Abs(l[i] - l[j])**2, (j, i + 1, 3), (i, 1, 2))/(16*pi**Rational(3, 2))))
|
||||
s = Dummy('s')
|
||||
assert level_spacing_distribution(G).dummy_eq(Lambda(s, 32*s**2*exp(-4*s**2/pi)/pi**2))
|
||||
|
||||
|
||||
def test_GaussianOrthogonalEnsemble():
|
||||
H = RandomMatrixSymbol('H', 3, 3)
|
||||
_H = MatrixSymbol('_H', 3, 3)
|
||||
G = GOE('O', 3)
|
||||
assert density(G)(H) == exp(-3*Trace(H**2)/4)/Integral(exp(-3*Trace(_H**2)/4), _H)
|
||||
i, j = (Dummy('i', integer=True, positive=True),
|
||||
Dummy('j', integer=True, positive=True))
|
||||
l = IndexedBase('l')
|
||||
assert joint_eigen_distribution(G).dummy_eq(
|
||||
Lambda((l[1], l[2], l[3]),
|
||||
9*sqrt(2)*exp(-3*l[1]**2/2 - 3*l[2]**2/2 - 3*l[3]**2/2)*
|
||||
Product(Abs(l[i] - l[j]), (j, i + 1, 3), (i, 1, 2))/(32*pi)))
|
||||
s = Dummy('s')
|
||||
assert level_spacing_distribution(G).dummy_eq(Lambda(s, s*pi*exp(-s**2*pi/4)/2))
|
||||
|
||||
def test_GaussianSymplecticEnsemble():
|
||||
H = RandomMatrixSymbol('H', 3, 3)
|
||||
_H = MatrixSymbol('_H', 3, 3)
|
||||
G = GSE('O', 3)
|
||||
assert density(G)(H) == exp(-3*Trace(H**2))/Integral(exp(-3*Trace(_H**2)), _H)
|
||||
i, j = (Dummy('i', integer=True, positive=True),
|
||||
Dummy('j', integer=True, positive=True))
|
||||
l = IndexedBase('l')
|
||||
assert joint_eigen_distribution(G).dummy_eq(
|
||||
Lambda((l[1], l[2], l[3]),
|
||||
162*sqrt(3)*exp(-3*l[1]**2/2 - 3*l[2]**2/2 - 3*l[3]**2/2)*
|
||||
Product(Abs(l[i] - l[j])**4, (j, i + 1, 3), (i, 1, 2))/(5*pi**Rational(3, 2))))
|
||||
s = Dummy('s')
|
||||
assert level_spacing_distribution(G).dummy_eq(Lambda(s, S(262144)*s**4*exp(-64*s**2/(9*pi))/(729*pi**3)))
|
||||
|
||||
def test_CircularUnitaryEnsemble():
|
||||
CU = CUE('U', 3)
|
||||
j, k = (Dummy('j', integer=True, positive=True),
|
||||
Dummy('k', integer=True, positive=True))
|
||||
t = IndexedBase('t')
|
||||
assert joint_eigen_distribution(CU).dummy_eq(
|
||||
Lambda((t[1], t[2], t[3]),
|
||||
Product(Abs(exp(I*t[j]) - exp(I*t[k]))**2,
|
||||
(j, k + 1, 3), (k, 1, 2))/(48*pi**3))
|
||||
)
|
||||
|
||||
def test_CircularOrthogonalEnsemble():
|
||||
CO = COE('U', 3)
|
||||
j, k = (Dummy('j', integer=True, positive=True),
|
||||
Dummy('k', integer=True, positive=True))
|
||||
t = IndexedBase('t')
|
||||
assert joint_eigen_distribution(CO).dummy_eq(
|
||||
Lambda((t[1], t[2], t[3]),
|
||||
Product(Abs(exp(I*t[j]) - exp(I*t[k])),
|
||||
(j, k + 1, 3), (k, 1, 2))/(48*pi**2))
|
||||
)
|
||||
|
||||
def test_CircularSymplecticEnsemble():
|
||||
CS = CSE('U', 3)
|
||||
j, k = (Dummy('j', integer=True, positive=True),
|
||||
Dummy('k', integer=True, positive=True))
|
||||
t = IndexedBase('t')
|
||||
assert joint_eigen_distribution(CS).dummy_eq(
|
||||
Lambda((t[1], t[2], t[3]),
|
||||
Product(Abs(exp(I*t[j]) - exp(I*t[k]))**4,
|
||||
(j, k + 1, 3), (k, 1, 2))/(720*pi**3))
|
||||
)
|
||||
|
||||
def test_JointEigenDistribution():
|
||||
A = Matrix([[Normal('A00', 0, 1), Normal('A01', 1, 1)],
|
||||
[Beta('A10', 1, 1), Beta('A11', 1, 1)]])
|
||||
assert JointEigenDistribution(A) == \
|
||||
JointDistributionHandmade(-sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)/2 +
|
||||
A[0, 0]/2 + A[1, 1]/2, sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)/2 + A[0, 0]/2 + A[1, 1]/2)
|
||||
raises(ValueError, lambda: JointEigenDistribution(Matrix([[1, 0], [2, 1]])))
|
||||
|
||||
def test_issue_19841():
|
||||
G1 = GUE('U', 2)
|
||||
G2 = G1.xreplace({2: 2})
|
||||
assert G1.args == G2.args
|
||||
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
G = GSE('U', 2)
|
||||
h_pspace = RandomMatrixPSpace('P', model=density(G))
|
||||
H = RandomMatrixSymbol('H', 2, 2, pspace=h_pspace)
|
||||
H2 = RandomMatrixSymbol('H', 2, 2, pspace=None)
|
||||
assert H.doit() == H
|
||||
|
||||
assert (2*H).xreplace({H: X}) == 2*X
|
||||
assert (2*H).xreplace({H2: X}) == 2*H
|
||||
assert (2*H2).xreplace({H: X}) == 2*H2
|
||||
assert (2*H2).xreplace({H2: X}) == 2*X
|
||||
@@ -0,0 +1,441 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import (Rational, nan, oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.combinatorial.factorials import (FallingFactorial, binomial)
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.functions.special.delta_functions import DiracDelta
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.logic.boolalg import (And, Or)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.tensor.indexed import Indexed
|
||||
from sympy.stats import (Die, Normal, Exponential, FiniteRV, P, E, H, variance,
|
||||
density, given, independent, dependent, where, pspace, GaussianUnitaryEnsemble,
|
||||
random_symbols, sample, Geometric, factorial_moment, Binomial, Hypergeometric,
|
||||
DiscreteUniform, Poisson, characteristic_function, moment_generating_function,
|
||||
BernoulliProcess, Variance, Expectation, Probability, Covariance, covariance, cmoment,
|
||||
moment, median)
|
||||
from sympy.stats.rv import (IndependentProductPSpace, rs_swap, Density, NamedArgsMixin,
|
||||
RandomSymbol, sample_iter, PSpace, is_random, RandomIndexedSymbol, RandomMatrixSymbol)
|
||||
from sympy.testing.pytest import raises, skip, XFAIL, warns_deprecated_sympy
|
||||
from sympy.external import import_module
|
||||
from sympy.core.numbers import comp
|
||||
from sympy.stats.frv_types import BernoulliDistribution
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
|
||||
def test_where():
|
||||
X, Y = Die('X'), Die('Y')
|
||||
Z = Normal('Z', 0, 1)
|
||||
|
||||
assert where(Z**2 <= 1).set == Interval(-1, 1)
|
||||
assert where(Z**2 <= 1).as_boolean() == Interval(-1, 1).as_relational(Z.symbol)
|
||||
assert where(And(X > Y, Y > 4)).as_boolean() == And(
|
||||
Eq(X.symbol, 6), Eq(Y.symbol, 5))
|
||||
|
||||
assert len(where(X < 3).set) == 2
|
||||
assert 1 in where(X < 3).set
|
||||
|
||||
X, Y = Normal('X', 0, 1), Normal('Y', 0, 1)
|
||||
assert where(And(X**2 <= 1, X >= 0)).set == Interval(0, 1)
|
||||
XX = given(X, And(X**2 <= 1, X >= 0))
|
||||
assert XX.pspace.domain.set == Interval(0, 1)
|
||||
assert XX.pspace.domain.as_boolean() == \
|
||||
And(0 <= X.symbol, X.symbol**2 <= 1, -oo < X.symbol, X.symbol < oo)
|
||||
|
||||
with raises(TypeError):
|
||||
XX = given(X, X + 3)
|
||||
|
||||
|
||||
def test_random_symbols():
|
||||
X, Y = Normal('X', 0, 1), Normal('Y', 0, 1)
|
||||
|
||||
assert set(random_symbols(2*X + 1)) == {X}
|
||||
assert set(random_symbols(2*X + Y)) == {X, Y}
|
||||
assert set(random_symbols(2*X + Y.symbol)) == {X}
|
||||
assert set(random_symbols(2)) == set()
|
||||
|
||||
|
||||
def test_characteristic_function():
|
||||
# Imports I from sympy
|
||||
from sympy.core.numbers import I
|
||||
X = Normal('X',0,1)
|
||||
Y = DiscreteUniform('Y', [1,2,7])
|
||||
Z = Poisson('Z', 2)
|
||||
t = symbols('_t')
|
||||
P = Lambda(t, exp(-t**2/2))
|
||||
Q = Lambda(t, exp(7*t*I)/3 + exp(2*t*I)/3 + exp(t*I)/3)
|
||||
R = Lambda(t, exp(2 * exp(t*I) - 2))
|
||||
|
||||
|
||||
assert characteristic_function(X).dummy_eq(P)
|
||||
assert characteristic_function(Y).dummy_eq(Q)
|
||||
assert characteristic_function(Z).dummy_eq(R)
|
||||
|
||||
|
||||
def test_moment_generating_function():
|
||||
|
||||
X = Normal('X',0,1)
|
||||
Y = DiscreteUniform('Y', [1,2,7])
|
||||
Z = Poisson('Z', 2)
|
||||
t = symbols('_t')
|
||||
P = Lambda(t, exp(t**2/2))
|
||||
Q = Lambda(t, (exp(7*t)/3 + exp(2*t)/3 + exp(t)/3))
|
||||
R = Lambda(t, exp(2 * exp(t) - 2))
|
||||
|
||||
|
||||
assert moment_generating_function(X).dummy_eq(P)
|
||||
assert moment_generating_function(Y).dummy_eq(Q)
|
||||
assert moment_generating_function(Z).dummy_eq(R)
|
||||
|
||||
def test_sample_iter():
|
||||
|
||||
X = Normal('X',0,1)
|
||||
Y = DiscreteUniform('Y', [1, 2, 7])
|
||||
Z = Poisson('Z', 2)
|
||||
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
expr = X**2 + 3
|
||||
iterator = sample_iter(expr)
|
||||
|
||||
expr2 = Y**2 + 5*Y + 4
|
||||
iterator2 = sample_iter(expr2)
|
||||
|
||||
expr3 = Z**3 + 4
|
||||
iterator3 = sample_iter(expr3)
|
||||
|
||||
def is_iterator(obj):
|
||||
if (
|
||||
hasattr(obj, '__iter__') and
|
||||
(hasattr(obj, 'next') or
|
||||
hasattr(obj, '__next__')) and
|
||||
callable(obj.__iter__) and
|
||||
obj.__iter__() is obj
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
assert is_iterator(iterator)
|
||||
assert is_iterator(iterator2)
|
||||
assert is_iterator(iterator3)
|
||||
|
||||
def test_pspace():
|
||||
X, Y = Normal('X', 0, 1), Normal('Y', 0, 1)
|
||||
x = Symbol('x')
|
||||
|
||||
raises(ValueError, lambda: pspace(5 + 3))
|
||||
raises(ValueError, lambda: pspace(x < 1))
|
||||
assert pspace(X) == X.pspace
|
||||
assert pspace(2*X + 1) == X.pspace
|
||||
assert pspace(2*X + Y) == IndependentProductPSpace(Y.pspace, X.pspace)
|
||||
|
||||
def test_rs_swap():
|
||||
X = Normal('x', 0, 1)
|
||||
Y = Exponential('y', 1)
|
||||
|
||||
XX = Normal('x', 0, 2)
|
||||
YY = Normal('y', 0, 3)
|
||||
|
||||
expr = 2*X + Y
|
||||
assert expr.subs(rs_swap((X, Y), (YY, XX))) == 2*XX + YY
|
||||
|
||||
|
||||
def test_RandomSymbol():
|
||||
|
||||
X = Normal('x', 0, 1)
|
||||
Y = Normal('x', 0, 2)
|
||||
assert X.symbol == Y.symbol
|
||||
assert X != Y
|
||||
|
||||
assert X.name == X.symbol.name
|
||||
|
||||
X = Normal('lambda', 0, 1) # make sure we can use protected terms
|
||||
X = Normal('Lambda', 0, 1) # make sure we can use SymPy terms
|
||||
|
||||
|
||||
def test_RandomSymbol_diff():
|
||||
X = Normal('x', 0, 1)
|
||||
assert (2*X).diff(X)
|
||||
|
||||
|
||||
def test_random_symbol_no_pspace():
|
||||
x = RandomSymbol(Symbol('x'))
|
||||
assert x.pspace == PSpace()
|
||||
|
||||
def test_overlap():
|
||||
X = Normal('x', 0, 1)
|
||||
Y = Normal('x', 0, 2)
|
||||
|
||||
raises(ValueError, lambda: P(X > Y))
|
||||
|
||||
|
||||
def test_IndependentProductPSpace():
|
||||
X = Normal('X', 0, 1)
|
||||
Y = Normal('Y', 0, 1)
|
||||
px = X.pspace
|
||||
py = Y.pspace
|
||||
assert pspace(X + Y) == IndependentProductPSpace(px, py)
|
||||
assert pspace(X + Y) == IndependentProductPSpace(py, px)
|
||||
|
||||
|
||||
def test_E():
|
||||
assert E(5) == 5
|
||||
|
||||
|
||||
def test_H():
|
||||
X = Normal('X', 0, 1)
|
||||
D = Die('D', sides = 4)
|
||||
G = Geometric('G', 0.5)
|
||||
assert H(X, X > 0) == -log(2)/2 + S.Half + log(pi)/2
|
||||
assert H(D, D > 2) == log(2)
|
||||
assert comp(H(G).evalf().round(2), 1.39)
|
||||
|
||||
|
||||
def test_Sample():
|
||||
X = Die('X', 6)
|
||||
Y = Normal('Y', 0, 1)
|
||||
z = Symbol('z', integer=True)
|
||||
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
assert sample(X) in [1, 2, 3, 4, 5, 6]
|
||||
assert isinstance(sample(X + Y), float)
|
||||
|
||||
assert P(X + Y > 0, Y < 0, numsamples=10).is_number
|
||||
assert E(X + Y, numsamples=10).is_number
|
||||
assert E(X**2 + Y, numsamples=10).is_number
|
||||
assert E((X + Y)**2, numsamples=10).is_number
|
||||
assert variance(X + Y, numsamples=10).is_number
|
||||
|
||||
raises(TypeError, lambda: P(Y > z, numsamples=5))
|
||||
|
||||
assert P(sin(Y) <= 1, numsamples=10) == 1.0
|
||||
assert P(sin(Y) <= 1, cos(Y) < 1, numsamples=10) == 1.0
|
||||
|
||||
assert all(i in range(1, 7) for i in density(X, numsamples=10))
|
||||
assert all(i in range(4, 7) for i in density(X, X>3, numsamples=10))
|
||||
|
||||
numpy = import_module('numpy')
|
||||
if not numpy:
|
||||
skip('Numpy is not installed. Abort tests')
|
||||
#Test Issue #21563: Output of sample must be a float or array
|
||||
assert isinstance(sample(X), (numpy.int32, numpy.int64))
|
||||
assert isinstance(sample(Y), numpy.float64)
|
||||
assert isinstance(sample(X, size=2), numpy.ndarray)
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
sample(X, numsamples=2)
|
||||
|
||||
@XFAIL
|
||||
def test_samplingE():
|
||||
scipy = import_module('scipy')
|
||||
if not scipy:
|
||||
skip('Scipy is not installed. Abort tests')
|
||||
Y = Normal('Y', 0, 1)
|
||||
z = Symbol('z', integer=True)
|
||||
assert E(Sum(1/z**Y, (z, 1, oo)), Y > 2, numsamples=3).is_number
|
||||
|
||||
|
||||
def test_given():
|
||||
X = Normal('X', 0, 1)
|
||||
Y = Normal('Y', 0, 1)
|
||||
A = given(X, True)
|
||||
B = given(X, Y > 2)
|
||||
|
||||
assert X == A == B
|
||||
|
||||
|
||||
def test_factorial_moment():
|
||||
X = Poisson('X', 2)
|
||||
Y = Binomial('Y', 2, S.Half)
|
||||
Z = Hypergeometric('Z', 4, 2, 2)
|
||||
assert factorial_moment(X, 2) == 4
|
||||
assert factorial_moment(Y, 2) == S.Half
|
||||
assert factorial_moment(Z, 2) == Rational(1, 3)
|
||||
|
||||
x, y, z, l = symbols('x y z l')
|
||||
Y = Binomial('Y', 2, y)
|
||||
Z = Hypergeometric('Z', 10, 2, 3)
|
||||
assert factorial_moment(Y, l) == y**2*FallingFactorial(
|
||||
2, l) + 2*y*(1 - y)*FallingFactorial(1, l) + (1 - y)**2*\
|
||||
FallingFactorial(0, l)
|
||||
assert factorial_moment(Z, l) == 7*FallingFactorial(0, l)/\
|
||||
15 + 7*FallingFactorial(1, l)/15 + FallingFactorial(2, l)/15
|
||||
|
||||
|
||||
def test_dependence():
|
||||
X, Y = Die('X'), Die('Y')
|
||||
assert independent(X, 2*Y)
|
||||
assert not dependent(X, 2*Y)
|
||||
|
||||
X, Y = Normal('X', 0, 1), Normal('Y', 0, 1)
|
||||
assert independent(X, Y)
|
||||
assert dependent(X, 2*X)
|
||||
|
||||
# Create a dependency
|
||||
XX, YY = given(Tuple(X, Y), Eq(X + Y, 3))
|
||||
assert dependent(XX, YY)
|
||||
|
||||
def test_dependent_finite():
|
||||
X, Y = Die('X'), Die('Y')
|
||||
# Dependence testing requires symbolic conditions which currently break
|
||||
# finite random variables
|
||||
assert dependent(X, Y + X)
|
||||
|
||||
XX, YY = given(Tuple(X, Y), X + Y > 5) # Create a dependency
|
||||
assert dependent(XX, YY)
|
||||
|
||||
|
||||
def test_normality():
|
||||
X, Y = Normal('X', 0, 1), Normal('Y', 0, 1)
|
||||
x = Symbol('x', real=True)
|
||||
z = Symbol('z', real=True)
|
||||
dens = density(X - Y, Eq(X + Y, z))
|
||||
|
||||
assert integrate(dens(x), (x, -oo, oo)) == 1
|
||||
|
||||
|
||||
def test_Density():
|
||||
X = Die('X', 6)
|
||||
d = Density(X)
|
||||
assert d.doit() == density(X)
|
||||
|
||||
def test_NamedArgsMixin():
|
||||
class Foo(Basic, NamedArgsMixin):
|
||||
_argnames = 'foo', 'bar'
|
||||
|
||||
a = Foo(S(1), S(2))
|
||||
|
||||
assert a.foo == 1
|
||||
assert a.bar == 2
|
||||
|
||||
raises(AttributeError, lambda: a.baz)
|
||||
|
||||
class Bar(Basic, NamedArgsMixin):
|
||||
pass
|
||||
|
||||
raises(AttributeError, lambda: Bar(S(1), S(2)).foo)
|
||||
|
||||
def test_density_constant():
|
||||
assert density(3)(2) == 0
|
||||
assert density(3)(3) == DiracDelta(0)
|
||||
|
||||
def test_cmoment_constant():
|
||||
assert variance(3) == 0
|
||||
assert cmoment(3, 3) == 0
|
||||
assert cmoment(3, 4) == 0
|
||||
x = Symbol('x')
|
||||
assert variance(x) == 0
|
||||
assert cmoment(x, 15) == 0
|
||||
assert cmoment(x, 0) == 1
|
||||
|
||||
def test_moment_constant():
|
||||
assert moment(3, 0) == 1
|
||||
assert moment(3, 1) == 3
|
||||
assert moment(3, 2) == 9
|
||||
x = Symbol('x')
|
||||
assert moment(x, 2) == x**2
|
||||
|
||||
def test_median_constant():
|
||||
assert median(3) == 3
|
||||
x = Symbol('x')
|
||||
assert median(x) == x
|
||||
|
||||
def test_real():
|
||||
x = Normal('x', 0, 1)
|
||||
assert x.is_real
|
||||
|
||||
|
||||
def test_issue_10052():
|
||||
X = Exponential('X', 3)
|
||||
assert P(X < oo) == 1
|
||||
assert P(X > oo) == 0
|
||||
assert P(X < 2, X > oo) == 0
|
||||
assert P(X < oo, X > oo) == 0
|
||||
assert P(X < oo, X > 2) == 1
|
||||
assert P(X < 3, X == 2) == 0
|
||||
raises(ValueError, lambda: P(1))
|
||||
raises(ValueError, lambda: P(X < 1, 2))
|
||||
|
||||
def test_issue_11934():
|
||||
density = {0: .5, 1: .5}
|
||||
X = FiniteRV('X', density)
|
||||
assert E(X) == 0.5
|
||||
assert P( X>= 2) == 0
|
||||
|
||||
def test_issue_8129():
|
||||
X = Exponential('X', 4)
|
||||
assert P(X >= X) == 1
|
||||
assert P(X > X) == 0
|
||||
assert P(X > X+1) == 0
|
||||
|
||||
def test_issue_12237():
|
||||
X = Normal('X', 0, 1)
|
||||
Y = Normal('Y', 0, 1)
|
||||
U = P(X > 0, X)
|
||||
V = P(Y < 0, X)
|
||||
W = P(X + Y > 0, X)
|
||||
assert W == P(X + Y > 0, X)
|
||||
assert U == BernoulliDistribution(S.Half, S.Zero, S.One)
|
||||
assert V == S.Half
|
||||
|
||||
def test_is_random():
|
||||
X = Normal('X', 0, 1)
|
||||
Y = Normal('Y', 0, 1)
|
||||
a, b = symbols('a, b')
|
||||
G = GaussianUnitaryEnsemble('U', 2)
|
||||
B = BernoulliProcess('B', 0.9)
|
||||
assert not is_random(a)
|
||||
assert not is_random(a + b)
|
||||
assert not is_random(a * b)
|
||||
assert not is_random(Matrix([a**2, b**2]))
|
||||
assert is_random(X)
|
||||
assert is_random(X**2 + Y)
|
||||
assert is_random(Y + b**2)
|
||||
assert is_random(Y > 5)
|
||||
assert is_random(B[3] < 1)
|
||||
assert is_random(G)
|
||||
assert is_random(X * Y * B[1])
|
||||
assert is_random(Matrix([[X, B[2]], [G, Y]]))
|
||||
assert is_random(Eq(X, 4))
|
||||
|
||||
def test_issue_12283():
|
||||
x = symbols('x')
|
||||
X = RandomSymbol(x)
|
||||
Y = RandomSymbol('Y')
|
||||
Z = RandomMatrixSymbol('Z', 2, 1)
|
||||
W = RandomMatrixSymbol('W', 2, 1)
|
||||
RI = RandomIndexedSymbol(Indexed('RI', 3))
|
||||
assert pspace(Z) == PSpace()
|
||||
assert pspace(RI) == PSpace()
|
||||
assert pspace(X) == PSpace()
|
||||
assert E(X) == Expectation(X)
|
||||
assert P(Y > 3) == Probability(Y > 3)
|
||||
assert variance(X) == Variance(X)
|
||||
assert variance(RI) == Variance(RI)
|
||||
assert covariance(X, Y) == Covariance(X, Y)
|
||||
assert covariance(W, Z) == Covariance(W, Z)
|
||||
|
||||
def test_issue_6810():
|
||||
X = Die('X', 6)
|
||||
Y = Normal('Y', 0, 1)
|
||||
assert P(Eq(X, 2)) == S(1)/6
|
||||
assert P(Eq(Y, 0)) == 0
|
||||
assert P(Or(X > 2, X < 3)) == 1
|
||||
assert P(And(X > 3, X > 2)) == S(1)/2
|
||||
|
||||
def test_issue_20286():
|
||||
n, p = symbols('n p')
|
||||
B = Binomial('B', n, p)
|
||||
k = Dummy('k', integer = True)
|
||||
eq = Sum(Piecewise((-p**k*(1 - p)**(-k + n)*log(p**k*(1 - p)**(-k + n)*binomial(n, k))*binomial(n, k), (k >= 0) & (k <= n)), (nan, True)), (k, 0, n))
|
||||
assert eq.dummy_eq(H(B))
|
||||
@@ -0,0 +1,763 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.core.numbers import (Float, Rational, oo, pi)
|
||||
from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.integers import ceiling
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.functions.special.error_functions import erf
|
||||
from sympy.functions.special.gamma_functions import (gamma, lowergamma)
|
||||
from sympy.logic.boolalg import (And, Not)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.sets.contains import Contains
|
||||
from sympy.sets.fancysets import Range
|
||||
from sympy.sets.sets import (FiniteSet, Interval)
|
||||
from sympy.stats import (DiscreteMarkovChain, P, TransitionMatrixOf, E,
|
||||
StochasticStateSpaceOf, variance, ContinuousMarkovChain,
|
||||
BernoulliProcess, PoissonProcess, WienerProcess,
|
||||
GammaProcess, sample_stochastic_process)
|
||||
from sympy.stats.joint_rv import JointDistribution
|
||||
from sympy.stats.joint_rv_types import JointDistributionHandmade
|
||||
from sympy.stats.rv import RandomIndexedSymbol
|
||||
from sympy.stats.symbolic_probability import Probability, Expectation
|
||||
from sympy.testing.pytest import (raises, skip, ignore_warnings,
|
||||
warns_deprecated_sympy)
|
||||
from sympy.external import import_module
|
||||
from sympy.stats.frv_types import BernoulliDistribution
|
||||
from sympy.stats.drv_types import PoissonDistribution
|
||||
from sympy.stats.crv_types import NormalDistribution, GammaDistribution
|
||||
from sympy.core.symbol import Str
|
||||
|
||||
|
||||
def test_DiscreteMarkovChain():
|
||||
|
||||
# pass only the name
|
||||
X = DiscreteMarkovChain("X")
|
||||
assert isinstance(X.state_space, Range)
|
||||
assert X.index_set == S.Naturals0
|
||||
assert isinstance(X.transition_probabilities, MatrixSymbol)
|
||||
t = symbols('t', positive=True, integer=True)
|
||||
assert isinstance(X[t], RandomIndexedSymbol)
|
||||
assert E(X[0]) == Expectation(X[0])
|
||||
raises(TypeError, lambda: DiscreteMarkovChain(1))
|
||||
raises(NotImplementedError, lambda: X(t))
|
||||
raises(NotImplementedError, lambda: X.communication_classes())
|
||||
raises(NotImplementedError, lambda: X.canonical_form())
|
||||
raises(NotImplementedError, lambda: X.decompose())
|
||||
|
||||
nz = Symbol('n', integer=True)
|
||||
TZ = MatrixSymbol('M', nz, nz)
|
||||
SZ = Range(nz)
|
||||
YZ = DiscreteMarkovChain('Y', SZ, TZ)
|
||||
assert P(Eq(YZ[2], 1), Eq(YZ[1], 0)) == TZ[0, 1]
|
||||
|
||||
raises(ValueError, lambda: sample_stochastic_process(t))
|
||||
raises(ValueError, lambda: next(sample_stochastic_process(X)))
|
||||
# pass name and state_space
|
||||
# any hashable object should be a valid state
|
||||
# states should be valid as a tuple/set/list/Tuple/Range
|
||||
sym, rainy, cloudy, sunny = symbols('a Rainy Cloudy Sunny', real=True)
|
||||
state_spaces = [(1, 2, 3), [Str('Hello'), sym, DiscreteMarkovChain("Y", (1,2,3))],
|
||||
Tuple(S(1), exp(sym), Str('World'), sympify=False), Range(-1, 5, 2),
|
||||
[rainy, cloudy, sunny]]
|
||||
chains = [DiscreteMarkovChain("Y", state_space) for state_space in state_spaces]
|
||||
|
||||
for i, Y in enumerate(chains):
|
||||
assert isinstance(Y.transition_probabilities, MatrixSymbol)
|
||||
assert Y.state_space == state_spaces[i] or Y.state_space == FiniteSet(*state_spaces[i])
|
||||
assert Y.number_of_states == 3
|
||||
|
||||
with ignore_warnings(UserWarning): # TODO: Restore tests once warnings are removed
|
||||
assert P(Eq(Y[2], 1), Eq(Y[0], 2), evaluate=False) == Probability(Eq(Y[2], 1), Eq(Y[0], 2))
|
||||
assert E(Y[0]) == Expectation(Y[0])
|
||||
|
||||
raises(ValueError, lambda: next(sample_stochastic_process(Y)))
|
||||
|
||||
raises(TypeError, lambda: DiscreteMarkovChain("Y", {1: 1}))
|
||||
Y = DiscreteMarkovChain("Y", Range(1, t, 2))
|
||||
assert Y.number_of_states == ceiling((t-1)/2)
|
||||
|
||||
# pass name and transition_probabilities
|
||||
chains = [DiscreteMarkovChain("Y", trans_probs=Matrix([])),
|
||||
DiscreteMarkovChain("Y", trans_probs=Matrix([[0, 1], [1, 0]])),
|
||||
DiscreteMarkovChain("Y", trans_probs=Matrix([[pi, 1-pi], [sym, 1-sym]]))]
|
||||
for Z in chains:
|
||||
assert Z.number_of_states == Z.transition_probabilities.shape[0]
|
||||
assert isinstance(Z.transition_probabilities, ImmutableMatrix)
|
||||
|
||||
# pass name, state_space and transition_probabilities
|
||||
T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]])
|
||||
TS = MatrixSymbol('T', 3, 3)
|
||||
Y = DiscreteMarkovChain("Y", [0, 1, 2], T)
|
||||
YS = DiscreteMarkovChain("Y", ['One', 'Two', 3], TS)
|
||||
assert Y.joint_distribution(1, Y[2], 3) == JointDistribution(Y[1], Y[2], Y[3])
|
||||
raises(ValueError, lambda: Y.joint_distribution(Y[1].symbol, Y[2].symbol))
|
||||
assert P(Eq(Y[3], 2), Eq(Y[1], 1)).round(2) == Float(0.36, 2)
|
||||
assert (P(Eq(YS[3], 2), Eq(YS[1], 1)) -
|
||||
(TS[0, 2]*TS[1, 0] + TS[1, 1]*TS[1, 2] + TS[1, 2]*TS[2, 2])).simplify() == 0
|
||||
assert P(Eq(YS[1], 1), Eq(YS[2], 2)) == Probability(Eq(YS[1], 1))
|
||||
assert P(Eq(YS[3], 3), Eq(YS[1], 1)) == TS[0, 2]*TS[1, 0] + TS[1, 1]*TS[1, 2] + TS[1, 2]*TS[2, 2]
|
||||
TO = Matrix([[0.25, 0.75, 0],[0, 0.25, 0.75],[0.75, 0, 0.25]])
|
||||
assert P(Eq(Y[3], 2), Eq(Y[1], 1) & TransitionMatrixOf(Y, TO)).round(3) == Float(0.375, 3)
|
||||
with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed
|
||||
assert E(Y[3], evaluate=False) == Expectation(Y[3])
|
||||
assert E(Y[3], Eq(Y[2], 1)).round(2) == Float(1.1, 3)
|
||||
TSO = MatrixSymbol('T', 4, 4)
|
||||
raises(ValueError, lambda: str(P(Eq(YS[3], 2), Eq(YS[1], 1) & TransitionMatrixOf(YS, TSO))))
|
||||
raises(TypeError, lambda: DiscreteMarkovChain("Z", [0, 1, 2], symbols('M')))
|
||||
raises(ValueError, lambda: DiscreteMarkovChain("Z", [0, 1, 2], MatrixSymbol('T', 3, 4)))
|
||||
raises(ValueError, lambda: E(Y[3], Eq(Y[2], 6)))
|
||||
raises(ValueError, lambda: E(Y[2], Eq(Y[3], 1)))
|
||||
|
||||
|
||||
# extended tests for probability queries
|
||||
TO1 = Matrix([[Rational(1, 4), Rational(3, 4), 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)],[0, Rational(1, 4), Rational(3, 4)]])
|
||||
assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)),
|
||||
Eq(Probability(Eq(Y[0], 0)), Rational(1, 4)) & TransitionMatrixOf(Y, TO1)) == Rational(1, 16)
|
||||
assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)), TransitionMatrixOf(Y, TO1)) == \
|
||||
Probability(Eq(Y[0], 0))/4
|
||||
assert P(Lt(X[1], 2) & Gt(X[1], 0), Eq(X[0], 2) &
|
||||
StochasticStateSpaceOf(X, [0, 1, 2]) & TransitionMatrixOf(X, TO1)) == Rational(1, 4)
|
||||
assert P(Lt(X[1], 2) & Gt(X[1], 0), Eq(X[0], 2) &
|
||||
StochasticStateSpaceOf(X, [S(0), '0', 1]) & TransitionMatrixOf(X, TO1)) == Rational(1, 4)
|
||||
assert P(Ne(X[1], 2) & Ne(X[1], 1), Eq(X[0], 2) &
|
||||
StochasticStateSpaceOf(X, [0, 1, 2]) & TransitionMatrixOf(X, TO1)) is S.Zero
|
||||
assert P(Ne(X[1], 2) & Ne(X[1], 1), Eq(X[0], 2) &
|
||||
StochasticStateSpaceOf(X, [S(0), '0', 1]) & TransitionMatrixOf(X, TO1)) is S.Zero
|
||||
assert P(And(Eq(Y[2], 1), Eq(Y[1], 1), Eq(Y[0], 0)), Eq(Y[1], 1)) == 0.1*Probability(Eq(Y[0], 0))
|
||||
|
||||
# testing properties of Markov chain
|
||||
TO2 = Matrix([[S.One, 0, 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)],[0, Rational(1, 4), Rational(3, 4)]])
|
||||
TO3 = Matrix([[Rational(1, 4), Rational(3, 4), 0],[Rational(1, 3), Rational(1, 3), Rational(1, 3)], [0, Rational(1, 4), Rational(3, 4)]])
|
||||
Y2 = DiscreteMarkovChain('Y', trans_probs=TO2)
|
||||
Y3 = DiscreteMarkovChain('Y', trans_probs=TO3)
|
||||
assert Y3.fundamental_matrix() == ImmutableMatrix([[176, 81, -132], [36, 141, -52], [-44, -39, 208]])/125
|
||||
assert Y2.is_absorbing_chain() == True
|
||||
assert Y3.is_absorbing_chain() == False
|
||||
assert Y2.canonical_form() == ([0, 1, 2], TO2)
|
||||
assert Y3.canonical_form() == ([0, 1, 2], TO3)
|
||||
assert Y2.decompose() == ([0, 1, 2], TO2[0:1, 0:1], TO2[1:3, 0:1], TO2[1:3, 1:3])
|
||||
assert Y3.decompose() == ([0, 1, 2], TO3, Matrix(0, 3, []), Matrix(0, 0, []))
|
||||
TO4 = Matrix([[Rational(1, 5), Rational(2, 5), Rational(2, 5)], [Rational(1, 10), S.Half, Rational(2, 5)], [Rational(3, 5), Rational(3, 10), Rational(1, 10)]])
|
||||
Y4 = DiscreteMarkovChain('Y', trans_probs=TO4)
|
||||
w = ImmutableMatrix([[Rational(11, 39), Rational(16, 39), Rational(4, 13)]])
|
||||
assert Y4.limiting_distribution == w
|
||||
assert Y4.is_regular() == True
|
||||
assert Y4.is_ergodic() == True
|
||||
TS1 = MatrixSymbol('T', 3, 3)
|
||||
Y5 = DiscreteMarkovChain('Y', trans_probs=TS1)
|
||||
assert Y5.limiting_distribution(w, TO4).doit() == True
|
||||
assert Y5.stationary_distribution(condition_set=True).subs(TS1, TO4).contains(w).doit() == S.true
|
||||
TO6 = Matrix([[S.One, 0, 0, 0, 0],[S.Half, 0, S.Half, 0, 0],[0, S.Half, 0, S.Half, 0], [0, 0, S.Half, 0, S.Half], [0, 0, 0, 0, 1]])
|
||||
Y6 = DiscreteMarkovChain('Y', trans_probs=TO6)
|
||||
assert Y6.fundamental_matrix() == ImmutableMatrix([[Rational(3, 2), S.One, S.Half], [S.One, S(2), S.One], [S.Half, S.One, Rational(3, 2)]])
|
||||
assert Y6.absorbing_probabilities() == ImmutableMatrix([[Rational(3, 4), Rational(1, 4)], [S.Half, S.Half], [Rational(1, 4), Rational(3, 4)]])
|
||||
with warns_deprecated_sympy():
|
||||
Y6.absorbing_probabilites()
|
||||
TO7 = Matrix([[Rational(1, 2), Rational(1, 4), Rational(1, 4)], [Rational(1, 2), 0, Rational(1, 2)], [Rational(1, 4), Rational(1, 4), Rational(1, 2)]])
|
||||
Y7 = DiscreteMarkovChain('Y', trans_probs=TO7)
|
||||
assert Y7.is_absorbing_chain() == False
|
||||
assert Y7.fundamental_matrix() == ImmutableMatrix([[Rational(86, 75), Rational(1, 25), Rational(-14, 75)],
|
||||
[Rational(2, 25), Rational(21, 25), Rational(2, 25)],
|
||||
[Rational(-14, 75), Rational(1, 25), Rational(86, 75)]])
|
||||
|
||||
# test for zero-sized matrix functionality
|
||||
X = DiscreteMarkovChain('X', trans_probs=Matrix([]))
|
||||
assert X.number_of_states == 0
|
||||
assert X.stationary_distribution() == Matrix([[]])
|
||||
assert X.communication_classes() == []
|
||||
assert X.canonical_form() == ([], Matrix([]))
|
||||
assert X.decompose() == ([], Matrix([]), Matrix([]), Matrix([]))
|
||||
assert X.is_regular() == False
|
||||
assert X.is_ergodic() == False
|
||||
|
||||
# test communication_class
|
||||
# see https://drive.google.com/drive/folders/1HbxLlwwn2b3U8Lj7eb_ASIUb5vYaNIjg?usp=sharing
|
||||
# tutorial 2.pdf
|
||||
TO7 = Matrix([[0, 5, 5, 0, 0],
|
||||
[0, 0, 0, 10, 0],
|
||||
[5, 0, 5, 0, 0],
|
||||
[0, 10, 0, 0, 0],
|
||||
[0, 3, 0, 3, 4]])/10
|
||||
Y7 = DiscreteMarkovChain('Y', trans_probs=TO7)
|
||||
tuples = Y7.communication_classes()
|
||||
classes, recurrence, periods = list(zip(*tuples))
|
||||
assert classes == ([1, 3], [0, 2], [4])
|
||||
assert recurrence == (True, False, False)
|
||||
assert periods == (2, 1, 1)
|
||||
|
||||
TO8 = Matrix([[0, 0, 0, 10, 0, 0],
|
||||
[5, 0, 5, 0, 0, 0],
|
||||
[0, 4, 0, 0, 0, 6],
|
||||
[10, 0, 0, 0, 0, 0],
|
||||
[0, 10, 0, 0, 0, 0],
|
||||
[0, 0, 0, 5, 5, 0]])/10
|
||||
Y8 = DiscreteMarkovChain('Y', trans_probs=TO8)
|
||||
tuples = Y8.communication_classes()
|
||||
classes, recurrence, periods = list(zip(*tuples))
|
||||
assert classes == ([0, 3], [1, 2, 5, 4])
|
||||
assert recurrence == (True, False)
|
||||
assert periods == (2, 2)
|
||||
|
||||
TO9 = Matrix([[2, 0, 0, 3, 0, 0, 3, 2, 0, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 2, 2, 0, 0, 0, 0, 0, 3, 3],
|
||||
[0, 0, 0, 3, 0, 0, 6, 1, 0, 0],
|
||||
[0, 0, 0, 0, 5, 5, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 10, 0, 0, 0, 0],
|
||||
[4, 0, 0, 5, 0, 0, 1, 0, 0, 0],
|
||||
[2, 0, 0, 4, 0, 0, 2, 2, 0, 0],
|
||||
[3, 0, 1, 0, 0, 0, 0, 0, 4, 2],
|
||||
[0, 0, 4, 0, 0, 0, 0, 0, 3, 3]])/10
|
||||
Y9 = DiscreteMarkovChain('Y', trans_probs=TO9)
|
||||
tuples = Y9.communication_classes()
|
||||
classes, recurrence, periods = list(zip(*tuples))
|
||||
assert classes == ([0, 3, 6, 7], [1], [2, 8, 9], [5], [4])
|
||||
assert recurrence == (True, True, False, True, False)
|
||||
assert periods == (1, 1, 1, 1, 1)
|
||||
|
||||
# test canonical form
|
||||
# see https://web.archive.org/web/20201230182007/https://www.dartmouth.edu/~chance/teaching_aids/books_articles/probability_book/Chapter11.pdf
|
||||
# example 11.13
|
||||
T = Matrix([[1, 0, 0, 0, 0],
|
||||
[S(1) / 2, 0, S(1) / 2, 0, 0],
|
||||
[0, S(1) / 2, 0, S(1) / 2, 0],
|
||||
[0, 0, S(1) / 2, 0, S(1) / 2],
|
||||
[0, 0, 0, 0, S(1)]])
|
||||
DW = DiscreteMarkovChain('DW', [0, 1, 2, 3, 4], T)
|
||||
states, A, B, C = DW.decompose()
|
||||
assert states == [0, 4, 1, 2, 3]
|
||||
assert A == Matrix([[1, 0], [0, 1]])
|
||||
assert B == Matrix([[S(1)/2, 0], [0, 0], [0, S(1)/2]])
|
||||
assert C == Matrix([[0, S(1)/2, 0], [S(1)/2, 0, S(1)/2], [0, S(1)/2, 0]])
|
||||
states, new_matrix = DW.canonical_form()
|
||||
assert states == [0, 4, 1, 2, 3]
|
||||
assert new_matrix == Matrix([[1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[S(1)/2, 0, 0, S(1)/2, 0],
|
||||
[0, 0, S(1)/2, 0, S(1)/2],
|
||||
[0, S(1)/2, 0, S(1)/2, 0]])
|
||||
|
||||
# test regular and ergodic
|
||||
# https://web.archive.org/web/20201230182007/https://www.dartmouth.edu/~chance/teaching_aids/books_articles/probability_book/Chapter11.pdf
|
||||
T = Matrix([[0, 4, 0, 0, 0],
|
||||
[1, 0, 3, 0, 0],
|
||||
[0, 2, 0, 2, 0],
|
||||
[0, 0, 3, 0, 1],
|
||||
[0, 0, 0, 4, 0]])/4
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert not X.is_regular()
|
||||
assert X.is_ergodic()
|
||||
T = Matrix([[0, 1], [1, 0]])
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert not X.is_regular()
|
||||
assert X.is_ergodic()
|
||||
# http://www.math.wisc.edu/~valko/courses/331/MC2.pdf
|
||||
T = Matrix([[2, 1, 1],
|
||||
[2, 0, 2],
|
||||
[1, 1, 2]])/4
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert X.is_regular()
|
||||
assert X.is_ergodic()
|
||||
# https://docs.ufpr.br/~lucambio/CE222/1S2014/Kemeny-Snell1976.pdf
|
||||
T = Matrix([[1, 1], [1, 1]])/2
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert X.is_regular()
|
||||
assert X.is_ergodic()
|
||||
|
||||
# test is_absorbing_chain
|
||||
T = Matrix([[0, 1, 0],
|
||||
[1, 0, 0],
|
||||
[0, 0, 1]])
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert not X.is_absorbing_chain()
|
||||
# https://en.wikipedia.org/wiki/Absorbing_Markov_chain
|
||||
T = Matrix([[1, 1, 0, 0],
|
||||
[0, 1, 1, 0],
|
||||
[1, 0, 0, 1],
|
||||
[0, 0, 0, 2]])/2
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert X.is_absorbing_chain()
|
||||
T = Matrix([[2, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 0, 2]])/2
|
||||
X = DiscreteMarkovChain('X', trans_probs=T)
|
||||
assert X.is_absorbing_chain()
|
||||
|
||||
# test custom state space
|
||||
Y10 = DiscreteMarkovChain('Y', [1, 2, 3], TO2)
|
||||
tuples = Y10.communication_classes()
|
||||
classes, recurrence, periods = list(zip(*tuples))
|
||||
assert classes == ([1], [2, 3])
|
||||
assert recurrence == (True, False)
|
||||
assert periods == (1, 1)
|
||||
assert Y10.canonical_form() == ([1, 2, 3], TO2)
|
||||
assert Y10.decompose() == ([1, 2, 3], TO2[0:1, 0:1], TO2[1:3, 0:1], TO2[1:3, 1:3])
|
||||
|
||||
# testing miscellaneous queries
|
||||
T = Matrix([[S.Half, Rational(1, 4), Rational(1, 4)],
|
||||
[Rational(1, 3), 0, Rational(2, 3)],
|
||||
[S.Half, S.Half, 0]])
|
||||
X = DiscreteMarkovChain('X', [0, 1, 2], T)
|
||||
assert P(Eq(X[1], 2) & Eq(X[2], 1) & Eq(X[3], 0),
|
||||
Eq(P(Eq(X[1], 0)), Rational(1, 4)) & Eq(P(Eq(X[1], 1)), Rational(1, 4))) == Rational(1, 12)
|
||||
assert P(Eq(X[2], 1) | Eq(X[2], 2), Eq(X[1], 1)) == Rational(2, 3)
|
||||
assert P(Eq(X[2], 1) & Eq(X[2], 2), Eq(X[1], 1)) is S.Zero
|
||||
assert P(Ne(X[2], 2), Eq(X[1], 1)) == Rational(1, 3)
|
||||
assert E(X[1]**2, Eq(X[0], 1)) == Rational(8, 3)
|
||||
assert variance(X[1], Eq(X[0], 1)) == Rational(8, 9)
|
||||
raises(ValueError, lambda: E(X[1], Eq(X[2], 1)))
|
||||
raises(ValueError, lambda: DiscreteMarkovChain('X', [0, 1], T))
|
||||
|
||||
# testing miscellaneous queries with different state space
|
||||
X = DiscreteMarkovChain('X', ['A', 'B', 'C'], T)
|
||||
assert P(Eq(X[1], 2) & Eq(X[2], 1) & Eq(X[3], 0),
|
||||
Eq(P(Eq(X[1], 0)), Rational(1, 4)) & Eq(P(Eq(X[1], 1)), Rational(1, 4))) == Rational(1, 12)
|
||||
assert P(Eq(X[2], 1) | Eq(X[2], 2), Eq(X[1], 1)) == Rational(2, 3)
|
||||
assert P(Eq(X[2], 1) & Eq(X[2], 2), Eq(X[1], 1)) is S.Zero
|
||||
assert P(Ne(X[2], 2), Eq(X[1], 1)) == Rational(1, 3)
|
||||
a = X.state_space.args[0]
|
||||
c = X.state_space.args[2]
|
||||
assert (E(X[1] ** 2, Eq(X[0], 1)) - (a**2/3 + 2*c**2/3)).simplify() == 0
|
||||
assert (variance(X[1], Eq(X[0], 1)) - (2*(-a/3 + c/3)**2/3 + (2*a/3 - 2*c/3)**2/3)).simplify() == 0
|
||||
raises(ValueError, lambda: E(X[1], Eq(X[2], 1)))
|
||||
|
||||
#testing queries with multiple RandomIndexedSymbols
|
||||
T = Matrix([[Rational(5, 10), Rational(3, 10), Rational(2, 10)], [Rational(2, 10), Rational(7, 10), Rational(1, 10)], [Rational(3, 10), Rational(3, 10), Rational(4, 10)]])
|
||||
Y = DiscreteMarkovChain("Y", [0, 1, 2], T)
|
||||
assert P(Eq(Y[7], Y[5]), Eq(Y[2], 0)).round(5) == Float(0.44428, 5)
|
||||
assert P(Gt(Y[3], Y[1]), Eq(Y[0], 0)).round(2) == Float(0.36, 2)
|
||||
assert P(Le(Y[5], Y[10]), Eq(Y[4], 2)).round(6) == Float(0.583120, 6)
|
||||
assert Float(P(Eq(Y[10], Y[5]), Eq(Y[4], 1)), 14) == Float(1 - P(Ne(Y[10], Y[5]), Eq(Y[4], 1)), 14)
|
||||
assert Float(P(Gt(Y[8], Y[9]), Eq(Y[3], 2)), 14) == Float(1 - P(Le(Y[8], Y[9]), Eq(Y[3], 2)), 14)
|
||||
assert Float(P(Lt(Y[1], Y[4]), Eq(Y[0], 0)), 14) == Float(1 - P(Ge(Y[1], Y[4]), Eq(Y[0], 0)), 14)
|
||||
assert P(Eq(Y[5], Y[10]), Eq(Y[2], 1)) == P(Eq(Y[10], Y[5]), Eq(Y[2], 1))
|
||||
assert P(Gt(Y[1], Y[2]), Eq(Y[0], 1)) == P(Lt(Y[2], Y[1]), Eq(Y[0], 1))
|
||||
assert P(Ge(Y[7], Y[6]), Eq(Y[4], 1)) == P(Le(Y[6], Y[7]), Eq(Y[4], 1))
|
||||
|
||||
#test symbolic queries
|
||||
a, b, c, d = symbols('a b c d')
|
||||
T = Matrix([[Rational(1, 10), Rational(4, 10), Rational(5, 10)], [Rational(3, 10), Rational(4, 10), Rational(3, 10)], [Rational(7, 10), Rational(2, 10), Rational(1, 10)]])
|
||||
Y = DiscreteMarkovChain("Y", [0, 1, 2], T)
|
||||
query = P(Eq(Y[a], b), Eq(Y[c], d))
|
||||
assert query.subs({a:10, b:2, c:5, d:1}).evalf().round(4) == P(Eq(Y[10], 2), Eq(Y[5], 1)).round(4)
|
||||
assert query.subs({a:15, b:0, c:10, d:1}).evalf().round(4) == P(Eq(Y[15], 0), Eq(Y[10], 1)).round(4)
|
||||
query_gt = P(Gt(Y[a], b), Eq(Y[c], d))
|
||||
query_le = P(Le(Y[a], b), Eq(Y[c], d))
|
||||
assert query_gt.subs({a:5, b:2, c:1, d:0}).evalf() + query_le.subs({a:5, b:2, c:1, d:0}).evalf() == 1.0
|
||||
query_ge = P(Ge(Y[a], b), Eq(Y[c], d))
|
||||
query_lt = P(Lt(Y[a], b), Eq(Y[c], d))
|
||||
assert query_ge.subs({a:4, b:1, c:0, d:2}).evalf() + query_lt.subs({a:4, b:1, c:0, d:2}).evalf() == 1.0
|
||||
|
||||
#test issue 20078
|
||||
assert (2*Y[1] + 3*Y[1]).simplify() == 5*Y[1]
|
||||
assert (2*Y[1] - 3*Y[1]).simplify() == -Y[1]
|
||||
assert (2*(0.25*Y[1])).simplify() == 0.5*Y[1]
|
||||
assert ((2*Y[1]) * (0.25*Y[1])).simplify() == 0.5*Y[1]**2
|
||||
assert (Y[1]**2 + Y[1]**3).simplify() == (Y[1] + 1)*Y[1]**2
|
||||
|
||||
def test_sample_stochastic_process():
|
||||
if not import_module('scipy'):
|
||||
skip('SciPy Not installed. Skip sampling tests')
|
||||
import random
|
||||
random.seed(0)
|
||||
numpy = import_module('numpy')
|
||||
if numpy:
|
||||
numpy.random.seed(0) # scipy uses numpy to sample so to set its seed
|
||||
T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]])
|
||||
Y = DiscreteMarkovChain("Y", [0, 1, 2], T)
|
||||
for samps in range(10):
|
||||
assert next(sample_stochastic_process(Y)) in Y.state_space
|
||||
Z = DiscreteMarkovChain("Z", ['1', 1, 0], T)
|
||||
for samps in range(10):
|
||||
assert next(sample_stochastic_process(Z)) in Z.state_space
|
||||
|
||||
T = Matrix([[S.Half, Rational(1, 4), Rational(1, 4)],
|
||||
[Rational(1, 3), 0, Rational(2, 3)],
|
||||
[S.Half, S.Half, 0]])
|
||||
X = DiscreteMarkovChain('X', [0, 1, 2], T)
|
||||
for samps in range(10):
|
||||
assert next(sample_stochastic_process(X)) in X.state_space
|
||||
W = DiscreteMarkovChain('W', [1, pi, oo], T)
|
||||
for samps in range(10):
|
||||
assert next(sample_stochastic_process(W)) in W.state_space
|
||||
|
||||
|
||||
def test_ContinuousMarkovChain():
|
||||
T1 = Matrix([[S(-2), S(2), S.Zero],
|
||||
[S.Zero, S.NegativeOne, S.One],
|
||||
[Rational(3, 2), Rational(3, 2), S(-3)]])
|
||||
C1 = ContinuousMarkovChain('C', [0, 1, 2], T1)
|
||||
assert C1.limiting_distribution() == ImmutableMatrix([[Rational(3, 19), Rational(12, 19), Rational(4, 19)]])
|
||||
|
||||
T2 = Matrix([[-S.One, S.One, S.Zero], [S.One, -S.One, S.Zero], [S.Zero, S.One, -S.One]])
|
||||
C2 = ContinuousMarkovChain('C', [0, 1, 2], T2)
|
||||
A, t = C2.generator_matrix, symbols('t', positive=True)
|
||||
assert C2.transition_probabilities(A)(t) == Matrix([[S.Half + exp(-2*t)/2, S.Half - exp(-2*t)/2, 0],
|
||||
[S.Half - exp(-2*t)/2, S.Half + exp(-2*t)/2, 0],
|
||||
[S.Half - exp(-t) + exp(-2*t)/2, S.Half - exp(-2*t)/2, exp(-t)]])
|
||||
with ignore_warnings(UserWarning): ### TODO: Restore tests once warnings are removed
|
||||
assert P(Eq(C2(1), 1), Eq(C2(0), 1), evaluate=False) == Probability(Eq(C2(1), 1), Eq(C2(0), 1))
|
||||
assert P(Eq(C2(1), 1), Eq(C2(0), 1)) == exp(-2)/2 + S.Half
|
||||
assert P(Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 1),
|
||||
Eq(P(Eq(C2(1), 0)), S.Half)) == (Rational(1, 4) - exp(-2)/4)*(exp(-2)/2 + S.Half)
|
||||
assert P(Not(Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 2)) |
|
||||
(Eq(C2(1), 0) & Eq(C2(2), 1) & Eq(C2(3), 2)),
|
||||
Eq(P(Eq(C2(1), 0)), Rational(1, 4)) & Eq(P(Eq(C2(1), 1)), Rational(1, 4))) is S.One
|
||||
assert E(C2(Rational(3, 2)), Eq(C2(0), 2)) == -exp(-3)/2 + 2*exp(Rational(-3, 2)) + S.Half
|
||||
assert variance(C2(Rational(3, 2)), Eq(C2(0), 1)) == ((S.Half - exp(-3)/2)**2*(exp(-3)/2 + S.Half)
|
||||
+ (Rational(-1, 2) - exp(-3)/2)**2*(S.Half - exp(-3)/2))
|
||||
raises(KeyError, lambda: P(Eq(C2(1), 0), Eq(P(Eq(C2(1), 1)), S.Half)))
|
||||
assert P(Eq(C2(1), 0), Eq(P(Eq(C2(5), 1)), S.Half)) == Probability(Eq(C2(1), 0))
|
||||
TS1 = MatrixSymbol('G', 3, 3)
|
||||
CS1 = ContinuousMarkovChain('C', [0, 1, 2], TS1)
|
||||
A = CS1.generator_matrix
|
||||
assert CS1.transition_probabilities(A)(t) == exp(t*A)
|
||||
|
||||
C3 = ContinuousMarkovChain('C', [Symbol('0'), Symbol('1'), Symbol('2')], T2)
|
||||
assert P(Eq(C3(1), 1), Eq(C3(0), 1)) == exp(-2)/2 + S.Half
|
||||
assert P(Eq(C3(1), Symbol('1')), Eq(C3(0), Symbol('1'))) == exp(-2)/2 + S.Half
|
||||
|
||||
#test probability queries
|
||||
G = Matrix([[-S(1), Rational(1, 10), Rational(9, 10)], [Rational(2, 5), -S(1), Rational(3, 5)], [Rational(1, 2), Rational(1, 2), -S(1)]])
|
||||
C = ContinuousMarkovChain('C', state_space=[0, 1, 2], gen_mat=G)
|
||||
assert P(Eq(C(7.385), C(3.19)), Eq(C(0.862), 0)).round(5) == Float(0.35469, 5)
|
||||
assert P(Gt(C(98.715), C(19.807)), Eq(C(11.314), 2)).round(5) == Float(0.32452, 5)
|
||||
assert P(Le(C(5.9), C(10.112)), Eq(C(4), 1)).round(6) == Float(0.675214, 6)
|
||||
assert Float(P(Eq(C(7.32), C(2.91)), Eq(C(2.63), 1)), 14) == Float(1 - P(Ne(C(7.32), C(2.91)), Eq(C(2.63), 1)), 14)
|
||||
assert Float(P(Gt(C(3.36), C(1.101)), Eq(C(0.8), 2)), 14) == Float(1 - P(Le(C(3.36), C(1.101)), Eq(C(0.8), 2)), 14)
|
||||
assert Float(P(Lt(C(4.9), C(2.79)), Eq(C(1.61), 0)), 14) == Float(1 - P(Ge(C(4.9), C(2.79)), Eq(C(1.61), 0)), 14)
|
||||
assert P(Eq(C(5.243), C(10.912)), Eq(C(2.174), 1)) == P(Eq(C(10.912), C(5.243)), Eq(C(2.174), 1))
|
||||
assert P(Gt(C(2.344), C(9.9)), Eq(C(1.102), 1)) == P(Lt(C(9.9), C(2.344)), Eq(C(1.102), 1))
|
||||
assert P(Ge(C(7.87), C(1.008)), Eq(C(0.153), 1)) == P(Le(C(1.008), C(7.87)), Eq(C(0.153), 1))
|
||||
|
||||
#test symbolic queries
|
||||
a, b, c, d = symbols('a b c d')
|
||||
query = P(Eq(C(a), b), Eq(C(c), d))
|
||||
assert query.subs({a:3.65, b:2, c:1.78, d:1}).evalf().round(10) == P(Eq(C(3.65), 2), Eq(C(1.78), 1)).round(10)
|
||||
query_gt = P(Gt(C(a), b), Eq(C(c), d))
|
||||
query_le = P(Le(C(a), b), Eq(C(c), d))
|
||||
assert query_gt.subs({a:13.2, b:0, c:3.29, d:2}).evalf() + query_le.subs({a:13.2, b:0, c:3.29, d:2}).evalf() == 1.0
|
||||
query_ge = P(Ge(C(a), b), Eq(C(c), d))
|
||||
query_lt = P(Lt(C(a), b), Eq(C(c), d))
|
||||
assert query_ge.subs({a:7.43, b:1, c:1.45, d:0}).evalf() + query_lt.subs({a:7.43, b:1, c:1.45, d:0}).evalf() == 1.0
|
||||
|
||||
#test issue 20078
|
||||
assert (2*C(1) + 3*C(1)).simplify() == 5*C(1)
|
||||
assert (2*C(1) - 3*C(1)).simplify() == -C(1)
|
||||
assert (2*(0.25*C(1))).simplify() == 0.5*C(1)
|
||||
assert (2*C(1) * 0.25*C(1)).simplify() == 0.5*C(1)**2
|
||||
assert (C(1)**2 + C(1)**3).simplify() == (C(1) + 1)*C(1)**2
|
||||
|
||||
def test_BernoulliProcess():
|
||||
|
||||
B = BernoulliProcess("B", p=0.6, success=1, failure=0)
|
||||
assert B.state_space == FiniteSet(0, 1)
|
||||
assert B.index_set == S.Naturals0
|
||||
assert B.success == 1
|
||||
assert B.failure == 0
|
||||
|
||||
X = BernoulliProcess("X", p=Rational(1,3), success='H', failure='T')
|
||||
assert X.state_space == FiniteSet('H', 'T')
|
||||
H, T = symbols("H,T")
|
||||
assert E(X[1]+X[2]*X[3]) == H**2/9 + 4*H*T/9 + H/3 + 4*T**2/9 + 2*T/3
|
||||
|
||||
t, x = symbols('t, x', positive=True, integer=True)
|
||||
assert isinstance(B[t], RandomIndexedSymbol)
|
||||
|
||||
raises(ValueError, lambda: BernoulliProcess("X", p=1.1, success=1, failure=0))
|
||||
raises(NotImplementedError, lambda: B(t))
|
||||
|
||||
raises(IndexError, lambda: B[-3])
|
||||
assert B.joint_distribution(B[3], B[9]) == JointDistributionHandmade(Lambda((B[3], B[9]),
|
||||
Piecewise((0.6, Eq(B[3], 1)), (0.4, Eq(B[3], 0)), (0, True))
|
||||
*Piecewise((0.6, Eq(B[9], 1)), (0.4, Eq(B[9], 0)), (0, True))))
|
||||
|
||||
assert B.joint_distribution(2, B[4]) == JointDistributionHandmade(Lambda((B[2], B[4]),
|
||||
Piecewise((0.6, Eq(B[2], 1)), (0.4, Eq(B[2], 0)), (0, True))
|
||||
*Piecewise((0.6, Eq(B[4], 1)), (0.4, Eq(B[4], 0)), (0, True))))
|
||||
|
||||
# Test for the sum distribution of Bernoulli Process RVs
|
||||
Y = B[1] + B[2] + B[3]
|
||||
assert P(Eq(Y, 0)).round(2) == Float(0.06, 1)
|
||||
assert P(Eq(Y, 2)).round(2) == Float(0.43, 2)
|
||||
assert P(Eq(Y, 4)).round(2) == 0
|
||||
assert P(Gt(Y, 1)).round(2) == Float(0.65, 2)
|
||||
# Test for independency of each Random Indexed variable
|
||||
assert P(Eq(B[1], 0) & Eq(B[2], 1) & Eq(B[3], 0) & Eq(B[4], 1)).round(2) == Float(0.06, 1)
|
||||
|
||||
assert E(2 * B[1] + B[2]).round(2) == Float(1.80, 3)
|
||||
assert E(2 * B[1] + B[2] + 5).round(2) == Float(6.80, 3)
|
||||
assert E(B[2] * B[4] + B[10]).round(2) == Float(0.96, 2)
|
||||
assert E(B[2] > 0, Eq(B[1],1) & Eq(B[2],1)).round(2) == Float(0.60,2)
|
||||
assert E(B[1]) == 0.6
|
||||
assert P(B[1] > 0).round(2) == Float(0.60, 2)
|
||||
assert P(B[1] < 1).round(2) == Float(0.40, 2)
|
||||
assert P(B[1] > 0, B[2] <= 1).round(2) == Float(0.60, 2)
|
||||
assert P(B[12] * B[5] > 0).round(2) == Float(0.36, 2)
|
||||
assert P(B[12] * B[5] > 0, B[4] < 1).round(2) == Float(0.36, 2)
|
||||
assert P(Eq(B[2], 1), B[2] > 0) == 1.0
|
||||
assert P(Eq(B[5], 3)) == 0
|
||||
assert P(Eq(B[1], 1), B[1] < 0) == 0
|
||||
assert P(B[2] > 0, Eq(B[2], 1)) == 1
|
||||
assert P(B[2] < 0, Eq(B[2], 1)) == 0
|
||||
assert P(B[2] > 0, B[2]==7) == 0
|
||||
assert P(B[5] > 0, B[5]) == BernoulliDistribution(0.6, 0, 1)
|
||||
raises(ValueError, lambda: P(3))
|
||||
raises(ValueError, lambda: P(B[3] > 0, 3))
|
||||
|
||||
# test issue 19456
|
||||
expr = Sum(B[t], (t, 0, 4))
|
||||
expr2 = Sum(B[t], (t, 1, 3))
|
||||
expr3 = Sum(B[t]**2, (t, 1, 3))
|
||||
assert expr.doit() == B[0] + B[1] + B[2] + B[3] + B[4]
|
||||
assert expr2.doit() == Y
|
||||
assert expr3.doit() == B[1]**2 + B[2]**2 + B[3]**2
|
||||
assert B[2*t].free_symbols == {B[2*t], t}
|
||||
assert B[4].free_symbols == {B[4]}
|
||||
assert B[x*t].free_symbols == {B[x*t], x, t}
|
||||
|
||||
#test issue 20078
|
||||
assert (2*B[t] + 3*B[t]).simplify() == 5*B[t]
|
||||
assert (2*B[t] - 3*B[t]).simplify() == -B[t]
|
||||
assert (2*(0.25*B[t])).simplify() == 0.5*B[t]
|
||||
assert (2*B[t] * 0.25*B[t]).simplify() == 0.5*B[t]**2
|
||||
assert (B[t]**2 + B[t]**3).simplify() == (B[t] + 1)*B[t]**2
|
||||
|
||||
def test_PoissonProcess():
|
||||
X = PoissonProcess("X", 3)
|
||||
assert X.state_space == S.Naturals0
|
||||
assert X.index_set == Interval(0, oo)
|
||||
assert X.lamda == 3
|
||||
|
||||
t, d, x, y = symbols('t d x y', positive=True)
|
||||
assert isinstance(X(t), RandomIndexedSymbol)
|
||||
assert X.distribution(t) == PoissonDistribution(3*t)
|
||||
with warns_deprecated_sympy():
|
||||
X.distribution(X(t))
|
||||
raises(ValueError, lambda: PoissonProcess("X", -1))
|
||||
raises(NotImplementedError, lambda: X[t])
|
||||
raises(IndexError, lambda: X(-5))
|
||||
|
||||
assert X.joint_distribution(X(2), X(3)) == JointDistributionHandmade(Lambda((X(2), X(3)),
|
||||
6**X(2)*9**X(3)*exp(-15)/(factorial(X(2))*factorial(X(3)))))
|
||||
|
||||
assert X.joint_distribution(4, 6) == JointDistributionHandmade(Lambda((X(4), X(6)),
|
||||
12**X(4)*18**X(6)*exp(-30)/(factorial(X(4))*factorial(X(6)))))
|
||||
|
||||
assert P(X(t) < 1) == exp(-3*t)
|
||||
assert P(Eq(X(t), 0), Contains(t, Interval.Lopen(3, 5))) == exp(-6) # exp(-2*lamda)
|
||||
res = P(Eq(X(t), 1), Contains(t, Interval.Lopen(3, 4)))
|
||||
assert res == 3*exp(-3)
|
||||
|
||||
# Equivalent to P(Eq(X(t), 1))**4 because of non-overlapping intervals
|
||||
assert P(Eq(X(t), 1) & Eq(X(d), 1) & Eq(X(x), 1) & Eq(X(y), 1), Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Lopen(1, 2)) & Contains(x, Interval.Lopen(2, 3))
|
||||
& Contains(y, Interval.Lopen(3, 4))) == res**4
|
||||
|
||||
# Return Probability because of overlapping intervals
|
||||
assert P(Eq(X(t), 2) & Eq(X(d), 3), Contains(t, Interval.Lopen(0, 2))
|
||||
& Contains(d, Interval.Ropen(2, 4))) == \
|
||||
Probability(Eq(X(d), 3) & Eq(X(t), 2), Contains(t, Interval.Lopen(0, 2))
|
||||
& Contains(d, Interval.Ropen(2, 4)))
|
||||
|
||||
raises(ValueError, lambda: P(Eq(X(t), 2) & Eq(X(d), 3),
|
||||
Contains(t, Interval.Lopen(0, 4)) & Contains(d, Interval.Lopen(3, oo)))) # no bound on d
|
||||
assert P(Eq(X(3), 2)) == 81*exp(-9)/2
|
||||
assert P(Eq(X(t), 2), Contains(t, Interval.Lopen(0, 5))) == 225*exp(-15)/2
|
||||
|
||||
# Check that probability works correctly by adding it to 1
|
||||
res1 = P(X(t) <= 3, Contains(t, Interval.Lopen(0, 5)))
|
||||
res2 = P(X(t) > 3, Contains(t, Interval.Lopen(0, 5)))
|
||||
assert res1 == 691*exp(-15)
|
||||
assert (res1 + res2).simplify() == 1
|
||||
|
||||
# Check Not and Or
|
||||
assert P(Not(Eq(X(t), 2) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) & \
|
||||
Contains(d, Interval.Lopen(7, 8))).simplify() == -18*exp(-6) + 234*exp(-9) + 1
|
||||
assert P(Eq(X(t), 2) | Ne(X(t), 4), Contains(t, Interval.Ropen(2, 4))) == 1 - 36*exp(-6)
|
||||
raises(ValueError, lambda: P(X(t) > 2, X(t) + X(d)))
|
||||
assert E(X(t)) == 3*t # property of the distribution at a given timestamp
|
||||
assert E(X(t)**2 + X(d)*2 + X(y)**3, Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Lopen(1, 2)) & Contains(y, Interval.Ropen(3, 4))) == 75
|
||||
assert E(X(t)**2, Contains(t, Interval.Lopen(0, 1))) == 12
|
||||
assert E(x*(X(t) + X(d))*(X(t)**2+X(d)**2), Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Ropen(1, 2))) == \
|
||||
Expectation(x*(X(d) + X(t))*(X(d)**2 + X(t)**2), Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Ropen(1, 2)))
|
||||
|
||||
# Value Error because of infinite time bound
|
||||
raises(ValueError, lambda: E(X(t)**3, Contains(t, Interval.Lopen(1, oo))))
|
||||
|
||||
# Equivalent to E(X(t)**2) - E(X(d)**2) == E(X(1)**2) - E(X(1)**2) == 0
|
||||
assert E((X(t) + X(d))*(X(t) - X(d)), Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Lopen(1, 2))) == 0
|
||||
assert E(X(2) + x*E(X(5))) == 15*x + 6
|
||||
assert E(x*X(1) + y) == 3*x + y
|
||||
assert P(Eq(X(1), 2) & Eq(X(t), 3), Contains(t, Interval.Lopen(1, 2))) == 81*exp(-6)/4
|
||||
Y = PoissonProcess("Y", 6)
|
||||
Z = X + Y
|
||||
assert Z.lamda == X.lamda + Y.lamda == 9
|
||||
raises(ValueError, lambda: X + 5) # should be added be only PoissonProcess instance
|
||||
N, M = Z.split(4, 5)
|
||||
assert N.lamda == 4
|
||||
assert M.lamda == 5
|
||||
raises(ValueError, lambda: Z.split(3, 2)) # 2+3 != 9
|
||||
|
||||
raises(ValueError, lambda :P(Eq(X(t), 0), Contains(t, Interval.Lopen(1, 3)) & Eq(X(1), 0)))
|
||||
# check if it handles queries with two random variables in one args
|
||||
res1 = P(Eq(N(3), N(5)))
|
||||
assert res1 == P(Eq(N(t), 0), Contains(t, Interval(3, 5)))
|
||||
res2 = P(N(3) > N(1))
|
||||
assert res2 == P((N(t) > 0), Contains(t, Interval(1, 3)))
|
||||
assert P(N(3) < N(1)) == 0 # condition is not possible
|
||||
res3 = P(N(3) <= N(1)) # holds only for Eq(N(3), N(1))
|
||||
assert res3 == P(Eq(N(t), 0), Contains(t, Interval(1, 3)))
|
||||
|
||||
# tests from https://www.probabilitycourse.com/chapter11/11_1_2_basic_concepts_of_the_poisson_process.php
|
||||
X = PoissonProcess('X', 10) # 11.1
|
||||
assert P(Eq(X(S(1)/3), 3) & Eq(X(1), 10)) == exp(-10)*Rational(8000000000, 11160261)
|
||||
assert P(Eq(X(1), 1), Eq(X(S(1)/3), 3)) == 0
|
||||
assert P(Eq(X(1), 10), Eq(X(S(1)/3), 3)) == P(Eq(X(S(2)/3), 7))
|
||||
|
||||
X = PoissonProcess('X', 2) # 11.2
|
||||
assert P(X(S(1)/2) < 1) == exp(-1)
|
||||
assert P(X(3) < 1, Eq(X(1), 0)) == exp(-4)
|
||||
assert P(Eq(X(4), 3), Eq(X(2), 3)) == exp(-4)
|
||||
|
||||
X = PoissonProcess('X', 3)
|
||||
assert P(Eq(X(2), 5) & Eq(X(1), 2)) == Rational(81, 4)*exp(-6)
|
||||
|
||||
# check few properties
|
||||
assert P(X(2) <= 3, X(1)>=1) == 3*P(Eq(X(1), 0)) + 2*P(Eq(X(1), 1)) + P(Eq(X(1), 2))
|
||||
assert P(X(2) <= 3, X(1) > 1) == 2*P(Eq(X(1), 0)) + 1*P(Eq(X(1), 1))
|
||||
assert P(Eq(X(2), 5) & Eq(X(1), 2)) == P(Eq(X(1), 3))*P(Eq(X(1), 2))
|
||||
assert P(Eq(X(3), 4), Eq(X(1), 3)) == P(Eq(X(2), 1))
|
||||
|
||||
#test issue 20078
|
||||
assert (2*X(t) + 3*X(t)).simplify() == 5*X(t)
|
||||
assert (2*X(t) - 3*X(t)).simplify() == -X(t)
|
||||
assert (2*(0.25*X(t))).simplify() == 0.5*X(t)
|
||||
assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2
|
||||
assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2
|
||||
|
||||
def test_WienerProcess():
|
||||
X = WienerProcess("X")
|
||||
assert X.state_space == S.Reals
|
||||
assert X.index_set == Interval(0, oo)
|
||||
|
||||
t, d, x, y = symbols('t d x y', positive=True)
|
||||
assert isinstance(X(t), RandomIndexedSymbol)
|
||||
assert X.distribution(t) == NormalDistribution(0, sqrt(t))
|
||||
with warns_deprecated_sympy():
|
||||
X.distribution(X(t))
|
||||
raises(ValueError, lambda: PoissonProcess("X", -1))
|
||||
raises(NotImplementedError, lambda: X[t])
|
||||
raises(IndexError, lambda: X(-2))
|
||||
|
||||
assert X.joint_distribution(X(2), X(3)) == JointDistributionHandmade(
|
||||
Lambda((X(2), X(3)), sqrt(6)*exp(-X(2)**2/4)*exp(-X(3)**2/6)/(12*pi)))
|
||||
assert X.joint_distribution(4, 6) == JointDistributionHandmade(
|
||||
Lambda((X(4), X(6)), sqrt(6)*exp(-X(4)**2/8)*exp(-X(6)**2/12)/(24*pi)))
|
||||
|
||||
assert P(X(t) < 3).simplify() == erf(3*sqrt(2)/(2*sqrt(t)))/2 + S(1)/2
|
||||
assert P(X(t) > 2, Contains(t, Interval.Lopen(3, 7))).simplify() == S(1)/2 -\
|
||||
erf(sqrt(2)/2)/2
|
||||
|
||||
# Equivalent to P(X(1)>1)**4
|
||||
assert P((X(t) > 4) & (X(d) > 3) & (X(x) > 2) & (X(y) > 1),
|
||||
Contains(t, Interval.Lopen(0, 1)) & Contains(d, Interval.Lopen(1, 2))
|
||||
& Contains(x, Interval.Lopen(2, 3)) & Contains(y, Interval.Lopen(3, 4))).simplify() ==\
|
||||
(1 - erf(sqrt(2)/2))*(1 - erf(sqrt(2)))*(1 - erf(3*sqrt(2)/2))*(1 - erf(2*sqrt(2)))/16
|
||||
|
||||
# Contains an overlapping interval so, return Probability
|
||||
assert P((X(t)< 2) & (X(d)> 3), Contains(t, Interval.Lopen(0, 2))
|
||||
& Contains(d, Interval.Ropen(2, 4))) == Probability((X(d) > 3) & (X(t) < 2),
|
||||
Contains(d, Interval.Ropen(2, 4)) & Contains(t, Interval.Lopen(0, 2)))
|
||||
|
||||
assert str(P(Not((X(t) < 5) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) &
|
||||
Contains(d, Interval.Lopen(7, 8))).simplify()) == \
|
||||
'-(1 - erf(3*sqrt(2)/2))*(2 - erfc(5/2))/4 + 1'
|
||||
# Distribution has mean 0 at each timestamp
|
||||
assert E(X(t)) == 0
|
||||
assert E(x*(X(t) + X(d))*(X(t)**2+X(d)**2), Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Ropen(1, 2))) == Expectation(x*(X(d) + X(t))*(X(d)**2 + X(t)**2),
|
||||
Contains(d, Interval.Ropen(1, 2)) & Contains(t, Interval.Lopen(0, 1)))
|
||||
assert E(X(t) + x*E(X(3))) == 0
|
||||
|
||||
#test issue 20078
|
||||
assert (2*X(t) + 3*X(t)).simplify() == 5*X(t)
|
||||
assert (2*X(t) - 3*X(t)).simplify() == -X(t)
|
||||
assert (2*(0.25*X(t))).simplify() == 0.5*X(t)
|
||||
assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2
|
||||
assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2
|
||||
|
||||
|
||||
def test_GammaProcess_symbolic():
|
||||
t, d, x, y, g, l = symbols('t d x y g l', positive=True)
|
||||
X = GammaProcess("X", l, g)
|
||||
|
||||
raises(NotImplementedError, lambda: X[t])
|
||||
raises(IndexError, lambda: X(-1))
|
||||
assert isinstance(X(t), RandomIndexedSymbol)
|
||||
assert X.state_space == Interval(0, oo)
|
||||
assert X.distribution(t) == GammaDistribution(g*t, 1/l)
|
||||
with warns_deprecated_sympy():
|
||||
X.distribution(X(t))
|
||||
assert X.joint_distribution(5, X(3)) == JointDistributionHandmade(Lambda(
|
||||
(X(5), X(3)), l**(8*g)*exp(-l*X(3))*exp(-l*X(5))*X(3)**(3*g - 1)*X(5)**(5*g
|
||||
- 1)/(gamma(3*g)*gamma(5*g))))
|
||||
# property of the gamma process at any given timestamp
|
||||
assert E(X(t)) == g*t/l
|
||||
assert variance(X(t)).simplify() == g*t/l**2
|
||||
|
||||
# Equivalent to E(2*X(1)) + E(X(1)**2) + E(X(1)**3), where E(X(1)) == g/l
|
||||
assert E(X(t)**2 + X(d)*2 + X(y)**3, Contains(t, Interval.Lopen(0, 1))
|
||||
& Contains(d, Interval.Lopen(1, 2)) & Contains(y, Interval.Ropen(3, 4))) == \
|
||||
2*g/l + (g**2 + g)/l**2 + (g**3 + 3*g**2 + 2*g)/l**3
|
||||
|
||||
assert P(X(t) > 3, Contains(t, Interval.Lopen(3, 4))).simplify() == \
|
||||
1 - lowergamma(g, 3*l)/gamma(g) # equivalent to P(X(1)>3)
|
||||
|
||||
|
||||
#test issue 20078
|
||||
assert (2*X(t) + 3*X(t)).simplify() == 5*X(t)
|
||||
assert (2*X(t) - 3*X(t)).simplify() == -X(t)
|
||||
assert (2*(0.25*X(t))).simplify() == 0.5*X(t)
|
||||
assert (2*X(t) * 0.25*X(t)).simplify() == 0.5*X(t)**2
|
||||
assert (X(t)**2 + X(t)**3).simplify() == (X(t) + 1)*X(t)**2
|
||||
def test_GammaProcess_numeric():
|
||||
t, d, x, y = symbols('t d x y', positive=True)
|
||||
X = GammaProcess("X", 1, 2)
|
||||
assert X.state_space == Interval(0, oo)
|
||||
assert X.index_set == Interval(0, oo)
|
||||
assert X.lamda == 1
|
||||
assert X.gamma == 2
|
||||
|
||||
raises(ValueError, lambda: GammaProcess("X", -1, 2))
|
||||
raises(ValueError, lambda: GammaProcess("X", 0, -2))
|
||||
raises(ValueError, lambda: GammaProcess("X", -1, -2))
|
||||
|
||||
# all are independent because of non-overlapping intervals
|
||||
assert P((X(t) > 4) & (X(d) > 3) & (X(x) > 2) & (X(y) > 1), Contains(t,
|
||||
Interval.Lopen(0, 1)) & Contains(d, Interval.Lopen(1, 2)) & Contains(x,
|
||||
Interval.Lopen(2, 3)) & Contains(y, Interval.Lopen(3, 4))).simplify() == \
|
||||
120*exp(-10)
|
||||
|
||||
# Check working with Not and Or
|
||||
assert P(Not((X(t) < 5) & (X(d) > 3)), Contains(t, Interval.Ropen(2, 4)) &
|
||||
Contains(d, Interval.Lopen(7, 8))).simplify() == -4*exp(-3) + 472*exp(-8)/3 + 1
|
||||
assert P((X(t) > 2) | (X(t) < 4), Contains(t, Interval.Ropen(1, 4))).simplify() == \
|
||||
-643*exp(-4)/15 + 109*exp(-2)/15 + 1
|
||||
|
||||
assert E(X(t)) == 2*t # E(X(t)) == gamma*t/l
|
||||
assert E(X(2) + x*E(X(5))) == 10*x + 4
|
||||
@@ -0,0 +1,172 @@
|
||||
from sympy.stats import Expectation, Normal, Variance, Covariance
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.stats.rv import RandomMatrixSymbol
|
||||
from sympy.stats.symbolic_multivariate_probability import (ExpectationMatrix,
|
||||
VarianceMatrix, CrossCovarianceMatrix)
|
||||
|
||||
j, k = symbols("j,k")
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
|
||||
A2 = MatrixSymbol("A2", 2, 2)
|
||||
B2 = MatrixSymbol("B2", 2, 2)
|
||||
|
||||
X = RandomMatrixSymbol("X", k, 1)
|
||||
Y = RandomMatrixSymbol("Y", k, 1)
|
||||
Z = RandomMatrixSymbol("Z", k, 1)
|
||||
W = RandomMatrixSymbol("W", k, 1)
|
||||
|
||||
R = RandomMatrixSymbol("R", k, k)
|
||||
|
||||
X2 = RandomMatrixSymbol("X2", 2, 1)
|
||||
|
||||
normal = Normal("normal", 0, 1)
|
||||
|
||||
m1 = Matrix([
|
||||
[1, j*Normal("normal2", 2, 1)],
|
||||
[normal, 0]
|
||||
])
|
||||
|
||||
def test_multivariate_expectation():
|
||||
expr = Expectation(a)
|
||||
assert expr == Expectation(a) == ExpectationMatrix(a)
|
||||
assert expr.expand() == a
|
||||
|
||||
expr = Expectation(X)
|
||||
assert expr == Expectation(X) == ExpectationMatrix(X)
|
||||
assert expr.shape == (k, 1)
|
||||
assert expr.rows == k
|
||||
assert expr.cols == 1
|
||||
assert isinstance(expr, ExpectationMatrix)
|
||||
|
||||
expr = Expectation(A*X + b)
|
||||
assert expr == ExpectationMatrix(A*X + b)
|
||||
assert expr.expand() == A*ExpectationMatrix(X) + b
|
||||
assert isinstance(expr, ExpectationMatrix)
|
||||
assert expr.shape == (k, 1)
|
||||
|
||||
expr = Expectation(m1*X2)
|
||||
assert expr.expand() == expr
|
||||
|
||||
expr = Expectation(A2*m1*B2*X2)
|
||||
assert expr.args[0].args == (A2, m1, B2, X2)
|
||||
assert expr.expand() == A2*ExpectationMatrix(m1*B2*X2)
|
||||
|
||||
expr = Expectation((X + Y)*(X - Y).T)
|
||||
assert expr.expand() == ExpectationMatrix(X*X.T) - ExpectationMatrix(X*Y.T) +\
|
||||
ExpectationMatrix(Y*X.T) - ExpectationMatrix(Y*Y.T)
|
||||
|
||||
expr = Expectation(A*X + B*Y)
|
||||
assert expr.expand() == A*ExpectationMatrix(X) + B*ExpectationMatrix(Y)
|
||||
|
||||
assert Expectation(m1).doit() == Matrix([[1, 2*j], [0, 0]])
|
||||
|
||||
x1 = Matrix([
|
||||
[Normal('N11', 11, 1), Normal('N12', 12, 1)],
|
||||
[Normal('N21', 21, 1), Normal('N22', 22, 1)]
|
||||
])
|
||||
x2 = Matrix([
|
||||
[Normal('M11', 1, 1), Normal('M12', 2, 1)],
|
||||
[Normal('M21', 3, 1), Normal('M22', 4, 1)]
|
||||
])
|
||||
|
||||
assert Expectation(Expectation(x1 + x2)).doit(deep=False) == ExpectationMatrix(x1 + x2)
|
||||
assert Expectation(Expectation(x1 + x2)).doit() == Matrix([[12, 14], [24, 26]])
|
||||
|
||||
|
||||
def test_multivariate_variance():
|
||||
raises(ShapeError, lambda: Variance(A))
|
||||
|
||||
expr = Variance(a)
|
||||
assert expr == Variance(a) == VarianceMatrix(a)
|
||||
assert expr.expand() == ZeroMatrix(k, k)
|
||||
expr = Variance(a.T)
|
||||
assert expr == Variance(a.T) == VarianceMatrix(a.T)
|
||||
assert expr.expand() == ZeroMatrix(k, k)
|
||||
|
||||
expr = Variance(X)
|
||||
assert expr == Variance(X) == VarianceMatrix(X)
|
||||
assert expr.shape == (k, k)
|
||||
assert expr.rows == k
|
||||
assert expr.cols == k
|
||||
assert isinstance(expr, VarianceMatrix)
|
||||
|
||||
expr = Variance(A*X)
|
||||
assert expr == VarianceMatrix(A*X)
|
||||
assert expr.expand() == A*VarianceMatrix(X)*A.T
|
||||
assert isinstance(expr, VarianceMatrix)
|
||||
assert expr.shape == (k, k)
|
||||
|
||||
expr = Variance(A*B*X)
|
||||
assert expr.expand() == A*B*VarianceMatrix(X)*B.T*A.T
|
||||
|
||||
expr = Variance(m1*X2)
|
||||
assert expr.expand() == expr
|
||||
|
||||
expr = Variance(A2*m1*B2*X2)
|
||||
assert expr.args[0].args == (A2, m1, B2, X2)
|
||||
assert expr.expand() == expr
|
||||
|
||||
expr = Variance(A*X + B*Y)
|
||||
assert expr.expand() == 2*A*CrossCovarianceMatrix(X, Y)*B.T +\
|
||||
A*VarianceMatrix(X)*A.T + B*VarianceMatrix(Y)*B.T
|
||||
|
||||
def test_multivariate_crosscovariance():
|
||||
raises(ShapeError, lambda: Covariance(X, Y.T))
|
||||
raises(ShapeError, lambda: Covariance(X, A))
|
||||
|
||||
|
||||
expr = Covariance(a.T, b.T)
|
||||
assert expr.shape == (1, 1)
|
||||
assert expr.expand() == ZeroMatrix(1, 1)
|
||||
|
||||
expr = Covariance(a, b)
|
||||
assert expr == Covariance(a, b) == CrossCovarianceMatrix(a, b)
|
||||
assert expr.expand() == ZeroMatrix(k, k)
|
||||
assert expr.shape == (k, k)
|
||||
assert expr.rows == k
|
||||
assert expr.cols == k
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
|
||||
expr = Covariance(A*X + a, b)
|
||||
assert expr.expand() == ZeroMatrix(k, k)
|
||||
|
||||
expr = Covariance(X, Y)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == expr
|
||||
|
||||
expr = Covariance(X, X)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == VarianceMatrix(X)
|
||||
|
||||
expr = Covariance(X + Y, Z)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == CrossCovarianceMatrix(X, Z) + CrossCovarianceMatrix(Y, Z)
|
||||
|
||||
expr = Covariance(A*X, Y)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == A*CrossCovarianceMatrix(X, Y)
|
||||
|
||||
expr = Covariance(X, B*Y)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == CrossCovarianceMatrix(X, Y)*B.T
|
||||
|
||||
expr = Covariance(A*X + a, B.T*Y + b)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == A*CrossCovarianceMatrix(X, Y)*B
|
||||
|
||||
expr = Covariance(A*X + B*Y + a, C.T*Z + D.T*W + b)
|
||||
assert isinstance(expr, CrossCovarianceMatrix)
|
||||
assert expr.expand() == A*CrossCovarianceMatrix(X, W)*D + A*CrossCovarianceMatrix(X, Z)*C \
|
||||
+ B*CrossCovarianceMatrix(Y, W)*D + B*CrossCovarianceMatrix(Y, Z)*C
|
||||
@@ -0,0 +1,175 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (oo, pi)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.symbol import (Dummy, symbols)
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.integrals.integrals import Integral
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.stats import (Normal, Poisson, variance, Covariance, Variance,
|
||||
Probability, Expectation, Moment, CentralMoment)
|
||||
from sympy.stats.rv import probability, expectation
|
||||
|
||||
|
||||
def test_literal_probability():
|
||||
X = Normal('X', 2, 3)
|
||||
Y = Normal('Y', 3, 4)
|
||||
Z = Poisson('Z', 4)
|
||||
W = Poisson('W', 3)
|
||||
x = symbols('x', real=True)
|
||||
y, w, z = symbols('y, w, z')
|
||||
|
||||
assert Probability(X > 0).evaluate_integral() == probability(X > 0)
|
||||
assert Probability(X > x).evaluate_integral() == probability(X > x)
|
||||
assert Probability(X > 0).rewrite(Integral).doit() == probability(X > 0)
|
||||
assert Probability(X > x).rewrite(Integral).doit() == probability(X > x)
|
||||
|
||||
assert Expectation(X).evaluate_integral() == expectation(X)
|
||||
assert Expectation(X).rewrite(Integral).doit() == expectation(X)
|
||||
assert Expectation(X**2).evaluate_integral() == expectation(X**2)
|
||||
assert Expectation(x*X).args == (x*X,)
|
||||
assert Expectation(x*X).expand() == x*Expectation(X)
|
||||
assert Expectation(2*X + 3*Y + z*X*Y).expand() == 2*Expectation(X) + 3*Expectation(Y) + z*Expectation(X*Y)
|
||||
assert Expectation(2*X + 3*Y + z*X*Y).args == (2*X + 3*Y + z*X*Y,)
|
||||
assert Expectation(sin(X)) == Expectation(sin(X)).expand()
|
||||
assert Expectation(2*x*sin(X)*Y + y*X**2 + z*X*Y).expand() == 2*x*Expectation(sin(X)*Y) \
|
||||
+ y*Expectation(X**2) + z*Expectation(X*Y)
|
||||
assert Expectation(X + Y).expand() == Expectation(X) + Expectation(Y)
|
||||
assert Expectation((X + Y)*(X - Y)).expand() == Expectation(X**2) - Expectation(Y**2)
|
||||
assert Expectation((X + Y)*(X - Y)).expand().doit() == -12
|
||||
assert Expectation(X + Y, evaluate=True).doit() == 5
|
||||
assert Expectation(X + Expectation(Y)).doit() == 5
|
||||
assert Expectation(X + Expectation(Y)).doit(deep=False) == 2 + Expectation(Expectation(Y))
|
||||
assert Expectation(X + Expectation(Y + Expectation(2*X))).doit(deep=False) == 2 \
|
||||
+ Expectation(Expectation(Y + Expectation(2*X)))
|
||||
assert Expectation(X + Expectation(Y + Expectation(2*X))).doit() == 9
|
||||
assert Expectation(Expectation(2*X)).doit() == 4
|
||||
assert Expectation(Expectation(2*X)).doit(deep=False) == Expectation(2*X)
|
||||
assert Expectation(4*Expectation(2*X)).doit(deep=False) == 4*Expectation(2*X)
|
||||
assert Expectation((X + Y)**3).expand() == 3*Expectation(X*Y**2) +\
|
||||
3*Expectation(X**2*Y) + Expectation(X**3) + Expectation(Y**3)
|
||||
assert Expectation((X - Y)**3).expand() == 3*Expectation(X*Y**2) -\
|
||||
3*Expectation(X**2*Y) + Expectation(X**3) - Expectation(Y**3)
|
||||
assert Expectation((X - Y)**2).expand() == -2*Expectation(X*Y) +\
|
||||
Expectation(X**2) + Expectation(Y**2)
|
||||
|
||||
assert Variance(w).args == (w,)
|
||||
assert Variance(w).expand() == 0
|
||||
assert Variance(X).evaluate_integral() == Variance(X).rewrite(Integral).doit() == variance(X)
|
||||
assert Variance(X + z).args == (X + z,)
|
||||
assert Variance(X + z).expand() == Variance(X)
|
||||
assert Variance(X*Y).args == (Mul(X, Y),)
|
||||
assert type(Variance(X*Y)) == Variance
|
||||
assert Variance(z*X).expand() == z**2*Variance(X)
|
||||
assert Variance(X + Y).expand() == Variance(X) + Variance(Y) + 2*Covariance(X, Y)
|
||||
assert Variance(X + Y + Z + W).expand() == (Variance(X) + Variance(Y) + Variance(Z) + Variance(W) +
|
||||
2 * Covariance(X, Y) + 2 * Covariance(X, Z) + 2 * Covariance(X, W) +
|
||||
2 * Covariance(Y, Z) + 2 * Covariance(Y, W) + 2 * Covariance(W, Z))
|
||||
assert Variance(X**2).evaluate_integral() == variance(X**2)
|
||||
assert unchanged(Variance, X**2)
|
||||
assert Variance(x*X**2).expand() == x**2*Variance(X**2)
|
||||
assert Variance(sin(X)).args == (sin(X),)
|
||||
assert Variance(sin(X)).expand() == Variance(sin(X))
|
||||
assert Variance(x*sin(X)).expand() == x**2*Variance(sin(X))
|
||||
|
||||
assert Covariance(w, z).args == (w, z)
|
||||
assert Covariance(w, z).expand() == 0
|
||||
assert Covariance(X, w).expand() == 0
|
||||
assert Covariance(w, X).expand() == 0
|
||||
assert Covariance(X, Y).args == (X, Y)
|
||||
assert type(Covariance(X, Y)) == Covariance
|
||||
assert Covariance(z*X + 3, Y).expand() == z*Covariance(X, Y)
|
||||
assert Covariance(X, X).args == (X, X)
|
||||
assert Covariance(X, X).expand() == Variance(X)
|
||||
assert Covariance(z*X + 3, w*Y + 4).expand() == w*z*Covariance(X,Y)
|
||||
assert Covariance(X, Y) == Covariance(Y, X)
|
||||
assert Covariance(X + Y, Z + W).expand() == Covariance(W, X) + Covariance(W, Y) + Covariance(X, Z) + Covariance(Y, Z)
|
||||
assert Covariance(x*X + y*Y, z*Z + w*W).expand() == (x*w*Covariance(W, X) + w*y*Covariance(W, Y) +
|
||||
x*z*Covariance(X, Z) + y*z*Covariance(Y, Z))
|
||||
assert Covariance(x*X**2 + y*sin(Y), z*Y*Z**2 + w*W).expand() == (w*x*Covariance(W, X**2) + w*y*Covariance(sin(Y), W) +
|
||||
x*z*Covariance(Y*Z**2, X**2) + y*z*Covariance(Y*Z**2, sin(Y)))
|
||||
assert Covariance(X, X**2).expand() == Covariance(X, X**2)
|
||||
assert Covariance(X, sin(X)).expand() == Covariance(sin(X), X)
|
||||
assert Covariance(X**2, sin(X)*Y).expand() == Covariance(sin(X)*Y, X**2)
|
||||
assert Covariance(w, X).evaluate_integral() == 0
|
||||
|
||||
|
||||
def test_probability_rewrite():
|
||||
X = Normal('X', 2, 3)
|
||||
Y = Normal('Y', 3, 4)
|
||||
Z = Poisson('Z', 4)
|
||||
W = Poisson('W', 3)
|
||||
x, y, w, z = symbols('x, y, w, z')
|
||||
|
||||
assert Variance(w).rewrite(Expectation) == 0
|
||||
assert Variance(X).rewrite(Expectation) == Expectation(X ** 2) - Expectation(X) ** 2
|
||||
assert Variance(X, condition=Y).rewrite(Expectation) == Expectation(X ** 2, Y) - Expectation(X, Y) ** 2
|
||||
assert Variance(X, Y) != Expectation(X**2) - Expectation(X)**2
|
||||
assert Variance(X + z).rewrite(Expectation) == Expectation((X + z) ** 2) - Expectation(X + z) ** 2
|
||||
assert Variance(X * Y).rewrite(Expectation) == Expectation(X ** 2 * Y ** 2) - Expectation(X * Y) ** 2
|
||||
|
||||
assert Covariance(w, X).rewrite(Expectation) == -w*Expectation(X) + Expectation(w*X)
|
||||
assert Covariance(X, Y).rewrite(Expectation) == Expectation(X*Y) - Expectation(X)*Expectation(Y)
|
||||
assert Covariance(X, Y, condition=W).rewrite(Expectation) == Expectation(X * Y, W) - Expectation(X, W) * Expectation(Y, W)
|
||||
|
||||
w, x, z = symbols("W, x, z")
|
||||
px = Probability(Eq(X, x))
|
||||
pz = Probability(Eq(Z, z))
|
||||
|
||||
assert Expectation(X).rewrite(Probability) == Integral(x*px, (x, -oo, oo))
|
||||
assert Expectation(Z).rewrite(Probability) == Sum(z*pz, (z, 0, oo))
|
||||
assert Variance(X).rewrite(Probability) == Integral(x**2*px, (x, -oo, oo)) - Integral(x*px, (x, -oo, oo))**2
|
||||
assert Variance(Z).rewrite(Probability) == Sum(z**2*pz, (z, 0, oo)) - Sum(z*pz, (z, 0, oo))**2
|
||||
assert Covariance(w, X).rewrite(Probability) == \
|
||||
-w*Integral(x*Probability(Eq(X, x)), (x, -oo, oo)) + Integral(w*x*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
|
||||
# To test rewrite as sum function
|
||||
assert Variance(X).rewrite(Sum) == Variance(X).rewrite(Integral)
|
||||
assert Expectation(X).rewrite(Sum) == Expectation(X).rewrite(Integral)
|
||||
|
||||
assert Covariance(w, X).rewrite(Sum) == 0
|
||||
|
||||
assert Covariance(w, X).rewrite(Integral) == 0
|
||||
|
||||
assert Variance(X, condition=Y).rewrite(Probability) == Integral(x**2*Probability(Eq(X, x), Y), (x, -oo, oo)) - \
|
||||
Integral(x*Probability(Eq(X, x), Y), (x, -oo, oo))**2
|
||||
|
||||
|
||||
def test_symbolic_Moment():
|
||||
mu = symbols('mu', real=True)
|
||||
sigma = symbols('sigma', positive=True)
|
||||
x = symbols('x')
|
||||
X = Normal('X', mu, sigma)
|
||||
M = Moment(X, 4, 2)
|
||||
assert M.rewrite(Expectation) == Expectation((X - 2)**4)
|
||||
assert M.rewrite(Probability) == Integral((x - 2)**4*Probability(Eq(X, x)),
|
||||
(x, -oo, oo))
|
||||
k = Dummy('k')
|
||||
expri = Integral(sqrt(2)*(k - 2)**4*exp(-(k - \
|
||||
mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo))
|
||||
assert M.rewrite(Integral).dummy_eq(expri)
|
||||
assert M.doit() == (mu**4 - 8*mu**3 + 6*mu**2*sigma**2 + \
|
||||
24*mu**2 - 24*mu*sigma**2 - 32*mu + 3*sigma**4 + 24*sigma**2 + 16)
|
||||
M = Moment(2, 5)
|
||||
assert M.doit() == 2**5
|
||||
|
||||
|
||||
def test_symbolic_CentralMoment():
|
||||
mu = symbols('mu', real=True)
|
||||
sigma = symbols('sigma', positive=True)
|
||||
x = symbols('x')
|
||||
X = Normal('X', mu, sigma)
|
||||
CM = CentralMoment(X, 6)
|
||||
assert CM.rewrite(Expectation) == Expectation((X - Expectation(X))**6)
|
||||
assert CM.rewrite(Probability) == Integral((x - Integral(x*Probability(True),
|
||||
(x, -oo, oo)))**6*Probability(Eq(X, x)), (x, -oo, oo))
|
||||
k = Dummy('k')
|
||||
expri = Integral(sqrt(2)*(k - Integral(sqrt(2)*k*exp(-(k - \
|
||||
mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo)))**6*exp(-(k - \
|
||||
mu)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), (k, -oo, oo))
|
||||
assert CM.rewrite(Integral).dummy_eq(expri)
|
||||
assert CM.doit().simplify() == 15*sigma**6
|
||||
CM = Moment(5, 5)
|
||||
assert CM.doit() == 5**5
|
||||
Reference in New Issue
Block a user