This commit is contained in:
2026-04-10 15:06:59 +02:00
parent 3031b7153b
commit e5a4711004
7806 changed files with 1918528 additions and 335 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,740 @@
"""
Algorithmic implementations for generating different types
of random distributions.
"""
import numpy as np
from numba.core.extending import register_jitable
from numba.np.random._constants import (wi_double, ki_double,
ziggurat_nor_r, fi_double,
wi_float, ki_float,
ziggurat_nor_inv_r_f,
ziggurat_nor_r_f, fi_float,
we_double, ke_double,
ziggurat_exp_r, fe_double,
we_float, ke_float,
ziggurat_exp_r_f, fe_float,
INT64_MAX, ziggurat_nor_inv_r)
from numba.np.random.generator_core import (next_double, next_float,
next_uint32, next_uint64)
from numba import float32, int64
from numba.np.numpy_support import numpy_version
# All of the following implementations are direct translations from:
# https://github.com/numpy/numpy/blob/7cfef93c77599bd387ecc6a15d186c5a46024dac/numpy/random/src/distributions/distributions.c
@register_jitable
def np_log1p(x):
return np.log1p(x)
@register_jitable
def np_log1pf(x):
return np.log1p(float32(x))
@register_jitable
def random_rayleigh(bitgen, mode):
return mode * np.sqrt(2.0 * random_standard_exponential(bitgen))
@register_jitable
def np_expm1(x):
return np.expm1(x)
@register_jitable
def random_standard_normal(bitgen):
while 1:
r = next_uint64(bitgen)
idx = r & 0xff
r >>= 8
sign = r & 0x1
rabs = (r >> 1) & 0x000fffffffffffff
x = rabs * wi_double[idx]
if (sign & 0x1):
x = -x
if rabs < ki_double[idx]:
return x
if idx == 0:
while 1:
xx = -ziggurat_nor_inv_r * np.log1p(-next_double(bitgen))
yy = -np.log1p(-next_double(bitgen))
if (yy + yy > xx * xx):
if ((rabs >> 8) & 0x1):
return -(ziggurat_nor_r + xx)
else:
return ziggurat_nor_r + xx
else:
if (((fi_double[idx - 1] - fi_double[idx]) *
next_double(bitgen) + fi_double[idx]) <
np.exp(-0.5 * x * x)):
return x
@register_jitable
def random_standard_normal_f(bitgen):
while 1:
r = next_uint32(bitgen)
idx = r & 0xff
sign = (r >> 8) & 0x1
rabs = (r >> 9) & 0x0007fffff
x = float32(float32(rabs) * wi_float[idx])
if (sign & 0x1):
x = -x
if (rabs < ki_float[idx]):
return x
if (idx == 0):
while 1:
xx = float32(-ziggurat_nor_inv_r_f *
np_log1pf(-next_float(bitgen)))
yy = float32(-np_log1pf(-next_float(bitgen)))
if (float32(yy + yy) > float32(xx * xx)):
if ((rabs >> 8) & 0x1):
return -float32(ziggurat_nor_r_f + xx)
else:
return float32(ziggurat_nor_r_f + xx)
else:
if (((fi_float[idx - 1] - fi_float[idx]) * next_float(bitgen) +
fi_float[idx]) < float32(np.exp(-float32(0.5) * x * x))):
return x
@register_jitable
def random_standard_exponential(bitgen):
while 1:
ri = next_uint64(bitgen)
ri >>= 3
idx = ri & 0xFF
ri >>= 8
x = ri * we_double[idx]
if (ri < ke_double[idx]):
return x
else:
if idx == 0:
return ziggurat_exp_r - np_log1p(-next_double(bitgen))
elif ((fe_double[idx - 1] - fe_double[idx]) * next_double(bitgen) +
fe_double[idx] < np.exp(-x)):
return x
@register_jitable
def random_standard_exponential_f(bitgen):
while 1:
ri = next_uint32(bitgen)
ri >>= 1
idx = ri & 0xFF
ri >>= 8
x = float32(float32(ri) * we_float[idx])
if (ri < ke_float[idx]):
return x
else:
if (idx == 0):
return float32(ziggurat_exp_r_f -
float32(np_log1pf(-next_float(bitgen))))
elif ((fe_float[idx - 1] - fe_float[idx]) * next_float(bitgen) +
fe_float[idx] < float32(np.exp(float32(-x)))):
return x
@register_jitable
def random_standard_exponential_inv(bitgen):
return -np_log1p(-next_double(bitgen))
@register_jitable
def random_standard_exponential_inv_f(bitgen):
return -np.log(float32(1.0) - next_float(bitgen))
@register_jitable
def random_standard_gamma(bitgen, shape):
if (shape == 1.0):
return random_standard_exponential(bitgen)
elif (shape == 0.0):
return 0.0
elif (shape < 1.0):
while 1:
U = next_double(bitgen)
V = random_standard_exponential(bitgen)
if (U <= 1.0 - shape):
X = pow(U, 1. / shape)
if (X <= V):
return X
else:
Y = -np.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. / np.sqrt(9 * b)
while 1:
while 1:
X = random_standard_normal(bitgen)
V = 1.0 + c * X
if (V > 0.0):
break
V = V * V * V
U = next_double(bitgen)
if (U < 1.0 - 0.0331 * (X * X) * (X * X)):
return (b * V)
if (np.log(U) < 0.5 * X * X + b * (1. - V + np.log(V))):
return (b * V)
@register_jitable
def random_standard_gamma_f(bitgen, shape):
f32_one = float32(1.0)
shape = float32(shape)
if (shape == f32_one):
return random_standard_exponential_f(bitgen)
elif (shape == float32(0.0)):
return float32(0.0)
elif (shape < f32_one):
while 1:
U = next_float(bitgen)
V = random_standard_exponential_f(bitgen)
if (U <= f32_one - shape):
X = float32(pow(U, float32(f32_one / shape)))
if (X <= V):
return X
else:
Y = float32(-np.log(float32((f32_one - U) / shape)))
X = float32(pow(f32_one - shape + float32(shape * Y),
float32(f32_one / shape)))
if (X <= (V + Y)):
return X
else:
b = shape - f32_one / float32(3.0)
c = float32(f32_one / float32(np.sqrt(float32(9.0) * b)))
while 1:
while 1:
X = float32(random_standard_normal_f(bitgen))
V = float32(f32_one + c * X)
if (V > float32(0.0)):
break
V = float32(V * V * V)
U = next_float(bitgen)
if (U < f32_one - float32(0.0331) * (X * X) * (X * X)):
return float32(b * V)
if (np.log(U) < float32(0.5) * X * X + b *
(f32_one - V + np.log(V))):
return float32(b * V)
@register_jitable
def random_normal(bitgen, loc, scale):
scaled_normal = scale * random_standard_normal(bitgen)
return loc + scaled_normal
@register_jitable
def random_normal_f(bitgen, loc, scale):
scaled_normal = float32(scale * random_standard_normal_f(bitgen))
return float32(loc + scaled_normal)
@register_jitable
def random_exponential(bitgen, scale):
return scale * random_standard_exponential(bitgen)
@register_jitable
def random_uniform(bitgen, lower, range):
scaled_uniform = range * next_double(bitgen)
return lower + scaled_uniform
@register_jitable
def random_gamma(bitgen, shape, scale):
return scale * random_standard_gamma(bitgen, shape)
@register_jitable
def random_gamma_f(bitgen, shape, scale):
return float32(scale * random_standard_gamma_f(bitgen, shape))
@register_jitable
def random_beta(bitgen, a, b):
if a <= 1.0 and b <= 1.0:
while 1:
U = next_double(bitgen)
V = next_double(bitgen)
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 = np.log(U) / a
logY = np.log(V) / b
logM = min(logX, logY)
logX -= logM
logY -= logM
return np.exp(logX - np.log(np.exp(logX) + np.exp(logY)))
else:
Ga = random_standard_gamma(bitgen, a)
Gb = random_standard_gamma(bitgen, b)
return Ga / (Ga + Gb)
@register_jitable
def random_chisquare(bitgen, df):
return 2.0 * random_standard_gamma(bitgen, df / 2.0)
@register_jitable
def random_f(bitgen, dfnum, dfden):
return ((random_chisquare(bitgen, dfnum) * dfden) /
(random_chisquare(bitgen, dfden) * dfnum))
@register_jitable
def random_standard_cauchy(bitgen):
return random_standard_normal(bitgen) / random_standard_normal(bitgen)
@register_jitable
def random_pareto(bitgen, a):
return np_expm1(random_standard_exponential(bitgen) / a)
@register_jitable
def random_weibull(bitgen, a):
if (a == 0.0):
return 0.0
return pow(random_standard_exponential(bitgen), 1. / a)
@register_jitable
def random_power(bitgen, a):
return pow(-np_expm1(-random_standard_exponential(bitgen)), 1. / a)
@register_jitable
def random_laplace(bitgen, loc, scale):
U = next_double(bitgen)
while U <= 0:
U = next_double(bitgen)
if (U >= 0.5):
U = loc - scale * np.log(2.0 - U - U)
elif (U > 0.0):
U = loc + scale * np.log(U + U)
return U
@register_jitable
def random_logistic(bitgen, loc, scale):
U = next_double(bitgen)
while U <= 0.0:
U = next_double(bitgen)
return loc + scale * np.log(U / (1.0 - U))
@register_jitable
def random_lognormal(bitgen, mean, sigma):
return np.exp(random_normal(bitgen, mean, sigma))
@register_jitable
def random_standard_t(bitgen, df):
num = random_standard_normal(bitgen)
denom = random_standard_gamma(bitgen, df / 2)
return np.sqrt(df / 2) * num / np.sqrt(denom)
@register_jitable
def random_wald(bitgen, mean, scale):
mu_2l = mean / (2 * scale)
Y = random_standard_normal(bitgen)
Y = mean * Y * Y
X = mean + mu_2l * (Y - np.sqrt(4 * scale * Y + Y * Y))
U = next_double(bitgen)
if (U <= mean / (mean + X)):
return X
else:
return mean * mean / X
@register_jitable
def random_geometric_search(bitgen, p):
X = 1
sum = prod = p
q = 1.0 - p
U = next_double(bitgen)
while (U > sum):
prod *= q
sum += prod
X = X + 1
return X
@register_jitable
def random_geometric_inversion(bitgen, p):
return np.ceil(-random_standard_exponential(bitgen) / np.log1p(-p))
@register_jitable
def random_geometric(bitgen, p):
if (p >= 0.333333333333333333333333):
return random_geometric_search(bitgen, p)
else:
return random_geometric_inversion(bitgen, p)
if numpy_version < (2, 1):
@register_jitable
def random_zipf(bitgen, a):
am1 = a - 1.0
b = pow(2.0, am1)
while 1:
U = 1.0 - next_double(bitgen)
V = next_double(bitgen)
X = np.floor(pow(U, -1.0 / am1))
if (X > INT64_MAX 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 X
else:
@register_jitable
def random_zipf(bitgen, a):
am1 = a - 1.0
b = pow(2.0, am1)
Umin = pow(INT64_MAX, -am1)
while 1:
U01 = next_double(bitgen)
U = U01 * Umin + (1 - U01)
V = next_double(bitgen)
X = np.floor(pow(U, -1.0 / am1))
if (X > INT64_MAX 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 X
@register_jitable
def random_triangular(bitgen, left, mode,
right):
base = right - left
leftbase = mode - left
ratio = leftbase / base
leftprod = leftbase * base
rightprod = (right - mode) * base
U = next_double(bitgen)
if (U <= ratio):
return left + np.sqrt(U * leftprod)
else:
return right - np.sqrt((1.0 - U) * rightprod)
@register_jitable
def random_loggam(x):
a = [8.333333333333333e-02, -2.777777777777778e-03,
7.936507936507937e-04, -5.952380952380952e-04,
8.417508417508418e-04, -1.917526917526918e-03,
6.410256410256410e-03, -2.955065359477124e-02,
1.796443723688307e-01, -1.39243221690590e+00]
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)
# /* log(2 * M_PI) */
lg2pi = 1.8378770664093453e+00
gl0 = a[9]
for k in range(0, 9):
gl0 *= x2
gl0 += a[8 - k]
gl = gl0 / x0 + 0.5 * lg2pi + (x0 - 0.5) * np.log(x0) - x0
if (x < 7.0):
for k in range(1, n + 1):
gl = gl - np.log(x0 - 1.0)
x0 = x0 - 1.0
return gl
@register_jitable
def random_poisson_mult(bitgen, lam):
enlam = np.exp(-lam)
X = 0
prod = 1.0
while (1):
U = next_double(bitgen)
prod *= U
if (prod > enlam):
X += 1
else:
return X
@register_jitable
def random_poisson_ptrs(bitgen, lam):
slam = np.sqrt(lam)
loglam = np.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 (1):
U = next_double(bitgen) - 0.5
V = next_double(bitgen)
us = 0.5 - np.fabs(U)
k = int((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
# /* log(V) == log(0.0) ok here */
# /* if U==0.0 so that us==0.0, log is ok since always returns */
if ((np.log(V) + np.log(invalpha) - np.log(a / (us * us) + b)) <=
(-lam + k * loglam - random_loggam(k + 1))):
return k
@register_jitable
def random_poisson(bitgen, lam):
if (lam >= 10):
return random_poisson_ptrs(bitgen, lam)
elif (lam == 0):
return 0
else:
return random_poisson_mult(bitgen, lam)
@register_jitable
def random_negative_binomial(bitgen, n, p):
Y = random_gamma(bitgen, n, (1 - p) / p)
return random_poisson(bitgen, Y)
@register_jitable
def random_noncentral_chisquare(bitgen, df, nonc):
if np.isnan(nonc):
return np.nan
if nonc == 0:
return random_chisquare(bitgen, df)
if 1 < df:
Chi2 = random_chisquare(bitgen, df - 1)
n = random_standard_normal(bitgen) + np.sqrt(nonc)
return Chi2 + n * n
else:
i = random_poisson(bitgen, nonc / 2.0)
return random_chisquare(bitgen, df + 2 * i)
@register_jitable
def random_noncentral_f(bitgen, dfnum, dfden, nonc):
t = random_noncentral_chisquare(bitgen, dfnum, nonc) * dfden
return t / (random_chisquare(bitgen, dfden) * dfnum)
@register_jitable
def random_logseries(bitgen, p):
r = np_log1p(-p)
while 1:
V = next_double(bitgen)
if (V >= p):
return 1
U = next_double(bitgen)
q = -np.expm1(r * U)
if (V <= q * q):
result = int64(np.floor(1 + np.log(V) / np.log(q)))
if result < 1 or V == 0.0:
continue
else:
return result
if (V >= q):
return 1
else:
return 2
@register_jitable
def random_binomial_btpe(bitgen, n, p):
r = min(p, 1.0 - p)
q = 1.0 - r
fm = n * r + r
m = int(np.floor(fm))
p1 = np.floor(2.195 * np.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)
a = (fm - xl) / (fm - xl * r)
laml = a * (1.0 + a / 2.0)
a = (xr - fm) / (xr * q)
lamr = a * (1.0 + a / 2.0)
p2 = p1 * (1.0 + 2.0 * c)
p3 = p2 + c / laml
p4 = p3 + c / lamr
case = 10
y = k = 0
while 1:
if case == 10:
nrq = n * r * q
u = next_double(bitgen) * p4
v = next_double(bitgen)
if (u > p1):
case = 20
continue
y = int(np.floor(xm - p1 * v + u))
case = 60
continue
elif case == 20:
if (u > p2):
case = 30
continue
x = xl + (u - p1) / c
v = v * c + 1.0 - np.fabs(m - x + 0.5) / p1
if (v > 1.0):
case = 10
continue
y = int(np.floor(x))
case = 50
continue
elif case == 30:
if (u > p3):
case = 40
continue
y = int(np.floor(xl + np.log(v) / laml))
if ((y < 0) or (v == 0.0)):
case = 10
continue
v = v * (u - p2) * laml
case = 50
continue
elif case == 40:
y = int(np.floor(xr - np.log(v) / lamr))
if ((y > n) or (v == 0.0)):
case = 10
continue
v = v * (u - p3) * lamr
case = 50
continue
elif case == 50:
k = abs(y - m)
if ((k > 20) and (k < ((nrq) / 2.0 - 1))):
case = 52
continue
s = r / q
a = s * (n + 1)
F = 1.0
if (m < y):
for i in range(m + 1, y + 1):
F = F * (a / i - s)
elif (m > y):
for i in range(y + 1, m + 1):
F = F / (a / i - s)
if (v > F):
case = 10
continue
case = 60
continue
elif case == 52:
rho = (k / (nrq)) * \
((k * (k / 3.0 + 0.625) + 0.16666666666666666) /
nrq + 0.5)
t = -k * k / (2 * nrq)
A = np.log(v)
if (A < (t - rho)):
case = 60
continue
if (A > (t + rho)):
case = 10
continue
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
if (A > (xm * np.log(f1 / x1) + (n - m + 0.5) * np.log(z / w) +
(y - m) * np.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 / 66320.)):
case = 10
continue
case = 60
continue
elif case == 60:
if (p > 0.5):
y = n - y
return y
@register_jitable
def random_binomial_inversion(bitgen, n, p):
q = 1.0 - p
qn = np.exp(n * np.log(q))
_np = n * p
bound = min(n, _np + 10.0 * np.sqrt(_np * q + 1))
X = 0
px = qn
U = next_double(bitgen)
while (U > px):
X = X + 1
if (X > bound):
X = 0
px = qn
U = next_double(bitgen)
else:
U -= px
px = ((n - X + 1) * p * px) / (X * q)
return X
@register_jitable
def random_binomial(bitgen, n, p):
if ((n == 0) or (p == 0.0)):
return 0
if (p <= 0.5):
if (p * n <= 30.0):
return random_binomial_inversion(bitgen, n, p)
else:
return random_binomial_btpe(bitgen, n, p)
else:
q = 1.0 - p
if (q * n <= 30.0):
return n - random_binomial_inversion(bitgen, n, q)
else:
return n - random_binomial_btpe(bitgen, n, q)

View File

@@ -0,0 +1,120 @@
"""
Core Implementations for Generator/BitGenerator Models.
"""
from llvmlite import ir
from numba.core import cgutils, types
from numba.core.extending import (intrinsic, make_attribute_wrapper, models,
overload, register_jitable,
register_model)
@register_model(types.NumPyRandomBitGeneratorType)
class NumPyRngBitGeneratorModel(models.StructModel):
def __init__(self, dmm, fe_type):
members = [
('parent', types.pyobject),
('state_address', types.uintp),
('state', types.uintp),
('fnptr_next_uint64', types.uintp),
('fnptr_next_uint32', types.uintp),
('fnptr_next_double', types.uintp),
('bit_generator', types.uintp),
]
super(NumPyRngBitGeneratorModel, self).__init__(dmm, fe_type, members)
_bit_gen_type = types.NumPyRandomBitGeneratorType('bit_generator')
@register_model(types.NumPyRandomGeneratorType)
class NumPyRandomGeneratorTypeModel(models.StructModel):
def __init__(self, dmm, fe_type):
members = [
('bit_generator', _bit_gen_type),
('meminfo', types.MemInfoPointer(types.voidptr)),
('parent', types.pyobject)
]
super(
NumPyRandomGeneratorTypeModel,
self).__init__(
dmm,
fe_type,
members)
# The Generator instances have a bit_generator attr
make_attribute_wrapper(
types.NumPyRandomGeneratorType,
'bit_generator',
'bit_generator')
def _generate_next_binding(overloadable_function, return_type):
"""
Generate the overloads for "next_(some type)" functions.
"""
@intrinsic
def intrin_NumPyRandomBitGeneratorType_next_ty(tyctx, inst):
sig = return_type(inst)
def codegen(cgctx, builder, sig, llargs):
name = overloadable_function.__name__
struct_ptr = cgutils.create_struct_proxy(inst)(cgctx, builder,
value=llargs[0])
# Get the 'state' and 'fnptr_next_(type)' members of the struct
state = struct_ptr.state
next_double_addr = getattr(struct_ptr, f'fnptr_{name}')
# LLVM IR types needed
ll_void_ptr_t = cgctx.get_value_type(types.voidptr)
ll_return_t = cgctx.get_value_type(return_type)
ll_uintp_t = cgctx.get_value_type(types.uintp)
# Convert the stored Generator function address to a pointer
next_fn_fnptr = builder.inttoptr(
next_double_addr, ll_void_ptr_t)
# Add the function to the module
fnty = ir.FunctionType(ll_return_t, (ll_uintp_t,))
next_fn = cgutils.get_or_insert_function(
builder.module, fnty, name)
# Bit cast the function pointer to the function type
fnptr_as_fntype = builder.bitcast(next_fn_fnptr, next_fn.type)
# call it with the "state" address as the arg
ret = builder.call(fnptr_as_fntype, (state,))
return ret
return sig, codegen
@overload(overloadable_function)
def ol_next_ty(bitgen):
if isinstance(bitgen, types.NumPyRandomBitGeneratorType):
def impl(bitgen):
return intrin_NumPyRandomBitGeneratorType_next_ty(bitgen)
return impl
# Some function stubs for "next(some type)", these will be overloaded
def next_double(bitgen):
return bitgen.ctypes.next_double(bitgen.ctypes.state)
def next_uint32(bitgen):
return bitgen.ctypes.next_uint32(bitgen.ctypes.state)
def next_uint64(bitgen):
return bitgen.ctypes.next_uint64(bitgen.ctypes.state)
_generate_next_binding(next_double, types.double)
_generate_next_binding(next_uint32, types.uint32)
_generate_next_binding(next_uint64, types.uint64)
# See: https://github.com/numpy/numpy/pull/20314
@register_jitable
def next_float(bitgen):
return types.float32(types.float32(next_uint32(bitgen) >> 8)
* types.float32(1.0)
/ types.float32(16777216.0))

View File

@@ -0,0 +1,971 @@
"""
Implementation of method overloads for Generator objects.
"""
import numpy as np
from numba.core import types
from numba.core.extending import overload_method, register_jitable
from numba.np.numpy_support import as_dtype, from_dtype
from numba.np.random.generator_core import next_float, next_double
from numba.np.numpy_support import is_nonelike
from numba.core.errors import TypingError
from numba.core.types.containers import Tuple, UniTuple
from numba.np.random.distributions import \
(random_standard_exponential_inv_f, random_standard_exponential_inv,
random_standard_exponential, random_standard_normal_f,
random_standard_gamma, random_standard_normal, random_uniform,
random_standard_exponential_f, random_standard_gamma_f, random_normal,
random_exponential, random_gamma, random_beta, random_power,
random_f,random_chisquare,random_standard_cauchy,random_pareto,
random_weibull, random_laplace, random_logistic,
random_lognormal, random_rayleigh, random_standard_t, random_wald,
random_geometric, random_zipf, random_triangular,
random_poisson, random_negative_binomial, random_logseries,
random_noncentral_chisquare, random_noncentral_f, random_binomial)
from numba.np.random import random_methods
def _get_proper_func(func_32, func_64, dtype, dist_name="the given"):
"""
Most of the standard NumPy distributions that accept dtype argument
only support either np.float32 or np.float64 as dtypes.
This is a helper function that helps Numba select the proper underlying
implementation according to provided dtype.
"""
if isinstance(dtype, types.Omitted):
dtype = dtype.value
np_dt = dtype
if isinstance(dtype, type):
nb_dt = from_dtype(np.dtype(dtype))
elif isinstance(dtype, types.NumberClass):
nb_dt = dtype
np_dt = as_dtype(nb_dt)
if np_dt not in [np.float32, np.float64]:
raise TypingError("Argument dtype is not one of the" +
" expected type(s): " +
" np.float32 or np.float64")
if np_dt == np.float32:
next_func = func_32
else:
next_func = func_64
return next_func, nb_dt
def check_size(size):
if not any([isinstance(size, UniTuple) and
isinstance(size.dtype, types.Integer),
isinstance(size, Tuple) and size.count == 0,
isinstance(size, types.Integer)]):
raise TypingError("Argument size is not one of the" +
" expected type(s): " +
" an integer, an empty tuple or a tuple of integers")
def check_types(obj, type_list, arg_name):
"""
Check if given object is one of the provided types.
If not raises an TypeError
"""
if isinstance(obj, types.Omitted):
obj = obj.value
if not isinstance(type_list, (list, tuple)):
type_list = [type_list]
if not any([isinstance(obj, _type) for _type in type_list]):
raise TypingError(f"Argument {arg_name} is not one of the" +
f" expected type(s): {type_list}")
# Overload the Generator().integers()
@overload_method(types.NumPyRandomGeneratorType, 'integers')
def NumPyRandomGeneratorType_integers(inst, low, high, size=None,
dtype=np.int64, endpoint=False):
check_types(low, [types.Integer,
types.Boolean, bool, int], 'low')
check_types(high, [types.Integer, types.Boolean,
bool, int], 'high')
check_types(endpoint, [types.Boolean, bool], 'endpoint')
if isinstance(size, types.Omitted):
size = size.value
if isinstance(dtype, types.Omitted):
dtype = dtype.value
if isinstance(dtype, type):
nb_dt = from_dtype(np.dtype(dtype))
_dtype = dtype
elif isinstance(dtype, types.NumberClass):
nb_dt = dtype
_dtype = as_dtype(nb_dt)
else:
raise TypingError("Argument dtype is not one of the" +
" expected type(s): " +
"np.int32, np.int64, np.int16, np.int8, "
"np.uint32, np.uint64, np.uint16, np.uint8, "
"np.bool_")
if _dtype == np.bool_:
int_func = random_methods.random_bounded_bool_fill
lower_bound = -1
upper_bound = 2
else:
try:
i_info = np.iinfo(_dtype)
except ValueError:
raise TypingError("Argument dtype is not one of the" +
" expected type(s): " +
"np.int32, np.int64, np.int16, np.int8, "
"np.uint32, np.uint64, np.uint16, np.uint8, "
"np.bool_")
int_func = getattr(random_methods,
f'random_bounded_uint{i_info.bits}_fill')
lower_bound = i_info.min
upper_bound = i_info.max
if is_nonelike(size):
def impl(inst, low, high, size=None,
dtype=np.int64, endpoint=False):
random_methods._randint_arg_check(low, high, endpoint,
lower_bound, upper_bound)
if not endpoint:
high -= dtype(1)
low = dtype(low)
high = dtype(high)
rng = high - low
return int_func(inst.bit_generator, low, rng, 1, dtype)[0]
else:
low = dtype(low)
high = dtype(high)
rng = high - low
return int_func(inst.bit_generator, low, rng, 1, dtype)[0]
return impl
else:
check_size(size)
def impl(inst, low, high, size=None,
dtype=np.int64, endpoint=False):
random_methods._randint_arg_check(low, high, endpoint,
lower_bound, upper_bound)
if not endpoint:
high -= dtype(1)
low = dtype(low)
high = dtype(high)
rng = high - low
return int_func(inst.bit_generator, low, rng, size, dtype)
else:
low = dtype(low)
high = dtype(high)
rng = high - low
return int_func(inst.bit_generator, low, rng, size, dtype)
return impl
# The following `shuffle` implementation is a direct translation from:
# https://github.com/numpy/numpy/blob/95e3e7f445407e4f355b23d6a9991d8774f0eb0c/numpy/random/_generator.pyx#L4578
# Overload the Generator().shuffle()
@overload_method(types.NumPyRandomGeneratorType, 'shuffle')
def NumPyRandomGeneratorType_shuffle(inst, x, axis=0):
check_types(x, [types.Array], 'x')
check_types(axis, [int, types.Integer], 'axis')
def impl(inst, x, axis=0):
if axis < 0:
axis = axis + x.ndim
if axis > x.ndim - 1 or axis < 0:
raise IndexError("Axis is out of bounds for the given array")
z = np.swapaxes(x, 0, axis)
buf = np.empty_like(z[0, ...])
for i in range(len(z) - 1, 0, -1):
j = types.intp(random_methods.random_interval(inst.bit_generator,
i))
if i == j:
continue
buf[...] = z[j, ...]
z[j, ...] = z[i, ...]
z[i, ...] = buf
return impl
# The following `permutation` implementation is a direct translation from:
# https://github.com/numpy/numpy/blob/95e3e7f445407e4f355b23d6a9991d8774f0eb0c/numpy/random/_generator.pyx#L4710
# Overload the Generator().permutation()
@overload_method(types.NumPyRandomGeneratorType, 'permutation')
def NumPyRandomGeneratorType_permutation(inst, x, axis=0):
check_types(x, [types.Array, types.Integer], 'x')
check_types(axis, [int, types.Integer], 'axis')
IS_INT = isinstance(x, types.Integer)
def impl(inst, x, axis=0):
if IS_INT:
new_arr = np.arange(x)
# NumPy ignores the axis argument when x is an integer
inst.shuffle(new_arr)
else:
new_arr = x.copy()
inst.shuffle(new_arr, axis=axis)
return new_arr
return impl
# Overload the Generator().random()
@overload_method(types.NumPyRandomGeneratorType, 'random')
def NumPyRandomGeneratorType_random(inst, size=None, dtype=np.float64):
dist_func, nb_dt = _get_proper_func(next_float, next_double,
dtype, "random")
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, size=None, dtype=np.float64):
return nb_dt(dist_func(inst.bit_generator))
return impl
else:
check_size(size)
def impl(inst, size=None, dtype=np.float64):
out = np.empty(size, dtype=dtype)
out_f = out.flat
for i in range(out.size):
out_f[i] = dist_func(inst.bit_generator)
return out
return impl
# Overload the Generator().standard_exponential() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_exponential')
def NumPyRandomGeneratorType_standard_exponential(inst, size=None,
dtype=np.float64,
method='zig'):
check_types(method, [types.UnicodeType, str], 'method')
dist_func_inv, nb_dt = _get_proper_func(
random_standard_exponential_inv_f,
random_standard_exponential_inv,
dtype
)
dist_func, nb_dt = _get_proper_func(random_standard_exponential_f,
random_standard_exponential,
dtype)
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, size=None, dtype=np.float64, method='zig'):
if method == 'zig':
return nb_dt(dist_func(inst.bit_generator))
elif method == 'inv':
return nb_dt(dist_func_inv(inst.bit_generator))
else:
raise ValueError("Method must be either 'zig' or 'inv'")
return impl
else:
check_size(size)
def impl(inst, size=None, dtype=np.float64, method='zig'):
out = np.empty(size, dtype=dtype)
out_f = out.flat
if method == 'zig':
for i in range(out.size):
out_f[i] = dist_func(inst.bit_generator)
elif method == 'inv':
for i in range(out.size):
out_f[i] = dist_func_inv(inst.bit_generator)
else:
raise ValueError("Method must be either 'zig' or 'inv'")
return out
return impl
# Overload the Generator().standard_normal() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_normal')
def NumPyRandomGeneratorType_standard_normal(inst, size=None, dtype=np.float64):
dist_func, nb_dt = _get_proper_func(random_standard_normal_f,
random_standard_normal,
dtype)
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, size=None, dtype=np.float64):
return nb_dt(dist_func(inst.bit_generator))
return impl
else:
check_size(size)
def impl(inst, size=None, dtype=np.float64):
out = np.empty(size, dtype=dtype)
out_f = out.flat
for i in range(out.size):
out_f[i] = dist_func(inst.bit_generator)
return out
return impl
# Overload the Generator().standard_gamma() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_gamma')
def NumPyRandomGeneratorType_standard_gamma(inst, shape, size=None,
dtype=np.float64):
check_types(shape, [types.Float, types.Integer, int, float], 'shape')
dist_func, nb_dt = _get_proper_func(random_standard_gamma_f,
random_standard_gamma,
dtype)
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, shape, size=None, dtype=np.float64):
return nb_dt(dist_func(inst.bit_generator, shape))
return impl
else:
check_size(size)
def impl(inst, shape, size=None, dtype=np.float64):
out = np.empty(size, dtype=dtype)
out_f = out.flat
for i in range(out.size):
out_f[i] = dist_func(inst.bit_generator, shape)
return out
return impl
# Overload the Generator().normal() method
@overload_method(types.NumPyRandomGeneratorType, 'normal')
def NumPyRandomGeneratorType_normal(inst, loc=0.0, scale=1.0,
size=None):
check_types(loc, [types.Float, types.Integer, int, float], 'loc')
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, loc=0.0, scale=1.0, size=None):
return random_normal(inst.bit_generator, loc, scale)
return impl
else:
check_size(size)
def impl(inst, loc=0.0, scale=1.0, size=None):
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_normal(inst.bit_generator, loc, scale)
return out
return impl
# Overload the Generator().uniform() method
@overload_method(types.NumPyRandomGeneratorType, 'uniform')
def NumPyRandomGeneratorType_uniform(inst, low=0.0, high=1.0,
size=None):
check_types(low, [types.Float, types.Integer, int, float], 'low')
check_types(high, [types.Float, types.Integer, int, float], 'high')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, low=0.0, high=1.0, size=None):
return random_uniform(inst.bit_generator, low, high - low)
return impl
else:
check_size(size)
def impl(inst, low=0.0, high=1.0, size=None):
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_uniform(inst.bit_generator, low, high - low)
return out
return impl
# Overload the Generator().exponential() method
@overload_method(types.NumPyRandomGeneratorType, 'exponential')
def NumPyRandomGeneratorType_exponential(inst, scale=1.0, size=None):
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, scale=1.0, size=None):
return random_exponential(inst.bit_generator, scale)
return impl
else:
check_size(size)
def impl(inst, scale=1.0, size=None):
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_exponential(inst.bit_generator, scale)
return out
return impl
# Overload the Generator().gamma() method
@overload_method(types.NumPyRandomGeneratorType, 'gamma')
def NumPyRandomGeneratorType_gamma(inst, shape, scale=1.0, size=None):
check_types(shape, [types.Float, types.Integer, int, float], 'shape')
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, shape, scale=1.0, size=None):
return random_gamma(inst.bit_generator, shape, scale)
return impl
else:
check_size(size)
def impl(inst, shape, scale=1.0, size=None):
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_gamma(inst.bit_generator, shape, scale)
return out
return impl
# Overload the Generator().beta() method
@overload_method(types.NumPyRandomGeneratorType, 'beta')
def NumPyRandomGeneratorType_beta(inst, a, b, size=None):
check_types(a, [types.Float, types.Integer, int, float], 'a')
check_types(b, [types.Float, types.Integer, int, float], 'b')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, a, b, size=None):
return random_beta(inst.bit_generator, a, b)
return impl
else:
check_size(size)
def impl(inst, a, b, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_beta(inst.bit_generator, a, b)
return out
return impl
# Overload the Generator().f() method
@overload_method(types.NumPyRandomGeneratorType, 'f')
def NumPyRandomGeneratorType_f(inst, dfnum, dfden, size=None):
check_types(dfnum, [types.Float, types.Integer, int, float], 'dfnum')
check_types(dfden, [types.Float, types.Integer, int, float], 'dfden')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, dfnum, dfden, size=None):
return random_f(inst.bit_generator, dfnum, dfden)
return impl
else:
check_size(size)
def impl(inst, dfnum, dfden, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_f(inst.bit_generator, dfnum, dfden)
return out
return impl
# Overload the Generator().chisquare() method
@overload_method(types.NumPyRandomGeneratorType, 'chisquare')
def NumPyRandomGeneratorType_chisquare(inst, df, size=None):
check_types(df, [types.Float, types.Integer, int, float], 'df')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, df, size=None):
return random_chisquare(inst.bit_generator, df)
return impl
else:
check_size(size)
def impl(inst, df, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_chisquare(inst.bit_generator, df)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'standard_cauchy')
def NumPyRandomGeneratorType_standard_cauchy(inst, size=None):
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, size=None):
return random_standard_cauchy(inst.bit_generator)
return impl
else:
check_size(size)
def impl(inst, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_standard_cauchy(inst.bit_generator)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'pareto')
def NumPyRandomGeneratorType_pareto(inst, a, size=None):
check_types(a, [types.Float, types.Integer, int, float], 'a')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, a, size=None):
return random_pareto(inst.bit_generator, a)
return impl
else:
check_size(size)
def impl(inst, a, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_pareto(inst.bit_generator, a)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'weibull')
def NumPyRandomGeneratorType_weibull(inst, a, size=None):
check_types(a, [types.Float, types.Integer, int, float], 'a')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, a, size=None):
return random_weibull(inst.bit_generator, a)
return impl
else:
check_size(size)
def impl(inst, a, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_weibull(inst.bit_generator, a)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'power')
def NumPyRandomGeneratorType_power(inst, a, size=None):
check_types(a, [types.Float, types.Integer, int, float], 'a')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, a, size=None):
return random_power(inst.bit_generator, a)
return impl
else:
check_size(size)
def impl(inst, a, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_power(inst.bit_generator, a)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'laplace')
def NumPyRandomGeneratorType_laplace(inst, loc=0.0, scale=1.0, size=None):
check_types(loc, [types.Float, types.Integer, int, float], 'loc')
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, loc=0.0, scale=1.0, size=None):
return random_laplace(inst.bit_generator, loc, scale)
return impl
else:
check_size(size)
def impl(inst, loc=0.0, scale=1.0, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_laplace(inst.bit_generator, loc, scale)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'logistic')
def NumPyRandomGeneratorType_logistic(inst, loc=0.0, scale=1.0, size=None):
check_types(loc, [types.Float, types.Integer, int, float], 'loc')
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, loc=0.0, scale=1.0, size=None):
return random_logistic(inst.bit_generator, loc, scale)
return impl
else:
check_size(size)
def impl(inst, loc=0.0, scale=1.0, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_logistic(inst.bit_generator, loc, scale)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'lognormal')
def NumPyRandomGeneratorType_lognormal(inst, mean=0.0, sigma=1.0, size=None):
check_types(mean, [types.Float, types.Integer, int, float], 'mean')
check_types(sigma, [types.Float, types.Integer, int, float], 'sigma')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, mean=0.0, sigma=1.0, size=None):
return random_lognormal(inst.bit_generator, mean, sigma)
return impl
else:
check_size(size)
def impl(inst, mean=0.0, sigma=1.0, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_lognormal(inst.bit_generator, mean, sigma)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'rayleigh')
def NumPyRandomGeneratorType_rayleigh(inst, scale=1.0, size=None):
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, scale=1.0, size=None):
return random_rayleigh(inst.bit_generator, scale)
return impl
else:
check_size(size)
def impl(inst, scale=1.0, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_rayleigh(inst.bit_generator, scale)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'standard_t')
def NumPyRandomGeneratorType_standard_t(inst, df, size=None):
check_types(df, [types.Float, types.Integer, int, float], 'df')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, df, size=None):
return random_standard_t(inst.bit_generator, df)
return impl
else:
check_size(size)
def impl(inst, df, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_standard_t(inst.bit_generator, df)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'wald')
def NumPyRandomGeneratorType_wald(inst, mean, scale, size=None):
check_types(mean, [types.Float, types.Integer, int, float], 'mean')
check_types(scale, [types.Float, types.Integer, int, float], 'scale')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, mean, scale, size=None):
return random_wald(inst.bit_generator, mean, scale)
return impl
else:
check_size(size)
def impl(inst, mean, scale, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_wald(inst.bit_generator, mean, scale)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'geometric')
def NumPyRandomGeneratorType_geometric(inst, p, size=None):
check_types(p, [types.Float, types.Integer, int, float], 'p')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, p, size=None):
return np.int64(random_geometric(inst.bit_generator, p))
return impl
else:
check_size(size)
def impl(inst, p, size=None):
out = np.empty(size, dtype=np.int64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_geometric(inst.bit_generator, p)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'zipf')
def NumPyRandomGeneratorType_zipf(inst, a, size=None):
check_types(a, [types.Float, types.Integer, int, float], 'a')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, a, size=None):
return np.int64(random_zipf(inst.bit_generator, a))
return impl
else:
check_size(size)
def impl(inst, a, size=None):
out = np.empty(size, dtype=np.int64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_zipf(inst.bit_generator, a)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'triangular')
def NumPyRandomGeneratorType_triangular(inst, left, mode, right, size=None):
check_types(left, [types.Float, types.Integer, int, float], 'left')
check_types(mode, [types.Float, types.Integer, int, float], 'mode')
check_types(right, [types.Float, types.Integer, int, float], 'right')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, left, mode, right, size=None):
return random_triangular(inst.bit_generator, left, mode, right)
return impl
else:
check_size(size)
def impl(inst, left, mode, right, size=None):
out = np.empty(size)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_triangular(inst.bit_generator,
left, mode, right)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'poisson')
def NumPyRandomGeneratorType_poisson(inst, lam , size=None):
check_types(lam, [types.Float, types.Integer, int, float], 'lam')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, lam , size=None):
return np.int64(random_poisson(inst.bit_generator, lam))
return impl
else:
check_size(size)
def impl(inst, lam , size=None):
out = np.empty(size, dtype=np.int64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_poisson(inst.bit_generator, lam)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'negative_binomial')
def NumPyRandomGeneratorType_negative_binomial(inst, n, p, size=None):
check_types(n, [types.Float, types.Integer, int, float], 'n')
check_types(p, [types.Float, types.Integer, int, float], 'p')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, n, p , size=None):
return np.int64(random_negative_binomial(inst.bit_generator, n, p))
return impl
else:
check_size(size)
def impl(inst, n, p , size=None):
out = np.empty(size, dtype=np.int64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_negative_binomial(inst.bit_generator, n, p)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'noncentral_chisquare')
def NumPyRandomGeneratorType_noncentral_chisquare(inst, df, nonc, size=None):
check_types(df, [types.Float, types.Integer, int, float], 'df')
check_types(nonc, [types.Float, types.Integer, int, float], 'nonc')
if isinstance(size, types.Omitted):
size = size.value
@register_jitable
def check_arg_bounds(df, nonc):
if df <= 0:
raise ValueError("df <= 0")
if nonc < 0:
raise ValueError("nonc < 0")
if is_nonelike(size):
def impl(inst, df, nonc, size=None):
check_arg_bounds(df, nonc)
return np.float64(random_noncentral_chisquare(inst.bit_generator,
df, nonc))
return impl
else:
check_size(size)
def impl(inst, df, nonc, size=None):
check_arg_bounds(df, nonc)
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_noncentral_chisquare(inst.bit_generator,
df, nonc)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'noncentral_f')
def NumPyRandomGeneratorType_noncentral_f(inst, dfnum, dfden, nonc, size=None):
check_types(dfnum, [types.Float, types.Integer, int, float], 'dfnum')
check_types(dfden, [types.Float, types.Integer, int, float], 'dfden')
check_types(nonc, [types.Float, types.Integer, int, float], 'nonc')
if isinstance(size, types.Omitted):
size = size.value
@register_jitable
def check_arg_bounds(dfnum, dfden, nonc):
if dfnum <= 0:
raise ValueError("dfnum <= 0")
if dfden <= 0:
raise ValueError("dfden <= 0")
if nonc < 0:
raise ValueError("nonc < 0")
if is_nonelike(size):
def impl(inst, dfnum, dfden, nonc, size=None):
check_arg_bounds(dfnum, dfden, nonc)
return np.float64(random_noncentral_f(inst.bit_generator,
dfnum, dfden, nonc))
return impl
else:
check_size(size)
def impl(inst, dfnum, dfden, nonc, size=None):
check_arg_bounds(dfnum, dfden, nonc)
out = np.empty(size, dtype=np.float64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_noncentral_f(inst.bit_generator,
dfnum, dfden, nonc)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'logseries')
def NumPyRandomGeneratorType_logseries(inst, p, size=None):
check_types(p, [types.Float, types.Integer, int, float], 'p')
if isinstance(size, types.Omitted):
size = size.value
@register_jitable
def check_arg_bounds(p):
if p < 0 or p >= 1 or np.isnan(p):
raise ValueError("p < 0, p >= 1 or p is NaN")
if is_nonelike(size):
def impl(inst, p, size=None):
check_arg_bounds(p)
return np.int64(random_logseries(inst.bit_generator, p))
return impl
else:
check_size(size)
def impl(inst, p, size=None):
check_arg_bounds(p)
out = np.empty(size, dtype=np.int64)
out_f = out.flat
for i in range(out.size):
out_f[i] = random_logseries(inst.bit_generator, p)
return out
return impl
@overload_method(types.NumPyRandomGeneratorType, 'binomial')
def NumPyRandomGeneratorType_binomial(inst, n, p, size=None):
check_types(n, [types.Float, types.Integer, int, float], 'n')
check_types(p, [types.Float, types.Integer, int, float], 'p')
if isinstance(size, types.Omitted):
size = size.value
if is_nonelike(size):
def impl(inst, n, p, size=None):
return np.int64(random_binomial(inst.bit_generator, n, p))
return impl
else:
check_size(size)
def impl(inst, n, p, size=None):
out = np.empty(size, dtype=np.int64)
for i in np.ndindex(size):
out[i] = random_binomial(inst.bit_generator, n, p)
return out
return impl

View File

@@ -0,0 +1,365 @@
import numpy as np
from numba import uint64, uint32, uint16, uint8
from numba.core.extending import register_jitable
from numba.np.random._constants import (UINT32_MAX, UINT64_MAX,
UINT16_MAX, UINT8_MAX)
from numba.np.random.generator_core import next_uint32, next_uint64
# All following implementations are direct translations from:
# https://github.com/numpy/numpy/blob/7cfef93c77599bd387ecc6a15d186c5a46024dac/numpy/random/src/distributions/distributions.c
@register_jitable
def gen_mask(max):
mask = uint64(max)
mask |= mask >> 1
mask |= mask >> 2
mask |= mask >> 4
mask |= mask >> 8
mask |= mask >> 16
mask |= mask >> 32
return mask
@register_jitable
def buffered_bounded_bool(bitgen, off, rng, bcnt, buf):
if (rng == 0):
return off, bcnt, buf
if not bcnt:
buf = next_uint32(bitgen)
bcnt = 31
else:
buf >>= 1
bcnt -= 1
return ((buf & 1) != 0), bcnt, buf
@register_jitable
def buffered_uint8(bitgen, bcnt, buf):
if not bcnt:
buf = next_uint32(bitgen)
bcnt = 3
else:
buf >>= 8
bcnt -= 1
return uint8(buf), bcnt, buf
@register_jitable
def buffered_uint16(bitgen, bcnt, buf):
if not bcnt:
buf = next_uint32(bitgen)
bcnt = 1
else:
buf >>= 16
bcnt -= 1
return uint16(buf), bcnt, buf
# The following implementations use Lemire's algorithm:
# https://arxiv.org/abs/1805.10941
@register_jitable
def buffered_bounded_lemire_uint8(bitgen, rng, bcnt, buf):
"""
Generates a random unsigned 8 bit integer bounded
within a given interval using Lemire's rejection.
The buffer acts as storage for a 32 bit integer
drawn from the associated BitGenerator so that
multiple integers of smaller bitsize can be generated
from a single draw of the BitGenerator.
"""
# Note: `rng` should not be 0xFF. When this happens `rng_excl` becomes
# zero.
rng_excl = uint8(rng) + uint8(1)
assert (rng != 0xFF)
# Generate a scaled random number.
n, bcnt, buf = buffered_uint8(bitgen, bcnt, buf)
m = uint16(n * rng_excl)
# Rejection sampling to remove any bias
leftover = m & 0xFF
if (leftover < rng_excl):
# `rng_excl` is a simple upper bound for `threshold`.
threshold = ((uint8(UINT8_MAX) - rng) % rng_excl)
while (leftover < threshold):
n, bcnt, buf = buffered_uint8(bitgen, bcnt, buf)
m = uint16(n * rng_excl)
leftover = m & 0xFF
return m >> 8, bcnt, buf
@register_jitable
def buffered_bounded_lemire_uint16(bitgen, rng, bcnt, buf):
"""
Generates a random unsigned 16 bit integer bounded
within a given interval using Lemire's rejection.
The buffer acts as storage for a 32 bit integer
drawn from the associated BitGenerator so that
multiple integers of smaller bitsize can be generated
from a single draw of the BitGenerator.
"""
# Note: `rng` should not be 0xFFFF. When this happens `rng_excl` becomes
# zero.
rng_excl = uint16(rng) + uint16(1)
assert (rng != 0xFFFF)
# Generate a scaled random number.
n, bcnt, buf = buffered_uint16(bitgen, bcnt, buf)
m = uint32(n * rng_excl)
# Rejection sampling to remove any bias
leftover = m & 0xFFFF
if (leftover < rng_excl):
# `rng_excl` is a simple upper bound for `threshold`.
threshold = ((uint16(UINT16_MAX) - rng) % rng_excl)
while (leftover < threshold):
n, bcnt, buf = buffered_uint16(bitgen, bcnt, buf)
m = uint32(n * rng_excl)
leftover = m & 0xFFFF
return m >> 16, bcnt, buf
@register_jitable
def buffered_bounded_lemire_uint32(bitgen, rng):
"""
Generates a random unsigned 32 bit integer bounded
within a given interval using Lemire's rejection.
"""
rng_excl = uint32(rng) + uint32(1)
assert (rng != 0xFFFFFFFF)
# Generate a scaled random number.
m = uint64(next_uint32(bitgen)) * uint64(rng_excl)
# Rejection sampling to remove any bias
leftover = m & 0xFFFFFFFF
if (leftover < rng_excl):
# `rng_excl` is a simple upper bound for `threshold`.
threshold = (UINT32_MAX - rng) % rng_excl
while (leftover < threshold):
m = uint64(next_uint32(bitgen)) * uint64(rng_excl)
leftover = m & 0xFFFFFFFF
return (m >> 32)
@register_jitable
def bounded_lemire_uint64(bitgen, rng):
"""
Generates a random unsigned 64 bit integer bounded
within a given interval using Lemire's rejection.
"""
rng_excl = uint64(rng) + uint64(1)
assert (rng != 0xFFFFFFFFFFFFFFFF)
x = next_uint64(bitgen)
leftover = uint64(x) * uint64(rng_excl)
if (leftover < rng_excl):
threshold = (UINT64_MAX - rng) % rng_excl
while (leftover < threshold):
x = next_uint64(bitgen)
leftover = uint64(x) * uint64(rng_excl)
x0 = x & uint64(0xFFFFFFFF)
x1 = x >> 32
rng_excl0 = rng_excl & uint64(0xFFFFFFFF)
rng_excl1 = rng_excl >> 32
w0 = x0 * rng_excl0
t = x1 * rng_excl0 + (w0 >> 32)
w1 = t & uint64(0xFFFFFFFF)
w2 = t >> 32
w1 += x0 * rng_excl1
m1 = x1 * rng_excl1 + w2 + (w1 >> 32)
return m1
@register_jitable
def random_bounded_uint64_fill(bitgen, low, rng, size, dtype):
"""
Returns a new array of given size with 64 bit integers
bounded by given interval.
"""
out = np.empty(size, dtype=dtype)
if rng == 0:
for i in np.ndindex(size):
out[i] = low
elif rng <= 0xFFFFFFFF:
if (rng == 0xFFFFFFFF):
for i in np.ndindex(size):
out[i] = low + next_uint32(bitgen)
else:
for i in np.ndindex(size):
out[i] = low + buffered_bounded_lemire_uint32(bitgen, rng)
elif (rng == 0xFFFFFFFFFFFFFFFF):
for i in np.ndindex(size):
out[i] = low + next_uint64(bitgen)
else:
for i in np.ndindex(size):
out[i] = low + bounded_lemire_uint64(bitgen, rng)
return out
@register_jitable
def random_bounded_uint32_fill(bitgen, low, rng, size, dtype):
"""
Returns a new array of given size with 32 bit integers
bounded by given interval.
"""
out = np.empty(size, dtype=dtype)
if rng == 0:
for i in np.ndindex(size):
out[i] = low
elif rng == 0xFFFFFFFF:
# Lemire32 doesn't support rng = 0xFFFFFFFF.
for i in np.ndindex(size):
out[i] = low + next_uint32(bitgen)
else:
for i in np.ndindex(size):
out[i] = low + buffered_bounded_lemire_uint32(bitgen, rng)
return out
@register_jitable
def random_bounded_uint16_fill(bitgen, low, rng, size, dtype):
"""
Returns a new array of given size with 16 bit integers
bounded by given interval.
"""
buf = 0
bcnt = 0
out = np.empty(size, dtype=dtype)
if rng == 0:
for i in np.ndindex(size):
out[i] = low
elif rng == 0xFFFF:
# Lemire16 doesn't support rng = 0xFFFF.
for i in np.ndindex(size):
val, bcnt, buf = buffered_uint16(bitgen, bcnt, buf)
out[i] = low + val
else:
for i in np.ndindex(size):
val, bcnt, buf = \
buffered_bounded_lemire_uint16(bitgen, rng,
bcnt, buf)
out[i] = low + val
return out
@register_jitable
def random_bounded_uint8_fill(bitgen, low, rng, size, dtype):
"""
Returns a new array of given size with 8 bit integers
bounded by given interval.
"""
buf = 0
bcnt = 0
out = np.empty(size, dtype=dtype)
if rng == 0:
for i in np.ndindex(size):
out[i] = low
elif rng == 0xFF:
# Lemire8 doesn't support rng = 0xFF.
for i in np.ndindex(size):
val, bcnt, buf = buffered_uint8(bitgen, bcnt, buf)
out[i] = low + val
else:
for i in np.ndindex(size):
val, bcnt, buf = \
buffered_bounded_lemire_uint8(bitgen, rng,
bcnt, buf)
out[i] = low + val
return out
@register_jitable
def random_bounded_bool_fill(bitgen, low, rng, size, dtype):
"""
Returns a new array of given size with boolean values.
"""
buf = 0
bcnt = 0
out = np.empty(size, dtype=dtype)
for i in np.ndindex(size):
val, bcnt, buf = buffered_bounded_bool(bitgen, low, rng, bcnt, buf)
out[i] = low + val
return out
@register_jitable
def _randint_arg_check(low, high, endpoint, lower_bound, upper_bound):
"""
Check that low and high are within the bounds
for the given datatype.
"""
if low < lower_bound:
raise ValueError("low is out of bounds")
# This is being done to avoid high being accidentally
# casted to int64/32 while subtracting 1 before
# checking bounds, avoids overflow.
if high > 0:
high = uint64(high)
if not endpoint:
high -= uint64(1)
upper_bound = uint64(upper_bound)
if low > 0:
low = uint64(low)
if high > upper_bound:
raise ValueError("high is out of bounds")
if low > high: # -1 already subtracted, closed interval
raise ValueError("low is greater than high in given interval")
else:
if high > upper_bound:
raise ValueError("high is out of bounds")
if low > high: # -1 already subtracted, closed interval
raise ValueError("low is greater than high in given interval")
@register_jitable
def random_interval(bitgen, max_val):
if (max_val == 0):
return 0
max_val = uint64(max_val)
mask = uint64(gen_mask(max_val))
if (max_val <= 0xffffffff):
value = uint64(next_uint32(bitgen)) & mask
while value > max_val:
value = uint64(next_uint32(bitgen)) & mask
else:
value = next_uint64(bitgen) & mask
while value > max_val:
value = next_uint64(bitgen) & mask
return uint64(value)