1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct SimulationSeed {
23 value: usize,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum SeedError {
28 ZeroCount,
29}
30
31impl SimulationSeed {
32 pub const fn new(value: usize) -> Self {
33 Self { value }
34 }
35
36 pub const fn value(self) -> usize {
37 self.value
38 }
39
40 pub fn mix(self, salt: usize) -> Self {
41 let salted = self
42 .value
43 .wrapping_add(salt.wrapping_mul(0x9E37_79B1usize))
44 .wrapping_add(0x85EB_CA6Busize);
45
46 Self {
47 value: mix_value(salted),
48 }
49 }
50
51 pub fn split(self, count: usize) -> Result<Vec<Self>, SeedError> {
52 if count == 0 {
53 return Err(SeedError::ZeroCount);
54 }
55
56 Ok((0..count).map(|index| self.mix(index + 1)).collect())
57 }
58
59 pub fn to_unit_f64(self) -> f64 {
60 mix_value(self.value) as f64 / usize::MAX as f64
61 }
62}
63
64pub fn mix_value(mut value: usize) -> usize {
65 value ^= value >> (usize::BITS / 3);
66 value = value.wrapping_mul(0x85EB_CA6Busize);
67 value ^= value >> (usize::BITS / 4);
68 value = value.wrapping_mul(0xC2B2_AE35usize);
69 value ^= value >> (usize::BITS / 5);
70 value
71}
72
73pub fn unit_f64_from_seed(seed: usize) -> f64 {
74 SimulationSeed::new(seed).to_unit_f64()
75}
76
77#[cfg(test)]
78mod tests {
79 use super::{SeedError, SimulationSeed, mix_value, unit_f64_from_seed};
80
81 #[test]
82 fn mixes_repeatably() {
83 let seed = SimulationSeed::new(42);
84
85 assert_eq!(seed.mix(3), seed.mix(3));
86 assert_ne!(seed.mix(1), seed.mix(2));
87 assert_eq!(mix_value(5), mix_value(5));
88 }
89
90 #[test]
91 fn splits_into_repeatable_branches() {
92 let seed = SimulationSeed::new(9);
93 let branches = seed.split(3).unwrap();
94
95 assert_eq!(branches.len(), 3);
96 assert_eq!(branches[0], seed.mix(1));
97 assert_eq!(branches[2], seed.mix(3));
98 }
99
100 #[test]
101 fn converts_to_unit_interval() {
102 let value = unit_f64_from_seed(7);
103
104 assert!((0.0..=1.0).contains(&value));
105 assert_eq!(value, SimulationSeed::new(7).to_unit_f64());
106 }
107
108 #[test]
109 fn rejects_zero_split_count() {
110 assert_eq!(SimulationSeed::new(1).split(0), Err(SeedError::ZeroCount));
111 }
112}