rustyqlib/core/montecarlo/
stats.rs1#[derive(Debug, Clone, Copy)]
8pub struct SimStats {
9 pub mean: f64,
10 pub std_err: f64,
11 pub n: usize,
12}
13
14pub fn mean_std_err(sum: f64, sum_sq: f64, n: usize) -> (f64, f64) {
17 let nf = n as f64;
18 let mean = sum / nf;
19 let var = (sum_sq / nf - mean * mean).max(0.0);
20 (mean, (var / nf).sqrt())
21}
22
23#[derive(Debug, Clone, Copy, Default)]
26pub struct RunningStats {
27 n: usize,
28 mean: f64,
29 m2: f64,
30}
31
32impl RunningStats {
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn push(&mut self, x: f64) {
38 self.n += 1;
39 let delta = x - self.mean;
40 self.mean += delta / self.n as f64;
41 self.m2 += delta * (x - self.mean);
42 }
43
44 pub fn merge(&mut self, other: &RunningStats) {
46 if other.n == 0 {
47 return;
48 }
49 if self.n == 0 {
50 *self = *other;
51 return;
52 }
53 let n1 = self.n as f64;
54 let n2 = other.n as f64;
55 let delta = other.mean - self.mean;
56 let n = n1 + n2;
57 self.mean += delta * n2 / n;
58 self.m2 += other.m2 + delta * delta * n1 * n2 / n;
59 self.n += other.n;
60 }
61
62 pub fn count(&self) -> usize {
63 self.n
64 }
65
66 pub fn mean(&self) -> f64 {
67 self.mean
68 }
69
70 pub fn variance(&self) -> f64 {
72 if self.n == 0 { 0.0 } else { (self.m2 / self.n as f64).max(0.0) }
73 }
74
75 pub fn std_err(&self) -> f64 {
76 if self.n == 0 { 0.0 } else { (self.variance() / self.n as f64).sqrt() }
77 }
78
79 pub fn stats(&self) -> SimStats {
80 SimStats { mean: self.mean(), std_err: self.std_err(), n: self.n }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn welford_matches_the_two_pass_computation() {
90 let xs: Vec<f64> = (0..1000).map(|i| ((i * 37) % 101) as f64 * 0.13 - 5.0).collect();
91 let mut running = RunningStats::new();
92 for &x in &xs {
93 running.push(x);
94 }
95 let sum: f64 = xs.iter().sum();
96 let sum_sq: f64 = xs.iter().map(|x| x * x).sum();
97 let (mean, se) = mean_std_err(sum, sum_sq, xs.len());
98 assert!((running.mean() - mean).abs() < 1e-10);
99 assert!((running.std_err() - se).abs() < 1e-10);
100 }
101
102 #[test]
103 fn merged_accumulators_equal_a_single_pass() {
104 let xs: Vec<f64> = (0..500).map(|i| (i as f64).sin() * 3.0).collect();
105 let mut whole = RunningStats::new();
106 let mut left = RunningStats::new();
107 let mut right = RunningStats::new();
108 for (i, &x) in xs.iter().enumerate() {
109 whole.push(x);
110 if i < 200 { left.push(x) } else { right.push(x) }
111 }
112 left.merge(&right);
113 assert_eq!(left.count(), whole.count());
114 assert!((left.mean() - whole.mean()).abs() < 1e-12);
115 assert!((left.variance() - whole.variance()).abs() < 1e-12);
116 }
117}