2025-01-30 04:41:43 +08:00
|
|
|
|
# Copyright (C) 2022-2025 Exaloop Inc. <https://exaloop.io>
|
2022-01-24 18:10:09 +08:00
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
import sys
|
2022-12-05 08:45:21 +08:00
|
|
|
|
from math import inf as INF, sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
|
2021-09-28 02:02:44 +08:00
|
|
|
|
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
|
|
|
|
|
from bisect import bisect as _bisect
|
|
|
|
|
from time import time as _time
|
|
|
|
|
|
|
|
|
|
N = 624
|
|
|
|
|
M = 397
|
|
|
|
|
LOG4 = _log(4.0)
|
|
|
|
|
NV_MAGICCONST = 4 * _exp(-0.5) / _sqrt(2.0)
|
|
|
|
|
SG_MAGICCONST = 1.0 + _log(4.5)
|
|
|
|
|
TWOPI = 2.0 * _pi
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
MATRIX_A = u32(0x9908b0df) # constant vector a
|
|
|
|
|
UPPER_MASK = u32(0x80000000) # most significant w-r bits
|
|
|
|
|
LOWER_MASK = u32(0x7fffffff) # least significant r bits
|
|
|
|
|
|
|
|
|
|
@tuple
|
2021-09-28 02:02:44 +08:00
|
|
|
|
class RandomGenerator:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
data: Ptr[u32]
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def __new__():
|
|
|
|
|
return RandomGenerator(Ptr[u32](N + 1))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
@property
|
|
|
|
|
def index(self):
|
|
|
|
|
return int(self.data[0])
|
2022-01-27 22:26:54 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
@property
|
|
|
|
|
def state(self):
|
|
|
|
|
return self.data + 1
|
2022-01-27 22:26:54 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def getstate(self):
|
|
|
|
|
from internal.gc import sizeof
|
|
|
|
|
p = Ptr[u32](N + 1)
|
|
|
|
|
str.memcpy(p.as_byte(), self.data.as_byte(), (N + 1) * sizeof(u32))
|
|
|
|
|
return p
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def setstate(self, state):
|
|
|
|
|
from internal.gc import sizeof
|
|
|
|
|
str.memcpy(self.data.as_byte(), state.as_byte(), (N + 1) * sizeof(u32))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def genrand_int32(self) -> u32:
|
|
|
|
|
mag01 = (u32(0), MATRIX_A)
|
|
|
|
|
mt = self.state
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
if self.index >= N:
|
|
|
|
|
kk = 0
|
|
|
|
|
|
|
|
|
|
while kk < int(N - M):
|
|
|
|
|
y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK)
|
|
|
|
|
mt[kk] = mt[kk + M] ^ (y >> u32(1)) ^ mag01[int(y & u32(1))]
|
|
|
|
|
kk += 1
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
while kk < int(N - 1):
|
|
|
|
|
y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK)
|
|
|
|
|
mt[kk] = mt[kk+(M-N)] ^ (y >> u32(1)) ^ mag01[int(y & u32(1))]
|
|
|
|
|
kk += 1
|
|
|
|
|
|
|
|
|
|
y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK)
|
|
|
|
|
mt[N-1] = mt[M-1] ^ (y >> u32(1)) ^ mag01[int(y & u32(1))]
|
|
|
|
|
self.data[0] = u32(0)
|
|
|
|
|
|
|
|
|
|
i = self.index
|
|
|
|
|
y = mt[i]
|
|
|
|
|
self.data[0] = u32(i + 1)
|
|
|
|
|
y ^= (y >> u32(11))
|
|
|
|
|
y ^= (y << u32(7)) & u32(0x9d2c5680)
|
|
|
|
|
y ^= (y << u32(15)) & u32(0xefc60000)
|
|
|
|
|
y ^= (y >> u32(18))
|
|
|
|
|
return y
|
|
|
|
|
|
|
|
|
|
def genrand_res53(self) -> float:
|
|
|
|
|
a = self.genrand_int32() >> u32(5)
|
|
|
|
|
b = self.genrand_int32() >> u32(6)
|
|
|
|
|
return (int(a) * 67108864.0 + int(b)) * (1.0 / 9007199254740992.0)
|
|
|
|
|
|
|
|
|
|
def random(self):
|
|
|
|
|
return self.genrand_res53()
|
|
|
|
|
|
|
|
|
|
def init_u32(self, s: u32):
|
|
|
|
|
mt = self.state
|
|
|
|
|
mt[0] = s
|
|
|
|
|
for mti in range(1, N):
|
|
|
|
|
mt[mti] = (u32(1812433253) * (mt[mti-1] ^ (mt[mti-1] >> u32(30))) + u32(mti))
|
|
|
|
|
self.data[0] = u32(N)
|
|
|
|
|
|
|
|
|
|
def init_array(self, init_key: Ptr[u32], key_length: int):
|
|
|
|
|
mt = self.state
|
|
|
|
|
self.init_u32(u32(19650218))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
i = 1
|
|
|
|
|
j = 0
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
k = N if N > key_length else key_length
|
|
|
|
|
while k:
|
|
|
|
|
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> u32(30))) * u32(1664525))) + init_key[j] + u32(j)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
i += 1
|
|
|
|
|
j += 1
|
|
|
|
|
if i >= N:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
mt[0] = mt[N - 1]
|
2021-09-28 02:02:44 +08:00
|
|
|
|
i = 1
|
|
|
|
|
if j >= key_length:
|
|
|
|
|
j = 0
|
|
|
|
|
k -= 1
|
|
|
|
|
|
|
|
|
|
k = N - 1
|
2022-12-05 08:45:21 +08:00
|
|
|
|
while k:
|
|
|
|
|
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> u32(30))) * u32(1566083941))) - u32(i)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
i += 1
|
|
|
|
|
if i >= N:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
mt[0] = mt[N - 1]
|
2021-09-28 02:02:44 +08:00
|
|
|
|
i = 1
|
|
|
|
|
k -= 1
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
mt[0] = u32(0x80000000)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def init_int(self, s: int):
|
|
|
|
|
init_key = (u32(s & ((1 << 32) - 1)), u32(s >> 32))
|
|
|
|
|
self.init_array(Ptr[u32](__ptr__(init_key).as_byte()), 2 if init_key[1] else 1)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def random_seed_time_pid(self):
|
2022-12-05 08:45:21 +08:00
|
|
|
|
now = _C.seq_time() * 1000
|
2021-09-28 02:02:44 +08:00
|
|
|
|
key = __array__[u32](5)
|
2022-12-05 08:45:21 +08:00
|
|
|
|
key[0] = u32(now & 0xFFFFFFFF)
|
|
|
|
|
key[1] = u32(now >> 32)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
key[2] = u32(_C.seq_pid())
|
2022-12-05 08:45:21 +08:00
|
|
|
|
now = _C.seq_time_monotonic()
|
|
|
|
|
key[3] = u32(now & 0xFFFFFFFF)
|
|
|
|
|
key[4] = u32(now >> 32)
|
|
|
|
|
self.init_array(key.ptr, len(key))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def seed(self, s: int):
|
|
|
|
|
self.init_int(s)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def seed(self):
|
|
|
|
|
self.random_seed_time_pid()
|
|
|
|
|
|
|
|
|
|
class Random:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
"""
|
|
|
|
|
Random number generator base class used by bound module functions.
|
|
|
|
|
Used to instantiate instances of Random to get generators that don't
|
|
|
|
|
share state.
|
|
|
|
|
Class Random can also be subclassed if you want to use a different basic
|
|
|
|
|
generator of your own devising: in that case, override the following
|
|
|
|
|
methods: random(), seed(), getstate(), and setstate().
|
|
|
|
|
Optionally, implement a getrandbits() method so that randrange()
|
|
|
|
|
can cover arbitrarily large ranges.
|
|
|
|
|
"""
|
2022-01-24 18:10:09 +08:00
|
|
|
|
gen: RandomGenerator # comment for another error
|
2022-12-05 08:45:21 +08:00
|
|
|
|
gauss_next: Optional[float]
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def __init__(self, seed: Optional[int] = None):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Initialize an instance.
|
|
|
|
|
|
|
|
|
|
Optional argument x controls seeding, as for Random.seed().
|
|
|
|
|
|
|
|
|
|
For now x is set to its default None.
|
|
|
|
|
"""
|
2022-12-05 08:45:21 +08:00
|
|
|
|
self.gen = RandomGenerator()
|
|
|
|
|
self.seed(seed)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def seed(self, a: Optional[int]):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Initialize internal state from hashable object.
|
|
|
|
|
|
|
|
|
|
None or no argument seeds from current time or from an operating
|
|
|
|
|
system specific randomness source if available.
|
|
|
|
|
|
|
|
|
|
If *a* is an int, all bits are used.
|
|
|
|
|
|
|
|
|
|
For version 2 (the default), all of the bits are used if *a* is a str,
|
|
|
|
|
bytes, or bytearray. For version 1 (provided for reproducing random
|
|
|
|
|
sequences from older versions of Python), the algorithm for str and
|
|
|
|
|
bytes generates a narrower range of seeds.
|
|
|
|
|
"""
|
2022-12-05 08:45:21 +08:00
|
|
|
|
if a is not None:
|
|
|
|
|
self.gen.seed(abs(a))
|
|
|
|
|
else:
|
|
|
|
|
self.gen.seed()
|
|
|
|
|
self.gauss_next = None
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-01-27 22:26:54 +08:00
|
|
|
|
def getstate(self):
|
2022-12-05 08:45:21 +08:00
|
|
|
|
return self.gen.getstate(), self.gauss_next
|
2022-02-16 23:51:16 +08:00
|
|
|
|
|
2022-01-27 22:26:54 +08:00
|
|
|
|
def setstate(self, state):
|
2022-12-05 08:45:21 +08:00
|
|
|
|
gen_state, gauss_next = state
|
|
|
|
|
self.gen.setstate(gen_state)
|
|
|
|
|
self.gauss_next = gauss_next
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def getrandbits(self, k: int) -> int:
|
|
|
|
|
"""
|
|
|
|
|
getrandbits(k) -> x
|
|
|
|
|
Generates an int with k random bits.
|
|
|
|
|
"""
|
2022-12-05 08:45:21 +08:00
|
|
|
|
if k == 0:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if k < 0:
|
|
|
|
|
raise ValueError("number of bits must be non-negative")
|
|
|
|
|
|
|
|
|
|
if k > 64:
|
|
|
|
|
raise ValueError("number of bits cannot be greater than 64")
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
if k <= 32: # Fast path
|
2021-09-28 02:02:44 +08:00
|
|
|
|
r = int(self.gen.genrand_int32())
|
|
|
|
|
m = r >> (32 - k)
|
|
|
|
|
return m
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
lo = u64(int(self.gen.genrand_int32()))
|
|
|
|
|
hi = u64(int(self.gen.genrand_int32()))
|
|
|
|
|
mask = ~((u64(1) << u64(64 - k)) - u64(1))
|
|
|
|
|
hi &= mask
|
|
|
|
|
hi >>= u64(64 - k)
|
|
|
|
|
return int((hi << u64(32)) | lo)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def bit_length(self, n: int) -> int:
|
2022-01-24 18:10:09 +08:00
|
|
|
|
""" """
|
2021-09-28 02:02:44 +08:00
|
|
|
|
len = 0
|
|
|
|
|
while n:
|
|
|
|
|
len += 1
|
|
|
|
|
n = int(u64(n) >> u64(1))
|
|
|
|
|
return len
|
|
|
|
|
|
|
|
|
|
def _randbelow_with_getrandbits(self, n: int) -> int:
|
|
|
|
|
"""
|
|
|
|
|
Return a random int in the range [0,n). Raises ValueError if n==0.
|
|
|
|
|
"""
|
|
|
|
|
getrandbits = self.getrandbits
|
|
|
|
|
k = self.bit_length(n) # don't use (n-1) here because n can be 1
|
2022-01-24 18:10:09 +08:00
|
|
|
|
r = getrandbits(k) # 0 <= r < 2**k
|
2021-09-28 02:02:44 +08:00
|
|
|
|
while r >= n:
|
|
|
|
|
r = getrandbits(k)
|
|
|
|
|
return r
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def randrange(self, start: int, stop: int, step: int = 1) -> int:
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Choose a random item from range(start, stop[, step]).
|
|
|
|
|
|
|
|
|
|
Return a randomly selected element from range(start, stop, step).
|
|
|
|
|
This is equivalent to choice(range(start, stop, step)), but
|
|
|
|
|
doesn’t actually build a range object.
|
|
|
|
|
|
|
|
|
|
For now stop == 0 for randrange(stop) where start = stop in our parameter.
|
|
|
|
|
Defaults include: stop = None, step = 1
|
|
|
|
|
for now we will use default value for step.
|
|
|
|
|
"""
|
|
|
|
|
if stop == 0:
|
|
|
|
|
if start > 0:
|
|
|
|
|
return self._randbelow_with_getrandbits(start)
|
|
|
|
|
raise ValueError("empty range for randrange()")
|
|
|
|
|
|
|
|
|
|
# stop argument supplied.
|
|
|
|
|
width = stop - start
|
|
|
|
|
if step == 1 and width > 0:
|
|
|
|
|
return start + self._randbelow_with_getrandbits(width)
|
|
|
|
|
if step == 1:
|
|
|
|
|
raise ValueError("empty range for randrange()")
|
|
|
|
|
|
|
|
|
|
# Non-unit step argument supplied.
|
|
|
|
|
n = INF
|
|
|
|
|
if step > 0:
|
|
|
|
|
n = float((width + step - 1) // step)
|
|
|
|
|
elif step < 0:
|
|
|
|
|
n = float((width + step + 1) // step)
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError("zero step for randrange()")
|
|
|
|
|
|
|
|
|
|
if n <= 0:
|
|
|
|
|
raise ValueError("empty range for randrange()")
|
|
|
|
|
|
|
|
|
|
return start + step * self._randbelow_with_getrandbits(int(n))
|
|
|
|
|
|
|
|
|
|
def randint(self, a: int, b: int):
|
|
|
|
|
"""
|
|
|
|
|
Return random integer in range [a, b], including both end points.
|
|
|
|
|
"""
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return self.randrange(a, b + 1, 1)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def random(self) -> float:
|
|
|
|
|
"""
|
|
|
|
|
random(self) -> float
|
|
|
|
|
|
|
|
|
|
Return the next random floating point number in the range [0.0, 1.0).
|
|
|
|
|
"""
|
|
|
|
|
return self.gen.genrand_res53()
|
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
def choice(self, sequence: Generator[T], T: type) -> T:
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Choose a random element from a non-empty sequence.
|
|
|
|
|
"""
|
|
|
|
|
i = 0
|
|
|
|
|
l = list(sequence)
|
|
|
|
|
try:
|
|
|
|
|
i = self._randbelow_with_getrandbits(len(l))
|
|
|
|
|
except ValueError:
|
|
|
|
|
raise IndexError("Cannot choose from an empty sequence")
|
|
|
|
|
return l[i]
|
|
|
|
|
|
|
|
|
|
def shuffle(self, x):
|
|
|
|
|
"""
|
|
|
|
|
Shuffle list x in place, and return None.
|
|
|
|
|
|
|
|
|
|
Optional argument random is a 0-argument function returning a
|
|
|
|
|
random float in [0.0, 1.0); if it is the default None, the
|
|
|
|
|
standard random.random will be used.
|
|
|
|
|
|
|
|
|
|
For now seq will use random = 0 (None = default)
|
|
|
|
|
"""
|
|
|
|
|
random = 0
|
|
|
|
|
if random == 0:
|
|
|
|
|
randbelow = self._randbelow_with_getrandbits
|
|
|
|
|
for i in reversed(range(1, len(x))):
|
|
|
|
|
# pick an element in x[:i+1] with which to exchange x[i]
|
2022-01-24 18:10:09 +08:00
|
|
|
|
j = randbelow(i + 1)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
x[i], x[j] = x[j], x[i]
|
|
|
|
|
else:
|
|
|
|
|
for i in reversed(range(1, len(x))):
|
|
|
|
|
# pick an element in x[:i+1] with which to exchange x[i]
|
2022-01-24 18:10:09 +08:00
|
|
|
|
j = int(self.random() * (i + 1))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
x[i], x[j] = x[j], x[i]
|
|
|
|
|
|
|
|
|
|
def uniform(self, a, b) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Get a random number in the range [a, b) or [a, b] depending on rounding.
|
|
|
|
|
"""
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return a + (b - a) * self.random()
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def triangular(self, low: float, high: float, mode: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Triangular distribution.
|
|
|
|
|
|
|
|
|
|
Continuous distribution bounded by given lower and upper limits,
|
|
|
|
|
and having a given mode value in-between.
|
|
|
|
|
|
|
|
|
|
http://en.wikipedia.org/wiki/Triangular_distribution
|
|
|
|
|
|
|
|
|
|
For now we mode to default: mode = None
|
|
|
|
|
default for low and high : low = 0.0, high = 1.0
|
|
|
|
|
"""
|
|
|
|
|
# mode = None
|
|
|
|
|
if high == low:
|
|
|
|
|
return low
|
|
|
|
|
u = self.random()
|
|
|
|
|
c = (mode - low) / (high - low)
|
|
|
|
|
if u > c:
|
|
|
|
|
u = 1.0 - u
|
|
|
|
|
c = 1.0 - c
|
|
|
|
|
low, high = high, low
|
|
|
|
|
return low + (high - low) * _sqrt(u * c)
|
|
|
|
|
|
|
|
|
|
def gammavariate(self, alpha: float, beta: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Gamma distribution. Not the gamma function!
|
|
|
|
|
|
|
|
|
|
Conditions on the parameters are alpha > 0 and beta > 0.
|
|
|
|
|
|
|
|
|
|
The probability distribution function is:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
x ** (alpha - 1) * math.exp(-x / beta)
|
|
|
|
|
pdf(x) = --------------------------------------
|
|
|
|
|
math.gamma(alpha) * beta ** alpha
|
|
|
|
|
"""
|
|
|
|
|
# alpha > 0, beta > 0, mean is alpha*beta, variance is alpha*beta**2
|
|
|
|
|
|
|
|
|
|
# Warning: a few older sources define the gamma distribution in terms
|
|
|
|
|
# of alpha > -1.0
|
|
|
|
|
if alpha <= 0.0 or beta <= 0.0:
|
2022-01-24 18:10:09 +08:00
|
|
|
|
raise ValueError("gammavariate: alpha and beta must be > 0.0")
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
if alpha > 1.0:
|
|
|
|
|
|
|
|
|
|
# Uses R.C.H. Cheng, "The generation of Gamma
|
|
|
|
|
# variables with non-integral shape parameters",
|
|
|
|
|
# Applied Statistics, (1977), 26, No. 1, p71-74
|
|
|
|
|
|
|
|
|
|
ainv = _sqrt(2.0 * alpha - 1.0)
|
|
|
|
|
bbb = alpha - LOG4
|
|
|
|
|
ccc = alpha + ainv
|
|
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
|
u1 = self.random()
|
2022-01-24 18:10:09 +08:00
|
|
|
|
if not 1e-7 < u1 < 0.9999999:
|
2021-09-28 02:02:44 +08:00
|
|
|
|
continue
|
|
|
|
|
u2 = 1.0 - self.random()
|
2022-01-24 18:10:09 +08:00
|
|
|
|
v = _log(u1 / (1.0 - u1)) / ainv
|
2021-09-28 02:02:44 +08:00
|
|
|
|
x = alpha * _exp(v)
|
|
|
|
|
z = u1 * u1 * u2
|
|
|
|
|
r = bbb + ccc * v - x
|
|
|
|
|
if r + SG_MAGICCONST - 4.5 * z >= 0.0 or r >= _log(z):
|
|
|
|
|
return x * beta
|
|
|
|
|
|
|
|
|
|
elif alpha == 1.0:
|
|
|
|
|
# expovariate(1/beta)
|
|
|
|
|
return -_log(1.0 - self.random()) * beta
|
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
else: # alpha is between 0 and 1 (exclusive)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
# Uses ALGORITHM GS of Statistical Computing - Kennedy & Gentle
|
|
|
|
|
x = 0.0
|
|
|
|
|
while 1:
|
|
|
|
|
u = self.random()
|
|
|
|
|
b = (_e + alpha) / _e
|
|
|
|
|
p = b * u
|
|
|
|
|
if p <= 1.0:
|
|
|
|
|
x = p ** (1.0 / alpha)
|
|
|
|
|
else:
|
|
|
|
|
x = -_log((b - p) / alpha)
|
|
|
|
|
u1 = self.random()
|
|
|
|
|
if p > 1.0:
|
|
|
|
|
if u1 <= x ** (alpha - 1.0):
|
|
|
|
|
break
|
|
|
|
|
elif u1 <= _exp(-x):
|
|
|
|
|
break
|
|
|
|
|
return x * beta
|
|
|
|
|
|
|
|
|
|
def betavariate(self, alpha: float, beta: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Beta distribution.
|
|
|
|
|
Conditions on the parameters are alpha > 0 and beta > 0.
|
|
|
|
|
Returned values range between 0 and 1.
|
|
|
|
|
"""
|
|
|
|
|
# This version due to Janne Sinkkonen, and matches all the std
|
|
|
|
|
# texts (e.g., Knuth Vol 2 Ed 3 pg 134 "the beta distribution").
|
|
|
|
|
y = self.gammavariate(alpha, 1.0)
|
|
|
|
|
if y == 0:
|
|
|
|
|
return 0.0
|
|
|
|
|
else:
|
|
|
|
|
return y / (y + self.gammavariate(beta, 1.0))
|
|
|
|
|
|
|
|
|
|
def expovariate(self, lambd: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Exponential distribution.
|
|
|
|
|
|
|
|
|
|
lambd is 1.0 divided by the desired mean. It should be
|
|
|
|
|
nonzero.
|
|
|
|
|
|
|
|
|
|
Returned values range from 0 to
|
|
|
|
|
positive infinity if lambd is positive, and from negative
|
|
|
|
|
infinity to 0 if lambd is negative.
|
|
|
|
|
"""
|
|
|
|
|
if lambd == 0.0:
|
|
|
|
|
raise ZeroDivisionError("Cannot divide by zero")
|
|
|
|
|
# lambd: rate lambd = 1/mean
|
|
|
|
|
# we use 1-random() instead of random() to preclude the
|
|
|
|
|
# possibility of taking the log of zero.
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return -_log(1.0 - self.random()) / lambd
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float:
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Gaussian distribution.
|
|
|
|
|
|
|
|
|
|
mu is the mean, and sigma is the standard deviation. This is
|
|
|
|
|
slightly faster than the normalvariate() function.
|
|
|
|
|
Not thread-safe without a lock around calls.
|
|
|
|
|
"""
|
2022-12-05 08:45:21 +08:00
|
|
|
|
z = self.gauss_next
|
|
|
|
|
self.gauss_next = None
|
|
|
|
|
if z is None:
|
|
|
|
|
x2pi = self.random() * TWOPI
|
|
|
|
|
g2rad = _sqrt(-2.0 * _log(1.0 - self.random()))
|
|
|
|
|
z = _cos(x2pi) * g2rad
|
|
|
|
|
self.gauss_next = _sin(x2pi) * g2rad
|
2021-09-28 02:02:44 +08:00
|
|
|
|
return mu + z * sigma
|
|
|
|
|
|
|
|
|
|
def paretovariate(self, alpha: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Pareto distribution. alpha is the shape parameter."""
|
|
|
|
|
u = 1.0 - self.random()
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return 1.0 / u ** (1.0 / alpha)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def weibullvariate(self, alpha: float, beta: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Weibull distribution.
|
|
|
|
|
|
|
|
|
|
alpha is the scale parameter and beta is the shape parameter.
|
|
|
|
|
"""
|
|
|
|
|
u = 1.0 - self.random()
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return alpha * (-_log(u)) ** (1.0 / beta)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def normalvariate(self, mu: float = 0.0, sigma: float = 1.0) -> float:
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Normal distribution.
|
|
|
|
|
|
|
|
|
|
mu is the mean, and sigma is the standard deviation.
|
|
|
|
|
"""
|
|
|
|
|
z = 0.0
|
|
|
|
|
while 1:
|
|
|
|
|
u1 = self.random()
|
|
|
|
|
u2 = 1.0 - self.random()
|
|
|
|
|
z = NV_MAGICCONST * (u1 - 0.5) / u2
|
|
|
|
|
zz = z * z / 4.0
|
2022-01-24 18:10:09 +08:00
|
|
|
|
if zz <= -_log(u2):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
break
|
|
|
|
|
return mu + z * sigma
|
|
|
|
|
|
|
|
|
|
def lognormvariate(self, mu: float, sigma: float) -> float:
|
2022-01-24 18:10:09 +08:00
|
|
|
|
"""
|
|
|
|
|
Log normal distribution.
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
If you take the natural logarithm of this distribution, you'll get a
|
|
|
|
|
normal distribution with mean mu and standard deviation sigma.
|
|
|
|
|
mu can have any value, and sigma must be greater than zero.
|
|
|
|
|
"""
|
|
|
|
|
return _exp(self.normalvariate(mu, sigma))
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
def vonmisesvariate(self, mu: float, kappa: float) -> float:
|
|
|
|
|
"""
|
|
|
|
|
Circular data distribution.
|
|
|
|
|
|
|
|
|
|
mu is the mean angle, expressed in radians between 0.0 and 2*pi, and
|
|
|
|
|
kappa is the concentration parameter, which must be greater than or
|
|
|
|
|
equal to zero. If kappa is equal to zero, this distribution reduces
|
|
|
|
|
to a uniform random angle over the range 0.0 to 2*pi.
|
|
|
|
|
"""
|
2022-12-05 08:45:21 +08:00
|
|
|
|
def _mod(a: float, b: float):
|
|
|
|
|
@pure
|
|
|
|
|
@llvm
|
|
|
|
|
def _truediv_float_float(self: float, other: float) -> float:
|
|
|
|
|
%0 = fdiv double %self, %other
|
|
|
|
|
ret double %0
|
|
|
|
|
|
|
|
|
|
@pure
|
|
|
|
|
@llvm
|
|
|
|
|
def _mod_float_float(self: float, other: float) -> float:
|
|
|
|
|
%0 = frem double %self, %other
|
|
|
|
|
ret double %0
|
|
|
|
|
|
|
|
|
|
mod = _mod_float_float(a, b)
|
|
|
|
|
div = _truediv_float_float(a - mod, b)
|
|
|
|
|
if mod:
|
|
|
|
|
if (b < 0) != (mod < 0):
|
|
|
|
|
mod += b
|
|
|
|
|
div -= 1.0
|
|
|
|
|
else:
|
|
|
|
|
mod = (0.0).copysign(b)
|
|
|
|
|
return mod
|
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
z = 0.0
|
|
|
|
|
theta = 0.0
|
|
|
|
|
|
|
|
|
|
if kappa <= 1e-6:
|
|
|
|
|
return TWOPI * self.random()
|
|
|
|
|
|
|
|
|
|
s = 0.5 / kappa
|
|
|
|
|
r = s + _sqrt(1.0 + s * s)
|
|
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
|
u1 = self.random()
|
|
|
|
|
z = _cos(_pi * u1)
|
|
|
|
|
|
|
|
|
|
d = z / (r + z)
|
|
|
|
|
u2 = self.random()
|
|
|
|
|
if u2 < 1.0 - d * d or u2 <= (1.0 - d) * _exp(d):
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
q = 1.0 / r
|
|
|
|
|
f = (q + z) / (1.0 + q * z)
|
|
|
|
|
u3 = self.random()
|
|
|
|
|
if u3 > 0.5:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
theta = _mod(mu + _acos(f), TWOPI)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
else:
|
2022-12-05 08:45:21 +08:00
|
|
|
|
theta = _mod(mu - _acos(f), TWOPI)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
return theta
|
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
def sample(self, population: List[T], k: int, T: type):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Chooses k unique random elements from a population sequence or set.
|
|
|
|
|
|
|
|
|
|
Returns a new list containing elements from the population while
|
|
|
|
|
leaving the original population unchanged. The resulting list is
|
|
|
|
|
in selection order so that all sub-slices will also be valid random
|
|
|
|
|
samples. This allows raffle winners (the sample) to be partitioned
|
|
|
|
|
into grand prize and second place winners (the subslices).
|
|
|
|
|
|
|
|
|
|
Members of the population need not be hashable or unique. If the
|
|
|
|
|
population contains repeats, then each occurrence is a possible
|
|
|
|
|
selection in the sample.
|
|
|
|
|
|
|
|
|
|
To choose a sample in a range of integers, use range as an argument.
|
|
|
|
|
This is especially fast and space efficient for sampling from a
|
|
|
|
|
large population: sample(range(10000000), 60)
|
|
|
|
|
|
|
|
|
|
For now seq will deal with only lists.
|
|
|
|
|
"""
|
|
|
|
|
randbelow = self._randbelow_with_getrandbits
|
|
|
|
|
n = len(population)
|
|
|
|
|
if not 0 <= k <= n:
|
|
|
|
|
raise ValueError("Sample larger than population or is negative")
|
|
|
|
|
result = [T() for _ in range(k)]
|
2022-01-24 18:10:09 +08:00
|
|
|
|
setsize = 21.0 # size of a small set minus size of an empty list
|
2021-09-28 02:02:44 +08:00
|
|
|
|
if k > 5:
|
|
|
|
|
# Should be _log(k * 3, 4)
|
2022-01-24 18:10:09 +08:00
|
|
|
|
setsize += 4 ** _ceil(_log(float(k * 3))) # table size for big sets
|
2021-09-28 02:02:44 +08:00
|
|
|
|
if n <= setsize:
|
|
|
|
|
# An n-length list is smaller than a k-length set
|
|
|
|
|
pool = list(population)
|
2022-01-24 18:10:09 +08:00
|
|
|
|
for i in range(k): # invariant: non-selected at [0,n-i)
|
|
|
|
|
j = randbelow(n - i)
|
2021-09-28 02:02:44 +08:00
|
|
|
|
result[i] = pool[j]
|
2022-01-24 18:10:09 +08:00
|
|
|
|
pool[j] = pool[n - i - 1] # move non-selected item into vacancy
|
2021-09-28 02:02:44 +08:00
|
|
|
|
else:
|
|
|
|
|
selected = Set[int]()
|
|
|
|
|
selected_add = selected.add
|
|
|
|
|
for i in range(k):
|
|
|
|
|
j = randbelow(n)
|
|
|
|
|
while j in selected:
|
|
|
|
|
j = randbelow(n)
|
|
|
|
|
selected_add(j)
|
|
|
|
|
result[i] = population[j]
|
|
|
|
|
return result
|
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
def choices(
|
|
|
|
|
self,
|
|
|
|
|
population,
|
|
|
|
|
weights: Optional[List[int]],
|
|
|
|
|
cum_weights: Optional[List[int]],
|
|
|
|
|
k: int,
|
|
|
|
|
):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
"""
|
|
|
|
|
Return a k sized list of population elements chosen with replacement.
|
|
|
|
|
|
|
|
|
|
If the relative weights or cumulative weights are not specified,
|
|
|
|
|
the selections are made with equal probability.
|
|
|
|
|
|
|
|
|
|
Since weights and cum_weights is assumed to be positive, we will replace None with [-1].
|
|
|
|
|
"""
|
2022-01-24 18:10:09 +08:00
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
def accumulate(weights: List[int]) -> List[int]:
|
|
|
|
|
"""
|
|
|
|
|
Calculate cum_weights
|
|
|
|
|
"""
|
|
|
|
|
n = len(weights)
|
|
|
|
|
cum_weight = List[int](n)
|
|
|
|
|
accum = 0
|
|
|
|
|
if n > 0:
|
|
|
|
|
for i in range(n):
|
|
|
|
|
accum += weights[i]
|
|
|
|
|
cum_weight.append(accum)
|
|
|
|
|
|
|
|
|
|
return cum_weight
|
|
|
|
|
|
|
|
|
|
n = len(population)
|
|
|
|
|
if cum_weights is None:
|
|
|
|
|
if weights is None:
|
|
|
|
|
return [population[int(self.random() * n)] for i in range(k)]
|
|
|
|
|
cum_weights = accumulate(weights)
|
|
|
|
|
elif weights is not None:
|
2022-01-24 18:10:09 +08:00
|
|
|
|
raise TypeError("Cannot specify both weights and cumulative weights")
|
2021-09-28 02:02:44 +08:00
|
|
|
|
if len(cum_weights) != n:
|
2022-01-24 18:10:09 +08:00
|
|
|
|
raise ValueError("The number of weights does not match the population")
|
2021-09-28 02:02:44 +08:00
|
|
|
|
|
|
|
|
|
total = float(cum_weights[-1]) # convert to float
|
|
|
|
|
hi = n - 1
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return [
|
|
|
|
|
population[_bisect(cum_weights, int(self.random() * total), 0, hi)]
|
|
|
|
|
for i in range(k)
|
|
|
|
|
]
|
|
|
|
|
|
2022-12-05 08:45:21 +08:00
|
|
|
|
_rnd = Random()
|
2022-01-24 18:10:09 +08:00
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
def seed(a: int):
|
2022-12-05 08:45:21 +08:00
|
|
|
|
_rnd.seed(a)
|
2022-01-24 18:10:09 +08:00
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
def getrandbits(k: int):
|
|
|
|
|
return _rnd.getrandbits(k)
|
|
|
|
|
|
|
|
|
|
def randrange(start: int, stop: Optional[int] = None, step: int = 1):
|
2022-08-03 02:53:17 +08:00
|
|
|
|
return _rnd.randrange(start, stop, step) if stop is not None else _rnd.randrange(0, start, step)
|
2022-01-24 18:10:09 +08:00
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
def randint(a: int, b: int):
|
|
|
|
|
return _rnd.randint(a, b)
|
|
|
|
|
|
|
|
|
|
def choice(s):
|
|
|
|
|
return _rnd.choice(s)
|
|
|
|
|
|
2022-01-24 18:10:09 +08:00
|
|
|
|
def choices(
|
|
|
|
|
population,
|
|
|
|
|
weights: Optional[List[int]] = None,
|
|
|
|
|
cum_weights: Optional[List[int]] = None,
|
|
|
|
|
k: int = 1,
|
|
|
|
|
):
|
2021-09-28 02:02:44 +08:00
|
|
|
|
return _rnd.choices(population, weights, cum_weights, k)
|
|
|
|
|
|
|
|
|
|
def shuffle(s):
|
|
|
|
|
_rnd.shuffle(s)
|
|
|
|
|
|
|
|
|
|
def sample(population, k: int):
|
|
|
|
|
return _rnd.sample(population, k)
|
|
|
|
|
|
|
|
|
|
def random():
|
|
|
|
|
return _rnd.random()
|
|
|
|
|
|
|
|
|
|
def uniform(a, b):
|
|
|
|
|
return _rnd.uniform(a, b)
|
|
|
|
|
|
|
|
|
|
def triangular(low: float = 0.0, high: float = 1.0, mode: Optional[float] = None):
|
2022-01-24 18:10:09 +08:00
|
|
|
|
return _rnd.triangular(low, high, mode if mode is not None else (low + high) / 2)
|
|
|
|
|
|
2021-09-28 02:02:44 +08:00
|
|
|
|
def betavariate(alpha: float, beta: float):
|
|
|
|
|
return _rnd.betavariate(alpha, beta)
|
|
|
|
|
|
|
|
|
|
def expovariate(lambd: float):
|
|
|
|
|
return _rnd.expovariate(lambd)
|
|
|
|
|
|
|
|
|
|
def gammavariate(alpha: float, beta: float):
|
|
|
|
|
return _rnd.gammavariate(alpha, beta)
|
|
|
|
|
|
|
|
|
|
def gauss(mu: float, sigma: float):
|
|
|
|
|
return _rnd.gauss(mu, sigma)
|
|
|
|
|
|
|
|
|
|
def lognormvariate(mu: float, sigma: float):
|
|
|
|
|
return _rnd.lognormvariate(mu, sigma)
|
|
|
|
|
|
|
|
|
|
def normalvariate(mu: float, sigma: float):
|
|
|
|
|
return _rnd.normalvariate(mu, sigma)
|
|
|
|
|
|
|
|
|
|
def vonmisesvariate(mu: float, kappa: float):
|
|
|
|
|
return _rnd.vonmisesvariate(mu, kappa)
|
|
|
|
|
|
|
|
|
|
def paretovariate(alpha: float):
|
|
|
|
|
return _rnd.paretovariate(alpha)
|
|
|
|
|
|
|
|
|
|
def weibullvariate(alpha: float, beta: float):
|
|
|
|
|
return _rnd.weibullvariate(alpha, beta)
|