# Copyright (C) 2022-2025 Exaloop Inc. 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