chore: 添加虚拟环境到仓库

- 添加 backend_service/venv 虚拟环境
- 包含所有Python依赖包
- 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
2025-12-03 10:19:25 +08:00
parent a6c2027caa
commit c4f851d387
12655 changed files with 3009376 additions and 0 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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])

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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