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;
const POOL_SIZE: usize = 4;
const PCG_MULTIPLIER: u128 = (2_549_297_995_355_413_924u128 << 64) + 4_865_540_595_714_422_341u128;
#[derive(Clone, Debug)]
pub(crate) struct SeedSequence {
pool: [u32; POOL_SIZE],
}
impl SeedSequence {
pub(crate) fn new(entropy: u128) -> Self {
let words = int_to_uint32_words(entropy);
let mut pool = [0u32; POOL_SIZE];
let mut hash_const = INIT_A;
let mut hashmix = |value: u32| -> u32 {
let mut value = value ^ hash_const;
hash_const = hash_const.wrapping_mul(MULT_A);
value = value.wrapping_mul(hash_const);
value ^ (value >> XSHIFT)
};
for (i, slot) in pool.iter_mut().enumerate() {
*slot = hashmix(words.get(i).copied().unwrap_or(0));
}
for i_src in 0..POOL_SIZE {
for i_dst in 0..POOL_SIZE {
if i_src != i_dst {
let hashed = hashmix(pool[i_src]);
pool[i_dst] = mix(pool[i_dst], hashed);
}
}
}
for word in words.iter().skip(POOL_SIZE) {
for slot in pool.iter_mut() {
let hashed = hashmix(*word);
*slot = mix(*slot, hashed);
}
}
SeedSequence { pool }
}
pub(crate) fn generate_state_u64(&self, n_words: usize) -> Vec<u64> {
let mut hash_const = INIT_B;
let words: Vec<u32> = (0..n_words * 2)
.map(|i_dst| {
let mut data = self.pool[i_dst % POOL_SIZE] ^ hash_const;
hash_const = hash_const.wrapping_mul(MULT_B);
data = data.wrapping_mul(hash_const);
data ^ (data >> XSHIFT)
})
.collect();
words
.chunks_exact(2)
.map(|pair| u64::from(pair[0]) | (u64::from(pair[1]) << 32))
.collect()
}
}
fn mix(x: u32, y: u32) -> u32 {
let result = MIX_MULT_L
.wrapping_mul(x)
.wrapping_sub(MIX_MULT_R.wrapping_mul(y));
result ^ (result >> XSHIFT)
}
fn int_to_uint32_words(mut value: u128) -> Vec<u32> {
if value == 0 {
return vec![0];
}
let mut words = Vec::new();
while value > 0 {
words.push((value & 0xFFFF_FFFF) as u32);
value >>= 32;
}
words
}
#[derive(Clone, Debug)]
pub(crate) struct Pcg64 {
state: u128,
inc: u128,
has_uint32: bool,
uinteger: u32,
}
impl Pcg64 {
pub(crate) fn from_seed_sequence(seed_sequence: &SeedSequence) -> Self {
let words = seed_sequence.generate_state_u64(4);
let initstate = (u128::from(words[0]) << 64) | u128::from(words[1]);
let initseq = (u128::from(words[2]) << 64) | u128::from(words[3]);
let mut rng = Pcg64 {
state: 0,
inc: (initseq << 1) | 1,
has_uint32: false,
uinteger: 0,
};
rng.step();
rng.state = rng.state.wrapping_add(initstate);
rng.step();
rng
}
fn step(&mut self) {
self.state = self.state.wrapping_mul(PCG_MULTIPLIER).wrapping_add(self.inc);
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.step();
let xored = ((self.state >> 64) as u64) ^ (self.state as u64);
let rotation = (self.state >> 122) as u32;
xored.rotate_right(rotation)
}
pub(crate) fn next_u32(&mut self) -> u32 {
if self.has_uint32 {
self.has_uint32 = false;
return self.uinteger;
}
let next = self.next_u64();
self.has_uint32 = true;
self.uinteger = (next >> 32) as u32;
(next & 0xFFFF_FFFF) as u32
}
fn bounded_lemire_u32(&mut self, rng: u32) -> u32 {
let rng_excl = rng + 1;
let mut m = u64::from(self.next_u32()) * u64::from(rng_excl);
let mut leftover = (m & 0xFFFF_FFFF) as u32;
if leftover < rng_excl {
let threshold = (u32::MAX - rng) % rng_excl;
while leftover < threshold {
m = u64::from(self.next_u32()) * u64::from(rng_excl);
leftover = (m & 0xFFFF_FFFF) as u32;
}
}
(m >> 32) as u32
}
fn bounded_lemire_u64(&mut self, rng: u64) -> u64 {
let rng_excl = rng + 1;
let mut m = u128::from(self.next_u64()) * u128::from(rng_excl);
let mut leftover = m as u64;
if leftover < rng_excl {
let threshold = (u64::MAX - rng) % rng_excl;
while leftover < threshold {
m = u128::from(self.next_u64()) * u128::from(rng_excl);
leftover = m as u64;
}
}
(m >> 64) as u64
}
pub(crate) fn random_bounded_u64(&mut self, rng: u64) -> u64 {
if rng == 0 {
0
} else if rng <= 0xFFFF_FFFF {
if rng == 0xFFFF_FFFF {
u64::from(self.next_u32())
} else {
u64::from(self.bounded_lemire_u32(rng as u32))
}
} else if rng == u64::MAX {
self.next_u64()
} else {
self.bounded_lemire_u64(rng)
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct Generator {
bit_generator: Pcg64,
}
impl Generator {
pub(crate) fn default_rng(seed: Option<u64>) -> Self {
let entropy = match seed {
Some(seed) => u128::from(seed),
None => {
let mut bytes = [0u8; 16];
getrandom::fill(&mut bytes).expect("the operating system entropy source is unavailable");
u128::from_le_bytes(bytes)
}
};
Generator {
bit_generator: Pcg64::from_seed_sequence(&SeedSequence::new(entropy)),
}
}
pub(crate) fn integers(&mut self, low: i64, high: i64, count: usize) -> Vec<i64> {
assert!(high > low, "integers requires high > low");
let rng = (high - 1).wrapping_sub(low) as u64;
let offset = low as u64;
let bits = &mut self.bit_generator;
(0..count)
.map(|_| {
let draw = if rng == 0 {
0
} else if rng <= 0xFFFF_FFFF {
if rng == 0xFFFF_FFFF {
u64::from(bits.next_u32())
} else {
u64::from(bits.bounded_lemire_u32(rng as u32))
}
} else if rng == u64::MAX {
bits.next_u64()
} else {
bits.bounded_lemire_u64(rng)
};
offset.wrapping_add(draw) as i64
})
.collect()
}
pub(crate) fn choice_without_replacement(&mut self, pop_size: i64, size: i64) -> Vec<i64> {
assert!(
(0..=pop_size).contains(&size),
"Cannot take a larger sample than population"
);
let bits = &mut self.bit_generator;
let cutoff = 50;
if pop_size > 10_000 && size > pop_size / cutoff {
let mut idx: Vec<i64> = (0..pop_size).collect();
shuffle_int(
bits,
pop_size as usize,
(pop_size - size).max(1) as usize,
&mut idx,
);
return idx[(pop_size - size) as usize..].to_vec();
}
let mut idx = vec![0i64; size as usize];
let set_size = (1.2 * size as f64) as u64;
let mask = gen_mask(set_size);
let mut hash_set = vec![u64::MAX; (mask + 1) as usize];
for j in (pop_size - size)..pop_size {
let val = bits.random_bounded_u64(j as u64);
let mut loc = val & mask;
while hash_set[loc as usize] != u64::MAX && hash_set[loc as usize] != val {
loc = (loc + 1) & mask;
}
let slot = (j - pop_size + size) as usize;
if hash_set[loc as usize] == u64::MAX {
hash_set[loc as usize] = val;
idx[slot] = val as i64;
} else {
loc = (j as u64) & mask;
while hash_set[loc as usize] != u64::MAX {
loc = (loc + 1) & mask;
}
hash_set[loc as usize] = j as u64;
idx[slot] = j;
}
}
shuffle_int(bits, size as usize, 1, &mut idx);
idx
}
}
fn shuffle_int(bits: &mut Pcg64, n: usize, first: usize, data: &mut [i64]) {
for i in (first..n).rev() {
let j = bits.random_bounded_u64(i as u64) as usize;
data.swap(i, j);
}
}
fn gen_mask(max_val: u64) -> u64 {
let mut mask = max_val;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;
mask |= mask >> 32;
mask
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_words_follow_numpy() {
assert_eq!(int_to_uint32_words(0), vec![0]);
assert_eq!(int_to_uint32_words(42), vec![42]);
assert_eq!(int_to_uint32_words((1u128 << 32) + 5), vec![5, 1]);
}
#[test]
fn gen_mask_is_smallest_all_ones_mask() {
assert_eq!(gen_mask(0), 0);
assert_eq!(gen_mask(1), 1);
assert_eq!(gen_mask(600), 1023);
assert_eq!(gen_mask(1024), 2047);
}
#[test]
fn choice_returns_distinct_values_in_range() {
for (pop, size) in [(10, 3), (600, 500), (20_000, 500), (20_000, 300)] {
let mut generator = Generator::default_rng(Some(7));
let mut values = generator.choice_without_replacement(pop, size);
assert_eq!(values.len(), size as usize);
assert!(values.iter().all(|value| (0..pop).contains(value)));
values.sort_unstable();
values.dedup();
assert_eq!(values.len(), size as usize);
}
}
#[test]
fn integers_stay_in_range() {
let mut generator = Generator::default_rng(None);
assert!(
generator
.integers(0, 17, 1000)
.iter()
.all(|value| (0..17).contains(value))
);
assert!(generator.integers(0, 1, 10).iter().all(|value| *value == 0));
}
}