mirror of
https://github.com/exaloop/codon.git
synced 2025-06-03 15:03:52 +08:00
2967 lines
102 KiB
Python
2967 lines
102 KiB
Python
# Copyright (C) 2022-2025 Exaloop Inc. <https://exaloop.io>
|
|
|
|
from ..routines import *
|
|
from ..util import PI, uitofp, zext, itrunc, signbit, isnan, nan64, fabs, cmod, floor, ceil, log, log1p, exp, expm1, pow, sqrt, cos, acos
|
|
from .logfactorial import logfactorial
|
|
from .seed import SeedSequence
|
|
from .ziggurat import *
|
|
|
|
u128 = UInt[128]
|
|
|
|
class Binomial:
|
|
psave: float
|
|
nsave: int
|
|
r: float
|
|
q: float
|
|
fm: float
|
|
m: int
|
|
p1: float
|
|
xm: float
|
|
xl: float
|
|
xr: float
|
|
c: float
|
|
laml: float
|
|
lamr: float
|
|
p2: float
|
|
p3: float
|
|
p4: float
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
class BitGenerator[G]:
|
|
state: G
|
|
binomial: Optional[Binomial]
|
|
uinteger: u32
|
|
has_uint32: bool
|
|
|
|
def __init__(self, state: G):
|
|
self.state = state
|
|
self.binomial = None
|
|
self.uinteger = u32(0)
|
|
self.has_uint32 = False
|
|
|
|
@property
|
|
def seed_seq(self):
|
|
return self.state.seed
|
|
|
|
def next64(self):
|
|
if hasattr(self.state, "next64"):
|
|
return self.state.next64()
|
|
else:
|
|
hi = zext(self.next32(), u64)
|
|
lo = zext(self.next32(), u64)
|
|
return (hi << u64(32)) | lo
|
|
|
|
def next32(self):
|
|
if hasattr(self.state, "next32"):
|
|
return self.state.next32()
|
|
else:
|
|
if self.has_uint32:
|
|
self.has_uint32 = False
|
|
return self.uinteger
|
|
|
|
n64 = self.next64()
|
|
self.has_uint32 = True
|
|
self.uinteger = itrunc(n64 >> u64(32), u32)
|
|
return itrunc(n64 & u64(0xffffffff), u32)
|
|
|
|
def next_double(self):
|
|
if hasattr(self.state, "next_double"):
|
|
return self.state.next_double()
|
|
else:
|
|
r = self.next64()
|
|
return uitofp(r >> u64(11), float) * (1.0 / 9007199254740992.0)
|
|
|
|
def next_float(self):
|
|
return uitofp(self.next32() >> u32(8), float32) * (float32(1.0) / float32(16777216.0))
|
|
|
|
def jumped(self, jumps: int):
|
|
if not hasattr(self.state, "jump_inplace"):
|
|
compile_error("generator '" + G.__name__ + "' is not jumpable")
|
|
|
|
s = self.state.__get_state__()
|
|
g = G.__new__()
|
|
g.__set_state__(s)
|
|
g.jump_inplace(jumps)
|
|
return BitGenerator(g)
|
|
|
|
# distributions
|
|
|
|
def standard_uniform(self):
|
|
return self.next_double()
|
|
|
|
def standard_uniform_f(self):
|
|
return self.next_float()
|
|
|
|
def random_standard_uniform_fill(self, cnt: int, out: Ptr[float]):
|
|
for i in range(cnt):
|
|
out[i] = self.next_double()
|
|
|
|
def random_standard_uniform_fill_f(self, cnt: int, out: Ptr[float32]):
|
|
for i in range(cnt):
|
|
out[i] = self.next_float()
|
|
|
|
def standard_exponential_unlikely(self, idx: int, x: float):
|
|
if idx == 0:
|
|
return ziggurat_exp_r - log1p(-self.next_double())
|
|
elif (fe_double(idx - 1) - fe_double(idx)) * self.next_double() + fe_double(idx) < exp(-x):
|
|
return x
|
|
else:
|
|
return self.random_standard_exponential()
|
|
|
|
def random_standard_exponential(self):
|
|
ri = self.next64()
|
|
ri >>= u64(3)
|
|
idx = int(ri & u64(0xff))
|
|
ri >>= u64(8)
|
|
x = uitofp(ri, float) * we_double(idx)
|
|
if ri < ke_double(idx):
|
|
return x
|
|
return self.standard_exponential_unlikely(idx, x)
|
|
|
|
def random_standard_exponential_fill(self, cnt: int, out: Ptr[float]):
|
|
for i in range(cnt):
|
|
out[i] = self.random_standard_exponential()
|
|
|
|
def standard_exponential_unlikely_f(self, idx: int, x: float32):
|
|
if idx == 0:
|
|
return ziggurat_exp_r_f - log1p(-self.next_float())
|
|
elif (fe_float(idx - 1) - fe_float(idx)) * self.next_float() + fe_float(idx) < exp(-x):
|
|
return x
|
|
else:
|
|
return self.random_standard_exponential_f()
|
|
|
|
def random_standard_exponential_f(self):
|
|
ri = self.next32()
|
|
ri >>= u32(1)
|
|
idx = int(ri & u32(0xff))
|
|
ri >>= u32(8)
|
|
x = uitofp(ri, float32) * we_float(idx)
|
|
if ri < ke_float(idx):
|
|
return x
|
|
return self.standard_exponential_unlikely_f(idx, x)
|
|
|
|
def random_standard_exponential_fill_f(self, cnt: int, out: Ptr[float32]):
|
|
for i in range(cnt):
|
|
out[i] = self.random_standard_exponential_f()
|
|
|
|
def random_standard_exponential_inv_fill(self, cnt: int, out: Ptr[float]):
|
|
for i in range(cnt):
|
|
out[i] = -log1p(-self.next_double())
|
|
|
|
def random_standard_exponential_inv_fill_f(self, cnt: int, out: Ptr[float32]):
|
|
for i in range(cnt):
|
|
out[i] = -log1p(-self.next_float())
|
|
|
|
def random_standard_normal(self):
|
|
while True:
|
|
r = self.next64()
|
|
idx = int(r & u64(0xff))
|
|
r >>= u64(8)
|
|
sign = r & u64(0x1)
|
|
rabs = (r >> u64(1)) & u64(0x000fffffffffffff)
|
|
x = uitofp(rabs, float) * wi_double(idx)
|
|
if sign & u64(0x1):
|
|
x = -x
|
|
if rabs < ki_double(idx):
|
|
return x
|
|
if idx == 0:
|
|
while True:
|
|
xx = -ziggurat_nor_inv_r * log1p(-self.next_double())
|
|
yy = -log1p(-self.next_double())
|
|
if yy + yy > xx * xx:
|
|
return -(ziggurat_nor_r + xx) if ((rabs >> u64(8)) & u64(0x1)) else ziggurat_nor_r + xx
|
|
else:
|
|
if (((fi_double(idx - 1) - fi_double(idx)) * self.next_double() +
|
|
fi_double(idx)) < exp(-0.5 * x * x)):
|
|
return x
|
|
|
|
def random_standard_normal_fill(self, cnt: int, out: Ptr[float]):
|
|
for i in range(cnt):
|
|
out[i] = self.random_standard_normal()
|
|
|
|
def random_standard_normal_f(self):
|
|
while True:
|
|
r = self.next32()
|
|
idx = int(r & u32(0xff))
|
|
sign = (r >> u32(8)) & u32(0x1)
|
|
rabs = (r >> u32(9)) & u32(0x0007fffff)
|
|
x = uitofp(rabs, float32) * wi_float(idx)
|
|
if sign & u32(0x1):
|
|
x = -x
|
|
if rabs < ki_float(idx):
|
|
return x
|
|
if idx == 0:
|
|
while True:
|
|
xx = -ziggurat_nor_inv_r_f * log1p(-self.next_float())
|
|
yy = -log1p(-self.next_float())
|
|
if yy + yy > xx * xx:
|
|
return -(ziggurat_nor_r_f + xx) if ((rabs >> u32(8)) & u32(0x1)) else ziggurat_nor_r_f + xx
|
|
else:
|
|
if (((fi_float(idx - 1) - fi_float(idx)) * self.next_float() +
|
|
fi_float(idx)) < exp(float32(-0.5) * x * x)):
|
|
return x
|
|
|
|
def random_standard_normal_fill_f(self, cnt: int, out: Ptr[float32]):
|
|
for i in range(cnt):
|
|
out[i] = self.random_standard_normal_f()
|
|
|
|
def random_standard_gamma(self, shape: float):
|
|
if shape == 1.0:
|
|
return self.random_standard_exponential()
|
|
elif shape == 0.0:
|
|
return 0.0
|
|
elif shape < 1.0:
|
|
while True:
|
|
U = self.next_double()
|
|
V = self.random_standard_exponential()
|
|
if U < 1.0 - shape:
|
|
X = pow(U, 1. / shape)
|
|
if X <= V:
|
|
return X
|
|
else:
|
|
Y = -log((1. - U) / shape)
|
|
X = pow(1.0 - shape + shape * Y, 1. / shape)
|
|
if X <= V + Y:
|
|
return X
|
|
else:
|
|
b = shape - 1. / 3.
|
|
c = 1. / sqrt(9 * b)
|
|
while True:
|
|
while True:
|
|
X = self.random_standard_normal()
|
|
V = 1.0 + c * X
|
|
if V > 0.0:
|
|
break
|
|
|
|
V = V * V * V
|
|
U = self.next_double()
|
|
if U < 1.0 - 0.0331 * (X * X) * (X * X):
|
|
return b * V
|
|
if log(U) < 0.5 * X * X + b * (1. - V + log(V)):
|
|
return b * V
|
|
|
|
def random_standard_gamma_f(self, shape: float32):
|
|
if shape == f32(1.0):
|
|
return self.random_standard_exponential_f()
|
|
elif shape == f32(0.0):
|
|
return f32(0.0)
|
|
elif shape < f32(1.0):
|
|
while True:
|
|
U = self.next_float()
|
|
V = self.random_standard_exponential_f()
|
|
if U < f32(1.0) - shape:
|
|
X = pow(U, f32(1.) / shape)
|
|
if X <= V:
|
|
return X
|
|
else:
|
|
Y = -log((f32(1.) - U) / shape)
|
|
X = pow(f32(1.0) - shape + shape * Y, f32(1.) / shape)
|
|
if X <= V + Y:
|
|
return X
|
|
else:
|
|
b = shape - f32(1.) / f32(3.)
|
|
c = f32(1.) / sqrt(f32(9.) * b)
|
|
while True:
|
|
while True:
|
|
X = self.random_standard_normal_f()
|
|
V = f32(1.0) + c * X
|
|
if V > f32(0.0):
|
|
break
|
|
|
|
V = V * V * V
|
|
U = self.next_float()
|
|
if U < f32(1.0) - f32(0.0331) * (X * X) * (X * X):
|
|
return b * V
|
|
if log(U) < f32(0.5) * X * X + b * (f32(1.) - V + log(V)):
|
|
return b * V
|
|
|
|
def random_positive_int64(self):
|
|
return int(self.next64() >> u64(1))
|
|
|
|
def random_positive_int32(self):
|
|
return i32(self.next32() >> u32(1))
|
|
|
|
def random_positive_int(self):
|
|
return self.random_positive_int64()
|
|
|
|
def random_uint(self):
|
|
return self.next64()
|
|
|
|
def random_loggam(x: float):
|
|
@pure
|
|
@llvm
|
|
def a(idx: int) -> float:
|
|
@data = private unnamed_addr constant [10 x double] [double 0x3FB5555555555555, double 0xBF66C16C16C16C17, double 0x3F4A01A01A01A01A, double 0xBF43813813813813, double 0x3F4B951E2B18FF24, double 0xBF5F6AB0D9993C7F, double 0x3F7A41A41A41A41A, double 0xBF9E4286CB0F5397, double 0x3FC6FE96381E0685, double 0xBFF6476701181F35], align 16
|
|
%p = getelementptr inbounds [10 x double], ptr @data, i64 0, i64 %idx
|
|
%x = load double, ptr %p, align 8
|
|
ret double %x
|
|
|
|
n = 0
|
|
if x == 1.0 or x == 2.0:
|
|
return 0.0
|
|
elif x < 7.0:
|
|
n = int(7 - x)
|
|
else:
|
|
n = 0
|
|
x0 = x + n
|
|
x2 = (1.0 / x0) * (1.0 / x0)
|
|
lg2pi = 1.8378770664093453e+00
|
|
gl0 = a(9)
|
|
k = 8
|
|
while k >= 0:
|
|
gl0 *= x2
|
|
gl0 += a(k)
|
|
k -= 1
|
|
gl = gl0 / x0 + 0.5 * lg2pi + (x0 - 0.5) * log(x0) - x0
|
|
if x < 7.0:
|
|
k = 1
|
|
while k <= n:
|
|
gl -= log(x0 - 1.0)
|
|
x0 -= 1.0
|
|
k += 1
|
|
return gl
|
|
|
|
def random_normal(self, loc: float, scale: float):
|
|
return loc + scale * self.random_standard_normal()
|
|
|
|
def random_exponential(self, scale: float):
|
|
return scale * self.random_standard_exponential()
|
|
|
|
def random_uniform(self, off: float, rng: float):
|
|
return off + rng * self.next_double()
|
|
|
|
def random_gamma(self, shape: float, scale: float):
|
|
return scale * self.random_standard_gamma(shape)
|
|
|
|
def random_gamma_f(self, shape: float32, scale: float32):
|
|
return scale * self.random_standard_gamma_f(shape)
|
|
|
|
def random_beta(self, a: float, b: float):
|
|
if a <= 1.0 and b <= 1.0:
|
|
while True:
|
|
U = self.next_double()
|
|
V = self.next_double()
|
|
X = pow(U, 1.0 / a)
|
|
Y = pow(V, 1.0 / b)
|
|
XpY = X + Y
|
|
|
|
if XpY <= 1.0 and XpY > 0.0:
|
|
if X + Y > 0:
|
|
return X / XpY
|
|
else:
|
|
logX = log(U) / a
|
|
logY = log(V) / b
|
|
logM = logX if logX > logY else logY
|
|
logX -= logM
|
|
logY -= logM
|
|
|
|
return exp(logX - log(exp(logX) + exp(logY)))
|
|
else:
|
|
Ga = self.random_standard_gamma(a)
|
|
Gb = self.random_standard_gamma(b)
|
|
return Ga / (Ga + Gb)
|
|
|
|
def random_chisquare(self, df: float):
|
|
return 2.0 * self.random_standard_gamma(df / 2.0)
|
|
|
|
def random_f(self, dfnum: float, dfden: float):
|
|
return (self.random_chisquare(dfnum) * dfden) / (self.random_chisquare(dfden) * dfnum)
|
|
|
|
def random_standard_cauchy(self):
|
|
return self.random_standard_normal() / self.random_standard_normal()
|
|
|
|
def random_pareto(self, a: float):
|
|
return expm1(self.random_standard_exponential() / a)
|
|
|
|
def random_weibull(self, a: float):
|
|
if a == 0.0:
|
|
return 0.0
|
|
return pow(self.random_standard_exponential(), 1. / a)
|
|
|
|
def random_power(self, a: float):
|
|
return pow(-expm1(-self.random_standard_exponential()), 1. / a)
|
|
|
|
def random_laplace(self, loc: float, scale: float):
|
|
U = self.next_double()
|
|
if U >= 0.5:
|
|
U = loc - scale * log(2.0 - U - U)
|
|
elif U > 0.0:
|
|
U = loc + scale * log(U + U)
|
|
else:
|
|
U = self.random_laplace(loc, scale)
|
|
return U
|
|
|
|
def random_gumbel(self, loc: float, scale: float):
|
|
U = 1.0 - self.next_double()
|
|
if U < 1.0:
|
|
return loc - scale * log(-log(U))
|
|
return self.random_gumbel(loc, scale)
|
|
|
|
def random_logistic(self, loc: float, scale: float):
|
|
U = self.next_double()
|
|
if U > 0.0:
|
|
return loc + scale * log(U / (1.0 - U))
|
|
return self.random_logistic(loc, scale)
|
|
|
|
def random_lognormal(self, mean: float, sigma: float):
|
|
return exp(self.random_normal(mean, sigma))
|
|
|
|
def random_rayleigh(self, mode: float):
|
|
return mode * sqrt(2.0 * self.random_standard_exponential())
|
|
|
|
def random_standard_t(self, df: float):
|
|
num = self.random_standard_normal()
|
|
denom = self.random_standard_gamma(df / 2)
|
|
return sqrt(df / 2) * num / sqrt(denom)
|
|
|
|
def random_poisson_mult(self, lam: float):
|
|
enlam = exp(-lam)
|
|
X = 0
|
|
prod = 1.0
|
|
while True:
|
|
U = self.next_double()
|
|
prod *= U
|
|
if prod > enlam:
|
|
X += 1
|
|
else:
|
|
return X
|
|
|
|
def random_poisson_ptrs(self, lam: float):
|
|
LS2PI = 0.91893853320467267
|
|
TWELFTH = 0.083333333333333333333333
|
|
|
|
slam = sqrt(lam)
|
|
loglam = log(lam)
|
|
b = 0.931 + 2.53 * slam
|
|
a = -0.059 + 0.02483 * b
|
|
invalpha = 1.1239 + 1.1328 / (b - 3.4)
|
|
vr = 0.9277 - 3.6224 / (b - 2)
|
|
|
|
while True:
|
|
U = self.next_double() - 0.5
|
|
V = self.next_double()
|
|
us = 0.5 - fabs(U)
|
|
k = int(floor((2 * a / us + b) * U + lam + 0.43))
|
|
if us >= 0.07 and V <= vr:
|
|
return k
|
|
if k < 0 or (us < 0.013 and V > us):
|
|
continue
|
|
if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <=
|
|
(-lam + k * loglam - BitGenerator.random_loggam(k + 1))):
|
|
return k
|
|
|
|
def random_poisson(self, lam: float):
|
|
if lam >= 10:
|
|
return self.random_poisson_ptrs(lam)
|
|
elif lam == 0:
|
|
return 0
|
|
else:
|
|
return self.random_poisson_mult(lam)
|
|
|
|
def random_negative_binomial(self, n: float, p: float):
|
|
Y = self.random_gamma(n, (1 - p) / p)
|
|
return self.random_poisson(Y)
|
|
|
|
def random_binomial_btpe(self, n: int, p: float):
|
|
# god help us...
|
|
r = 0.0
|
|
q = 0.0
|
|
fm = 0.0
|
|
p1 = 0.0
|
|
xm = 0.0
|
|
xl = 0.0
|
|
xr = 0.0
|
|
c = 0.0
|
|
laml = 0.0
|
|
lamr = 0.0
|
|
p2 = 0.0
|
|
p3 = 0.0
|
|
p4 = 0.0
|
|
m = 0
|
|
|
|
binomial = self.binomial
|
|
if binomial is None or binomial.nsave != n or binomial.psave != p:
|
|
b = Binomial()
|
|
b.nsave = n
|
|
b.psave = p
|
|
|
|
r = min(p, 1.0 - p)
|
|
q = 1.0 - r
|
|
fm = n * r + r
|
|
m = int(floor(fm))
|
|
p1 = floor(2.195 * sqrt(n * r * q) - 4.6 * q) + 0.5
|
|
xm = m + 0.5
|
|
xl = xm - p1
|
|
xr = xm + p1
|
|
c = 0.134 + 20.5 / (15.3 + m)
|
|
|
|
b.r = r
|
|
b.q = q
|
|
b.fm = fm
|
|
b.m = m
|
|
b.p1 = p1
|
|
b.xm = xm
|
|
b.xl = xl
|
|
b.xr = xr
|
|
b.c = c
|
|
|
|
a = (fm - xl) / (fm - xl * r)
|
|
laml = a * (1.0 + a / 2.0)
|
|
b.laml = laml
|
|
a = (xr - fm) / (xr * q)
|
|
lamr = a * (1.0 + a / 2.0)
|
|
b.lamr = lamr
|
|
|
|
p2 = p1 * (1.0 + 2.0 * c)
|
|
p3 = p2 + c / laml
|
|
p4 = p3 + c / lamr
|
|
|
|
b.p2 = p2
|
|
b.p3 = p3
|
|
b.p4 = p4
|
|
|
|
self.binomial = b
|
|
else:
|
|
r = binomial.r
|
|
q = binomial.q
|
|
fm = binomial.fm
|
|
m = binomial.m
|
|
p1 = binomial.p1
|
|
xm = binomial.xm
|
|
xl = binomial.xl
|
|
xr = binomial.xr
|
|
c = binomial.c
|
|
laml = binomial.laml
|
|
lamr = binomial.lamr
|
|
p2 = binomial.p2
|
|
p3 = binomial.p3
|
|
p4 = binomial.p4
|
|
|
|
a = 0.0
|
|
u = 0.0
|
|
v = 0.0
|
|
s = 0.0
|
|
F = 0.0
|
|
rho = 0.0
|
|
t = 0.0
|
|
A = 0.0
|
|
nrq = 0.0
|
|
x1 = 0.0
|
|
x2 = 0.0
|
|
f1 = 0.0
|
|
f2 = 0.0
|
|
z = 0.0
|
|
z2 = 0.0
|
|
w = 0.0
|
|
w2 = 0.0
|
|
x = 0.0
|
|
|
|
y = 0
|
|
k = 0
|
|
i = 0
|
|
|
|
def step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
nrq = n * r * q
|
|
u = self.next_double() * p4
|
|
v = self.next_double()
|
|
if u > p1:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step20(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
y = int(floor(xm - p1 * v + u))
|
|
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step60(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step20(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
if u > p2:
|
|
return step30(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
x = xl + (u - p1) / c
|
|
v = v * c + 1.0 - fabs(m - x + 0.5) / p1
|
|
if v > 1.0:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
y = int(floor(x))
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step50(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step30(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
if u > p3:
|
|
return step40(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
y = int(floor(xl + log(v) / laml))
|
|
if y < 0 or v == 0.0:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
v = v * (u - p2) * laml
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step50(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step40(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
y = int(floor(xr - log(v) / lamr))
|
|
if y > n or v == 0.0:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
v = v * (u - p3) * lamr
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step50(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step50(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
k = abs(y - m)
|
|
if k > 20 and k < (nrq / 2.0 - 1):
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step52(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
s = r / q
|
|
a = s * (n + 1)
|
|
F = 1.0
|
|
if m < y:
|
|
for i in range(m + 1, y + 1):
|
|
F *= (a / i - s)
|
|
elif m > y:
|
|
for i in range(y + 1, m + 1):
|
|
F /= (a / i - s)
|
|
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
if v > F:
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
return step60(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step52(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
rho = \
|
|
(k / (nrq)) * ((k * (k / 3.0 + 0.625) + 0.16666666666666666) / nrq + 0.5)
|
|
t = -k * k / (2 * nrq)
|
|
A = log(v)
|
|
if A < t - rho:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step60(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
if A > t + rho:
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
x1 = y + 1
|
|
f1 = m + 1
|
|
z = n + 1 - m
|
|
w = n - y + 1
|
|
x2 = x1 * x1
|
|
f2 = f1 * f1
|
|
z2 = z * z
|
|
w2 = w * w
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
if (A > (xm * log(f1 / x1) + (n - m + 0.5) * log(z / w) +
|
|
(y - m) * log(w * r / (x1 * q)) +
|
|
(13680. - (462. - (132. - (99. - 140. / f2) / f2) / f2) / f2) / f1 /
|
|
166320. +
|
|
(13680. - (462. - (132. - (99. - 140. / z2) / z2) / z2) / z2) / z /
|
|
166320. +
|
|
(13680. - (462. - (132. - (99. - 140. / x2) / x2) / x2) / x2) / x1 /
|
|
166320. +
|
|
(13680. - (462. - (132. - (99. - 140. / w2) / w2) / w2) / w2) / w /
|
|
166320.)):
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
return step60(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def step60(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p):
|
|
r, q, fm, p1, xm, xl, xr, c, laml, \
|
|
lamr, p2, p3, p4, m, a, u, v, s, F, \
|
|
rho, t, A, nrq, x1, x2, f1, f2, z, \
|
|
z2, w, w2, x, y, k, i = args
|
|
|
|
if p > 0.5:
|
|
y = n - y
|
|
|
|
return y
|
|
|
|
args = (r, q, fm, p1, xm, xl, xr, c, laml,
|
|
lamr, p2, p3, p4, m, a, u, v, s, F,
|
|
rho, t, A, nrq, x1, x2, f1, f2, z,
|
|
z2, w, w2, x, y, k, i)
|
|
return step10(step10, step20, step30, step40,
|
|
step50, step52, step60, args, self, n, p)
|
|
|
|
def random_binomial_inversion(self, n: int, p: float):
|
|
q = 0.0
|
|
qn = 0.0
|
|
np = 0.0
|
|
bound = 0
|
|
|
|
binomial = self.binomial
|
|
if binomial is None or binomial.nsave != n or binomial.psave != p:
|
|
b = Binomial()
|
|
b.nsave = n
|
|
b.psave = p
|
|
q = 1.0 - p
|
|
qn = exp(n * log(q))
|
|
np = n * p
|
|
bound = int(min(float(n), np + 10.0 * sqrt(np * q + 1)))
|
|
b.q = q
|
|
b.r = qn
|
|
b.c = np
|
|
b.m = bound
|
|
self.binomial = b
|
|
else:
|
|
q = binomial.q
|
|
qn = binomial.r
|
|
np = binomial.c
|
|
bound = binomial.m
|
|
|
|
X = 0
|
|
px = qn
|
|
U = self.next_double()
|
|
while U > px:
|
|
X += 1
|
|
if X > bound:
|
|
X = 0
|
|
px = qn
|
|
U = self.next_double()
|
|
else:
|
|
U -= px
|
|
px = ((n - X + 1) * p * px) / (X * q)
|
|
return X
|
|
|
|
def random_binomial(self, p: float, n: int):
|
|
if n == 0 or p == 0.0:
|
|
return 0
|
|
|
|
if p <= 0.5:
|
|
if p * n <= 30.0:
|
|
return self.random_binomial_inversion(n, p)
|
|
else:
|
|
return self.random_binomial_btpe(n, p)
|
|
else:
|
|
q = 1.0 - p
|
|
if q * n <= 30.0:
|
|
return n - self.random_binomial_inversion(n, q)
|
|
else:
|
|
return n - self.random_binomial_btpe(n, q)
|
|
|
|
def random_noncentral_chisquare(self, df: float, nonc: float):
|
|
if isnan(nonc):
|
|
return nan64()
|
|
if nonc == 0:
|
|
return self.random_chisquare(df)
|
|
if 1 < df:
|
|
Chi2 = self.random_chisquare(df - 1)
|
|
n = self.random_standard_normal() + sqrt(nonc)
|
|
return Chi2 + n * n
|
|
else:
|
|
i = self.random_poisson(nonc / 2.0)
|
|
return self.random_chisquare(df + 2 * i)
|
|
|
|
def random_noncentral_f(self, dfnum: float, dfden: float, nonc: float):
|
|
t = self.random_noncentral_chisquare(dfnum, nonc) * dfden
|
|
return t / (self.random_chisquare(dfden) * dfnum)
|
|
|
|
def random_wald(self, mean: float, scale: float):
|
|
mu_2l = mean / (2 * scale)
|
|
Y = self.random_standard_normal()
|
|
Y = mean * Y * Y
|
|
X = mean + mu_2l * (Y - sqrt(4 * scale * Y + Y * Y))
|
|
U = self.next_double()
|
|
if U <= mean / (mean + X):
|
|
return X
|
|
else:
|
|
return mean * mean / X
|
|
|
|
def random_vonmises(self, mu: float, kappa: float):
|
|
if isnan(kappa):
|
|
return nan64()
|
|
|
|
s = 0.0
|
|
if kappa < 1e-8:
|
|
return PI * (2 * self.next_double() - 1)
|
|
else:
|
|
if kappa < 1e-5:
|
|
s = 1. / kappa + kappa
|
|
else:
|
|
if kappa < 1e6:
|
|
r = 1 + sqrt(1 + 4 * kappa * kappa)
|
|
rho = (r - sqrt(2 * r)) / (2 * kappa)
|
|
s = (1 + rho * rho) / (2 * rho)
|
|
else:
|
|
result = mu + sqrt(1. / kappa) + self.random_standard_normal()
|
|
if result < -PI:
|
|
result += 2*PI
|
|
if result > PI:
|
|
result -= 2*PI
|
|
return result
|
|
|
|
while True:
|
|
U = self.next_double()
|
|
Z = cos(PI * U)
|
|
W = (1 + s * Z) / (s + Z)
|
|
Y = kappa * (s - W)
|
|
V = self.next_double()
|
|
|
|
if (Y * (2 - Y) - V >= 0) or (log(Y / V) + 1 - Y >= 0):
|
|
break
|
|
|
|
U = self.next_double()
|
|
|
|
result = acos(W)
|
|
if U < 0.5:
|
|
result = -result
|
|
result += mu
|
|
neg = (result < 0)
|
|
mod = fabs(result)
|
|
mod = cmod(mod + PI, 2 * PI) - PI
|
|
if neg:
|
|
mod *= -1
|
|
|
|
return mod
|
|
|
|
def random_logseries(self, p: float):
|
|
r = log1p(-p)
|
|
|
|
while True:
|
|
V = self.next_double()
|
|
if V >= p:
|
|
return 1
|
|
U = self.next_double()
|
|
q = -expm1(r * U)
|
|
if V <= q * q:
|
|
result = int(floor(1 + log(V) / log(q)))
|
|
if result < 1 or V == 0.0:
|
|
continue
|
|
else:
|
|
return result
|
|
if V >= q:
|
|
return 1
|
|
return 2
|
|
|
|
def random_geometric_search(self, p: float):
|
|
X = 1
|
|
s = p
|
|
prod = p
|
|
q = 1.0 - p
|
|
U = self.next_double()
|
|
while U > s:
|
|
prod *= q
|
|
s += prod
|
|
X += 1
|
|
return X
|
|
|
|
def random_geometric_inversion(self, p: float):
|
|
z = ceil(-self.random_standard_exponential() / log1p(-p))
|
|
if z >= 9.223372036854776e+18:
|
|
return 0x7FFFFFFFFFFFFFFF
|
|
return int(z)
|
|
|
|
def random_geometric(self, p: float):
|
|
if p >= 0.333333333333333333333333:
|
|
return self.random_geometric_search(p)
|
|
else:
|
|
return self.random_geometric_inversion(p)
|
|
|
|
def random_zipf(self, a: float):
|
|
am1 = a - 1.0
|
|
b = pow(2.0, am1)
|
|
while True:
|
|
U = 1.0 - self.next_double()
|
|
V = self.next_double()
|
|
X = floor(pow(U, -1.0 / am1))
|
|
|
|
if X > 0x7FFFFFFFFFFFFFFF or X < 1.0:
|
|
continue
|
|
|
|
T = pow(1.0 + 1.0 / X, am1)
|
|
if V * X * (T - 1.0) / (b - 1.0) <= T / b:
|
|
return int(X)
|
|
|
|
def random_triangular(self, left: float, mode: float, right: float):
|
|
base = right - left
|
|
leftbase = mode - left
|
|
ratio = leftbase / base
|
|
leftprod = leftbase * base
|
|
rightprod = (right - mode) * base
|
|
|
|
U = self.next_double()
|
|
if U <= ratio:
|
|
return left + sqrt(U * leftprod)
|
|
else:
|
|
return right - sqrt((1.0 - U) * rightprod)
|
|
|
|
def random_interval(self, max: u64):
|
|
if max == u64(0):
|
|
return u64(0)
|
|
|
|
mask = max
|
|
value = u64(0)
|
|
|
|
mask |= mask >> u64(1)
|
|
mask |= mask >> u64(2)
|
|
mask |= mask >> u64(4)
|
|
mask |= mask >> u64(8)
|
|
mask |= mask >> u64(16)
|
|
mask |= mask >> u64(32)
|
|
|
|
if max <= u64(0xffffffff):
|
|
while True:
|
|
value = zext(self.next32(), u64) & mask
|
|
if value <= max:
|
|
break
|
|
else:
|
|
while True:
|
|
value = self.next64() & mask
|
|
if value <= max:
|
|
break
|
|
|
|
return value
|
|
|
|
def gen_mask(max: u64):
|
|
mask = max
|
|
mask |= mask >> u64(1)
|
|
mask |= mask >> u64(2)
|
|
mask |= mask >> u64(4)
|
|
mask |= mask >> u64(8)
|
|
mask |= mask >> u64(16)
|
|
mask |= mask >> u64(32)
|
|
return mask
|
|
|
|
def buffered_uint16(self, bcnt: int, buf: u32):
|
|
if not bcnt:
|
|
buf = self.next32()
|
|
bcnt = 1
|
|
else:
|
|
buf >>= u32(16)
|
|
bcnt -= 1
|
|
return itrunc(buf, u16), bcnt, buf
|
|
|
|
def buffered_uint8(self, bcnt: int, buf: u32):
|
|
if not bcnt:
|
|
buf = self.next32()
|
|
bcnt = 3
|
|
else:
|
|
buf >>= u32(8)
|
|
bcnt -= 1
|
|
return itrunc(buf, u8), bcnt, buf
|
|
|
|
def bounded_masked_uint64(self, rng: u64, mask: u64):
|
|
val = u64(0)
|
|
while True:
|
|
val = self.next64() & mask
|
|
if val <= rng:
|
|
break
|
|
return val
|
|
|
|
def buffered_bounded_masked_uint32(self, rng: u32, mask: u32, bcnt: int, buf: u32):
|
|
val = u32(0)
|
|
while True:
|
|
val = self.next32() & mask
|
|
if val <= rng:
|
|
break
|
|
return val, bcnt, buf
|
|
|
|
def buffered_bounded_masked_uint16(self, rng: u16, mask: u16, bcnt: int, buf: u32):
|
|
val = u16(0)
|
|
while True:
|
|
val, bcnt, buf = self.buffered_uint16(bcnt, buf)
|
|
val &= mask
|
|
if val <= rng:
|
|
break
|
|
return val, bcnt, buf
|
|
|
|
def buffered_bounded_masked_uint8(self, rng: u8, mask: u8, bcnt: int, buf: u32):
|
|
val = u8(0)
|
|
while True:
|
|
val, bcnt, buf = self.buffered_uint8(bcnt, buf)
|
|
val &= mask
|
|
if val <= rng:
|
|
break
|
|
return val, bcnt, buf
|
|
|
|
def buffered_bounded_bool(self, off: bool, rng: bool, mask: bool, bcnt: int, buf: u32):
|
|
if not rng:
|
|
return off, bcnt, buf
|
|
|
|
if not bcnt:
|
|
buf = self.next32()
|
|
bcnt = 31
|
|
else:
|
|
buf >>= u32(1)
|
|
bcnt -= 1
|
|
|
|
return bool(buf & u32(1)), bcnt, buf
|
|
|
|
def bounded_lemire_uint64(self, rng: u64):
|
|
rng_excl = rng + u64(1)
|
|
|
|
m = zext(self.next64(), u128) * zext(rng_excl, u128)
|
|
leftover = itrunc(m & u128(0xFFFFFFFFFFFFFFFF), u64)
|
|
|
|
if leftover < rng_excl:
|
|
threshold = (u64(0xFFFFFFFFFFFFFFFF) - rng) % rng_excl
|
|
|
|
while leftover < threshold:
|
|
m = zext(self.next64(), u128) * zext(rng_excl, u128)
|
|
leftover = itrunc(m & u128(0xFFFFFFFFFFFFFFFF), u64)
|
|
|
|
return itrunc(m >> u128(64), u64)
|
|
|
|
def buffered_bounded_lemire_uint32(self, rng: u32, bcnt: int, buf: u32):
|
|
rng_excl = rng + u32(1)
|
|
|
|
m = zext(self.next32(), u64) * zext(rng_excl, u64)
|
|
leftover = itrunc(m & u64(0xFFFFFFFF), u32)
|
|
|
|
if leftover < rng_excl:
|
|
threshold = (u32(0xFFFFFFFF) - rng) % rng_excl
|
|
|
|
while leftover < threshold:
|
|
m = zext(self.next32(), u64) * zext(rng_excl, u64)
|
|
leftover = itrunc(m & u64(0xFFFFFFFF), u32)
|
|
|
|
return itrunc(m >> u64(32), u32), bcnt, buf
|
|
|
|
def buffered_bounded_lemire_uint16(self, rng: u16, bcnt: int, buf: u32):
|
|
rng_excl = rng + u16(1)
|
|
|
|
val, bcnt, buf = self.buffered_uint16(bcnt, buf)
|
|
m = zext(val, u32) * zext(rng_excl, u32)
|
|
leftover = itrunc(m & u32(0xFFFF), u16)
|
|
|
|
if leftover < rng_excl:
|
|
threshold = (u16(0xFFFF) - rng) % rng_excl
|
|
|
|
while leftover < threshold:
|
|
val, bcnt, buf = self.buffered_uint16(bcnt, buf)
|
|
m = zext(val, u32) * zext(rng_excl, u32)
|
|
leftover = itrunc(m & u32(0xFFFF), u16)
|
|
|
|
return itrunc(m >> u32(16), u16), bcnt, buf
|
|
|
|
def buffered_bounded_lemire_uint8(self, rng: u8, bcnt: int, buf: u32):
|
|
rng_excl = rng + u8(1)
|
|
|
|
val, bcnt, buf = self.buffered_uint8(bcnt, buf)
|
|
m = zext(val, u16) * zext(rng_excl, u16)
|
|
leftover = itrunc(m & u16(0xFF), u8)
|
|
|
|
if leftover < rng_excl:
|
|
threshold = (u8(0xFF) - rng) % rng_excl
|
|
|
|
while leftover < threshold:
|
|
val, bcnt, buf = self.buffered_uint8(bcnt, buf)
|
|
m = zext(val, u16) * zext(rng_excl, u16)
|
|
leftover = itrunc(m & u16(0xFF), u8)
|
|
|
|
return itrunc(m >> u16(8), u8), bcnt, buf
|
|
|
|
def random_bounded_uint64(self, off: u64, rng: u64, mask: u64, use_masked: bool):
|
|
if rng == u64(0):
|
|
return off
|
|
elif rng <= u64(0xFFFFFFFF):
|
|
if rng == u64(0xFFFFFFFF):
|
|
return off + zext(self.next32(), u64)
|
|
|
|
if use_masked:
|
|
return off + zext(self.buffered_bounded_masked_uint32(itrunc(rng, u32), itrunc(mask, u32), 0, u32(0))[0], u64)
|
|
else:
|
|
return off + zext(self.buffered_bounded_lemire_uint32(itrunc(rng, u32), 0, u32(0))[0], u64)
|
|
elif rng == u64(0xFFFFFFFFFFFFFFFF):
|
|
return off + self.next64()
|
|
else:
|
|
if use_masked:
|
|
return off + self.bounded_masked_uint64(rng, mask)
|
|
else:
|
|
return off + self.bounded_lemire_uint64(rng)
|
|
|
|
def random_buffered_bounded_uint32(self, off: u32, rng: u32, mask: u32, use_masked: bool, bcnt: int, buf: u32):
|
|
if rng == u32(0):
|
|
return off, bcnt, buf
|
|
elif rng == u32(0xFFFFFFFF):
|
|
return off + self.next32(), bcnt, buf
|
|
else:
|
|
if use_masked:
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint32(rng, mask, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
else:
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint32(rng, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
|
|
def random_buffered_bounded_uint16(self, off: u16, rng: u16, mask: u16, use_masked: bool, bcnt: int, buf: u32):
|
|
if rng == u16(0):
|
|
return off, bcnt, buf
|
|
elif rng == u16(0xFFFF):
|
|
val, bcnt, buf = self.buffered_uint16(bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
else:
|
|
if use_masked:
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint16(rng, mask, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
else:
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint16(rng, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
|
|
def random_buffered_bounded_uint8(self, off: u8, rng: u8, mask: u8, use_masked: bool, bcnt: int, buf: u32):
|
|
if rng == u8(0):
|
|
return off, bcnt, buf
|
|
elif rng == u8(0xFF):
|
|
val, bcnt, buf = self.buffered_uint8(bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
else:
|
|
if use_masked:
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint8(rng, mask, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
else:
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint8(rng, bcnt, buf)
|
|
return off + val, bcnt, buf
|
|
|
|
def random_buffered_bounded_bool(self, off: bool, rng: bool, mask: bool, use_masked_bool, bcnt: int, buf: u32):
|
|
return self.buffered_bounded_bool(off, rng, mask, bcnt, buf)
|
|
|
|
def random_bounded_uint64_fill(self, off: u64, rng: u64, cnt: int, use_masked: bool, out: Ptr[u64]):
|
|
if rng == u64(0):
|
|
for i in range(cnt):
|
|
out[i] = off
|
|
elif rng <= u64(0xFFFFFFFF):
|
|
if rng == u64(0xFFFFFFFF):
|
|
for i in range(cnt):
|
|
out[i] = off + zext(self.next32(), u64)
|
|
else:
|
|
rng32 = util.itrunc(rng, u32)
|
|
buf = u32(0)
|
|
bcnt = 0
|
|
|
|
if use_masked:
|
|
mask = itrunc(BitGenerator.gen_mask(rng), u32)
|
|
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint32(rng32, mask, bcnt, buf)
|
|
out[i] = off + zext(val, u64)
|
|
else:
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint32(rng32, bcnt, buf)
|
|
out[i] = off + zext(val, u64)
|
|
elif rng == u64(0xFFFFFFFFFFFFFFFF):
|
|
for i in range(cnt):
|
|
out[i] = off + self.next64()
|
|
else:
|
|
if use_masked:
|
|
mask = BitGenerator.gen_mask(rng)
|
|
|
|
for i in range(cnt):
|
|
out[i] = off + self.bounded_masked_uint64(rng, mask)
|
|
else:
|
|
for i in range(cnt):
|
|
out[i] = off + self.bounded_lemire_uint64(rng)
|
|
|
|
def random_bounded_uint32_fill(self, off: u32, rng: u32, cnt: int, use_masked: bool, out: Ptr[u32]):
|
|
buf = u32(0)
|
|
bcnt = 0
|
|
|
|
if rng == u32(0):
|
|
for i in range(cnt):
|
|
out[i] = off
|
|
elif rng == u32(0xFFFFFFFF):
|
|
for i in range(cnt):
|
|
out[i] = off + self.next32()
|
|
else:
|
|
if use_masked:
|
|
mask = itrunc(BitGenerator.gen_mask(zext(rng, u64)), u32)
|
|
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint32(rng, mask, bcnt, buf)
|
|
out[i] = off + val
|
|
else:
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint32(rng, bcnt, buf)
|
|
out[i] = off + val
|
|
|
|
def random_bounded_uint16_fill(self, off: u16, rng: u16, cnt: int, use_masked: bool, out: Ptr[u16]):
|
|
buf = u32(0)
|
|
bcnt = 0
|
|
|
|
if rng == u16(0):
|
|
for i in range(cnt):
|
|
out[i] = off
|
|
elif rng == u16(0xFFFF):
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_uint16(bcnt, buf)
|
|
out[i] = off + val
|
|
else:
|
|
if use_masked:
|
|
mask = itrunc(BitGenerator.gen_mask(zext(rng, u64)), u16)
|
|
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint16(rng, mask, bcnt, buf)
|
|
out[i] = off + val
|
|
else:
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint16(rng, bcnt, buf)
|
|
out[i] = off + val
|
|
|
|
def random_bounded_uint8_fill(self, off: u8, rng: u8, cnt: int, use_masked: bool, out: Ptr[u8]):
|
|
buf = u32(0)
|
|
bcnt = 0
|
|
|
|
if rng == u8(0):
|
|
for i in range(cnt):
|
|
out[i] = off
|
|
elif rng == u8(0xFF):
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_uint8(bcnt, buf)
|
|
out[i] = off + val
|
|
else:
|
|
if use_masked:
|
|
mask = itrunc(BitGenerator.gen_mask(zext(rng, u64)), u8)
|
|
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_masked_uint8(rng, mask, bcnt, buf)
|
|
out[i] = off + val
|
|
else:
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_lemire_uint8(rng, bcnt, buf)
|
|
out[i] = off + val
|
|
|
|
def random_bounded_bool_fill(self, off: bool, rng: bool, cnt: int, use_masked: bool, out: Ptr[bool]):
|
|
buf = u32(0)
|
|
bcnt = 0
|
|
mask = False
|
|
|
|
for i in range(cnt):
|
|
val, bcnt, buf = self.buffered_bounded_bool(off, rng, mask, bcnt, buf)
|
|
out[i] = val
|
|
|
|
def random_multinomial(self, n: int, mnix: Ptr[int], pix: Ptr[float], d: int):
|
|
remaining_p = 1.0
|
|
dn = n
|
|
|
|
for j in range(d - 1):
|
|
mnix[j] = self.random_binomial(pix[j] / remaining_p, dn)
|
|
dn = dn - mnix[j]
|
|
if dn <= 0:
|
|
break
|
|
remaining_p -= pix[j]
|
|
|
|
if dn > 0:
|
|
mnix[d - 1] = dn
|
|
|
|
def hypergeometric_sample(self, good: int, bad: int, sample: int):
|
|
computed_sample = 0
|
|
total = good + bad
|
|
|
|
if sample > total // 2:
|
|
computed_sample = total - sample
|
|
else:
|
|
computed_sample = sample
|
|
|
|
remaining_total = total
|
|
remaining_good = good
|
|
|
|
while (computed_sample > 0 and
|
|
remaining_good > 0 and
|
|
remaining_total > remaining_good):
|
|
remaining_total -= 1
|
|
if int(self.random_interval(u64(remaining_total))) < remaining_good:
|
|
remaining_good -= 1
|
|
computed_sample -= 1
|
|
|
|
if remaining_total == remaining_good:
|
|
remaining_good -= computed_sample
|
|
|
|
result = 0
|
|
if sample > total // 2:
|
|
result = remaining_good
|
|
else:
|
|
result = good - remaining_good
|
|
|
|
return result
|
|
|
|
def hypergeometric_hrua(self, good: int, bad: int, sample: int):
|
|
D1 = 1.7155277699214135
|
|
D2 = 0.8989161620588988
|
|
|
|
popsize = good + bad
|
|
computed_sample = min(sample, popsize - sample)
|
|
mingoodbad = min(good, bad)
|
|
maxgoodbad = max(good, bad)
|
|
|
|
p = mingoodbad / popsize
|
|
q = maxgoodbad / popsize
|
|
|
|
mu = computed_sample * p
|
|
|
|
a = mu + 0.5
|
|
|
|
var = float(popsize - computed_sample) * computed_sample * p * q / (popsize - 1)
|
|
|
|
c = sqrt(var + 0.5)
|
|
|
|
h = D1*c + D2
|
|
|
|
m = int(floor(float(computed_sample + 1) * (mingoodbad + 1) / (popsize + 2)))
|
|
|
|
g = (logfactorial(m) +
|
|
logfactorial(mingoodbad - m) +
|
|
logfactorial(computed_sample - m) +
|
|
logfactorial(maxgoodbad - computed_sample + m))
|
|
|
|
b = min(min(computed_sample, mingoodbad) + 1., floor(a + 16*c))
|
|
K = 0
|
|
|
|
while True:
|
|
U = self.next_double()
|
|
V = self.next_double()
|
|
X = a + h*(V - 0.5) / U
|
|
|
|
if X < 0.0 or X >= b:
|
|
continue
|
|
|
|
K = int(floor(X))
|
|
|
|
gp = (logfactorial(K) +
|
|
logfactorial(mingoodbad - K) +
|
|
logfactorial(computed_sample - K) +
|
|
logfactorial(maxgoodbad - computed_sample + K))
|
|
|
|
T = g - gp
|
|
|
|
if (U*(4.0 - U) - 3.0) <= T:
|
|
break
|
|
|
|
if U*(U - T) >= 1:
|
|
continue
|
|
|
|
if 2.0*log(U) <= T:
|
|
break
|
|
|
|
if good > bad:
|
|
K = computed_sample - K
|
|
|
|
if computed_sample < sample:
|
|
K = good - K
|
|
|
|
return K
|
|
|
|
def random_hypergeometric(self, good: int, bad: int, sample: int):
|
|
if sample >= 10 and sample <= good + bad - 10:
|
|
return self.hypergeometric_hrua(good, bad, sample)
|
|
else:
|
|
return self.hypergeometric_sample(good, bad, sample)
|
|
|
|
def random_multivariate_hypergeometric_count(self, total: int, num_colors: int, colors: Ptr[int],
|
|
nsample: int, num_variates: int, variates: Ptr[int]):
|
|
from internal.gc import free
|
|
|
|
if total == 0 or nsample == 0 or num_variates == 0:
|
|
return
|
|
|
|
choices = Ptr[int](total)
|
|
|
|
k = 0
|
|
for i in range(num_colors):
|
|
for j in range(colors[i]):
|
|
choices[k] = i
|
|
k += 1
|
|
|
|
more_than_half = nsample > (total // 2)
|
|
if more_than_half:
|
|
nsample = total - nsample
|
|
|
|
for i in range(0, num_variates * num_colors, num_colors):
|
|
for j in range(nsample):
|
|
k = j + int(self.random_interval(u64(total - j - 1)))
|
|
tmp = choices[k]
|
|
choices[k] = choices[j]
|
|
choices[j] = tmp
|
|
|
|
for j in range(nsample):
|
|
idx = i + choices[j]
|
|
variates[idx] += 1
|
|
|
|
if more_than_half:
|
|
for k in range(num_colors):
|
|
variates[i + k] = colors[k] - variates[i + k]
|
|
|
|
free(choices.as_byte())
|
|
|
|
def random_multivariate_hypergeometric_marginals(self, total: int, num_colors: int, colors: Ptr[int],
|
|
nsample: int, num_variates: int, variates: Ptr[int]):
|
|
if total == 0 or nsample == 0 or num_variates == 0:
|
|
return
|
|
|
|
more_than_half = nsample > total // 2
|
|
if more_than_half:
|
|
nsample = total - nsample
|
|
|
|
for i in range(0, num_variates * num_colors, num_colors):
|
|
num_to_sample = nsample
|
|
remaining = total
|
|
j = 0
|
|
|
|
while num_to_sample > 0 and j + 1 < num_colors:
|
|
remaining -= colors[j]
|
|
r = self.random_hypergeometric(colors[j], remaining, num_to_sample)
|
|
variates[i + j] = r
|
|
num_to_sample -= r
|
|
j += 1
|
|
|
|
if num_to_sample > 0:
|
|
variates[i + num_colors - 1] = num_to_sample
|
|
|
|
if more_than_half:
|
|
for k in range(num_colors):
|
|
variates[i + k] = colors[k] - variates[i + k]
|
|
|
|
def next_raw(self):
|
|
return self.next64()
|
|
|
|
def random_raw(self, lock, size, output):
|
|
if isinstance(size, int):
|
|
return self.random_raw(lock, (size,), output)
|
|
|
|
if output is None:
|
|
if size is None:
|
|
with lock:
|
|
self.next_raw()
|
|
return None
|
|
|
|
n = 0
|
|
for a in size:
|
|
n += a
|
|
with lock:
|
|
for _ in range(n):
|
|
self.next_raw()
|
|
return None
|
|
|
|
if size is None:
|
|
with lock:
|
|
return self.next_raw()
|
|
|
|
randoms = empty(size, u64)
|
|
randoms_data = randoms.data
|
|
n = randoms.size
|
|
|
|
with lock:
|
|
for i in range(n):
|
|
randoms_data[i] = self.next_raw()
|
|
return randoms
|
|
|
|
def random_raw(self, size = None):
|
|
if isinstance(size, int):
|
|
return self.random_raw((size,))
|
|
elif size is None:
|
|
return int(self.next64())
|
|
else:
|
|
arr = empty(size, u64)
|
|
p = arr.data
|
|
for i in range(arr.size):
|
|
p[i] = self.next64()
|
|
return arr
|
|
|
|
def shuffle_raw(self, n: int, first: int, itemsize: int,
|
|
stride: int, data: cobj, buf: cobj):
|
|
for i in range(n - 1, first - 1, -1):
|
|
j = int(self.random_interval(u64(i)))
|
|
str.memcpy(buf, data + j * stride, itemsize)
|
|
str.memcpy(data + j * stride, data + i * stride, itemsize)
|
|
str.memcpy(data + i * stride, buf, itemsize)
|
|
|
|
def shuffle_int(self, n: int, first: int, data: Ptr[int]):
|
|
for i in range(n - 1, first - 1, -1):
|
|
j = int(self.random_bounded_uint64(u64(0), u64(i), u64(0), False))
|
|
temp = data[j]
|
|
data[j] = data[i]
|
|
data[i] = temp
|
|
|
|
def shuffle_raw_wrap(self, n: int, first: int, itemsize: int,
|
|
stride: int, data: cobj, buf: cobj):
|
|
int_size = util.sizeof(int)
|
|
if itemsize == int_size:
|
|
self.shuffle_raw(n, first, int_size, stride, data, buf)
|
|
else:
|
|
self.shuffle_raw(n, first, itemsize, stride, data, buf)
|
|
|
|
def validate_output_shape(iter_shape, output: ndarray):
|
|
dims = output.shape
|
|
ndim: Static[int] = staticlen(dims)
|
|
|
|
if ndim != staticlen(iter_shape):
|
|
compile_error("Output size is not compatible with broadcast dimensions of inputs")
|
|
|
|
if iter_shape != dims:
|
|
raise ValueError(f"Output size {dims} is not compatible with broadcast "
|
|
f"dimensions of inputs {iter_shape}.")
|
|
|
|
def check_output(out, dtype: type, size, require_c_array: bool):
|
|
if isinstance(size, int):
|
|
return check_output(out, dtype, (size,), require_c_array)
|
|
|
|
if out is None:
|
|
return
|
|
|
|
T = out.dtype
|
|
if T is not dtype:
|
|
compile_error("Supplied output array has the wrong type. Expected "
|
|
+ dtype.__name__ + " but got " + T.__name__)
|
|
|
|
cc, fc = out._contig
|
|
if not (cc or (fc and not require_c_array)):
|
|
req = "C-" if require_c_array else ""
|
|
raise ValueError(
|
|
f'Supplied output array must be {req}contiguous, writable, '
|
|
f'aligned, and in machine byte-order.'
|
|
)
|
|
|
|
if size is not None:
|
|
if staticlen(size) != staticlen(out.shape):
|
|
compile_error("size must match out.shape when used together")
|
|
|
|
if size != out.shape:
|
|
raise ValueError("size must match out.shape when used together")
|
|
|
|
def double_fill(func, size, lock, out):
|
|
out_val = 0.0
|
|
|
|
if size is None and out is None:
|
|
with lock:
|
|
func(1, __ptr__(out_val))
|
|
return out_val
|
|
else:
|
|
if out is not None:
|
|
check_output(out, float, size, False)
|
|
out_array = out
|
|
else:
|
|
out_array = empty(size, float)
|
|
|
|
n = out_array.size
|
|
out_array_data = out_array.data
|
|
with lock:
|
|
func(n, out_array_data)
|
|
return out_array
|
|
|
|
def float_fill(func, size, lock, out):
|
|
out_val = float32(0.0)
|
|
|
|
if size is None and out is None:
|
|
with lock:
|
|
func(1, __ptr__(out_val))
|
|
return out_val
|
|
else:
|
|
if out is not None:
|
|
check_output(out, float32, size, False)
|
|
out_array = out
|
|
else:
|
|
out_array = empty(size, float32)
|
|
|
|
n = out_array.size
|
|
out_array_data = out_array.data
|
|
with lock:
|
|
func(n, out_array_data)
|
|
return out_array
|
|
|
|
def float_fill_from_double(func, size, lock, out):
|
|
if size is None and out is None:
|
|
with lock:
|
|
return func()
|
|
else:
|
|
if out is not None:
|
|
check_output(out, float32, size, False)
|
|
out_array = out
|
|
else:
|
|
out_array = empty(size, float32)
|
|
|
|
n = out_array.size
|
|
out_array_data = out_array.data
|
|
with lock:
|
|
for i in range(n):
|
|
out_array_data[i] = float32(func())
|
|
return out_array
|
|
|
|
def _check_all(fn, val, name: str, msg):
|
|
if isinstance(val, ndarray):
|
|
cc, fc = val._contig
|
|
|
|
if cc or fc:
|
|
val_data = val.data
|
|
for i in range(val.size):
|
|
e = util.cast(val_data[i], float)
|
|
if not fn(e):
|
|
raise ValueError(msg(name))
|
|
else:
|
|
for idx in util.multirange(val.shape):
|
|
e = util.cast(val._ptr(idx)[0], float)
|
|
if not fn(e):
|
|
raise ValueError(msg(name))
|
|
else:
|
|
if not fn(val):
|
|
raise ValueError(msg(name))
|
|
|
|
CONS_NONE : Static[int] = 0
|
|
CONS_NON_NEGATIVE : Static[int] = 1
|
|
CONS_POSITIVE : Static[int] = 2
|
|
CONS_POSITIVE_NOT_NAN: Static[int] = 3
|
|
CONS_BOUNDED_0_1 : Static[int] = 4
|
|
CONS_BOUNDED_GT_0_1 : Static[int] = 5
|
|
CONS_BOUNDED_LT_0_1 : Static[int] = 6
|
|
CONS_GT_1 : Static[int] = 7
|
|
CONS_GTE_1 : Static[int] = 8
|
|
CONS_POISSON : Static[int] = 9
|
|
|
|
MAX_INT = 0x7FFFFFFFFFFFFFFF
|
|
MAXSIZE = MAX_INT
|
|
POISSON_LAM_MAX = MAX_INT - sqrt(float(MAX_INT))*10
|
|
|
|
def _signbit(x: T, T: type):
|
|
if isinstance(x, float) or isinstance(x, float32):
|
|
return signbit(x)
|
|
else:
|
|
zero = T()
|
|
return x < zero
|
|
|
|
def check_array_constraint(val, name: str, const: int):
|
|
if const == CONS_NONE:
|
|
pass
|
|
elif const == CONS_NON_NEGATIVE:
|
|
fn = lambda x: not ((not isnan(x)) and _signbit(x))
|
|
msg = lambda name: f"{name} < 0"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_POSITIVE or const == CONS_POSITIVE_NOT_NAN:
|
|
if const == CONS_POSITIVE_NOT_NAN:
|
|
fn = lambda x: not isnan(x)
|
|
msg = lambda name: f"{name} must not be NaN"
|
|
_check_all(fn, val, name, msg)
|
|
|
|
fn = lambda x: x > type(x)(0)
|
|
msg = lambda name: f"{name} <= 0"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_BOUNDED_0_1:
|
|
fn = lambda x: x >= type(x)(0) and x <= type(x)(1)
|
|
msg = lambda name: f"{name} < 0, {name} > 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_BOUNDED_GT_0_1:
|
|
fn = lambda x: x > type(x)(0) and x <= type(x)(1)
|
|
msg = lambda name: f"{name} <= 0, {name} > 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_BOUNDED_LT_0_1:
|
|
fn = lambda x: x >= type(x)(0) and x < type(x)(1)
|
|
msg = lambda name: f"{name} < 0, {name} >= 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_BOUNDED_LT_0_1:
|
|
fn = lambda x: x >= type(x)(0) and x < type(x)(1)
|
|
msg = lambda name: f"{name} < 0, {name} >= 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_GT_1:
|
|
fn = lambda x: x > type(x)(1)
|
|
msg = lambda name: f"{name} <= 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_GTE_1:
|
|
fn = lambda x: x >= type(x)(1)
|
|
msg = lambda name: f"{name} < 1 or {name} contains NaNs"
|
|
_check_all(fn, val, name, msg)
|
|
elif const == CONS_POISSON:
|
|
fn = lambda x: util.cast(x, float) >= 0.0
|
|
msg = lambda name: f"{name} < 0 or {name} is NaN"
|
|
_check_all(fn, val, name, msg)
|
|
|
|
fn = lambda x: util.cast(x, float) <= POISSON_LAM_MAX
|
|
msg = lambda name: f"{name} value too large"
|
|
_check_all(fn, val, name, msg)
|
|
|
|
def convert_array_like(a):
|
|
if (isinstance(a, ndarray) or
|
|
isinstance(a, List) or
|
|
isinstance(a, Tuple)):
|
|
return asarray(a)
|
|
else:
|
|
return a
|
|
|
|
def gather_arrays(t):
|
|
if staticlen(t) == 0:
|
|
return ()
|
|
|
|
a, rest = t[0], gather_arrays(t[1:])
|
|
|
|
if isinstance(a, ndarray):
|
|
return (a, *rest)
|
|
else:
|
|
return rest
|
|
|
|
def cont(fn, size, lock, arrays, names, constraints, out = None, dtype: type = float):
|
|
if not (staticlen(arrays) == staticlen(names) and staticlen(names) == staticlen(constraints)):
|
|
compile_error("[internal error] tuple size mismatch")
|
|
|
|
if isinstance(size, int):
|
|
return cont(fn, (size,), lock, arrays, names, constraints, out)
|
|
|
|
arrays = tuple(convert_array_like(arr) for arr in arrays)
|
|
|
|
for i in staticrange(staticlen(names)):
|
|
check_array_constraint(arrays[i], names[i], constraints[i])
|
|
|
|
# constant parameters case
|
|
if staticlen(gather_arrays(arrays)) == 0:
|
|
args = arrays
|
|
|
|
if size is not None and out is None:
|
|
randoms = empty(size, dtype)
|
|
randoms_data = randoms.data
|
|
n = randoms.size
|
|
|
|
for i in range(n):
|
|
randoms_data[i] = fn(*args)
|
|
|
|
return randoms
|
|
else:
|
|
if out is None:
|
|
return fn(*args)
|
|
else:
|
|
randoms = out
|
|
validate_output_shape((), randoms)
|
|
randoms_data = randoms.data
|
|
n = randoms.size
|
|
cc, fc = randoms._contig
|
|
|
|
if cc or fc:
|
|
for i in range(n):
|
|
randoms_data[i] = fn(*args)
|
|
else:
|
|
for idx in util.multirange(randoms.shape):
|
|
p = randoms._ptr(idx)
|
|
p[0] = fn(*args)
|
|
|
|
return randoms
|
|
|
|
arrays = tuple(asarray(arr) for arr in arrays)
|
|
|
|
if size is not None and out is None:
|
|
shapes = tuple(arr.shape for arr in arrays)
|
|
broadcast_shapes(*shapes, size) # error check
|
|
randoms = empty(size, dtype)
|
|
else:
|
|
shapes = tuple(arr.shape for arr in arrays)
|
|
if size is None:
|
|
bshape = broadcast_shapes(*shapes)
|
|
else:
|
|
bshape = broadcast_shapes(*shapes, size)
|
|
|
|
if out is None:
|
|
randoms = empty(bshape, dtype)
|
|
else:
|
|
randoms = out
|
|
validate_output_shape(bshape, randoms)
|
|
|
|
randoms_data = randoms.data
|
|
n = randoms.size
|
|
|
|
for idx in util.multirange(randoms.shape):
|
|
args = tuple(arr._ptr(idx, broadcast=True)[0] for arr in arrays)
|
|
p = randoms._ptr(idx)
|
|
p[0] = fn(*args)
|
|
|
|
return randoms
|
|
|
|
def kahan_sum(darr: Ptr[float], n: int):
|
|
if n <= 0:
|
|
return 0.0
|
|
sum = darr[0]
|
|
c = 0.0
|
|
for i in range(1, n):
|
|
y = darr[i] - c
|
|
t = sum + y
|
|
c = (t-sum) - y
|
|
sum = t
|
|
return sum
|
|
|
|
|
|
from threading import Lock
|
|
|
|
@tuple
|
|
class Generator[G]:
|
|
lock: Lock
|
|
bit_generator: BitGenerator[G]
|
|
|
|
def __new__(g: G):
|
|
return Generator[G](Lock(), BitGenerator[G](g))
|
|
|
|
def __str__(self):
|
|
return f"{self.__class__.__name__}({self.bit_generator.__class__.__name__})"
|
|
|
|
# TODO: pickle support
|
|
|
|
def spawn(self, n_children: int):
|
|
return [Generator(G(seed)) for seed in self.bit_generator.seed_seq.spawn(n_children)]
|
|
|
|
def random(self, size = None, dtype: type = float, out = None):
|
|
if dtype is float:
|
|
return double_fill(self.bit_generator.random_standard_uniform_fill, size, self.lock, out)
|
|
elif dtype is float32:
|
|
return float_fill(self.bit_generator.random_standard_uniform_fill_f, size, self.lock, out)
|
|
else:
|
|
compile_error("Unsupported dtype " + dtype.__name__ + " for random")
|
|
|
|
def beta(self, a, b, size = None):
|
|
return cont(self.bit_generator.random_beta,
|
|
size, self.lock, (a, b), ('a', 'b'),
|
|
(CONS_POSITIVE, CONS_POSITIVE))
|
|
|
|
def exponential(self, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_exponential,
|
|
size, self.lock, (scale,), ('scale',),
|
|
(CONS_NON_NEGATIVE,))
|
|
|
|
def standard_exponential(self, size = None, dtype: type = float,
|
|
method: str = 'zig', out = None):
|
|
def bad_method():
|
|
raise ValueError("'method' argument must be either 'zig' or 'inv'")
|
|
|
|
if dtype is float:
|
|
if method == 'zig':
|
|
return double_fill(self.bit_generator.random_standard_exponential_fill, size, self.lock, out)
|
|
elif method == 'inv':
|
|
return double_fill(self.bit_generator.random_standard_exponential_inv_fill, size, self.lock, out)
|
|
else:
|
|
bad_method()
|
|
elif dtype is float32:
|
|
if method == 'zig':
|
|
return float_fill(self.bit_generator.random_standard_exponential_fill_f, size, self.lock, out)
|
|
elif method == 'inv':
|
|
return float_fill(self.bit_generator.random_standard_exponential_inv_fill_f, size, self.lock, out)
|
|
else:
|
|
bad_method()
|
|
else:
|
|
compile_error("Unsupported dtype " + dtype.__name__ + " for standard_exponential")
|
|
|
|
def integers(self, low, high = None, size = None, dtype: type = int, endpoint: bool = False):
|
|
if high is None:
|
|
return self.integers(0, low, size=size, dtype=dtype, endpoint=endpoint)
|
|
|
|
if isinstance(size, int):
|
|
return self.integers(low, high, size=(size,), dtype=dtype, endpoint=endpoint)
|
|
elif size is not None:
|
|
for s in size:
|
|
if s == 0:
|
|
return empty(size, dtype)
|
|
|
|
if dtype is u64:
|
|
lb = 0
|
|
ub = 0x7FFFFFFFFFFFFFFF
|
|
fn = self.bit_generator.random_bounded_uint64_fill
|
|
out_val = u64()
|
|
elif dtype is u32:
|
|
lb = 0
|
|
ub = 0xFFFFFFFF
|
|
fn = self.bit_generator.random_bounded_uint32_fill
|
|
out_val = u32()
|
|
elif dtype is u16:
|
|
lb = 0
|
|
ub = 0xFFFF
|
|
fn = self.bit_generator.random_bounded_uint16_fill
|
|
out_val = u16()
|
|
elif dtype is u8:
|
|
lb = 0
|
|
ub = 0xFF
|
|
fn = self.bit_generator.random_bounded_uint8_fill
|
|
out_val = u8()
|
|
elif dtype is bool:
|
|
lb = 0
|
|
ub = 1
|
|
fn = self.bit_generator.random_bounded_bool_fill
|
|
out_val = False
|
|
elif dtype is i64 or dtype is int:
|
|
lb = -9223372036854775808
|
|
ub = 0x7FFFFFFFFFFFFFFF
|
|
fn = self.bit_generator.random_bounded_uint64_fill
|
|
out_val = u64()
|
|
elif dtype is i32:
|
|
lb = -0x80000000
|
|
ub = 0x7FFFFFFF
|
|
fn = self.bit_generator.random_bounded_uint32_fill
|
|
out_val = u32()
|
|
elif dtype is i16:
|
|
lb = -0x8000
|
|
ub = 0x7FFF
|
|
fn = self.bit_generator.random_bounded_uint16_fill
|
|
out_val = u16()
|
|
elif dtype is i8:
|
|
lb = -0x80
|
|
ub = 0x7F
|
|
fn = self.bit_generator.random_bounded_uint8_fill
|
|
out_val = u8()
|
|
else:
|
|
compile_error("Unsupported dtype " + dtype.__name__ + " for integers")
|
|
|
|
use_masked = False
|
|
low = convert_array_like(low)
|
|
high = convert_array_like(high)
|
|
|
|
if staticlen(gather_arrays((low, high))) == 0:
|
|
ilow = int(low)
|
|
ihigh = int(high)
|
|
|
|
if not endpoint:
|
|
ihigh -= 1
|
|
|
|
if ilow < lb:
|
|
raise ValueError("low is out of bounds for " + dtype.__name__)
|
|
|
|
if ihigh > ub:
|
|
raise ValueError("high is out of bounds for " + dtype.__name__)
|
|
|
|
if ilow > ihigh:
|
|
raise ValueError("low > high" if endpoint else "low >= high")
|
|
|
|
rng = type(out_val)(ihigh - ilow)
|
|
off = type(out_val)(ilow)
|
|
|
|
if size is None:
|
|
with self.lock:
|
|
fn(off, rng, 1, use_masked, __ptr__(out_val))
|
|
return util.cast(out_val, dtype)
|
|
else:
|
|
out_arr = empty(size, dtype)
|
|
cnt = out_arr.size
|
|
out_data = Ptr[type(out_val)](out_arr.data.as_byte())
|
|
with self.lock:
|
|
fn(off, rng, cnt, use_masked, out_data)
|
|
return out_arr
|
|
else:
|
|
low = asarray(low)
|
|
high = asarray(high)
|
|
|
|
if size is None:
|
|
bshape = broadcast_shapes(low.shape, high.shape)
|
|
else:
|
|
bshape = broadcast_shapes(low.shape, high.shape, size)
|
|
|
|
randoms = empty(bshape, dtype)
|
|
randoms_data = randoms.data
|
|
n = randoms.size
|
|
|
|
for idx in util.multirange(randoms.shape):
|
|
e_low = int(low._ptr(idx, broadcast=True)[0])
|
|
e_high = int(high._ptr(idx, broadcast=True)[0])
|
|
|
|
if not endpoint:
|
|
e_high -= 1
|
|
|
|
if e_low < lb:
|
|
raise ValueError("low is out of bounds for " + dtype.__name__)
|
|
|
|
if e_high > ub:
|
|
raise ValueError("high is out of bounds for " + dtype.__name__)
|
|
|
|
if e_low > e_high:
|
|
raise ValueError("low > high" if endpoint else "low >= high")
|
|
|
|
rng = type(out_val)(e_high - e_low)
|
|
off = type(out_val)(e_low)
|
|
|
|
with self.lock:
|
|
fn(off, rng, 1, use_masked, __ptr__(out_val))
|
|
p = randoms._ptr(idx)
|
|
p[0] = util.cast(out_val, dtype)
|
|
|
|
return randoms
|
|
|
|
def bytes(self, length: int):
|
|
if length < 0:
|
|
raise ValueError("length must be non-negative")
|
|
n_uint32 = ((length - 1) // 4 + 1)
|
|
arr = self.integers(0, 4294967296, size=n_uint32, dtype=u32)
|
|
return str(arr.data.as_byte(), length)
|
|
|
|
def choice(self, a, size = None, replace: bool = True, p = None, axis: int = 0, shuffle: bool = True):
|
|
def prod(s):
|
|
if s is None:
|
|
return 0
|
|
else:
|
|
return util.count(s)
|
|
|
|
def bisect_right(a, x, n: int):
|
|
lo = 0
|
|
hi = n
|
|
while lo < hi:
|
|
mid = (lo + hi) // 2
|
|
if x < a[mid]:
|
|
hi = mid
|
|
else:
|
|
lo = mid + 1
|
|
return lo
|
|
|
|
if isinstance(size, int):
|
|
return self.choice(a, size=(size,), replace=replace, p=p, axis=axis, shuffle=shuffle)
|
|
|
|
pop_size = 0
|
|
if isinstance(a, int):
|
|
pop_size = a
|
|
if pop_size <= 0 and prod(size) != 0:
|
|
raise ValueError("a must be a positive integer unless no "
|
|
"samples are taken")
|
|
a1 = asarray(a)
|
|
else:
|
|
a1 = asarray(a)
|
|
pop_size = a1.shape[axis]
|
|
if pop_size == 0 and prod(size) != 0:
|
|
raise ValueError("a cannot be empty unless no samples are "
|
|
"taken")
|
|
|
|
a = a1
|
|
pix = Ptr[float]()
|
|
|
|
if p is not None:
|
|
p1 = asarray(p, order='C')
|
|
d = len(p1)
|
|
|
|
if staticlen(p1.shape) != 1:
|
|
compile_error("p must be 1-dimensional")
|
|
|
|
if p1.dtype is not float:
|
|
compile_error("p must contain floats")
|
|
|
|
atol = util.sqrt(util.eps64())
|
|
pix = p1.data
|
|
|
|
if p1.size != pop_size:
|
|
raise ValueError("a and p must have same size")
|
|
p_sum = kahan_sum(pix, d)
|
|
if util.isnan(p_sum):
|
|
raise ValueError("probabilities contain NaN")
|
|
for i in range(pop_size):
|
|
if pix[i] < 0:
|
|
raise ValueError("probabilities are not non-negative")
|
|
if abs(p_sum - 1.) > atol:
|
|
raise ValueError("probabilities do not sum to 1")
|
|
|
|
is_scalar: Static[int] = size is None
|
|
if not is_scalar:
|
|
shape = size
|
|
size1 = prod(shape)
|
|
else:
|
|
shape = ()
|
|
size1 = 1
|
|
|
|
if replace:
|
|
if p is not None:
|
|
cdf = Ptr[float](pop_size)
|
|
s = 0.0
|
|
for i in range(pop_size):
|
|
s += pix[i]
|
|
cdf[i] = s
|
|
for i in range(pop_size):
|
|
cdf[i] /= cdf[pop_size - 1]
|
|
uniform_samples = atleast_1d(self.random(shape))
|
|
nu = len(uniform_samples)
|
|
idx1 = empty(shape, dtype=int)
|
|
|
|
for idx in util.multirange(shape):
|
|
px = uniform_samples._ptr(idx)
|
|
qx = idx1._ptr(idx)
|
|
qx[0] = bisect_right(cdf, px[0], pop_size)
|
|
else:
|
|
idx1 = self.integers(0, pop_size, size=shape, dtype=int)
|
|
|
|
idx2 = idx1
|
|
else:
|
|
if size1 > pop_size:
|
|
raise ValueError("Cannot take a larger sample than "
|
|
"population when replace is False")
|
|
elif size1 < 0:
|
|
raise ValueError("negative dimensions are not allowed")
|
|
|
|
if p is not None:
|
|
num_non_zero = 0
|
|
for i in range(pop_size):
|
|
if pix[i] > 0:
|
|
num_non_zero += 1
|
|
if num_non_zero < size1:
|
|
raise ValueError("Fewer non-zero entries in p than size")
|
|
|
|
n_uniq = 0
|
|
p1 = p1.copy()
|
|
found = zeros(shape, dtype=int)
|
|
flat_found = found.ravel()
|
|
|
|
while n_uniq < size1:
|
|
m = size1 - n_uniq
|
|
x = self.random((m,))
|
|
if n_uniq > 0:
|
|
for i in flat_found[0:n_uniq]:
|
|
pix[i] = 0
|
|
|
|
cdf = Ptr[float](pop_size)
|
|
s = 0.0
|
|
for i in range(pop_size):
|
|
s += pix[i]
|
|
cdf[i] = s
|
|
for i in range(pop_size):
|
|
cdf[i] /= cdf[pop_size - 1]
|
|
|
|
new = Ptr[int](m)
|
|
for i in range(m):
|
|
new[i] = bisect_right(cdf, x[i], pop_size)
|
|
|
|
idx_val = [(new[i], i) for i in range(m)]
|
|
idx_val.sort()
|
|
unique_indices = []
|
|
|
|
i = 0
|
|
j = 1
|
|
while i < m:
|
|
e, k = idx_val[i]
|
|
unique_indices.append(k)
|
|
while j < m and idx_val[j][0] == e:
|
|
j += 1
|
|
i = j
|
|
unique_indices.sort()
|
|
|
|
for i in range(n_uniq, n_uniq + len(unique_indices)):
|
|
flat_found[i] = new[unique_indices[i - n_uniq]]
|
|
|
|
n_uniq += len(unique_indices)
|
|
idx2 = found
|
|
else:
|
|
size_i = size1
|
|
pop_size_i = pop_size
|
|
if shuffle:
|
|
cutoff = 50
|
|
else:
|
|
cutoff = 20
|
|
|
|
if pop_size_i > 10000 and (size_i > (pop_size_i // cutoff)):
|
|
idx1a = arange(0, pop_size_i, 1, int)
|
|
idx_data = idx1a.data
|
|
with self.lock:
|
|
self.bit_generator.shuffle_int(pop_size_i, max(pop_size_i - size_i, 1), idx_data)
|
|
idx1a = idx1a[(pop_size - size1):].copy()
|
|
else:
|
|
idx1a = empty(size1, dtype=int)
|
|
idx_data = idx1a.data
|
|
set_size = u64(int(1.2 * size_i))
|
|
mask = BitGenerator.gen_mask(set_size)
|
|
set_size = u64(1) + mask
|
|
hash_set = full(int(set_size), u64(-1), u64)
|
|
with self.lock:
|
|
for j in range(pop_size_i - size_i, pop_size_i):
|
|
val = self.bit_generator.random_bounded_uint64(u64(0), u64(j), u64(0), False)
|
|
loc = int(val & mask)
|
|
while hash_set[loc] != u64(-1) and hash_set[loc] != val:
|
|
loc = (loc + 1) & int(mask)
|
|
if hash_set[loc] == u64(-1): # then val not in hash_set
|
|
hash_set[loc] = val
|
|
idx_data[j - pop_size_i + size_i] = int(val)
|
|
else: # we need to insert j instead
|
|
loc = j & int(mask)
|
|
while hash_set[loc] != u64(-1):
|
|
loc = (loc + 1) & int(mask)
|
|
hash_set[loc] = u64(j)
|
|
idx_data[j - pop_size_i + size_i] = int(j)
|
|
if shuffle:
|
|
self.bit_generator.shuffle_int(size_i, 1, idx_data)
|
|
|
|
idx2 = idx1a.reshape(shape)
|
|
|
|
if is_scalar and isinstance(idx2, ndarray):
|
|
idx3 = idx2.item(0)
|
|
else:
|
|
idx3 = idx2
|
|
|
|
if staticlen(a.shape) == 0:
|
|
return idx2
|
|
|
|
if not is_scalar and staticlen(asarray(idx3).shape) == 0:
|
|
res = empty((), dtype=a.dtype)
|
|
res[()] = a[asarray(idx3).item()]
|
|
return res
|
|
|
|
axis = util.normalize_axis_index(axis, a.ndim)
|
|
idx3 = asarray(idx3)
|
|
|
|
if staticlen(idx3.shape) == 0 and staticlen(a.shape) == 1:
|
|
return a._ptr((idx3.item(),))[0]
|
|
|
|
idx3 = atleast_1d(idx3)
|
|
new_shape = util.tuple_set(a.shape, axis, len(idx3))
|
|
rest_shape = util.tuple_delete(a.shape, axis)
|
|
res = empty(new_shape, dtype=a.dtype)
|
|
|
|
ai1 = 0
|
|
for ai2 in idx3:
|
|
for idx_rest in util.multirange(rest_shape):
|
|
i2 = util.tuple_insert(idx_rest, axis, ai1)
|
|
i1 = util.tuple_insert(idx_rest, axis, ai2)
|
|
p = a._ptr(i1)
|
|
q = res._ptr(i2)
|
|
q[0] = p[0]
|
|
|
|
ai1 += 1
|
|
|
|
return res
|
|
|
|
def uniform(self, low = 0.0, high = 1.0, size = None):
|
|
low = convert_array_like(low)
|
|
high = convert_array_like(high)
|
|
|
|
if staticlen(gather_arrays((low, high))) == 0:
|
|
rng = float(high - low)
|
|
if not util.isfinite(rng):
|
|
raise OverflowError("high - low range exceeds valid bounds")
|
|
|
|
def random_uniform_low_high(low: float, high: float):
|
|
rng = high - low
|
|
if not util.isfinite(rng):
|
|
raise OverflowError("high - low range exceeds valid bounds")
|
|
if util.signbit(rng):
|
|
raise ValueError("low > high")
|
|
return self.bit_generator.random_uniform(low, rng)
|
|
|
|
return cont(random_uniform_low_high,
|
|
size, self.lock, (low, high), ('low', 'high'),
|
|
(CONS_NONE, CONS_NONE))
|
|
|
|
def standard_normal(self, size = None, dtype: type = float, out = None):
|
|
if dtype is float:
|
|
return double_fill(self.bit_generator.random_standard_normal_fill, size, self.lock, out)
|
|
elif dtype is float32:
|
|
return float_fill(self.bit_generator.random_standard_normal_fill_f, size, self.lock, out)
|
|
else:
|
|
compile_error("Unsupported dtype " + dtype.__name__ + " for standard_normal")
|
|
|
|
def normal(self, loc = 0.0, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_normal,
|
|
size, self.lock, (loc, scale), ('loc', 'scale'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def standard_gamma(self, shape, size = None, dtype: type = float, out = None):
|
|
if dtype is float:
|
|
return cont(self.bit_generator.random_standard_gamma,
|
|
size, self.lock, (shape,), ('shape',),
|
|
(CONS_NON_NEGATIVE,))
|
|
elif dtype is float32:
|
|
@tuple
|
|
class U[G]:
|
|
bit_generator: G
|
|
|
|
def standard_gamma_f_cast(self, shape: float):
|
|
return self.bit_generator.random_standard_gamma_f(util.cast(shape, float32))
|
|
|
|
return cont(U(self.bit_generator).standard_gamma_f_cast,
|
|
size, self.lock, (shape,), ('shape',),
|
|
(CONS_NON_NEGATIVE,), dtype=float32)
|
|
else:
|
|
compile_error("Unsupported dtype " + dtype.__name__ + " for standard_gamma")
|
|
|
|
def gamma(self, shape, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_gamma,
|
|
size, self.lock, (shape, scale), ('shape', 'scale'),
|
|
(CONS_NON_NEGATIVE, CONS_NON_NEGATIVE))
|
|
|
|
def f(self, dfnum, dfden, size = None):
|
|
return cont(self.bit_generator.random_f,
|
|
size, self.lock, (dfnum, dfden), ('dfnum', 'dfden'),
|
|
(CONS_POSITIVE, CONS_POSITIVE))
|
|
|
|
def noncentral_f(self, dfnum, dfden, nonc, size = None):
|
|
return cont(self.bit_generator.random_noncentral_f,
|
|
size, self.lock, (dfnum, dfden, nonc), ('dfnum', 'dfden', 'nonc'),
|
|
(CONS_POSITIVE, CONS_POSITIVE, CONS_NON_NEGATIVE))
|
|
|
|
def chisquare(self, df, size = None):
|
|
return cont(self.bit_generator.random_chisquare,
|
|
size, self.lock, (df,), ('df',),
|
|
(CONS_POSITIVE,))
|
|
|
|
def noncentral_chisquare(self, df, nonc, size = None):
|
|
return cont(self.bit_generator.random_noncentral_chisquare,
|
|
size, self.lock, (df, nonc), ('df', 'nonc'),
|
|
(CONS_POSITIVE, CONS_NON_NEGATIVE))
|
|
|
|
def standard_cauchy(self, size = None):
|
|
return cont(self.bit_generator.random_standard_cauchy,
|
|
size, self.lock, (), (), ())
|
|
|
|
def standard_t(self, df, size = None):
|
|
return cont(self.bit_generator.random_standard_t,
|
|
size, self.lock, (df,), ('df',),
|
|
(CONS_POSITIVE,))
|
|
|
|
def vonmises(self, mu, kappa, size = None):
|
|
return cont(self.bit_generator.random_vonmises,
|
|
size, self.lock, (mu, kappa), ('mu', 'kappa'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def pareto(self, a, size = None):
|
|
return cont(self.bit_generator.random_pareto,
|
|
size, self.lock, (a,), ('a',),
|
|
(CONS_POSITIVE,))
|
|
|
|
def weibull(self, a, size = None):
|
|
return cont(self.bit_generator.random_weibull,
|
|
size, self.lock, (a,), ('a',),
|
|
(CONS_NON_NEGATIVE,))
|
|
|
|
def power(self, a, size = None):
|
|
return cont(self.bit_generator.random_power,
|
|
size, self.lock, (a,), ('a',),
|
|
(CONS_POSITIVE,))
|
|
|
|
def laplace(self, loc = 0.0, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_laplace,
|
|
size, self.lock, (loc, scale), ('loc', 'scale'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def gumbel(self, loc = 0.0, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_gumbel,
|
|
size, self.lock, (loc, scale), ('loc', 'scale'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def logistic(self, loc = 0.0, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_logistic,
|
|
size, self.lock, (loc, scale), ('loc', 'scale'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def lognormal(self, mean = 0.0, sigma = 1.0, size = None):
|
|
return cont(self.bit_generator.random_lognormal,
|
|
size, self.lock, (mean, sigma), ('mean', 'sigma'),
|
|
(CONS_NONE, CONS_NON_NEGATIVE))
|
|
|
|
def rayleigh(self, scale = 1.0, size = None):
|
|
return cont(self.bit_generator.random_rayleigh,
|
|
size, self.lock, (scale,), ('scale',),
|
|
(CONS_NON_NEGATIVE,))
|
|
|
|
def wald(self, mean, scale, size = None):
|
|
return cont(self.bit_generator.random_wald,
|
|
size, self.lock, (mean, scale), ('mean', 'scale'),
|
|
(CONS_POSITIVE, CONS_POSITIVE))
|
|
|
|
def triangular(self, left, mode, right, size = None):
|
|
if staticlen(gather_arrays((asarray(left), asarray(mode), asarray(right)))) == 0:
|
|
if left > mode:
|
|
raise ValueError("left > mode")
|
|
if mode > right:
|
|
raise ValueError("mode > right")
|
|
if left == right:
|
|
raise ValueError("left == right")
|
|
else:
|
|
oleft = asarray(left)
|
|
omode = asarray(mode)
|
|
oright = asarray(right)
|
|
|
|
if (oleft > omode).any():
|
|
raise ValueError("left > mode")
|
|
if (omode > oright).any():
|
|
raise ValueError("mode > right")
|
|
if (oleft == oright).any():
|
|
raise ValueError("left == right")
|
|
|
|
return cont(self.bit_generator.random_triangular,
|
|
size, self.lock, (left, mode, right), ('left', 'mode', 'right'),
|
|
(CONS_NONE, CONS_NONE, CONS_NONE))
|
|
|
|
def binomial(self, n, p, size = None):
|
|
if isinstance(size, int):
|
|
return self.binomial(n, p, size=(size,))
|
|
|
|
n = convert_array_like(n)
|
|
p = convert_array_like(p)
|
|
if staticlen(gather_arrays((n, p))) > 0:
|
|
an = asarray(n).astype(int, copy=False)
|
|
ap = asarray(p)
|
|
check_array_constraint(ap, 'p', CONS_BOUNDED_0_1)
|
|
check_array_constraint(an, 'n', CONS_NON_NEGATIVE)
|
|
bshape = broadcast_shapes(ap.shape, an.shape)
|
|
if size is not None:
|
|
randoms = empty(size, int)
|
|
broadcast_shapes(randoms.shape, bshape) # error check
|
|
else:
|
|
randoms = empty(bshape, int)
|
|
|
|
with self.lock:
|
|
for idx in util.multirange(randoms.shape):
|
|
e_n = an._ptr(idx, broadcast=True)[0]
|
|
e_p = ap._ptr(idx, broadcast=True)[0]
|
|
randoms._ptr(idx)[0] = self.bit_generator.random_binomial(e_p, e_n)
|
|
|
|
return randoms
|
|
|
|
p = float(p)
|
|
n = int(n)
|
|
check_array_constraint(p, 'p', CONS_BOUNDED_0_1)
|
|
check_array_constraint(n, 'n', CONS_NON_NEGATIVE)
|
|
|
|
if size is None:
|
|
with self.lock:
|
|
return self.bit_generator.random_binomial(p, n)
|
|
else:
|
|
randoms = empty(size, int)
|
|
cnt = randoms.size
|
|
randoms_data = randoms.data
|
|
|
|
with self.lock:
|
|
for i in range(cnt):
|
|
randoms_data[i] = self.bit_generator.random_binomial(p, n)
|
|
|
|
return randoms
|
|
|
|
def negative_binomial(self, n, p, size = None):
|
|
if isinstance(size, int):
|
|
return self.negative_binomial(n, p, size=(size,))
|
|
|
|
n = convert_array_like(n)
|
|
p = convert_array_like(p)
|
|
if staticlen(gather_arrays((n, p))) > 0:
|
|
an = asarray(n)
|
|
ap = asarray(p)
|
|
check_array_constraint(an, 'n', CONS_POSITIVE_NOT_NAN)
|
|
check_array_constraint(ap, 'p', CONS_BOUNDED_GT_0_1)
|
|
|
|
for idx in util.multirange(broadcast_shapes(an.shape, ap.shape)):
|
|
e_n = an._ptr(idx, broadcast=True)[0]
|
|
e_p = ap._ptr(idx, broadcast=True)[0]
|
|
if (1 - e_p) / e_p * (e_n + 10 * util.sqrt(util.cast(e_n, float))) > POISSON_LAM_MAX:
|
|
raise ValueError("n too large or p too small, see Generator.negative_binomial Notes")
|
|
else:
|
|
check_array_constraint(n, 'n', CONS_POSITIVE_NOT_NAN)
|
|
check_array_constraint(p, 'p', CONS_BOUNDED_GT_0_1)
|
|
|
|
dmax_lam = (1 - p) / p * (n + 10 * util.sqrt(util.cast(n, float)))
|
|
if dmax_lam > POISSON_LAM_MAX:
|
|
raise ValueError("n too large or p too small, see Generator.negative_binomial Notes")
|
|
|
|
return cont(self.bit_generator.random_negative_binomial,
|
|
size, self.lock, (n, p), ('n', 'p'),
|
|
(CONS_NONE, CONS_NONE), dtype=int)
|
|
|
|
def poisson(self, lam = 1.0, size = None):
|
|
return cont(self.bit_generator.random_poisson,
|
|
size, self.lock, (lam,), ('lam',),
|
|
(CONS_POISSON,), dtype=int)
|
|
|
|
def zipf(self, a, size = None):
|
|
return cont(self.bit_generator.random_zipf,
|
|
size, self.lock, (a,), ('a',),
|
|
(CONS_GT_1,), dtype=int)
|
|
|
|
def geometric(self, p, size = None):
|
|
return cont(self.bit_generator.random_geometric,
|
|
size, self.lock, (p,), ('p',),
|
|
(CONS_BOUNDED_GT_0_1,), dtype=int)
|
|
|
|
def hypergeometric(self, ngood, nbad, nsample, size = None):
|
|
HYPERGEOM_MAX = 1_000_000_000
|
|
ngood = convert_array_like(ngood)
|
|
nbad = convert_array_like(nbad)
|
|
nsample = convert_array_like(nsample)
|
|
|
|
if staticlen(gather_arrays((ngood, nbad, nsample))) == 0:
|
|
if ngood >= HYPERGEOM_MAX or nbad >= HYPERGEOM_MAX:
|
|
raise ValueError("both ngood and nbad must be less than 1000000000")
|
|
if ngood + nbad < nsample:
|
|
raise ValueError("ngood + nbad < nsample")
|
|
else:
|
|
angood = asarray(ngood)
|
|
anbad = asarray(nbad)
|
|
ansample = asarray(nsample)
|
|
|
|
for idx in util.multirange(broadcast_shapes(angood.shape, anbad.shape, ansample.shape)):
|
|
e_ngood = angood._ptr(idx, broadcast=True)[0]
|
|
e_nbad = anbad._ptr(idx, broadcast=True)[0]
|
|
e_nsample = ansample._ptr(idx, broadcast=True)[0]
|
|
|
|
if e_ngood >= HYPERGEOM_MAX or e_nbad >= HYPERGEOM_MAX:
|
|
raise ValueError("both ngood and nbad must be less than 1000000000")
|
|
if e_ngood + e_nbad < e_nsample:
|
|
raise ValueError("ngood + nbad < nsample")
|
|
|
|
return cont(self.bit_generator.random_hypergeometric,
|
|
size, self.lock, (ngood, nbad, nsample), ('ngood', 'nbad', 'nsample'),
|
|
(CONS_NON_NEGATIVE, CONS_NON_NEGATIVE, CONS_NON_NEGATIVE), dtype=int)
|
|
|
|
def logseries(self, p, size = None):
|
|
return cont(self.bit_generator.random_logseries,
|
|
size, self.lock, (p,), ('p',),
|
|
(CONS_BOUNDED_LT_0_1,), dtype=int)
|
|
|
|
def multivariate_normal(self, mean, cov, size = None, check_valid: Static[str] = 'warn',
|
|
tol: float = 1e-8, method: Static[str] = 'svd'):
|
|
if method != 'eigh' and method != 'svd' and method != 'cholesky':
|
|
compile_error(
|
|
"method must be one of {'eigh', 'svd', 'cholesky'}")
|
|
|
|
mean = asarray(mean)
|
|
cov = asarray(cov)
|
|
|
|
dtype_mean = mean.dtype
|
|
dtype_cov = cov.dtype
|
|
if (dtype_mean is complex or dtype_mean is complex64 or
|
|
dtype_cov is complex or dtype_cov is complex64):
|
|
compile_error("mean and cov must not be complex")
|
|
|
|
if size is None:
|
|
shape = ()
|
|
elif isinstance(size, int):
|
|
shape = (size,)
|
|
else:
|
|
shape = size
|
|
|
|
if staticlen(mean.shape) != 1:
|
|
compile_error("mean must be 1 dimensional")
|
|
if staticlen(cov.shape) != 2:
|
|
compile_error("cov must be 2 dimensional")
|
|
if cov.shape[0] != cov.shape[1]:
|
|
raise ValueError("cov must be square")
|
|
if mean.shape[0] != cov.shape[0]:
|
|
raise ValueError("mean and cov must have same length")
|
|
|
|
final_shape = shape + (mean.shape[0],)
|
|
x = self.standard_normal(final_shape).reshape(-1, mean.shape[0])
|
|
|
|
cov = cov.astype(float)
|
|
if method == 'svd':
|
|
from ..linalg import svd
|
|
(u, s, vh) = svd(cov)
|
|
elif method == 'eigh':
|
|
from ..linalg import eigh
|
|
(s, u) = eigh(cov)
|
|
else:
|
|
from ..linalg import cholesky
|
|
l = cholesky(cov)
|
|
|
|
if check_valid != 'ignore' and method != 'cholesky':
|
|
if check_valid != 'warn' and check_valid != 'raise':
|
|
compile_error(
|
|
"check_valid must equal 'warn', 'raise', or 'ignore'")
|
|
if method == 'svd':
|
|
from ..linalg import _linalg_dot
|
|
psd = allclose(_linalg_dot(vh.T * s, vh), cov, rtol=tol, atol=tol)
|
|
else:
|
|
psd = True
|
|
for a in s:
|
|
if a < -tol:
|
|
psd = False
|
|
if not psd:
|
|
if check_valid == 'warn':
|
|
# TODO: warn "covariance is not symmetric positive-semidefinite"
|
|
pass
|
|
else:
|
|
raise ValueError("covariance is not symmetric positive-semidefinite.")
|
|
|
|
if method == 'cholesky':
|
|
_factor = l
|
|
elif method == 'eigh':
|
|
for i in range(len(s)):
|
|
s[i] = util.sqrt(abs(s[i]))
|
|
_factor = u * s
|
|
else:
|
|
for i in range(len(s)):
|
|
s[i] = util.sqrt(s[i])
|
|
_factor = u * s
|
|
|
|
x = mean + x @ _factor.T
|
|
x = x.reshape(final_shape)
|
|
return x
|
|
|
|
def multinomial(self, n, pvals, size = None):
|
|
if isinstance(size, int):
|
|
return self.multinomial(n, pvals, size=(size,))
|
|
|
|
on = asarray(n).astype(int, copy=False)
|
|
parr = asarray(pvals)
|
|
ndim: Static[int] = staticlen(parr.shape)
|
|
|
|
if ndim >= 1:
|
|
d = parr.shape[ndim - 1]
|
|
else:
|
|
d = 0
|
|
|
|
if d == 0:
|
|
raise ValueError(
|
|
"pvals must have at least 1 dimension and the last dimension "
|
|
"of pvals must be greater than 0."
|
|
)
|
|
|
|
check_array_constraint(parr, 'pvals', CONS_BOUNDED_0_1)
|
|
pix = parr.data
|
|
sz = parr.size
|
|
offset = 0
|
|
|
|
while offset < sz:
|
|
if kahan_sum(pix + offset, d - 1) > 1.0 + 1e-12:
|
|
slice_repr = "[:-1]" if ndim == 1 else "[...,:-1]"
|
|
raise ValueError(f"sum(pvals{slice_repr}) > 1.0")
|
|
offset += d
|
|
|
|
check_array_constraint(on, 'n', CONS_NON_NEGATIVE)
|
|
|
|
if staticlen(on.shape) != 0 or ndim > 1:
|
|
offsets = arange(0, parr.size, d, dtype=int).reshape(parr.shape[:ndim - 1])
|
|
if size is None:
|
|
it = broadcast_shapes(on.shape, offsets.shape)
|
|
else:
|
|
it = broadcast_shapes(on.shape, offsets.shape, size)
|
|
|
|
if not util.tuple_equal(it, size):
|
|
raise ValueError(
|
|
f"Output size {size} is not compatible with "
|
|
f"broadcast dimensions of inputs."
|
|
)
|
|
|
|
shape = it + (d,)
|
|
multin = zeros(shape, dtype=int)
|
|
mnix = multin.data
|
|
offset = 0
|
|
sz = util.count(it)
|
|
with self.lock:
|
|
for idx in util.multirange(shape):
|
|
ni = on._ptr(idx, broadcast=True)[0]
|
|
pi = offsets._ptr(idx, broadcast=True)[0]
|
|
self.bit_generator.random_multinomial(ni, mnix + offset, pix + pi, d)
|
|
offset += d
|
|
return multin
|
|
|
|
if size is None:
|
|
shape = (d,)
|
|
else:
|
|
shape = size + (d,)
|
|
|
|
multin = zeros(shape, dtype=int)
|
|
mnix = multin.data
|
|
sz = multin.size
|
|
ni = on.data[0]
|
|
check_array_constraint(ni, 'n', CONS_NON_NEGATIVE)
|
|
offset = 0
|
|
with self.lock:
|
|
for i in range(sz // d):
|
|
self.bit_generator.random_multinomial(ni, mnix + offset, pix, d)
|
|
offset += d
|
|
|
|
return multin
|
|
|
|
def multivariate_hypergeometric(self, colors, nsample: int, size = None, method: str = 'marginals'):
|
|
def safe_sum_nonneg_int64(num_colors: int, colors: Ptr[int]):
|
|
sum = 0
|
|
for i in range(num_colors):
|
|
if colors[i] > MAX_INT - sum:
|
|
return -1
|
|
sum += colors[i]
|
|
return sum
|
|
|
|
if isinstance(size, int):
|
|
return self.multivariate_hypergeometric(colors, nsample, size=(size,), method=method)
|
|
|
|
if method != 'count' and method != 'marginals':
|
|
raise ValueError('method must be "count" or "marginals"')
|
|
|
|
if nsample < 0:
|
|
raise ValueError("nsample must be nonnegative.")
|
|
nsamp = nsample
|
|
|
|
invalid_colors = False
|
|
colors = asarray(colors)
|
|
if staticlen(colors.shape) != 1:
|
|
compile_error("colors must be one-dimensional")
|
|
|
|
if colors.size == 0 or any(c < 0 for c in colors):
|
|
raise ValueError("colors must be a one-dimensional "
|
|
"sequence of nonnegative integers")
|
|
|
|
colors = ascontiguousarray(colors, dtype=int)
|
|
num_colors = colors.size
|
|
colors_ptr = colors.data
|
|
|
|
total = safe_sum_nonneg_int64(num_colors, colors_ptr)
|
|
if total == -1:
|
|
raise ValueError("sum(colors) must not exceed the maximum value "
|
|
"of a 64 bit signed integer")
|
|
|
|
if method == 'marginals':
|
|
if total >= 1000000000:
|
|
raise ValueError('When method is "marginals", sum(colors) must '
|
|
'be less than 1000000000.')
|
|
|
|
max_index = MAXSIZE // util.sizeof(int)
|
|
if method == 'count' and total > max_index:
|
|
raise ValueError(f"When method is 'count', sum(colors) must not "
|
|
f"exceed {max_index}")
|
|
if nsamp > total:
|
|
raise ValueError("nsample > sum(colors)")
|
|
|
|
if size is None:
|
|
shape = (num_colors,)
|
|
else:
|
|
shape = size + (num_colors,)
|
|
|
|
variates = zeros(shape, dtype=int)
|
|
|
|
if num_colors == 0:
|
|
return variates
|
|
|
|
num_variates = variates.size // num_colors
|
|
variates_ptr = variates.data
|
|
|
|
if method == 'count':
|
|
with self.lock:
|
|
self.bit_generator.random_multivariate_hypergeometric_count(
|
|
total, num_colors, colors_ptr, nsamp, num_variates, variates_ptr)
|
|
else:
|
|
with self.lock:
|
|
self.bit_generator.random_multivariate_hypergeometric_marginals(
|
|
total, num_colors, colors_ptr, nsamp, num_variates, variates_ptr)
|
|
|
|
return variates
|
|
|
|
def dirichlet(self, alpha, size = None):
|
|
if isinstance(size, int):
|
|
return self.dirichlet(alpha, size=(size,))
|
|
|
|
alpha = ascontiguousarray(asarray(alpha), dtype=float)
|
|
if staticlen(alpha.shape) != 1:
|
|
compile_error("alpha must be a one-dimensional sequence of floats")
|
|
k = len(alpha)
|
|
|
|
m = alpha[0] if k > 0 else 0.0
|
|
for a in alpha:
|
|
if a < 0.0:
|
|
raise ValueError("alpha < 0")
|
|
if a > m:
|
|
m = a
|
|
|
|
alpha_data = alpha.data
|
|
|
|
if size is None:
|
|
shape = (k,)
|
|
else:
|
|
shape = size + (k,)
|
|
|
|
diric = zeros(shape, dtype=float)
|
|
val_data = diric.data
|
|
|
|
i = 0
|
|
totsize = diric.size
|
|
|
|
if k > 0 and m < 0.1:
|
|
alpha_csum_arr = empty_like(alpha)
|
|
alpha_csum_data = alpha_csum_arr.data
|
|
csum = 0.0
|
|
for j in range(k - 1, -1, -1):
|
|
csum += alpha_data[j]
|
|
alpha_csum_data[j] = csum
|
|
|
|
with self.lock:
|
|
while i < totsize:
|
|
acc = 1.
|
|
for j in range(k - 1):
|
|
v = self.bit_generator.random_beta(alpha_data[j], alpha_csum_data[j + 1])
|
|
val_data[i + j] = acc * v
|
|
acc *= (1. - v)
|
|
val_data[i + k - 1] = acc
|
|
i = i + k
|
|
else:
|
|
with self.lock:
|
|
while i < totsize:
|
|
acc = 0.
|
|
for j in range(k):
|
|
val_data[i + j] = self.bit_generator.random_standard_gamma(alpha_data[j])
|
|
acc = acc + val_data[i + j]
|
|
invacc = 1. / acc
|
|
for j in range(k):
|
|
val_data[i + j] = val_data[i + j] * invacc
|
|
i = i + k
|
|
|
|
return diric
|
|
|
|
def permuted(self, x, axis = None, out = None):
|
|
x = asarray(x)
|
|
|
|
if out is None:
|
|
return self.permuted(x, axis=axis, out=x.copy(order='K'))
|
|
else:
|
|
if not isinstance(out, ndarray):
|
|
compile_error("out must be a numpy array")
|
|
if not util.tuple_equal(out.shape, x.shape):
|
|
raise ValueError("out must have the same shape as x")
|
|
copyto(out, x)
|
|
|
|
if axis is None:
|
|
if staticlen(x.shape) > 1:
|
|
cc, fc = out._contig
|
|
if not (cc or fc):
|
|
to_shuffle = array(out, order='C')
|
|
self.shuffle(to_shuffle.ravel(order='K'))
|
|
copyto(out, to_shuffle)
|
|
else:
|
|
self.shuffle(out.ravel(order='A'))
|
|
else:
|
|
self.shuffle(out)
|
|
return out
|
|
|
|
ax = util.normalize_axis_index(axis, out.ndim)
|
|
itemsize = out.itemsize
|
|
axlen = out.shape[ax]
|
|
axstride = out.strides[ax]
|
|
|
|
shape = out.shape
|
|
buf = cobj(itemsize)
|
|
|
|
with self.lock:
|
|
for idx0 in util.multirange(util.tuple_delete(shape, ax)):
|
|
idx = util.tuple_insert(idx0, ax, 0)
|
|
data = out._ptr(idx).as_byte()
|
|
self.bit_generator.shuffle_raw_wrap(axlen, 0, itemsize, axstride, data, buf)
|
|
|
|
return out
|
|
|
|
def shuffle(self, x, axis: int = 0):
|
|
n = len(x)
|
|
|
|
if isinstance(x, ndarray):
|
|
ndim: Static[int] = staticlen(x.shape)
|
|
axis = util.normalize_axis_index(axis, ndim)
|
|
if x.size <= 1:
|
|
return
|
|
|
|
if ndim == 1:
|
|
x_ptr = x.data.as_byte()
|
|
stride = x.strides[0]
|
|
itemsize = x.itemsize
|
|
buf_ptr = cobj(itemsize)
|
|
with self.lock:
|
|
self.bit_generator.shuffle_raw_wrap(n, 1, itemsize, stride, x_ptr, buf_ptr)
|
|
else:
|
|
x = swapaxes(x, 0, axis)
|
|
buf = empty_like(x[0, ...])
|
|
|
|
with self.lock:
|
|
for i in range(len(x) - 1, 0, -1):
|
|
j = int(self.bit_generator.random_interval(u64(i)))
|
|
if i == j:
|
|
continue
|
|
buf[...] = x[j, ...]
|
|
x[j, ...] = x[i, ...]
|
|
x[i, ...] = buf
|
|
else:
|
|
if axis != 0:
|
|
raise ValueError("Axis argument is only supported on ndarray objects")
|
|
|
|
with self.lock:
|
|
for i in range(len(x) - 1, 0, -1):
|
|
j = int(self.bit_generator.random_interval(u64(i)))
|
|
x[i], x[j] = x[j], x[i]
|
|
|
|
def permutation(self, x, axis: int = 0):
|
|
if isinstance(x, int):
|
|
arr = arange(x)
|
|
self.shuffle(arr)
|
|
return arr
|
|
|
|
x = asarray(x)
|
|
arr = x.copy()
|
|
ndim: Static[int] = staticlen(arr.shape)
|
|
axis = util.normalize_axis_index(axis, ndim)
|
|
|
|
if ndim == 1:
|
|
self.shuffle(arr)
|
|
return arr
|
|
|
|
shape = x.shape
|
|
axlen = shape[axis]
|
|
perm = arange(arr.shape[axis], dtype=int)
|
|
self.shuffle(perm)
|
|
|
|
with self.lock:
|
|
for ai in range(axlen):
|
|
src_on_axis = perm[ai]
|
|
for idx0 in util.multirange(util.tuple_delete(shape, axis)):
|
|
src_idx = util.tuple_insert(idx0, axis, src_on_axis)
|
|
dst_idx = util.tuple_insert(idx0, axis, ai)
|
|
p = x._ptr(src_idx)
|
|
q = arr._ptr(dst_idx)
|
|
q[0] = p[0]
|
|
|
|
return arr
|