sciforge_hub/tools/
deterministic.rs1#[derive(Debug, Clone)]
6pub struct Rng {
7 s: [u64; 4],
8}
9
10impl Rng {
11 pub fn new(seed: u64) -> Self {
13 let mut z = seed;
14 let mut s = [0u64; 4];
15 for si in &mut s {
16 z = z.wrapping_add(0x9e3779b97f4a7c15);
17 z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
18 z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
19 *si = z ^ (z >> 31);
20 }
21 Self { s }
22 }
23
24 pub fn next_u64(&mut self) -> u64 {
26 let result = (self.s[1].wrapping_mul(5)).rotate_left(7).wrapping_mul(9);
27 let t = self.s[1] << 17;
28 self.s[2] ^= self.s[0];
29 self.s[3] ^= self.s[1];
30 self.s[1] ^= self.s[2];
31 self.s[0] ^= self.s[3];
32 self.s[2] ^= t;
33 self.s[3] = self.s[3].rotate_left(45);
34 result
35 }
36
37 pub fn next_f64(&mut self) -> f64 {
39 (self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
40 }
41
42 pub fn uniform(&mut self, lo: f64, hi: f64) -> f64 {
44 lo + self.next_f64() * (hi - lo)
45 }
46
47 pub fn next_usize(&mut self, n: usize) -> usize {
49 (self.next_u64() % n as u64) as usize
50 }
51
52 pub fn normal(&mut self) -> f64 {
54 let u1 = self.next_f64().max(1e-300);
55 let u2 = self.next_f64();
56 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
57 }
58
59 pub fn normal_params(&mut self, mu: f64, sigma: f64) -> f64 {
61 mu + sigma * self.normal()
62 }
63
64 pub fn fill_uniform(&mut self, buf: &mut [f64]) {
66 for v in buf.iter_mut() {
67 *v = self.next_f64();
68 }
69 }
70
71 pub fn fill_normal(&mut self, buf: &mut [f64]) {
73 for v in buf.iter_mut() {
74 *v = self.normal();
75 }
76 }
77
78 pub fn shuffle<T>(&mut self, data: &mut [T]) {
80 let n = data.len();
81 for i in (1..n).rev() {
82 let j = self.next_usize(i + 1);
83 data.swap(i, j);
84 }
85 }
86
87 pub fn sample_uniform(&mut self, n: usize, lo: f64, hi: f64) -> Vec<f64> {
89 (0..n).map(|_| self.uniform(lo, hi)).collect()
90 }
91
92 pub fn sample_normal(&mut self, n: usize, mu: f64, sigma: f64) -> Vec<f64> {
94 (0..n).map(|_| self.normal_params(mu, sigma)).collect()
95 }
96}
97
98pub fn fingerprint(data: &[f64]) -> u64 {
100 let mut h: u64 = 0xcbf29ce484222325;
101 for &v in data {
102 let bytes = v.to_le_bytes();
103 for &b in &bytes {
104 h ^= b as u64;
105 h = h.wrapping_mul(0x100000001b3);
106 }
107 }
108 h
109}
110
111pub fn fingerprint_scalar(v: f64) -> u64 {
113 fingerprint(&[v])
114}
115
116pub fn fingerprints_match(a: u64, b: u64) -> bool {
118 a == b
119}
120
121#[derive(Debug, Clone)]
123pub struct ReproducibleContext {
124 pub seed: u64,
126 pub rng: Rng,
128 pub audit_trail: Vec<String>,
130}
131
132impl ReproducibleContext {
133 pub fn new(seed: u64) -> Self {
135 Self {
136 seed,
137 rng: Rng::new(seed),
138 audit_trail: Vec::new(),
139 }
140 }
141
142 pub fn log(&mut self, entry: &str) {
144 self.audit_trail.push(entry.to_string());
145 }
146
147 pub fn reset(&mut self) {
149 self.rng = Rng::new(self.seed);
150 self.audit_trail.clear();
151 }
152
153 pub fn fork(&self, sub_seed: u64) -> Self {
155 let combined = self
156 .seed
157 .wrapping_mul(0x9e3779b97f4a7c15)
158 .wrapping_add(sub_seed);
159 Self::new(combined)
160 }
161
162 pub fn audit_summary(&self) -> String {
164 let mut out = format!("Seed: {}\nSteps: {}\n", self.seed, self.audit_trail.len());
165 for (i, entry) in self.audit_trail.iter().enumerate() {
166 out.push_str(&format!(" [{}] {}\n", i + 1, entry));
167 }
168 out
169 }
170}
171
172pub fn assert_reproducible(computed: f64, expected: f64, tol: f64) -> bool {
174 if expected == 0.0 {
175 computed.abs() <= tol
176 } else {
177 ((computed - expected) / expected).abs() <= tol
178 }
179}
180
181pub fn kahan_sum(data: &[f64]) -> f64 {
183 let mut sum = 0.0;
184 let mut c = 0.0;
185 for &x in data {
186 let y = x - c;
187 let t = sum + y;
188 c = (t - sum) - y;
189 sum = t;
190 }
191 sum
192}
193
194pub fn kahan_dot(a: &[f64], b: &[f64]) -> f64 {
196 let mut sum = 0.0;
197 let mut c = 0.0;
198 let n = a.len().min(b.len());
199 for i in 0..n {
200 let y = a[i] * b[i] - c;
201 let t = sum + y;
202 c = (t - sum) - y;
203 sum = t;
204 }
205 sum
206}