qualia_core_db/solvers/statistics/
timeseries.rs1use super::descriptive::mean;
11
12pub fn autocorrelation(values: &[f64], lag: usize) -> Option<f64> {
22 let n = values.len();
23 if n == 0 || lag >= n {
24 return None;
25 }
26 let m = mean(values)?;
27 let mut denom = 0.0;
28 let mut i = 0;
29 while i < n {
30 let d = values[i] - m;
31 denom += d * d;
32 i += 1;
33 }
34 if denom == 0.0 {
35 return None; }
37 let mut num = 0.0;
38 let mut t = lag;
39 while t < n {
40 num += (values[t] - m) * (values[t - lag] - m);
41 t += 1;
42 }
43 Some(num / denom)
44}
45
46pub fn moving_average_into(values: &[f64], window: usize, out: &mut [f64]) -> Option<usize> {
53 let n = values.len();
54 if window == 0 || window > n {
55 return None;
56 }
57 let count = n - window + 1;
58 if out.len() < count {
59 return None;
60 }
61 let mut acc = 0.0;
62 let mut i = 0;
63 while i < window {
64 acc += values[i];
65 i += 1;
66 }
67 let w = window as f64;
68 out[0] = acc / w;
69 let mut j = 1;
70 while j < count {
71 acc += values[j + window - 1] - values[j - 1];
72 out[j] = acc / w;
73 j += 1;
74 }
75 Some(count)
76}
77
78pub fn exponential_smoothing_into(values: &[f64], alpha: f64, out: &mut [f64]) -> Option<usize> {
85 let n = values.len();
86 if n == 0 || out.len() < n || !(alpha > 0.0 && alpha <= 1.0) {
87 return None;
88 }
89 out[0] = values[0];
90 let mut t = 1;
91 while t < n {
92 out[t] = alpha * values[t] + (1.0 - alpha) * out[t - 1];
93 t += 1;
94 }
95 Some(n)
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 const EPS: f64 = 1e-12;
103
104 #[test]
105 fn autocorr_lag0_is_one() {
106 let v = [1.0, 2.0, 3.0, 4.0, 5.0];
107 assert!((autocorrelation(&v, 0).unwrap() - 1.0).abs() < EPS);
108 }
109
110 #[test]
111 fn autocorr_lag1_known_value() {
112 let v = [1.0, 2.0, 3.0, 4.0, 5.0];
114 assert!((autocorrelation(&v, 1).unwrap() - 0.4).abs() < EPS);
115 }
116
117 #[test]
118 fn autocorr_constant_is_none() {
119 let v = [7.0, 7.0, 7.0];
120 assert_eq!(autocorrelation(&v, 1), None);
121 }
122
123 #[test]
124 fn moving_average_window2() {
125 let v = [1.0, 2.0, 3.0, 4.0];
126 let mut out = [0.0; 3];
127 assert_eq!(moving_average_into(&v, 2, &mut out), Some(3));
128 assert!((out[0] - 1.5).abs() < EPS);
129 assert!((out[1] - 2.5).abs() < EPS);
130 assert!((out[2] - 3.5).abs() < EPS);
131 }
132
133 #[test]
134 fn moving_average_rejects_bad_window() {
135 let v = [1.0, 2.0];
136 let mut out = [0.0; 2];
137 assert_eq!(moving_average_into(&v, 0, &mut out), None);
138 assert_eq!(moving_average_into(&v, 3, &mut out), None);
139 }
140
141 #[test]
142 fn exponential_smoothing_known_value() {
143 let v = [1.0, 2.0, 3.0];
145 let mut out = [0.0; 3];
146 assert_eq!(exponential_smoothing_into(&v, 0.5, &mut out), Some(3));
147 assert!((out[0] - 1.0).abs() < EPS);
148 assert!((out[1] - 1.5).abs() < EPS);
149 assert!((out[2] - 2.25).abs() < EPS);
150 }
151
152 #[test]
153 fn exponential_smoothing_rejects_bad_alpha() {
154 let v = [1.0, 2.0];
155 let mut out = [0.0; 2];
156 assert_eq!(exponential_smoothing_into(&v, 0.0, &mut out), None);
157 assert_eq!(exponential_smoothing_into(&v, 1.5, &mut out), None);
158 }
159}