-
Notifications
You must be signed in to change notification settings - Fork 0
/
random.pyx
300 lines (224 loc) · 8.19 KB
/
random.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# cython: boundscheck=False
# cython: cdivision=True
# cython: wraparound=False
cimport cython
from libc.stdlib cimport rand, srand
from libc.math cimport log, sqrt, cos, round
cimport numpy as np
import time
# MT Stuff
cdef unsigned NN = 312
cdef unsigned MM = 156
cdef unsigned long long MATRIX_A = 0xB5026F5AA96619E9ULL
cdef unsigned long long UM = 0xFFFFFFFF80000000ULL
cdef unsigned long long LM = 0x7FFFFFFFULL
cdef unsigned long long mt[312]
cdef unsigned mti = NN + 1
cdef unsigned long long mag01[2]
cdef mt_seed(unsigned long long seed):
global mt
global mti
global mag01
global NN
global MATRIX_A
mt[0] = seed
for mti in range(1,NN):
mt[mti] = (6364136223846793005ULL * (mt[mti-1] ^ (mt[mti-1] >> 62)) + mti)
mag01[0] = 0ULL
mag01[1] = MATRIX_A
mti = NN
cdef unsigned long long genrand64():
cdef int i
cdef unsigned long long x
global mag01
global mti
global mt
global NN
global MM
global UM
global LM
if mti >= NN:
for i in range(NN-MM):
x = (mt[i]&UM) | (mt[i+1]&LM)
mt[i] = mt[i+MM] ^ (x>>1) ^ mag01[int(x&1ULL)]
for i in range(NN-MM, NN-1):
x = (mt[i]&UM)|(mt[i+1]&LM)
mt[i] = mt[i+(MM-NN)] ^ (x>>1) ^ mag01[int(x&1ULL)]
x = (mt[NN-1]&UM)|(mt[0]&LM)
mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[int(x&1ULL)]
mti = 0
x = mt[mti]
mti += 1
x ^= (x >> 29) & 0x5555555555555555ULL
x ^= (x << 17) & 0x71D67FFFEDA60000ULL
x ^= (x << 37) & 0xFFF7EEE000000000ULL
x ^= (x >> 43);
return x
cdef int choose(unsigned modulo):
return int(genrand64() % modulo)
def py_rand_int():
return genrand64()
# Functions
# Seed the random number generator
cdef seed_random(unsigned long long seed):
"""
Seed the C random number generator with the current system time.
:return: none
"""
if seed == 0:
mt_seed(time.time())
else:
mt_seed(seed)
def py_seed_random(unsigned long long seed = 0):
seed_random(seed)
cdef double exponential_rv(double Lambda):
"""
Generate an exponentially distributed random number
:param Lambda: (double) the rate parameter of the distribution
:return: (double) a randomly distributed number
"""
return -1.0/Lambda* log( uniform_rv() )
def py_exponential_rv(double Lambda):
return exponential_rv(Lambda)
cdef double uniform_rv():
"""
Generate a uniform random variable in [0,1]
:return: (double) a random uniform number in [0,1]
"""
return (genrand64() >> 11) * (1.0/9007199254740991.0)
def py_uniform_rv():
return uniform_rv()
cdef double normal_rv(double mean, double std):
"""
Generate a normal random variable
:param mean: (double) the mean
:param std: (double) the standard deviation
:return:
"""
cdef double u, v, R, theta
u = uniform_rv()
v = uniform_rv()
R = sqrt(-2*log(u))
theta = 2*3.141592653589793238462643383279502884*v
return R*cos(theta)*std + mean
def py_normal_rv(double mean, double std):
return normal_rv(mean,std)
cdef double gamma_rv(double k, double theta):
"""
Generate a gamma random variable with parameters (k, theta). The mean is k*theta. Variance k*theta^2. Can think of
this as adding together k independent exponentials each with mean theta.
:param k: (double) the first parameter of the gamma distribution. if k is an integer, it is the number of
independent exponentially distributed steps.
:param theta: (double) the second parameter. theta is the mean of each exponential step.
:return: (double) a randomly generated gamma random variable.
"""
cdef double d, c, v, x, UNI
d = k - 1.0/3
c = 1/sqrt(9.0*d)
while True:
x = normal_rv(0,1)
v = (1+c*x)**3
UNI = uniform_rv()
if v > 0 and log(UNI) < 0.5*x**2+d-d*v+d*log(v):
return d*v*theta
def py_gamma_rv(double k, double theta):
return gamma_rv(k,theta)
cdef double erlang_rv(double k , double theta):
"""
Generate an Erlang random variable with k independent exponential steps each with mean theta. The mean is k*theta
and the variance is k*theta^2
:param k: (double) number of exponentially distributed steps. will be rounded to the nearest integer.
:param theta: (double) mean of each step.
:return: (double) the erlang random variable.
"""
cdef double answer = 0.0
cdef unsigned i = 0
cdef unsigned num_iterations = int(k+0.5)
for i in range(num_iterations):
answer += -1.0 * theta * log( uniform_rv() )
return answer
def py_erlang_rv(double k, double theta):
return erlang_rv(k,theta)
cdef int sample_discrete(int choices, double* data, double Lambda):
"""
Sample from a discrete set of options according to some un-normalized probability weights.
:param choices: (int) the number of possible choices. should be positive.
:param data: (double *) pointer to an array containing an un-normalized weight associated with each choice. (must
have length = choices)
:param Lambda: (double) the sum of all the probability weights in data. Used as the normalization factor.
:return: (int) A non-negative index randomly sampled according to the weights. 0 <= return < choices
"""
cdef double q = uniform_rv()*Lambda
cdef int i = 0
cdef double p_sum = 0.0
while p_sum < q and i < choices:
p_sum += data[i]
i += 1
return i - 1
def py_sample_discrete(int choices, np.ndarray[np.double_t,ndim=1] data, double Lambda):
return sample_discrete(choices, <double*> (data.data), Lambda)
cdef double array_sum(double* data, int length):
"""
Sum an array of floating point numbers
:param data: (double *) pointer to the array of numbers (must have len(data) >= length)
:param length: (int) the length of the array.
:return: (double) the sum of all the numbers
"""
cdef double answer = 0.0
cdef int i = 0
for i in range(length):
answer += data[i]
return answer
def py_array_sum(np.ndarray[np.double_t,ndim=1] data, int length):
return array_sum(<double*> data.data, length)
cdef unsigned binom_rnd(unsigned n, double p):
"""
Generate a binomial random number selected from binom(n,p)
:param n: (unsigned) the population size, n>= 1
:param p: (double) 0 <= p <= 1, the probability of success.
:return: (unsigned) binomial random number
"""
cdef unsigned answer = 0
cdef unsigned i = 0
for i in range(n):
if uniform_rv() < p:
answer += 1
return answer
def py_binom_rnd(unsigned n, double p):
return binom_rnd(n,p)
cdef unsigned binom_rnd_f(double N, double p):
"""
Generate a binomial random number selected from binom(N,p), N >= 1
:param N: (double) will be rounded to nearest integer
:param p: (double) 0 <= p <= 1, the probability of success.
:return: (unsigned) binomial random numbe
"""
cdef unsigned answer = 0
cdef unsigned n = int(N+0.5)
cdef unsigned i = 0
for i in range(n):
if uniform_rv() < p:
answer += 1
return answer
def py_binom_rnd_f(double N, double p):
return binom_rnd_f( N, p)
cdef unsigned approx_binom_rnd(unsigned n, double p):
"""
Generate an approximately binomial random variable using the normal approximation.
:param n: (unsigned) The population size n >= 1, but should be at least 20 for good approximation.
:param p: (double) 0 <= p <= 1. p closer to 0.5 gives a better approximation
:return: (unsigned) random number sample.
"""
return int( normal_rv( n*p , sqrt(n*p*(1-p)) ) + 0.5 )
def py_approx_binom_rnd(unsigned n, double p):
return approx_binom_rnd(n,p)
cdef unsigned approx_binom_rnd_f(double n, double p):
"""
Generate an approximately binomial random variable using the normal approximation.
:param n: (double) The population size n >= 1, but should be at least 20 for good approximation.
:param p: (double) 0 <= p <= 1. p closer to 0.5 gives a better approximation
:return: (unsigned) random number sample.
"""
return int( normal_rv( n*p , sqrt(n*p*(1-p)) ) + 0.5 )
def py_approx_binom_rnd_f(double n, double p):
return approx_binom_rnd_f(n,p)