1
0
mirror of https://github.com/exaloop/codon.git synced 2025-06-03 15:03:52 +08:00
codon/stdlib/numpy/random/seed.codon

169 lines
5.4 KiB
Python
Raw Normal View History

# Copyright (C) 2022-2025 Exaloop Inc. <https://exaloop.io>
from ..routines import *
DEFAULT_POOL_SIZE = 4
INIT_A = u32(0x43b0d7e5)
MULT_A = u32(0x931e8875)
INIT_B = u32(0x8b51f9dd)
MULT_B = u32(0x58f38ded)
MIX_MULT_L = u32(0xca01f9dd)
MIX_MULT_R = u32(0x4973f715)
XSHIFT = u32(4 * 8 // 2)
MASK32 = u32(0xFFFFFFFF)
def _int_to_uint32_array(n: int):
if n < 0:
raise ValueError("expected non-negative integer")
elif n < int(MASK32):
return array((u32(n),))
else:
return array((u32(n & 0xFFFFFFFF), u32(n >> 32)))
def _coerce_to_uint32_array(x):
if isinstance(x, ndarray):
if type(x.data[0]) is u32:
return x.copy()
if isinstance(x, str):
if x.startswith('0x'):
return _int_to_uint32_array(int(x, base=16))
elif x.isdigit():
return _int_to_uint32_array(int(x))
else:
raise ValueError("unrecognized seed string")
if isinstance(x, int):
return _int_to_uint32_array(x)
if isinstance(x, float):
compile_error("seed must be integer")
if len(x) == 0:
return empty(0, dtype=u32)
subseqs = [_coerce_to_uint32_array(v) for v in x]
return concatenate(subseqs)
def hashmix(value: u32, hash_const: u32):
value ^= hash_const
hash_const *= MULT_A
value *= hash_const
value ^= value >> XSHIFT
return value, hash_const
def mix(x: u32, y: u32):
result = (MIX_MULT_L * x - MIX_MULT_R * y)
result ^= result >> XSHIFT
return result
# TODO: use NumPy's default seeding scheme,
# which falls back to Python's os.urandom()
def random_seed_time_pid():
now = _C.seq_time() * 1000
key = empty((5,), dtype=u32)
key[0] = u32(now & 0xFFFFFFFF)
key[1] = u32(now >> 32)
key[2] = u32(_C.seq_pid())
now = _C.seq_time_monotonic()
key[3] = u32(now & 0xFFFFFFFF)
key[4] = u32(now >> 32)
return key
class SeedSequence:
entropy: ndarray[u32, 1]
_spawn_key: Optional[List[int]]
pool_size: int
n_children_spawned: int
pool: ndarray[u32, 1]
@property
def spawn_key(self) -> List[int]:
if self._spawn_key is not None:
return self._spawn_key.copy()
else:
return []
def __init__(self,
entropy = None,
spawn_key: Optional[List[int]] = None,
pool_size: int = DEFAULT_POOL_SIZE,
n_children_spawned: int = 0):
if pool_size < DEFAULT_POOL_SIZE:
raise ValueError(f"The size of the entropy pool should be at least {DEFAULT_POOL_SIZE}")
if entropy is None:
run_entropy = random_seed_time_pid()
else:
run_entropy = _coerce_to_uint32_array(entropy)
spawn_entropy = _coerce_to_uint32_array(spawn_key) if spawn_key is not None else empty(0, dtype=u32)
# assemble entropy
if len(spawn_entropy) > 0 and len(run_entropy) < pool_size:
diff = pool_size - len(run_entropy)
run_entropy = concatenate((run_entropy, zeros(diff, dtype=u32)))
entropy_array = concatenate((run_entropy, spawn_entropy))
self.entropy = entropy_array # standard NumPy stores original entropy, but much easier to just store array
self._spawn_key = spawn_key
self.pool_size = pool_size
self.n_children_spawned = n_children_spawned
self.pool = zeros(pool_size, dtype=u32)
# mix entropy
mixer = self.pool
hash_const = INIT_A
for i in range(len(mixer)):
if i < len(entropy_array):
mixer[i], hash_const = hashmix(entropy_array[i], hash_const)
else:
mixer[i], hash_const = hashmix(u32(0), hash_const)
for i_src in range(len(mixer)):
for i_dst in range(len(mixer)):
if i_src != i_dst:
value, hash_const = hashmix(mixer[i_src], hash_const)
mixer[i_dst] = mix(mixer[i_dst], value)
for i_src in range(len(mixer), len(entropy_array)):
for i_dst in range(len(mixer)):
value, hash_const = hashmix(entropy_array[i_src], hash_const)
mixer[i_dst] = mix(mixer[i_dst], value)
def generate_state(self, n_words: int, dtype: type = u32):
hash_const = INIT_B
if dtype is u64:
n_words *= 2
elif dtype is not u32:
compile_error("only support uint32 or uint64")
state = zeros(n_words, dtype=u32)
pool = self.pool
npool = len(self.pool)
for i_dst in range(n_words):
data_val = pool[i_dst % npool]
data_val ^= hash_const
hash_const *= MULT_B
data_val *= hash_const
data_val ^= data_val >> XSHIFT
state[i_dst] = data_val
if dtype is u64:
return ndarray((n_words // 2,), Ptr[u64](state.data.as_byte()))
else:
return state
def spawn(self, n_children: int):
seqs = []
sk: List[int] = self._spawn_key if self._spawn_key is not None else []
for i in range(self.n_children_spawned,
self.n_children_spawned + n_children):
seqs.append(SeedSequence(
self.entropy,
spawn_key=(sk + [i]),
pool_size=self.pool_size
))
return seqs