use crate::bits::check_probability;
use crate::errors::{Result, TicitError};
pub fn next_random_u64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
pub fn rand_float(state: &mut u64) -> f64 {
((next_random_u64(state) >> 11) as f64) * f64::from_bits(0x3CA0_0000_0000_0000)
}
pub fn sample_bernoulli(rng_state: &mut u64, probability: f64) -> Result<bool> {
let p = check_probability(probability)?;
if p <= 0.0 {
return Ok(false);
}
if p >= 1.0 {
return Ok(true);
}
Ok(rand_float(rng_state) < p)
}
pub fn sample_categorical_row(rng_state: &mut u64, probabilities: &[f64]) -> usize {
let r = rand_float(rng_state);
let mut cumulative = 0.0;
for (i, &probability) in probabilities.iter().enumerate() {
cumulative += probability;
if r <= cumulative {
return i;
}
}
probabilities.len() - 1
}
pub fn geometric_gap_denominator(probability: f64) -> Result<f64> {
if !(probability > 0.0 && probability < 1.0) {
return Err(TicitError::new(
"geometric gap probability must be in (0, 1)",
));
}
Ok((-probability).ln_1p())
}
pub fn sample_geometric_gap_with_denominator(rng_state: &mut u64, denominator: f64) -> f64 {
let u = rand_float(rng_state).max(f64::MIN_POSITIVE);
let gap = (u.ln() / denominator).floor();
if !gap.is_finite() || gap >= i32::MAX as f64 {
return i32::MAX as f64;
}
gap
}
pub fn block_seed(base: u64, seed: u64, block_index: u64) -> u64 {
base ^ 0x9e37_79b9_7f4a_7c15u64.wrapping_mul(block_index.wrapping_add(1))
^ 0xbf58_476d_1ce4_e5b9u64.wrapping_mul(seed.wrapping_add(1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splitmix64_matches_the_reference_sequence() {
let mut state = 0u64;
assert_eq!(next_random_u64(&mut state), 0xe220a8397b1dcdaf);
assert_eq!(next_random_u64(&mut state), 0x6e789e6aa1b965f4);
assert_eq!(next_random_u64(&mut state), 0x06c45d188009454f);
assert_eq!(state, 0x9e3779b97f4a7c15u64.wrapping_mul(3));
let mut state = 1u64;
assert_eq!(next_random_u64(&mut state), 0x910a2dec89025cc1);
let mut state = 42u64;
assert_eq!(next_random_u64(&mut state), 0xbdd732262feb6e95);
}
#[test]
fn rand_float_uses_the_high_53_bits() {
let mut state = 0u64;
let expected = ((0xe220a8397b1dcdafu64 >> 11) as f64) * (0.5f64).powi(53);
let drawn = rand_float(&mut state);
assert_eq!(drawn, expected);
#[allow(clippy::excessive_precision)]
let pinned = 0.88331080821364261_f64;
assert_eq!(drawn, pinned);
}
#[test]
fn certain_bernoulli_consumes_no_randomness() {
let mut state = 7u64;
assert!(!sample_bernoulli(&mut state, 0.0).expect("valid probability"));
assert!(sample_bernoulli(&mut state, 1.0).expect("valid probability"));
assert_eq!(
state, 7,
"deterministic outcomes must not advance the state"
);
sample_bernoulli(&mut state, 0.5).expect("valid probability");
assert_ne!(state, 7);
}
#[test]
fn bernoulli_rejects_invalid_probability() {
let mut state = 1u64;
assert!(sample_bernoulli(&mut state, -0.1).is_err());
assert!(sample_bernoulli(&mut state, f64::NAN).is_err());
}
#[test]
fn categorical_walk_is_inclusive_and_falls_through() {
let mut state = 0u64;
let row = sample_categorical_row(&mut state, &[0.0, 0.0]);
assert_eq!(row, 1);
let mut state = 0u64;
assert_eq!(sample_categorical_row(&mut state, &[1.0, 0.0]), 0);
}
#[test]
fn block_seed_separates_seeds_and_blocks() {
let a = block_seed(0x7eed0000, 0, 0);
assert_ne!(a, block_seed(0x7eed0000, 1, 0));
assert_ne!(a, block_seed(0x7eed0000, 0, 1));
assert_ne!(a, block_seed(0x5eed1234, 0, 0));
assert_eq!(
block_seed(2, 3, 4),
2u64 ^ 0x9e37_79b9_7f4a_7c15u64.wrapping_mul(5)
^ 0xbf58_476d_1ce4_e5b9u64.wrapping_mul(4)
);
}
}