12
12
13
13
from sympy import (Interval , Intersection , symbols , sympify , Dummy , nan ,
14
14
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 )
16
17
from sympy .core .function import PoleError
17
18
from sympy .functions .special .delta_functions import DiracDelta
18
19
from sympy .polys .polyerrors import PolynomialError
19
20
from sympy .solvers .solveset import solveset
20
21
from sympy .solvers .inequalities import reduce_rational_inequalities
21
22
from sympy .core .sympify import _sympify
23
+ from sympy .external import import_module
22
24
from sympy .stats .rv import (RandomDomain , SingleDomain , ConditionalDomain ,
23
25
ProductDomain , PSpace , SinglePSpace , random_symbols , NamedArgsMixin )
24
- import random
25
26
27
+ scipy = import_module ('scipy' )
28
+ numpy = import_module ('numpy' )
29
+ pymc3 = import_module ('pymc3' )
26
30
27
31
class ContinuousDomain (RandomDomain ):
28
32
"""
@@ -145,6 +149,132 @@ def __call__(self, *args):
145
149
return self .pdf (* args )
146
150
147
151
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
+
148
278
class SingleContinuousDistribution (ContinuousDistribution , NamedArgsMixin ):
149
279
""" Continuous distribution of a single variable
150
280
@@ -171,34 +301,25 @@ def __new__(cls, *args):
171
301
def check (* args ):
172
302
pass
173
303
174
- def sample (self , size = () ):
304
+ def sample (self , size = 1 , library = 'scipy' ):
175
305
""" 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
181
306
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 )
185
313
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 )
198
315
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
+ )
200
322
201
- return Lambda (z , icdf )
202
323
203
324
@cacheit
204
325
def compute_cdf (self , ** kwargs ):
@@ -495,13 +616,13 @@ def set(self):
495
616
def domain (self ):
496
617
return SingleContinuousDomain (sympify (self .symbol ), self .set )
497
618
498
- def sample (self , size = () ):
619
+ def sample (self , size = 1 , library = 'scipy' ):
499
620
"""
500
621
Internal sample method
501
622
502
623
Returns dictionary mapping RandomSymbol to realization value.
503
624
"""
504
- return {self .value : self .distribution .sample (size )}
625
+ return {self .value : self .distribution .sample (size , library = library )}
505
626
506
627
def compute_expectation (self , expr , rvs = None , evaluate = False , ** kwargs ):
507
628
rvs = rvs or (self .value ,)
0 commit comments