@@ -11,6 +11,8 @@ except:
1111import numpy as np
1212cimport numpy as np
1313cimport cython
14+
15+ from libc cimport string
1416from libc .stdint cimport (uint8_t , uint16_t , uint32_t , uint64_t , int8_t ,
1517 int16_t , int32_t , int64_t , intptr_t )
1618from cpython cimport Py_INCREF
@@ -129,6 +131,13 @@ cdef double kahan_sum(double *darr, np.npy_intp n):
129131 sum = t
130132 return sum
131133
134+ cdef object _ensure_string (object s ):
135+ try :
136+ return '' .join (map (chr , s ))
137+ except :
138+ return s
139+
140+
132141cdef class RandomState :
133142 CLASS_DOCSTRING
134143
@@ -140,6 +149,7 @@ cdef class RandomState:
140149 poisson_lam_max = POISSON_LAM_MAX
141150 __MAXSIZE = < uint64_t > sys .maxsize
142151
152+
143153 IF RS_RNG_SEED == 1 :
144154 def __init__ (self , seed = None ):
145155 self .rng_state .rng = < rng_t * > PyArray_malloc_aligned (sizeof (rng_t ))
@@ -162,17 +172,7 @@ cdef class RandomState:
162172 IF RS_RNG_MOD_NAME == 'dsfmt' :
163173 PyArray_free_aligned (self .rng_state .buffered_uniforms )
164174
165- # Pickling support:
166- def __getstate__ (self ):
167- return self .get_state ()
168-
169- def __setstate__ (self , state ):
170- self .set_state (state )
171-
172- def __reduce__ (self ):
173- return (randomstate .prng .__generic_ctor , (RS_RNG_MOD_NAME ,), self .get_state ())
174-
175- IF RS_RNG_NAME == 'mt19937' :
175+ IF RS_RNG_MOD_NAME == 'mt19937' :
176176 def seed (self , seed = None ):
177177 """
178178 seed(seed=None)
@@ -273,7 +273,7 @@ cdef class RandomState:
273273 raise ValueError ('val < 0' )
274274 if inc < 0 :
275275 raise ValueError ('inc < 0' )
276- IF RS_RNG_NAME == 'pcg64' :
276+ IF RS_RNG_MOD_NAME == 'pcg64' :
277277 IF RS_PCG128_EMULATED :
278278 set_seed (& self .rng_state ,
279279 pcg128_from_pylong (val ),
@@ -315,7 +315,7 @@ cdef class RandomState:
315315 Advancing the prng state resets any pre-computed random numbers.
316316 This is required to ensure exact reproducibility.
317317 """
318- IF RS_RNG_NAME == 'pcg64' :
318+ IF RS_RNG_MOD_NAME == 'pcg64' :
319319 IF RS_PCG128_EMULATED :
320320 advance_state (& self .rng_state , pcg128_from_pylong (delta ))
321321 ELSE :
@@ -357,7 +357,7 @@ cdef class RandomState:
357357 self .rng_state .gauss = 0.0
358358 return None
359359
360- IF RS_RNG_NAME == 'mt19937' :
360+ IF RS_RNG_MOD_NAME == 'mt19937' :
361361 def get_state (self , legacy = False ):
362362 """
363363 get_state()
@@ -394,12 +394,13 @@ cdef class RandomState:
394394 For information about the specific structure of the PRNG-specific
395395 component, see the class documentation.
396396 """
397+ rng_name = _ensure_string (RS_RNG_NAME )
397398 if legacy :
398- return (RS_RNG_NAME ,) \
399+ return (rng_name . upper () ,) \
399400 + _get_state (self .rng_state ) \
400401 + (self .rng_state .has_gauss , self .rng_state .gauss )
401402
402- return {'name' : RS_RNG_NAME ,
403+ return {'name' : rng_name ,
403404 'state' : _get_state (self .rng_state ),
404405 'gauss' : {'has_gauss' : self .rng_state .has_gauss , 'gauss' : self .rng_state .gauss },
405406 'uint32' : {'has_uint32' : self .rng_state .has_uint32 , 'uint32' : self .rng_state .uinteger }
@@ -436,7 +437,8 @@ cdef class RandomState:
436437 For information about the specific structure of the PRNG-specific
437438 component, see the class documentation.
438439 """
439- return {'name' : RS_RNG_NAME ,
440+ rng_name = _ensure_string (RS_RNG_NAME )
441+ return {'name' : rng_name ,
440442 'state' : _get_state (self .rng_state ),
441443 'gauss' : {'has_gauss' : self .rng_state .has_gauss , 'gauss' : self .rng_state .gauss },
442444 'uint32' : {'has_uint32' : self .rng_state .has_uint32 , 'uint32' : self .rng_state .uinteger }
@@ -479,14 +481,18 @@ cdef class RandomState:
479481 For information about the specific structure of the PRNG-specific
480482 component, see the class documentation.
481483 """
482- rng_name = RS_RNG_NAME
483- IF RS_RNG_NAME == 'mt19937' :
484+ rng_name = _ensure_string ( RS_RNG_NAME )
485+ IF RS_RNG_MOD_NAME == 'mt19937' :
484486 if isinstance (state , tuple ):
485487 if state [0 ] != 'MT19937' :
486488 raise ValueError ('Not a ' + rng_name + ' RNG state' )
487489 _set_state (& self .rng_state , (state [1 ], state [2 ]))
488- self .rng_state .has_gauss = state [3 ]
489- self .rng_state .gauss = state [4 ]
490+ if len (state ) > 3 :
491+ self .rng_state .has_gauss = state [3 ]
492+ self .rng_state .gauss = state [4 ]
493+ else :
494+ self .rng_state .has_gauss = 0
495+ self .rng_state .gauss = 0.0
490496 self .rng_state .has_uint32 = 0
491497 self .rng_state .uinteger = 0
492498 return None
@@ -552,6 +558,17 @@ cdef class RandomState:
552558 else :
553559 raise ValueError ('Unknown value of bits. Must be either 32 or 64.' )
554560
561+ # Pickling support:
562+ def __getstate__ (self ):
563+ return self .get_state ()
564+
565+ def __setstate__ (self , state ):
566+ self .set_state (state )
567+
568+ def __reduce__ (self ):
569+ return (randomstate .prng .__generic_ctor , (RS_RNG_MOD_NAME ,), self .get_state ())
570+
571+ # Basic distributions:
555572 def random_sample (self , size = None ):
556573 """
557574 random_sample(size=None)
@@ -595,17 +612,6 @@ cdef class RandomState:
595612
596613 """
597614 return double_fill (& self .rng_state , & random_uniform_fill , size , self .lock )
598- # cdef double out
599- # cdef double [::1] out_array
600- # cdef Py_ssize_t n
601- # if size is None:
602- # random_uniform_fill(&self.rng_state, 1, &out)
603- # return out
604- # else:
605- # n = compute_numel(size)
606- # out_array = np.empty(n, np.double)
607- # random_uniform_fill(&self.rng_state, n, &out_array[0])
608- # return np.asarray(out_array).reshape(size)
609615
610616 def tomaxint (self , size = None ):
611617 """
@@ -652,17 +658,20 @@ cdef class RandomState:
652658 [ True, True]]], dtype=bool)
653659
654660 """
655- cdef Py_ssize_t n
656- cdef np .int_t [::1 ] randoms
661+ cdef np .npy_intp n
662+ cdef np .ndarray randoms
663+ cdef long * randoms_data
657664
658665 if size is None :
659666 return random_positive_int (& self .rng_state )
660667
661- n = compute_numel (size )
662- randoms = np .empty (n , np .int )
668+ randoms = < np .ndarray > np .empty (size , dtype = np .int )
669+ randoms_data = < long * > np .PyArray_DATA (randoms )
670+ n = np .PyArray_SIZE (randoms )
671+
663672 for i in range (n ):
664- randoms [i ] = random_positive_int (& self .rng_state )
665- return np . asarray ( randoms ). reshape ( size )
673+ randoms_data [i ] = random_positive_int (& self .rng_state )
674+ return randoms
666675
667676 def randint (self , low , high = None , size = None , dtype = 'l' ):
668677 """
@@ -757,7 +766,7 @@ cdef class RandomState:
757766 elif key == 'bool' :
758767 return _rand_bool (low , high - 1 , size , & self .rng_state , self .lock )
759768
760- def bytes (self , Py_ssize_t length ):
769+ def bytes (self , np . npy_intp length ):
761770 """
762771 bytes(length)
763772
@@ -3200,7 +3209,7 @@ cdef class RandomState:
32003209 """
32013210 return disc (& self .rng_state , & random_binomial , size , self .lock , 1 , 1 ,
32023211 p , 'p' , CONS_BOUNDED_0_1_NOTNAN ,
3203- n , 'n' , CONS_POSITIVE ,
3212+ n , 'n' , CONS_NON_NEGATIVE ,
32043213 0.0 , '' , CONS_NONE )
32053214
32063215 def negative_binomial (self , n , p , size = None ):
@@ -3466,7 +3475,6 @@ cdef class RandomState:
34663475 0.34889999999999999 #random
34673476
34683477 """
3469-
34703478 return disc (& self .rng_state , & random_geometric , size , self .lock , 1 , 0 ,
34713479 p , 'p' , CONS_BOUNDED_0_1 ,
34723480 0.0 , '' , CONS_NONE ,
@@ -3800,8 +3808,8 @@ cdef class RandomState:
38003808 # standard normally distributed random numbers. The matrix has rows
38013809 # with the same length as mean and as many rows are necessary to
38023810 # form a matrix of shape final_shape.
3803- final_shape = tuple (shape [:])
3804- final_shape += (mean .shape [0 ], )
3811+ final_shape = list (shape [:])
3812+ final_shape . append (mean .shape [0 ])
38053813 x = self .standard_normal (final_shape , method = method ).reshape (- 1 , mean .shape [0 ])
38063814
38073815 # Transform matrix of standard normals into matrix where each row
@@ -3912,7 +3920,7 @@ cdef class RandomState:
39123920 cdef double Sum
39133921
39143922 d = len (pvals )
3915- parr = < np .ndarray > np .PyArray_ContiguousFromObject (pvals , np .NPY_DOUBLE , 1 , 1 )
3923+ parr = < np .ndarray > np .PyArray_FROM_OTF (pvals , np .NPY_DOUBLE , np . NPY_ALIGNED )
39163924 pix = < double * > np .PyArray_DATA (parr )
39173925
39183926 if kahan_sum (pix , d - 1 ) > (1.0 + 1e-12 ):
@@ -3944,6 +3952,7 @@ cdef class RandomState:
39443952 Sum = Sum - pix [j ]
39453953 if dn > 0 :
39463954 mnix [i + d - 1 ] = dn
3955+
39473956 i = i + d
39483957
39493958 return multin
@@ -4102,34 +4111,53 @@ cdef class RandomState:
41024111 [0, 1, 2]])
41034112
41044113 """
4105- cdef Py_ssize_t i
4106- cdef long j
4107-
4108- i = len (x ) - 1
4109-
4110- # Logic adapted from random.shuffle()
4111- if isinstance (x , np .ndarray ) and \
4112- (x .ndim > 1 or x .dtype .fields is not None ):
4113- # For a multi-dimensional ndarray, indexing returns a view onto
4114- # each row. So we can't just use ordinary assignment to swap the
4115- # rows; we need a bounce buffer.
4114+ cdef :
4115+ np .npy_intp i , j , n = len (x ), stride , itemsize
4116+ char * x_ptr
4117+ char * buf_ptr
4118+
4119+ if type (x ) is np .ndarray and x .ndim == 1 and x .size :
4120+ # Fast, statically typed path: shuffle the underlying buffer.
4121+ # Only for non-empty, 1d objects of class ndarray (subclasses such
4122+ # as MaskedArrays may not support this approach).
4123+ x_ptr = < char * > < size_t > x .ctypes .data
4124+ stride = x .strides [0 ]
4125+ itemsize = x .dtype .itemsize
4126+ buf = np .empty_like (x [0 ]) # GC'd at function exit
4127+ buf_ptr = < char * > < size_t > buf .ctypes .data
4128+ with self .lock :
4129+ # We trick gcc into providing a specialized implementation for
4130+ # the most common case, yielding a ~33% performance improvement.
4131+ # Note that apparently, only one branch can ever be specialized.
4132+ if itemsize == sizeof (np .npy_intp ):
4133+ self ._shuffle_raw (n , sizeof (np .npy_intp ), stride , x_ptr , buf_ptr )
4134+ else :
4135+ self ._shuffle_raw (n , itemsize , stride , x_ptr , buf_ptr )
4136+ elif isinstance (x , np .ndarray ) and x .ndim > 1 and x .size :
4137+ # Multidimensional ndarrays require a bounce buffer.
41164138 buf = np .empty_like (x [0 ])
41174139 with self .lock :
4118- while i > 0 :
4140+ for i in reversed ( range ( 1 , n )) :
41194141 j = random_interval (& self .rng_state , i )
41204142 buf [...] = x [j ]
41214143 x [j ] = x [i ]
41224144 x [i ] = buf
4123- i = i - 1
41244145 else :
4125- # For single-dimensional arrays, lists, and any other Python
4126- # sequence types, indexing returns a real object that's
4127- # independent of the array contents, so we can just swap directly.
4146+ # Untyped path.
41284147 with self .lock :
4129- while i > 0 :
4148+ for i in reversed ( range ( 1 , n )) :
41304149 j = random_interval (& self .rng_state , i )
41314150 x [i ], x [j ] = x [j ], x [i ]
4132- i = i - 1
4151+
4152+ cdef inline _shuffle_raw (self , np .npy_intp n , np .npy_intp itemsize ,
4153+ np .npy_intp stride , char * data , char * buf ):
4154+ cdef np .npy_intp i , j
4155+ for i in reversed (range (1 , n )):
4156+ j = random_interval (& self .rng_state , i )
4157+ string .memcpy (buf , data + j * stride , itemsize )
4158+ string .memcpy (data + j * stride , data + i * stride , itemsize )
4159+ string .memcpy (data + i * stride , buf , itemsize )
4160+
41334161
41344162 def permutation (self , object x ):
41354163 """
0 commit comments