use scirs2_core::random::prelude::*;
const POOL_SIZE: usize = 4;
const INIT_A: u32 = 0x43b0_d7e5;
const MULT_A: u32 = 0x931e_8875;
const INIT_B: u32 = 0x8b51_f9dd;
const MULT_B: u32 = 0x58f3_8ded;
const MIX_MULT_L: u32 = 0xca01_f9dd;
const MIX_MULT_R: u32 = 0x4973_f715;
const XSHIFT: u32 = 16;
fn int_to_u32_words(mut n: u64) -> Vec<u32> {
if n == 0 {
return vec![0];
}
let mut words = Vec::with_capacity(2);
while n > 0 {
words.push((n & 0xFFFF_FFFF) as u32);
n >>= 32;
}
words
}
struct HashMix {
hash_const: u32,
mult: u32,
}
impl HashMix {
fn new(init: u32, mult: u32) -> Self {
Self {
hash_const: init,
mult,
}
}
fn mix(&mut self, value: u32) -> u32 {
let mut v = value ^ self.hash_const;
self.hash_const = self.hash_const.wrapping_mul(self.mult);
v = v.wrapping_mul(self.hash_const);
v ^= v >> XSHIFT;
v
}
}
fn mix_words(x: u32, y: u32) -> u32 {
let mut result = MIX_MULT_L
.wrapping_mul(x)
.wrapping_sub(MIX_MULT_R.wrapping_mul(y));
result ^= result >> XSHIFT;
result
}
#[derive(Debug, Clone)]
pub struct SeedSequence {
run_entropy: Vec<u32>,
spawn_key: Vec<u32>,
pool: Vec<u32>,
n_children_spawned: u32,
}
impl SeedSequence {
pub fn new(entropy: u64) -> Self {
Self::from_entropy_words(int_to_u32_words(entropy))
}
pub fn from_u64_sequence(entropy: &[u64]) -> Self {
let words = entropy.iter().flat_map(|&e| int_to_u32_words(e)).collect();
Self::from_entropy_words(words)
}
pub fn from_entropy_words(words: Vec<u32>) -> Self {
Self::from_parts(words, Vec::new())
}
pub fn from_os_entropy() -> Self {
let mut rng = thread_rng();
let words: Vec<u32> = (0..POOL_SIZE).map(|_| rng.random::<u32>()).collect();
Self::from_entropy_words(words)
}
fn from_parts(run_entropy: Vec<u32>, spawn_key: Vec<u32>) -> Self {
let mut seq = Self {
run_entropy,
spawn_key,
pool: vec![0u32; POOL_SIZE],
n_children_spawned: 0,
};
seq.mix_entropy();
seq
}
fn assembled_entropy(&self) -> Vec<u32> {
let mut run_entropy = self.run_entropy.clone();
if !self.spawn_key.is_empty() && run_entropy.len() < POOL_SIZE {
run_entropy.resize(POOL_SIZE, 0);
}
run_entropy.extend_from_slice(&self.spawn_key);
run_entropy
}
fn mix_entropy(&mut self) {
let entropy = self.assembled_entropy();
let mut hm = HashMix::new(INIT_A, MULT_A);
for (i, slot) in self.pool.iter_mut().enumerate() {
let value = entropy.get(i).copied().unwrap_or(0);
*slot = hm.mix(value);
}
let n = self.pool.len();
for i_src in 0..n {
for i_dst in 0..n {
if i_src == i_dst {
continue;
}
let src_val = self.pool[i_src];
let mixed_src = hm.mix(src_val);
self.pool[i_dst] = mix_words(self.pool[i_dst], mixed_src);
}
}
for &extra in entropy.iter().skip(n) {
for slot in self.pool.iter_mut() {
let mixed_src = hm.mix(extra);
*slot = mix_words(*slot, mixed_src);
}
}
}
pub fn generate_state_u32(&self, n_words: usize) -> Vec<u32> {
let mut hm = HashMix::new(INIT_B, MULT_B);
let pool_len = self.pool.len();
(0..n_words)
.map(|i| {
let data_val = self.pool[i % pool_len];
hm.mix(data_val)
})
.collect()
}
pub fn generate_state_u64(&self, n_words: usize) -> Vec<u64> {
let words = self.generate_state_u32(n_words * 2);
words
.chunks_exact(2)
.map(|pair| (pair[0] as u64) | ((pair[1] as u64) << 32))
.collect()
}
pub fn spawn(&mut self, n_children: usize) -> Vec<SeedSequence> {
let mut children = Vec::with_capacity(n_children);
for offset in 0..n_children {
let child_index = self.n_children_spawned + offset as u32;
let mut spawn_key = self.spawn_key.clone();
spawn_key.push(child_index);
children.push(SeedSequence::from_parts(
self.run_entropy.clone(),
spawn_key,
));
}
self.n_children_spawned += n_children as u32;
children
}
pub fn pool(&self) -> &[u32] {
&self.pool
}
pub fn spawn_key(&self) -> &[u32] {
&self.spawn_key
}
pub fn n_children_spawned(&self) -> u32 {
self.n_children_spawned
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_state_u32_matches_numpy_entropy_0() {
let seq = SeedSequence::new(0);
assert_eq!(
seq.generate_state_u32(4),
vec![2968811710, 3677149159, 745650761, 2884920346]
);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_1() {
let seq = SeedSequence::new(1);
assert_eq!(
seq.generate_state_u32(4),
vec![1835504127, 1731038949, 1320224556, 2330041505]
);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_42() {
let seq = SeedSequence::new(42);
assert_eq!(
seq.generate_state_u32(4),
vec![3444837047, 2669555309, 2046530742, 3581440988]
);
assert_eq!(seq.pool(), &[1662858758, 128880814, 1875164712, 753753205]);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_12345() {
let seq = SeedSequence::new(12345);
assert_eq!(
seq.generate_state_u32(4),
vec![2688385916, 3048105090, 4196366895, 3152189807]
);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_2_pow_31() {
let seq = SeedSequence::new(1u64 << 31);
assert_eq!(
seq.generate_state_u32(4),
vec![3155214650, 3649071964, 1060818499, 334526526]
);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_2_pow_63_plus_7() {
let seq = SeedSequence::new((1u64 << 63) + 7);
assert_eq!(
seq.generate_state_u32(4),
vec![319677963, 4187467128, 3671185491, 765353098]
);
}
#[test]
fn generate_state_u32_matches_numpy_entropy_u64_max() {
let seq = SeedSequence::new(u64::MAX);
assert_eq!(
seq.generate_state_u32(4),
vec![2458692877, 2931597649, 2251873402, 295448644]
);
}
#[test]
fn generate_state_u32_matches_numpy_sequence_entropy() {
let seq = SeedSequence::from_u64_sequence(&[1, 2, 3]);
assert_eq!(
seq.generate_state_u32(4),
vec![3822189696, 3026158655, 540542919, 1119972918]
);
}
#[test]
fn generate_state_u32_matches_numpy_sequence_entropy_with_a_wide_element() {
let seq = SeedSequence::from_u64_sequence(&[1u64 << 40, 5]);
assert_eq!(
seq.generate_state_u32(4),
vec![2527941005, 1857525667, 905782527, 2553882280]
);
}
#[test]
fn generate_state_u32_of_8_matches_numpy_entropy_42() {
let seq = SeedSequence::new(42);
assert_eq!(
seq.generate_state_u32(8),
vec![
3444837047, 2669555309, 2046530742, 3581440988, 1691623607, 2099784219, 1184028159,
862288241
]
);
}
#[test]
fn generate_state_u64_matches_numpy_entropy_42() {
let seq = SeedSequence::new(42);
assert_eq!(
seq.generate_state_u64(2),
vec![11465652750463011511, 15382171918060459190]
);
assert_eq!(
seq.generate_state_u64(4),
vec![
11465652750463011511,
15382171918060459190,
9018504550953525431,
3703499796004394495,
]
);
}
#[test]
fn spawn_matches_numpy_entropy_42() {
let mut seq = SeedSequence::new(42);
let children = seq.spawn(3);
assert_eq!(children.len(), 3);
assert_eq!(seq.n_children_spawned(), 3);
assert_eq!(children[0].spawn_key(), &[0]);
assert_eq!(
children[0].generate_state_u32(4),
vec![2684470948, 3757501821, 1691896351, 1126406280]
);
assert_eq!(children[1].spawn_key(), &[1]);
assert_eq!(
children[1].generate_state_u32(4),
vec![4091952314, 31242083, 366899054, 1794014678]
);
assert_eq!(children[2].spawn_key(), &[2]);
assert_eq!(
children[2].generate_state_u32(4),
vec![233227757, 2701265274, 3388095807, 2508111505]
);
}
#[test]
fn spawn_of_spawn_matches_numpy_entropy_42() {
let mut seq = SeedSequence::new(42);
let mut children = seq.spawn(3);
let grandchildren = children[0].spawn(2);
assert_eq!(grandchildren[0].spawn_key(), &[0, 0]);
assert_eq!(
grandchildren[0].generate_state_u32(4),
vec![3142992634, 1861194734, 1430013548, 2319789260]
);
assert_eq!(grandchildren[1].spawn_key(), &[0, 1]);
assert_eq!(
grandchildren[1].generate_state_u32(4),
vec![3908812709, 3341582407, 3454793571, 679120907]
);
}
#[test]
fn spawn_never_reuses_a_key_across_repeated_calls() {
let mut seq = SeedSequence::new(7);
let first = seq.spawn(2);
let second = seq.spawn(2);
assert_eq!(first[0].spawn_key(), &[0]);
assert_eq!(first[1].spawn_key(), &[1]);
assert_eq!(second[0].spawn_key(), &[2]);
assert_eq!(second[1].spawn_key(), &[3]);
assert_ne!(
first[0].generate_state_u32(4),
second[0].generate_state_u32(4)
);
}
#[test]
fn from_os_entropy_produces_varying_state() {
let a = SeedSequence::from_os_entropy();
let b = SeedSequence::from_os_entropy();
assert_ne!(a.generate_state_u32(4), b.generate_state_u32(4));
}
#[test]
fn new_is_deterministic() {
let a = SeedSequence::new(999);
let b = SeedSequence::new(999);
assert_eq!(a.generate_state_u32(8), b.generate_state_u32(8));
assert_eq!(a.pool(), b.pool());
}
}