use rand::SeedableRng;
use rand::rngs::StdRng;
use rlevo_core::util::seed::splitmix64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SeedPurpose {
Init = 0,
Selection = 1,
Crossover = 2,
Mutation = 3,
Replacement = 4,
Trial = 5,
Other = 6,
LocalSearch = 7,
EdaSampling = 8,
Representative = 9,
Transposition = 10,
CmaSampling = 11,
}
impl SeedPurpose {
const fn constant(self) -> u64 {
match self {
SeedPurpose::Init => 0xA5A5_A5A5_A5A5_A5A5,
SeedPurpose::Selection => 0x1234_5678_9ABC_DEF0,
SeedPurpose::Crossover => 0xDEAD_BEEF_CAFE_F00D,
SeedPurpose::Mutation => 0xFEED_FACE_0BAD_F00D,
SeedPurpose::Replacement => 0x0123_4567_89AB_CDEF,
SeedPurpose::Trial => 0xBAAD_F00D_DEAD_C0DE,
SeedPurpose::Other => 0x9E37_79B9_7F4A_7C15,
SeedPurpose::LocalSearch => 0xC0FF_EE15_600D_F00D,
SeedPurpose::EdaSampling => 0xEDA0_5EED_BEEF_CAFE,
SeedPurpose::Representative => 0xC0EA_5E1E_C7ED_0009,
SeedPurpose::Transposition => 0x7A05_9051_70F0_000A,
SeedPurpose::CmaSampling => 0xC3A0_5EED_1AC0_B11B,
}
}
}
#[must_use]
pub fn seed_stream(base: u64, generation: u64, purpose: SeedPurpose) -> StdRng {
let mut x = base
.wrapping_add(generation.wrapping_mul(0x9E37_79B9_7F4A_7C15))
.wrapping_add(purpose.constant());
x = splitmix64(x);
x = splitmix64(x);
StdRng::seed_from_u64(x)
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{Rng, RngExt};
#[test]
fn seed_stream_is_deterministic() {
let mut a = seed_stream(42, 0, SeedPurpose::Init);
let mut b = seed_stream(42, 0, SeedPurpose::Init);
for _ in 0..8 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
const ALL_PURPOSES: [SeedPurpose; 12] = [
SeedPurpose::Init,
SeedPurpose::Selection,
SeedPurpose::Crossover,
SeedPurpose::Mutation,
SeedPurpose::Replacement,
SeedPurpose::Trial,
SeedPurpose::Other,
SeedPurpose::LocalSearch,
SeedPurpose::EdaSampling,
SeedPurpose::Representative,
SeedPurpose::Transposition,
SeedPurpose::CmaSampling,
];
#[allow(dead_code)]
fn _exhaustiveness_guard(p: SeedPurpose) {
match p {
SeedPurpose::Init
| SeedPurpose::Selection
| SeedPurpose::Crossover
| SeedPurpose::Mutation
| SeedPurpose::Replacement
| SeedPurpose::Trial
| SeedPurpose::Other
| SeedPurpose::LocalSearch
| SeedPurpose::EdaSampling
| SeedPurpose::Representative
| SeedPurpose::Transposition
| SeedPurpose::CmaSampling => {}
}
}
#[test]
fn all_purpose_domain_constants_are_pairwise_distinct() {
for (i, &p) in ALL_PURPOSES.iter().enumerate() {
for &q in &ALL_PURPOSES[i + 1..] {
assert_ne!(
p.constant(),
q.constant(),
"domain constants collide for {p:?} and {q:?}"
);
}
}
}
#[test]
fn all_purposes_produce_distinct_first_draws() {
for (i, &p) in ALL_PURPOSES.iter().enumerate() {
let first_p = seed_stream(42, 0, p).next_u64();
for &q in &ALL_PURPOSES[i + 1..] {
let first_q = seed_stream(42, 0, q).next_u64();
assert_ne!(first_p, first_q, "first draws collide for {p:?} and {q:?}");
}
}
}
#[test]
fn different_generations_produce_different_streams() {
let a = seed_stream(42, 0, SeedPurpose::Mutation).next_u64();
let b = seed_stream(42, 1, SeedPurpose::Mutation).next_u64();
assert_ne!(a, b);
}
#[test]
fn different_bases_produce_different_streams() {
let a = seed_stream(1, 0, SeedPurpose::Init).next_u64();
let b = seed_stream(2, 0, SeedPurpose::Init).next_u64();
assert_ne!(a, b);
}
#[test]
fn rng_generates_bounded_values() {
let mut rng = seed_stream(7, 0, SeedPurpose::Init);
let x: u32 = rng.random_range(0..100);
assert!(x < 100);
}
}