Skip to content

Commit 6c4df17

Browse files
authored
Merge pull request sympy#18754 from Smit-create/sample_crv
Added sampling methods of crv_types
2 parents b561fee + 5370976 commit 6c4df17

File tree

10 files changed

+465
-272
lines changed

10 files changed

+465
-272
lines changed

doc/src/modules/stats.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ Interface
117117
.. autofunction:: std
118118
.. autofunction:: sample
119119
.. autofunction:: sample_iter
120-
.. autofunction:: sympy.stats.rv.sample_iter_lambdify
121-
.. autofunction:: sympy.stats.rv.sample_iter_subs
122120
.. autofunction:: sympy.stats.rv.sampling_density
123121
.. autofunction:: sympy.stats.rv.sampling_P
124122
.. autofunction:: sympy.stats.rv.sampling_E

sympy/stats/crv.py

Lines changed: 148 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,21 @@
1212

1313
from sympy import (Interval, Intersection, symbols, sympify, Dummy, nan,
1414
Integral, And, Or, Piecewise, cacheit, integrate, oo, Lambda,
15-
Basic, S, exp, I, FiniteSet, Ne, Eq, Union, poly, series, factorial)
15+
Basic, S, exp, I, FiniteSet, Ne, Eq, Union, poly, series, factorial,
16+
lambdify)
1617
from sympy.core.function import PoleError
1718
from sympy.functions.special.delta_functions import DiracDelta
1819
from sympy.polys.polyerrors import PolynomialError
1920
from sympy.solvers.solveset import solveset
2021
from sympy.solvers.inequalities import reduce_rational_inequalities
2122
from sympy.core.sympify import _sympify
23+
from sympy.external import import_module
2224
from sympy.stats.rv import (RandomDomain, SingleDomain, ConditionalDomain,
2325
ProductDomain, PSpace, SinglePSpace, random_symbols, NamedArgsMixin)
24-
import random
2526

27+
scipy = import_module('scipy')
28+
numpy = import_module('numpy')
29+
pymc3 = import_module('pymc3')
2630

2731
class ContinuousDomain(RandomDomain):
2832
"""
@@ -145,6 +149,132 @@ def __call__(self, *args):
145149
return self.pdf(*args)
146150

147151

152+
class SampleExternalContinuous:
153+
"""Class consisting of the methods that are used to sample values of random
154+
variables from external libraries."""
155+
156+
157+
scipy_rv_map = {
158+
'BetaDistribution': lambda dist, size: scipy.stats.beta.rvs(a=float(dist.alpha),
159+
b=float(dist.beta), size=size),
160+
'BetaPrimeDistribution':lambda dist, size: scipy.stats.betaprime.rvs(a=float(dist.alpha),
161+
b=float(dist.beta), size=size),
162+
'CauchyDistribution': lambda dist, size: scipy.stats.cauchy.rvs(loc=float(dist.x0),
163+
scale=float(dist.gamma), size=size),
164+
'ChiDistribution': lambda dist, size: scipy.stats.chi.rvs(df=float(dist.k),
165+
size=size),
166+
'ChiSquaredDistribution': lambda dist, size: scipy.stats.chi2.rvs(df=float(dist.k),
167+
size=size),
168+
'ExponentialDistribution': lambda dist, size: scipy.stats.expon.rvs(loc=0,
169+
scale=1/float(dist.rate), size=size),
170+
'GammaDistribution': lambda dist, size: scipy.stats.gamma.rvs(a=float(dist.k), loc=0,
171+
scale=float(dist.theta), size=size),
172+
'GammaInverseDistribution': lambda dist, size: scipy.stats.invgamma.rvs(a=float(dist.a),
173+
loc=0, scale=float(dist.b), size=size),
174+
'LogNormalDistribution': lambda dist, size: scipy.stats.lognorm.rvs(s=float(dist.std),
175+
loc=0, scale=exp(float(dist.mean)), size=size),
176+
'NormalDistribution': lambda dist, size: scipy.stats.norm.rvs(float(dist.mean),
177+
float(dist.std), size=size),
178+
'GaussianInverseDistribution': lambda dist, size: scipy.stats.invgauss.rvs(
179+
mu=float(dist.mean)/float(dist.shape), scale=float(dist.shape), size=size),
180+
'ParetoDistribution': lambda dist, size: scipy.stats.pareto.rvs(b=float(dist.alpha),
181+
scale=float(dist.xm), size=size),
182+
'StudentTDistribution': lambda dist, size: scipy.stats.t.rvs(df=float(dist.nu),
183+
size=size),
184+
'UniformDistribution': lambda dist, size: scipy.stats.uniform.rvs(loc=float(dist.left),
185+
scale=float(dist.right)-float(dist.left), size=size),
186+
'WeibullDistribution': lambda dist, size: scipy.stats.weibull_min.rvs(loc=0,
187+
c=float(dist.beta), scale=float(dist.alpha), size=size)
188+
}
189+
190+
numpy_rv_map = {
191+
'BetaDistribution': lambda dist, size: numpy.random.beta(a=float(dist.alpha),
192+
b=float(dist.beta), size=size),
193+
'ChiSquaredDistribution': lambda dist, size: numpy.random.chisquare(
194+
df=float(dist.k), size=size),
195+
'ExponentialDistribution': lambda dist, size: numpy.random.exponential(
196+
1/float(dist.rate), size=size),
197+
'GammaDistribution': lambda dist, size: numpy.random.gamma(float(dist.k),
198+
float(dist.theta), size=size),
199+
'LogNormalDistribution': lambda dist, size: numpy.random.lognormal(
200+
float(dist.mean), float(dist.std), size=size),
201+
'NormalDistribution': lambda dist, size: numpy.random.normal(
202+
float(dist.mean), float(dist.std), size=size),
203+
'ParetoDistribution': lambda dist, size: (numpy.random.pareto(
204+
a=float(dist.alpha), size=size) + 1) * float(dist.xm),
205+
'UniformDistribution': lambda dist, size: numpy.random.uniform(
206+
low=float(dist.left), high=float(dist.right), size=size)
207+
}
208+
209+
pymc3_rv_map = {
210+
'BetaDistribution': lambda dist:
211+
pymc3.Beta('X', alpha=float(dist.alpha), beta=float(dist.beta)),
212+
'CauchyDistribution': lambda dist:
213+
pymc3.Cauchy('X', alpha=float(dist.x0), beta=float(dist.gamma)),
214+
'ChiSquaredDistribution': lambda dist:
215+
pymc3.ChiSquared('X', nu=float(dist.k)),
216+
'ExponentialDistribution': lambda dist:
217+
pymc3.Exponential('X', lam=float(dist.rate)),
218+
'GammaDistribution': lambda dist:
219+
pymc3.Gamma('X', alpha=float(dist.k), beta=1/float(dist.theta)),
220+
'LogNormalDistribution': lambda dist:
221+
pymc3.Lognormal('X', mu=float(dist.mean), sigma=float(dist.std)),
222+
'NormalDistribution': lambda dist:
223+
pymc3.Normal('X', float(dist.mean), float(dist.std)),
224+
'GaussianInverseDistribution': lambda dist:
225+
pymc3.Wald('X', mu=float(dist.mean), lam=float(dist.shape)),
226+
'ParetoDistribution': lambda dist:
227+
pymc3.Pareto('X', alpha=float(dist.alpha), m=float(dist.xm)),
228+
'UniformDistribution': lambda dist:
229+
pymc3.Uniform('X', lower=float(dist.left), upper=float(dist.right))
230+
}
231+
232+
@classmethod
233+
def _sample_scipy(cls, dist, size):
234+
"""Sample from SciPy."""
235+
236+
dist_list = cls.scipy_rv_map.keys()
237+
238+
if dist.__class__.__name__ == 'ContinuousDistributionHandmade':
239+
from scipy.stats import rv_continuous
240+
z = Dummy('z')
241+
handmade_pdf = lambdify(z, dist.pdf(z), 'scipy')
242+
class scipy_pdf(rv_continuous):
243+
def _pdf(self, x):
244+
return handmade_pdf(x)
245+
scipy_rv = scipy_pdf(a=dist.set._inf, b=dist.set._sup, name='scipy_pdf')
246+
return scipy_rv.rvs(size=size)
247+
248+
if dist.__class__.__name__ not in dist_list:
249+
return None
250+
251+
return cls.scipy_rv_map[dist.__class__.__name__](dist, size)
252+
253+
@classmethod
254+
def _sample_numpy(cls, dist, size):
255+
"""Sample from NumPy."""
256+
257+
dist_list = cls.numpy_rv_map.keys()
258+
259+
if dist.__class__.__name__ not in dist_list:
260+
return None
261+
262+
return cls.numpy_rv_map[dist.__class__.__name__](dist, size)
263+
264+
@classmethod
265+
def _sample_pymc3(cls, dist, size):
266+
"""Sample from PyMC3."""
267+
268+
dist_list = cls.pymc3_rv_map.keys()
269+
270+
if dist.__class__.__name__ not in dist_list:
271+
return None
272+
273+
with pymc3.Model():
274+
cls.pymc3_rv_map[dist.__class__.__name__](dist)
275+
return pymc3.sample(size, chains=1, progressbar=False)[:]['X']
276+
277+
148278
class SingleContinuousDistribution(ContinuousDistribution, NamedArgsMixin):
149279
""" Continuous distribution of a single variable
150280
@@ -171,34 +301,25 @@ def __new__(cls, *args):
171301
def check(*args):
172302
pass
173303

174-
def sample(self, size=()):
304+
def sample(self, size=1, library='scipy'):
175305
""" A random realization from the distribution """
176-
icdf = self._inverse_cdf_expression()
177-
if not size:
178-
return icdf(random.uniform(0, 1))
179-
else:
180-
return [icdf(random.uniform(0, 1))]*size
181306

182-
@cacheit
183-
def _inverse_cdf_expression(self):
184-
""" Inverse of the CDF
307+
libraries = ['scipy', 'numpy', 'pymc3']
308+
if library not in libraries:
309+
raise NotImplementedError("Sampling from %s is not supported yet."
310+
% str(library))
311+
if not import_module(library):
312+
raise ValueError("Failed to import %s" % library)
185313

186-
Used by sample
187-
"""
188-
x, z = symbols('x, z', positive=True, cls=Dummy)
189-
# Invert CDF
190-
try:
191-
inverse_cdf = solveset(self.cdf(x) - z, x, S.Reals)
192-
if isinstance(inverse_cdf, Intersection) and S.Reals in inverse_cdf.args:
193-
inverse_cdf = list(inverse_cdf.args[1])
194-
except NotImplementedError:
195-
inverse_cdf = None
196-
if not inverse_cdf or len(inverse_cdf) != 1:
197-
raise NotImplementedError("Could not invert CDF")
314+
samps = getattr(SampleExternalContinuous, '_sample_' + library)(self, size)
198315

199-
(icdf,) = inverse_cdf
316+
if samps is not None:
317+
return samps
318+
raise NotImplementedError(
319+
"Sampling for %s is not currently implemented from %s"
320+
% (self.__class__.__name__, library)
321+
)
200322

201-
return Lambda(z, icdf)
202323

203324
@cacheit
204325
def compute_cdf(self, **kwargs):
@@ -495,13 +616,13 @@ def set(self):
495616
def domain(self):
496617
return SingleContinuousDomain(sympify(self.symbol), self.set)
497618

498-
def sample(self, size=()):
619+
def sample(self, size=1, library='scipy'):
499620
"""
500621
Internal sample method
501622
502623
Returns dictionary mapping RandomSymbol to realization value.
503624
"""
504-
return {self.value: self.distribution.sample(size)}
625+
return {self.value: self.distribution.sample(size, library=library)}
505626

506627
def compute_expectation(self, expr, rvs=None, evaluate=False, **kwargs):
507628
rvs = rvs or (self.value,)

sympy/stats/crv_types.py

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,13 @@
5454

5555
from __future__ import print_function, division
5656

57-
import random
5857

5958
from sympy import beta as beta_fn
6059
from sympy import cos, sin, tan, atan, exp, besseli, besselj, besselk
6160
from sympy import (log, sqrt, pi, S, Dummy, Interval, sympify, gamma, sign,
6261
Piecewise, And, Eq, binomial, factorial, Sum, floor, Abs,
6362
Lambda, Basic, lowergamma, erf, erfc, erfi, erfinv, I, asin,
6463
hyper, uppergamma, sinh, Ne, expint, Rational, integrate)
65-
from sympy.external import import_module
6664
from sympy.matrices import MatrixBase, MatrixExpr
6765
from sympy.stats.crv import SingleContinuousPSpace, SingleContinuousDistribution
6866
from sympy.stats.joint_rv import JointPSpace, CompoundDistribution
@@ -379,12 +377,6 @@ def pdf(self, x):
379377
alpha, beta = self.alpha, self.beta
380378
return x**(alpha - 1) * (1 - x)**(beta - 1) / beta_fn(alpha, beta)
381379

382-
def sample(self, size=()):
383-
if not size:
384-
return random.betavariate(self.alpha, self.beta)
385-
else:
386-
return [random.betavariate(self.alpha, self.beta)]*size
387-
388380
def _characteristic_function(self, t):
389381
return hyper((self.alpha,), (self.alpha + self.beta,), I*t)
390382

@@ -1191,12 +1183,6 @@ def check(rate):
11911183
def pdf(self, x):
11921184
return self.rate * exp(-self.rate*x)
11931185

1194-
def sample(self, size=()):
1195-
if not size:
1196-
return random.expovariate(self.rate)
1197-
else:
1198-
return [random.expovariate(self.rate)]*size
1199-
12001186
def _cdf(self, x):
12011187
return Piecewise(
12021188
(S.One - exp(-self.rate*x), x >= 0),
@@ -1626,12 +1612,6 @@ def pdf(self, x):
16261612
k, theta = self.k, self.theta
16271613
return x**(k - 1) * exp(-x/theta) / (gamma(k)*theta**k)
16281614

1629-
def sample(self, size=()):
1630-
if not size:
1631-
return random.gammavariate(self.k, self.theta)
1632-
else:
1633-
return [random.gammavariate(self.k, self.theta)]*size
1634-
16351615
def _cdf(self, x):
16361616
k, theta = self.k, self.theta
16371617
return Piecewise(
@@ -1739,14 +1719,6 @@ def _cdf(self, x):
17391719
return Piecewise((uppergamma(a,b/x)/gamma(a), x > 0),
17401720
(S.Zero, True))
17411721

1742-
def sample(self, size=()):
1743-
scipy = import_module('scipy')
1744-
if scipy:
1745-
from scipy.stats import invgamma
1746-
return invgamma.rvs(float(self.a), 0, float(self.b), size=size)
1747-
else:
1748-
raise NotImplementedError('Sampling the Inverse Gamma Distribution requires Scipy.')
1749-
17501722
def _characteristic_function(self, t):
17511723
a, b = self.a, self.b
17521724
return 2 * (-I*b*t)**(a/2) * besselk(a, sqrt(-4*I*b*t)) / gamma(a)
@@ -2414,12 +2386,6 @@ def pdf(self, x):
24142386
mean, std = self.mean, self.std
24152387
return exp(-(log(x) - mean)**2 / (2*std**2)) / (x*sqrt(2*pi)*std)
24162388

2417-
def sample(self, size=()):
2418-
if not size:
2419-
return random.lognormvariate(self.mean, self.std)
2420-
else:
2421-
return [random.lognormvariate(self.mean, self.std)]*size
2422-
24232389
def _cdf(self, x):
24242390
mean, std = self.mean, self.std
24252391
return Piecewise(
@@ -2750,12 +2716,6 @@ def check(mean, std):
27502716
def pdf(self, x):
27512717
return exp(-(x - self.mean)**2 / (2*self.std**2)) / (sqrt(2*pi)*self.std)
27522718

2753-
def sample(self, size=()):
2754-
if not size:
2755-
return random.normalvariate(self.mean, self.std)
2756-
else:
2757-
return [random.normalvariate(self.mean, self.std)]*size
2758-
27592719
def _cdf(self, x):
27602720
mean, std = self.mean, self.std
27612721
return erf(sqrt(2)*(-mean + x)/(2*std))/2 + S.Half
@@ -2884,15 +2844,6 @@ def pdf(self, x):
28842844
mu, s = self.mean, self.shape
28852845
return exp(-s*(x - mu)**2 / (2*x*mu**2)) * sqrt(s/((2*pi*x**3)))
28862846

2887-
def sample(self, size=()):
2888-
scipy = import_module('scipy')
2889-
if scipy:
2890-
from scipy.stats import invgauss
2891-
return invgauss.rvs(float(self.mean/self.shape), 0, float(self.shape), size=size)
2892-
else:
2893-
raise NotImplementedError(
2894-
'Sampling the Inverse Gaussian Distribution requires Scipy.')
2895-
28962847
def _cdf(self, x):
28972848
from sympy.stats import cdf
28982849
mu, s = self.mean, self.shape
@@ -2997,12 +2948,6 @@ def pdf(self, x):
29972948
xm, alpha = self.xm, self.alpha
29982949
return alpha * xm**alpha / x**(alpha + 1)
29992950

3000-
def sample(self, size=()):
3001-
if not size:
3002-
return random.paretovariate(self.alpha)
3003-
else:
3004-
return [random.paretovariate(self.alpha)]*size
3005-
30062951
def _cdf(self, x):
30072952
xm, alpha = self.xm, self.alpha
30082953
return Piecewise(
@@ -3852,12 +3797,6 @@ def expectation(self, expr, var, **kwargs):
38523797
Min(self.left, self.right): self.left})
38533798
return result
38543799

3855-
def sample(self, size=()):
3856-
if not size:
3857-
return random.uniform(self.left, self.right)
3858-
else:
3859-
return [random.uniform(self.left, self.right)]*size
3860-
38613800

38623801
def Uniform(name, left, right):
38633802
r"""
@@ -4110,11 +4049,6 @@ def pdf(self, x):
41104049
alpha, beta = self.alpha, self.beta
41114050
return beta * (x/alpha)**(beta - 1) * exp(-(x/alpha)**beta) / alpha
41124051

4113-
def sample(self, size=()):
4114-
if not size:
4115-
return random.weibullvariate(self.alpha, self.beta)
4116-
else:
4117-
return [random.weibullvariate(self.alpha, self.beta)]*size
41184052

41194053
def Weibull(name, alpha, beta):
41204054
r"""

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy