harmos_storage/signal/waveform/
summary.rs1#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct Summary {
36 count: u64,
37 sum: f64,
38 mean: f64,
39 lowest: f64,
40 highest: f64,
41 deviations: f64,
42}
43
44impl Default for Summary {
51 fn default() -> Self {
52 Self {
53 count: 0,
54 sum: 0.0,
55 mean: 0.0,
56 lowest: f64::INFINITY,
57 highest: f64::NEG_INFINITY,
58 deviations: 0.0,
59 }
60 }
61}
62
63impl Summary {
64 #[must_use]
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 #[must_use]
70 pub fn of(values: &[f64]) -> Self {
71 let mut summary = Self::new();
72 for value in values {
73 summary.push(*value);
74 }
75 summary
76 }
77
78 pub fn push(&mut self, value: f64) {
79 if !value.is_finite() {
80 return;
81 }
82 let before = self.mean;
83
84 self.count += 1;
85 self.sum += value;
86 self.lowest = self.lowest.min(value);
87 self.highest = self.highest.max(value);
88
89 let n = self.count as f64;
93 self.mean = if self.count == 1 {
94 value
95 } else if before.signum() == value.signum() {
96 before + (value - before) / n
97 } else {
98 before * ((n - 1.0) / n) + value / n
99 };
100 self.deviations += (value - before) * (value - self.mean);
101 }
102
103 #[must_use]
104 pub fn merge(&self, other: &Self) -> Self {
105 if self.count == 0 {
106 return *other;
107 }
108 if other.count == 0 {
109 return *self;
110 }
111
112 let count = self.count + other.count;
113 let total = count as f64;
114 let mean = self.mean * (self.count as f64 / total) + other.mean * (other.count as f64 / total);
115 let apart = other.mean - self.mean;
116 let weight = (self.count as f64 * other.count as f64) / total;
117
118 Self {
119 count,
120 sum: self.sum + other.sum,
121 mean,
122 lowest: self.lowest.min(other.lowest),
123 highest: self.highest.max(other.highest),
124 deviations: self.deviations + other.deviations + apart * apart * weight,
125 }
126 }
127
128 #[must_use]
129 pub fn count(&self) -> u64 {
130 self.count
131 }
132
133 #[must_use]
134 pub fn is_empty(&self) -> bool {
135 self.count == 0
136 }
137
138 #[must_use]
139 pub fn sum(&self) -> f64 {
140 self.sum
141 }
142
143 #[must_use]
144 pub fn min(&self) -> Option<f64> {
145 (self.count > 0).then_some(self.lowest)
146 }
147
148 #[must_use]
149 pub fn max(&self) -> Option<f64> {
150 (self.count > 0).then_some(self.highest)
151 }
152
153 #[must_use]
154 pub fn span(&self) -> Option<f64> {
155 (self.count > 0).then_some(self.highest - self.lowest)
156 }
157
158 #[must_use]
159 pub fn mean(&self) -> Option<f64> {
160 (self.count > 0).then_some(self.mean)
161 }
162
163 #[must_use]
166 pub fn variance(&self) -> Option<f64> {
167 (self.count > 0).then(|| (self.deviations / self.count as f64).max(0.0))
168 }
169
170 #[must_use]
171 pub fn std(&self) -> Option<f64> {
172 self.variance().map(f64::sqrt)
173 }
174
175 #[must_use]
178 pub fn rms(&self) -> Option<f64> {
179 Some(self.mean()?.hypot(self.std()?))
180 }
181}
182
183#[cfg(test)]
190mod tests {
191 use super::Summary;
192 use crate::signal::Series;
193
194 const CLOSE: f64 = 1e-9;
195
196 fn close(found: Option<f64>, expected: f64) {
197 let found = found.expect("an answer");
198 assert!((found - expected).abs() <= CLOSE * expected.abs().max(1.0), "{found} is not {expected}");
199 }
200
201 #[test]
204 fn chunked_summaries_agree() {
205 let values: Vec<f64> = (0..97).map(|index| ((index * 37) % 23) as f64 * 1.5 - 11.0).chain([f64::NAN, f64::INFINITY]).collect();
206 let series = Series::new(&values);
207 let summary = values.chunks(10).map(Summary::of).fold(Summary::new(), |held, next| held.merge(&next));
208
209 assert_eq!(series.count(), summary.count());
210 close(Some(series.sum()), summary.sum());
211 close(series.mean(), summary.mean().unwrap());
212 assert_eq!(series.min(), summary.min());
213 assert_eq!(series.max(), summary.max());
214 close(series.rms(), summary.rms().unwrap());
215 close(series.std(), summary.std().unwrap());
216 }
217}