1414# along with this program; if not, write to the Free Software
1515# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1616
17- # 2017-08-31 Paul Kienzle
18- # - cache tables so repeated calls are faster
19- # - support scalar and vector inputs
20- # - code linting
21-
2217"""
2318This is a package for calculation of Levy stable distributions
2419(probability density function and cumulative density function) and for
4843- pylevy does not support alpha values less than 0.5.
4944"""
5045
51- from __future__ import print_function , division
52-
5346import sys
5447import os
5548import numpy as np
5649from scipy .special import gamma
5750from scipy import optimize
5851
59- __version__ = "1.0 "
52+ __version__ = "1.1 "
6053
6154# Some constants of the program.
6255# Dimensions: 0 - x, 1 - alpha, 2 - beta
8679_data_cache = {}
8780
8881
89- def _cdf ():
90- try :
91- return _data_cache ['cdf' ]
92- except KeyError :
93- _data_cache ['cdf' ] = np .load (os .path .join (ROOT , 'cdf.npz' ))['arr_0' ]
94- return _data_cache ['cdf' ]
95-
96-
97- def _pdf ():
98- try :
99- return _data_cache ['pdf' ]
100- except KeyError :
101- _data_cache ['pdf' ] = np .load (os .path .join (ROOT , 'pdf.npz' ))['arr_0' ]
102- return _data_cache ['pdf' ]
103-
104-
105- def _limits ():
82+ def _read_from_cache (key ):
83+ """ Loads the file given by key """
10684 try :
107- return _data_cache ['limits' ]
85+ return _data_cache [key ]
10886 except KeyError :
109- _data_cache ['limits' ] = np .load (os .path .join (ROOT , 'limits .npz' ))['arr_0' ]
110- return _data_cache ['limits' ]
87+ _data_cache [key ] = np .load (os .path .join (ROOT , '{} .npz' . format ( key ) ))['arr_0' ]
88+ return _data_cache [key ]
11189
11290
11391def _reflect (x , lower , upper ):
@@ -149,8 +127,8 @@ def _interpolate(points, grid, lower, upper):
149127 ravel_offset = 0
150128 for j in range (dims ):
151129 n = (i >> (j * 2 )) % 4
152- ravel_offset = ravel_offset * grid_shape [j ] + \
153- np . maximum ( 0 , np . minimum ( grid_shape [ j ] - 1 , floors [:, j ] + (n - 1 )))
130+ ravel_offset = ravel_offset * grid_shape [j ] + np . maximum ( 0 , np . minimum ( grid_shape [ j ] - 1 , floors [:, j ] +
131+ (n - 1 )))
154132 weights *= weighters [n ][:, j ]
155133
156134 result += weights * np .take (ravel_grid , ravel_offset )
@@ -191,7 +169,7 @@ def _phi(alpha, beta):
191169 x [0 ],
192170 np .tan (x [1 ] * _psi (x [0 ])) / np .tan (x [0 ] * np .pi / 2 ),
193171 x [3 ] * (x [2 ] + np .sin (x [1 ] * _psi (x [0 ]))),
194- (x [3 ] * np .cos (x [1 ] * _psi (x [0 ]))) ** (1 / x [0 ])
172+ (x [3 ] * np .cos (x [1 ] * _psi (x [0 ]))) ** (1 / x [0 ])
195173 ])
196174}
197175
@@ -232,7 +210,6 @@ class Parameters(object):
232210 The only useful function to be used directly is `convert`, which allows
233211 to transform parameters from one parametrization to another.
234212 Available parametrizations are {0, 1, A, B, M}.
235-
236213 """
237214
238215 @classmethod
@@ -251,7 +228,7 @@ def convert(cls, pars, par_in, par_out):
251228 >>> np.testing.assert_allclose(a, c)
252229
253230 :param pars: array of parameters to be converted
254- :type x : :class:`~numpy.ndarray`
231+ :type pars : :class:`~numpy.ndarray`
255232 :param par_in: parametrization of the input array
256233 :type par_in: str
257234 :param par_out: parametrization of the output array
@@ -317,38 +294,46 @@ def _calculate_levy(x, alpha, beta, cdf=False):
317294 This is used in the creation of the lookup table.
318295 Notice that to compute it in a 'true' x, the tangent must be applied.
319296 Example: levy(2, 1.5, 0) = _calculate_levy(np.tan(2), 1.5, 0)
320- "0" parameterization as per http://academic2.americanp.edu/~jpnolan/stable/stable.html
321- Note: fails for alpha=1.0 (so make sure alpha=1.0 isn't exactly on the interpolation grid)
297+ "0" parametrization as per http://academic2.americanp.edu/~jpnolan/stable/stable.html
298+ Addition: the special case alpha=1.0 was added. Due to an error in the
299+ numerical integration, the limit was changed from 0 to 1e-10.
322300 """
323301 from scipy import integrate
324302
325303 beta = - beta
326- C = _phi (alpha , beta )
327304
328- def func_cos (u ):
329- ua = u ** alpha
330- # if ua > 700.0: return 0.0
331- return np .exp (- ua ) * np .cos (C * ua - C * u )
305+ if alpha == 1 :
306+ li = 1e-10
307+
308+ def func_cos (u ):
309+ return np .exp (- u ) * np .cos (- beta * 2 / np .pi * (u * np .log (u ) - u ))
310+
311+ def func_sin (u ):
312+ return np .exp (- u ) * np .sin (- beta * 2 / np .pi * (u * np .log (u ) - u ))
313+
314+ else :
315+ li = 0
316+
317+ def func_cos (u ):
318+ ua = u ** alpha
319+ return np .exp (- ua ) * np .cos (_phi (alpha , beta ) * (ua - u ))
332320
333- def func_sin (u ):
334- ua = u ** alpha
335- # if ua > 700.0: return 0.0
336- return np .exp (- ua ) * np .sin (C * ua - C * u )
321+ def func_sin (u ):
322+ ua = u ** alpha
323+ return np .exp (- ua ) * np .sin (_phi (alpha , beta ) * (ua - u ))
337324
338325 if cdf :
339326 # Cumulative density function
340- return (integrate .quad (
341- lambda u : u and func_cos (u ) / u or 0.0 , 0.0 , np .Inf , weight = "sin" , wvar = x , limlst = 1000 )[0 ]
342- + integrate .quad (
343- lambda u : u and func_sin (u ) / u or 0.0 , 0.0 , np .Inf , weight = "cos" , wvar = x , limlst = 1000 )[0 ]
344- ) / np .pi + 0.5
327+ return (
328+ integrate .quad (lambda u : u and func_cos (u ) / u or 0.0 , li , np .Inf , weight = "sin" , wvar = x , limlst = 1000 )[0 ]
329+ + integrate .quad (lambda u : u and func_sin (u ) / u or 0.0 , li , np .Inf , weight = "cos" , wvar = x , limlst = 1000 )[0 ]
330+ ) / np .pi + 0.5
345331 else :
346332 # Probability density function
347- return (integrate .quad (
348- func_cos , 0.0 , np .Inf , weight = "cos" , wvar = x , limlst = 1000 )[0 ]
349- - integrate .quad (
350- func_sin , 0.0 , np .Inf , weight = "sin" , wvar = x , limlst = 1000 )[0 ]
351- ) / np .pi
333+ return (
334+ integrate .quad (func_cos , li , np .Inf , weight = "cos" , wvar = x , limlst = 1000 )[0 ]
335+ - integrate .quad (func_sin , li , np .Inf , weight = "sin" , wvar = x , limlst = 1000 )[0 ]
336+ ) / np .pi
352337
353338
354339def _approximate (x , alpha , beta , cdf = False ):
@@ -357,7 +342,7 @@ def _approximate(x, alpha, beta, cdf=False):
357342 values [mask ] *= (1.0 + beta )
358343 values [~ mask ] *= (1.0 - beta )
359344 if cdf :
360- return 1.0 - values
345+ return 1.0 - values * x
361346 else :
362347 return values * alpha
363348
@@ -367,15 +352,16 @@ def _make_dist_data_file():
367352
368353 xs , alphas , betas = [np .linspace (_lower [i ], _upper [i ], size [i ], endpoint = True ) for i in [0 , 1 , 2 ]]
369354 ts = np .tan (xs )
370- print ("Generating levy_data.py ..." )
371355
356+ print ("Generating pdf.npz ..." )
372357 pdf = np .zeros (size , 'float64' )
373358 for i , alpha in enumerate (alphas ):
374359 for j , beta in enumerate (betas ):
375360 print ("Calculating alpha={:.2f}, beta={:.2f}" .format (alpha , beta ))
376361 pdf [:, i , j ] = [_calculate_levy (t , alpha , beta , False ) for t in ts ]
377362 np .savez (os .path .join (ROOT , 'pdf.npz' ), pdf )
378363
364+ print ("Generating cdf.npz ..." )
379365 cdf = np .zeros (size , 'float64' )
380366 for i , alpha in enumerate (alphas ):
381367 for j , beta in enumerate (betas ):
@@ -395,34 +381,42 @@ def _int_levy(x, alpha, beta, cdf=False):
395381 points = np .empty (np .shape (x ) + (3 ,), 'float64' )
396382 points [..., 0 ] = np .arctan (x )
397383 points [..., 1 ] = alpha
398- points [..., 2 ] = np . abs ( beta )
384+ points [..., 2 ] = beta
399385
400- what = _cdf ( ) if cdf else _pdf ( )
386+ what = _read_from_cache ( 'cdf' ) if cdf else _read_from_cache ( 'pdf' )
401387 return _interpolate (points , what , _lower , _upper )
402388
403389
404- def _get_closest_approx (alpha , beta ):
405- x0 , x1 , n = - 50.0 , 10000.0 - 50.0 , 100000
406- dx = (x1 - x0 ) / n
407- x = np .linspace (x0 , x1 , num = n , endpoint = True )
390+ def _get_closest_approx (alpha , beta , upper = True ):
391+ n = 100000
392+ x1 , x2 = - 50.0 , 1e4 - 50.0
393+ li1 , li2 = 10 , 500
394+ if upper is False :
395+ x1 , x2 = - 1e4 + 50 , 50
396+ li1 , li2 = - 500 , - 10
397+ dx = (x2 - x1 ) / n
398+ x = np .linspace (x1 , x2 , num = n + 1 , endpoint = True )
408399 y = 1.0 - _int_levy (x , alpha , beta , cdf = True )
409400 z = 1.0 - _approximate (x , alpha , beta , cdf = True )
410- mask = (10.0 < x ) & (x < 500.0 )
411- return 10.0 + dx * np .argmin ((np .log (z [mask ]) - np .log (y [mask ])) ** 2.0 )
401+ mask = (li1 < x ) & (x < li2 )
402+ return li1 + dx * np .argmin ((np .log (z [mask ]) - np .log (y [mask ])) ** 2.0 )
412403
413404
414- def _make_limit_data_file ():
415- limits = np . zeros ( size [ 1 :], 'float64' )
416- alphas , betas = [ np . linspace ( _lower [ i ], _upper [ i ], size [ i ], endpoint = True ) for i in [ 1 , 2 ]]
405+ def _make_limit_data_files ():
406+ for upper in [ True , False ]:
407+ string = 'lower' if upper is False else 'upper'
417408
418- print ("Generating levy_approx_data.py ..." )
409+ limits = np .zeros (size [1 :], 'float64' )
410+ alphas , betas = [np .linspace (_lower [i ], _upper [i ], size [i ], endpoint = True ) for i in [1 , 2 ]]
419411
420- for i , alpha in enumerate (alphas ):
421- for j , beta in enumerate (betas ):
422- limits [i , j ] = _get_closest_approx (alpha , beta )
423- print ("Calculating alpha={:.2f}, beta={:.2f}, limit={:.2f}" .format (alpha , beta , limits [i , j ]))
412+ print ("Generating {}_limit.npz ..." .format (string ))
413+
414+ for i , alpha in enumerate (alphas ):
415+ for j , beta in enumerate (betas ):
416+ limits [i , j ] = _get_closest_approx (alpha , beta , upper = upper )
417+ print ("Calculating alpha={:.2f}, beta={:.2f}, limit={:.2f}" .format (alpha , beta , limits [i , j ]))
424418
425- np .savez (os .path .join (ROOT , 'limits .npz' ), limits )
419+ np .savez (os .path .join (ROOT , '{}_limit .npz' . format ( string ) ), limits )
426420
427421
428422def levy (x , alpha , beta , mu = 0.0 , sigma = 1.0 , cdf = False ):
@@ -441,7 +435,7 @@ def levy(x, alpha, beta, mu=0.0, sigma=1.0, cdf=False):
441435 Example:
442436 >>> x = np.array([1, 2, 3])
443437 >>> levy(x, 1.5, 0, cdf=True)
444- array([0.20203811 , 0.08453908 , 0.03150926 ])
438+ array([0.75634202 , 0.89496045 , 0.94840227 ])
445439
446440 :param x: values where the function is evaluated
447441 :type x: :class:`~numpy.ndarray`
@@ -461,20 +455,24 @@ def levy(x, alpha, beta, mu=0.0, sigma=1.0, cdf=False):
461455
462456 loc = mu
463457
464- what = _cdf () if cdf else _pdf ()
465- limits = _limits ()
458+ what = _read_from_cache ('cdf' ) if cdf else _read_from_cache ('pdf' )
459+ # limits = _limits()
460+ lower_limit = _read_from_cache ('lower_limit' )
461+ upper_limit = _read_from_cache ('upper_limit' )
466462
467463 xr = (np .asarray (x , 'd' ) - loc ) / sigma
468- alpha_index = int ((alpha - _lower [1 ]) / (_upper [1 ] - _lower [1 ]) * (size [1 ] - 1 ))
464+ alpha_index = int ((alpha - _lower [1 ]) / (_upper [1 ] - _lower [1 ]) * (size [1 ] - 1 ))
469465 beta_index = int ((beta - _lower [2 ]) / (_upper [2 ] - _lower [2 ]) * (size [2 ] - 1 ))
470466 try :
471- l = limits [alpha_index , beta_index ]
467+ # lims = limits[alpha_index, beta_index]
468+ low_lims = lower_limit [alpha_index , beta_index ]
469+ up_lims = upper_limit [alpha_index , beta_index ]
472470 except IndexError :
473471 print (alpha , alpha_index )
474472 print (beta , beta_index )
475473 print ('This should not happen! If so, please open an issue in the pylevy github page please.' )
476474 raise
477- mask = (np . abs ( xr ) < l )
475+ mask = (low_lims <= xr ) & ( xr <= up_lims )
478476 z = xr [mask ]
479477
480478 points = np .empty (np .shape (z ) + (3 ,), 'float64' )
@@ -535,36 +533,29 @@ def fit_levy(x, par='0', **kwargs):
535533
536534 Examples:
537535 >>> np.random.seed(0)
538- >>> x = random(1.5, 0, 0, 1, shape=200)
536+ >>> x = random(1.5, 0, 0, 1, shape=( 200,) )
539537 >>> fit_levy(x) # -- Fit a stable distribution to x
540- (par=0, alpha=1.52, beta=-0.08, mu=0.05, sigma=0.99, 402.44279062026806 )
538+ (par=0, alpha=1.52, beta=-0.08, mu=0.05, sigma=0.99, 402.37150603509247 )
541539
542540 >>> fit_levy(x, beta=0.0) # -- Fit a symmetric stable distribution to x
543- (par=0, alpha=1.53, beta=0.00, mu=0.03, sigma=0.99, 402.5204574692911 )
541+ (par=0, alpha=1.53, beta=0.00, mu=0.03, sigma=0.99, 402.43833088693725 )
544542
545543 >>> fit_levy(x, beta=0.0, mu=0.0) # -- Fit a symmetric distribution centered on zero to x
546- (par=0, alpha=1.53, beta=0.00, mu=0.00, sigma=0.99, 402.5557826203093 )
544+ (par=0, alpha=1.53, beta=0.00, mu=0.00, sigma=0.99, 402.4736618823546 )
547545
548546 >>> fit_levy(x, alpha=1.0, beta=0.0) # -- Fit a Cauchy distribution to x
549- (par=0, alpha=1.00, beta=0.00, mu=0.10, sigma=0.90, 416.5364751270402 )
547+ (par=0, alpha=1.00, beta=0.00, mu=0.10, sigma=0.90, 416.54249079255976 )
550548
551549 :param x: values to be fitted
552550 :type x: :class:`~numpy.ndarray`
553- :param alpha: alpha
554- :type alpha: float
555- :param beta: beta
556- :type beta: float
557- :param mu: mu
558- :type mu: float
559- :param sigma: sigma
560- :type sigma: float
561551 :param par: parametrization
562- :type par: int
552+ :type par: str
563553 :return: a tuple with a `Parameters` object and the negative log likelihood of the data.
564554 :rtype: tuple
565555 """
566556
567- values = {par_name : None if par_name not in kwargs else kwargs [par_name ] for i , par_name in enumerate (par_names [par ])}
557+ values = {par_name : None if par_name not in kwargs else kwargs [par_name ] for i , par_name in
558+ enumerate (par_names [par ])}
568559
569560 parameters = Parameters (par = par , ** values )
570561 temp = Parameters (par = par , ** values )
@@ -589,8 +580,9 @@ def random(alpha, beta, mu=0.0, sigma=1.0, shape=()):
589580 It uses parametrization 0 (to get it from another parametrization, convert).
590581
591582 Example:
592- >>> x = random(1.5, 0, shape=100) # parametrization 0 is implicit
593- >>> x = random(*Parameters.convert([1.5, 0.905, 0.707, 1.414] ,'B' ,'0'), shape=100) # example with conversion
583+ >>> rnd = random(1.5, 0, shape=(100,)) # parametrization 0 is implicit
584+ >>> par = np.array([1.5, 0.905, 0.707, 1.414])
585+ >>> rnd = random(*Parameters.convert(par ,'B' ,'0'), shape=(100,)) # example with convert
594586
595587 :param alpha: alpha
596588 :type alpha: float
@@ -638,13 +630,13 @@ def random(alpha, beta, mu=0.0, sigma=1.0, shape=()):
638630if __name__ == "__main__" :
639631 if "build" in sys .argv [1 :]:
640632 _make_dist_data_file ()
641- _make_limit_data_file ()
633+ _make_limit_data_files ()
642634
643635 print ("Testing fit_levy using parametrization 0 and fixed alpha (1.5)." )
644636
645637 N = 1000
646638 print ("{} points, result should be (1.5, 0.5, 0.0, 1.0)." .format (N ))
647- x = random (1.5 , 0.5 , 0.0 , 1.0 , 1000 )
639+ x0 = random (1.5 , 0.5 , 0.0 , 1.0 , shape = ( 1000 ) )
648640
649- result = fit_levy (x , par = '0' , alpha = 1.5 )
650- print (result )
641+ result0 = fit_levy (x0 , par = '0' , alpha = 1.5 )
642+ print (result0 )
0 commit comments