@@ -11,6 +11,8 @@ except:
11
11
import numpy as np
12
12
cimport numpy as np
13
13
cimport cython
14
+
15
+ from libc cimport string
14
16
from libc .stdint cimport (uint8_t , uint16_t , uint32_t , uint64_t , int8_t ,
15
17
int16_t , int32_t , int64_t , intptr_t )
16
18
from cpython cimport Py_INCREF
@@ -129,6 +131,13 @@ cdef double kahan_sum(double *darr, np.npy_intp n):
129
131
sum = t
130
132
return sum
131
133
134
+ cdef object _ensure_string (object s ):
135
+ try :
136
+ return '' .join (map (chr , s ))
137
+ except :
138
+ return s
139
+
140
+
132
141
cdef class RandomState :
133
142
CLASS_DOCSTRING
134
143
@@ -140,6 +149,7 @@ cdef class RandomState:
140
149
poisson_lam_max = POISSON_LAM_MAX
141
150
__MAXSIZE = < uint64_t > sys .maxsize
142
151
152
+
143
153
IF RS_RNG_SEED == 1 :
144
154
def __init__ (self , seed = None ):
145
155
self .rng_state .rng = < rng_t * > PyArray_malloc_aligned (sizeof (rng_t ))
@@ -162,17 +172,7 @@ cdef class RandomState:
162
172
IF RS_RNG_MOD_NAME == 'dsfmt' :
163
173
PyArray_free_aligned (self .rng_state .buffered_uniforms )
164
174
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' :
176
176
def seed (self , seed = None ):
177
177
"""
178
178
seed(seed=None)
@@ -273,7 +273,7 @@ cdef class RandomState:
273
273
raise ValueError ('val < 0' )
274
274
if inc < 0 :
275
275
raise ValueError ('inc < 0' )
276
- IF RS_RNG_NAME == 'pcg64' :
276
+ IF RS_RNG_MOD_NAME == 'pcg64' :
277
277
IF RS_PCG128_EMULATED :
278
278
set_seed (& self .rng_state ,
279
279
pcg128_from_pylong (val ),
@@ -315,7 +315,7 @@ cdef class RandomState:
315
315
Advancing the prng state resets any pre-computed random numbers.
316
316
This is required to ensure exact reproducibility.
317
317
"""
318
- IF RS_RNG_NAME == 'pcg64' :
318
+ IF RS_RNG_MOD_NAME == 'pcg64' :
319
319
IF RS_PCG128_EMULATED :
320
320
advance_state (& self .rng_state , pcg128_from_pylong (delta ))
321
321
ELSE :
@@ -357,7 +357,7 @@ cdef class RandomState:
357
357
self .rng_state .gauss = 0.0
358
358
return None
359
359
360
- IF RS_RNG_NAME == 'mt19937' :
360
+ IF RS_RNG_MOD_NAME == 'mt19937' :
361
361
def get_state (self , legacy = False ):
362
362
"""
363
363
get_state()
@@ -394,12 +394,13 @@ cdef class RandomState:
394
394
For information about the specific structure of the PRNG-specific
395
395
component, see the class documentation.
396
396
"""
397
+ rng_name = _ensure_string (RS_RNG_NAME )
397
398
if legacy :
398
- return (RS_RNG_NAME ,) \
399
+ return (rng_name . upper () ,) \
399
400
+ _get_state (self .rng_state ) \
400
401
+ (self .rng_state .has_gauss , self .rng_state .gauss )
401
402
402
- return {'name' : RS_RNG_NAME ,
403
+ return {'name' : rng_name ,
403
404
'state' : _get_state (self .rng_state ),
404
405
'gauss' : {'has_gauss' : self .rng_state .has_gauss , 'gauss' : self .rng_state .gauss },
405
406
'uint32' : {'has_uint32' : self .rng_state .has_uint32 , 'uint32' : self .rng_state .uinteger }
@@ -436,7 +437,8 @@ cdef class RandomState:
436
437
For information about the specific structure of the PRNG-specific
437
438
component, see the class documentation.
438
439
"""
439
- return {'name' : RS_RNG_NAME ,
440
+ rng_name = _ensure_string (RS_RNG_NAME )
441
+ return {'name' : rng_name ,
440
442
'state' : _get_state (self .rng_state ),
441
443
'gauss' : {'has_gauss' : self .rng_state .has_gauss , 'gauss' : self .rng_state .gauss },
442
444
'uint32' : {'has_uint32' : self .rng_state .has_uint32 , 'uint32' : self .rng_state .uinteger }
@@ -479,14 +481,18 @@ cdef class RandomState:
479
481
For information about the specific structure of the PRNG-specific
480
482
component, see the class documentation.
481
483
"""
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' :
484
486
if isinstance (state , tuple ):
485
487
if state [0 ] != 'MT19937' :
486
488
raise ValueError ('Not a ' + rng_name + ' RNG state' )
487
489
_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
490
496
self .rng_state .has_uint32 = 0
491
497
self .rng_state .uinteger = 0
492
498
return None
@@ -552,6 +558,17 @@ cdef class RandomState:
552
558
else :
553
559
raise ValueError ('Unknown value of bits. Must be either 32 or 64.' )
554
560
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:
555
572
def random_sample (self , size = None ):
556
573
"""
557
574
random_sample(size=None)
@@ -595,17 +612,6 @@ cdef class RandomState:
595
612
596
613
"""
597
614
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)
609
615
610
616
def tomaxint (self , size = None ):
611
617
"""
@@ -652,17 +658,20 @@ cdef class RandomState:
652
658
[ True, True]]], dtype=bool)
653
659
654
660
"""
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
657
664
658
665
if size is None :
659
666
return random_positive_int (& self .rng_state )
660
667
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
+
663
672
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
666
675
667
676
def randint (self , low , high = None , size = None , dtype = 'l' ):
668
677
"""
@@ -757,7 +766,7 @@ cdef class RandomState:
757
766
elif key == 'bool' :
758
767
return _rand_bool (low , high - 1 , size , & self .rng_state , self .lock )
759
768
760
- def bytes (self , Py_ssize_t length ):
769
+ def bytes (self , np . npy_intp length ):
761
770
"""
762
771
bytes(length)
763
772
@@ -3200,7 +3209,7 @@ cdef class RandomState:
3200
3209
"""
3201
3210
return disc (& self .rng_state , & random_binomial , size , self .lock , 1 , 1 ,
3202
3211
p , 'p' , CONS_BOUNDED_0_1_NOTNAN ,
3203
- n , 'n' , CONS_POSITIVE ,
3212
+ n , 'n' , CONS_NON_NEGATIVE ,
3204
3213
0.0 , '' , CONS_NONE )
3205
3214
3206
3215
def negative_binomial (self , n , p , size = None ):
@@ -3466,7 +3475,6 @@ cdef class RandomState:
3466
3475
0.34889999999999999 #random
3467
3476
3468
3477
"""
3469
-
3470
3478
return disc (& self .rng_state , & random_geometric , size , self .lock , 1 , 0 ,
3471
3479
p , 'p' , CONS_BOUNDED_0_1 ,
3472
3480
0.0 , '' , CONS_NONE ,
@@ -3800,8 +3808,8 @@ cdef class RandomState:
3800
3808
# standard normally distributed random numbers. The matrix has rows
3801
3809
# with the same length as mean and as many rows are necessary to
3802
3810
# 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 ])
3805
3813
x = self .standard_normal (final_shape , method = method ).reshape (- 1 , mean .shape [0 ])
3806
3814
3807
3815
# Transform matrix of standard normals into matrix where each row
@@ -3912,7 +3920,7 @@ cdef class RandomState:
3912
3920
cdef double Sum
3913
3921
3914
3922
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 )
3916
3924
pix = < double * > np .PyArray_DATA (parr )
3917
3925
3918
3926
if kahan_sum (pix , d - 1 ) > (1.0 + 1e-12 ):
@@ -3944,6 +3952,7 @@ cdef class RandomState:
3944
3952
Sum = Sum - pix [j ]
3945
3953
if dn > 0 :
3946
3954
mnix [i + d - 1 ] = dn
3955
+
3947
3956
i = i + d
3948
3957
3949
3958
return multin
@@ -4102,34 +4111,53 @@ cdef class RandomState:
4102
4111
[0, 1, 2]])
4103
4112
4104
4113
"""
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.
4116
4138
buf = np .empty_like (x [0 ])
4117
4139
with self .lock :
4118
- while i > 0 :
4140
+ for i in reversed ( range ( 1 , n )) :
4119
4141
j = random_interval (& self .rng_state , i )
4120
4142
buf [...] = x [j ]
4121
4143
x [j ] = x [i ]
4122
4144
x [i ] = buf
4123
- i = i - 1
4124
4145
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.
4128
4147
with self .lock :
4129
- while i > 0 :
4148
+ for i in reversed ( range ( 1 , n )) :
4130
4149
j = random_interval (& self .rng_state , i )
4131
4150
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
+
4133
4161
4134
4162
def permutation (self , object x ):
4135
4163
"""
0 commit comments