llm_optimizer_processor/aggregation/
stddev.rs1use super::trait_::{Aggregator, ToF64};
2use anyhow::anyhow;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct StdDevAccumulator {
8 count: u64,
9 mean: f64,
10 m2: f64, }
12
13#[derive(Debug, Clone)]
36pub struct StandardDeviationAggregator<T> {
37 count: u64,
38 mean: f64,
39 m2: f64,
40 sample: bool, _phantom: std::marker::PhantomData<T>,
42}
43
44impl<T: ToF64 + Clone> StandardDeviationAggregator<T> {
45 pub fn new() -> Self {
47 Self {
48 count: 0,
49 mean: 0.0,
50 m2: 0.0,
51 sample: true,
52 _phantom: std::marker::PhantomData,
53 }
54 }
55
56 pub fn population() -> Self {
58 Self {
59 count: 0,
60 mean: 0.0,
61 m2: 0.0,
62 sample: false,
63 _phantom: std::marker::PhantomData,
64 }
65 }
66
67 pub fn variance(&self) -> Option<f64> {
69 if self.count == 0 {
70 return None;
71 }
72
73 let divisor = if self.sample {
74 if self.count < 2 {
75 return None;
76 }
77 self.count - 1
78 } else {
79 self.count
80 } as f64;
81
82 Some(self.m2 / divisor)
83 }
84
85 pub fn std_dev(&self) -> Option<f64> {
87 self.variance().map(|v| v.sqrt())
88 }
89
90 pub fn mean(&self) -> Option<f64> {
92 if self.count == 0 {
93 None
94 } else {
95 Some(self.mean)
96 }
97 }
98}
99
100impl<T: ToF64 + Clone> Default for StandardDeviationAggregator<T> {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl<T: ToF64 + Clone + Send + Sync + std::fmt::Debug> Aggregator for StandardDeviationAggregator<T> {
107 type Input = T;
108 type Output = f64;
109 type Accumulator = StdDevAccumulator;
110
111 fn new() -> Self {
112 Self::new()
113 }
114
115 fn update(&mut self, value: <Self as Aggregator>::Input) -> anyhow::Result<()> {
116 let x = value.to_f64();
117 self.count += 1;
118
119 let delta = x - self.mean;
121 self.mean += delta / self.count as f64;
122 let delta2 = x - self.mean;
123 self.m2 += delta * delta2;
124
125 Ok(())
126 }
127
128 fn finalize(&self) -> anyhow::Result<<Self as Aggregator>::Output> {
129 self.std_dev()
130 .ok_or_else(|| anyhow!("Cannot compute standard deviation with less than 2 values"))
131 }
132
133 fn accumulator(&self) -> <Self as Aggregator>::Accumulator {
134 StdDevAccumulator {
135 count: self.count,
136 mean: self.mean,
137 m2: self.m2,
138 }
139 }
140
141 fn merge(&mut self, other: <Self as Aggregator>::Accumulator) -> anyhow::Result<()> {
142 if other.count == 0 {
143 return Ok(());
144 }
145
146 if self.count == 0 {
147 self.count = other.count;
148 self.mean = other.mean;
149 self.m2 = other.m2;
150 return Ok(());
151 }
152
153 let delta = other.mean - self.mean;
155 let total_count = self.count + other.count;
156
157 self.m2 = self.m2 + other.m2
158 + delta * delta * (self.count as f64 * other.count as f64) / total_count as f64;
159
160 self.mean = (self.count as f64 * self.mean + other.count as f64 * other.mean)
161 / total_count as f64;
162
163 self.count = total_count;
164
165 Ok(())
166 }
167
168 fn reset(&mut self) {
169 self.count = 0;
170 self.mean = 0.0;
171 self.m2 = 0.0;
172 }
173
174 fn count(&self) -> u64 {
175 self.count
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_stddev_basic() {
185 let mut agg = StandardDeviationAggregator::<f64>::new();
186
187 agg.update(10.0).unwrap();
192 agg.update(12.0).unwrap();
193 agg.update(14.0).unwrap();
194 agg.update(16.0).unwrap();
195 agg.update(18.0).unwrap();
196
197 assert_eq!(agg.count(), 5);
198 assert!((agg.mean().unwrap() - 14.0).abs() < 0.01);
199 assert!((agg.variance().unwrap() - 10.0).abs() < 0.01);
200 assert!((agg.std_dev().unwrap() - 3.162).abs() < 0.01);
201 assert!((agg.finalize().unwrap() - 3.162).abs() < 0.01);
202 }
203
204 #[test]
205 fn test_stddev_population() {
206 let mut agg = StandardDeviationAggregator::<f64>::population();
207
208 agg.update(10.0).unwrap();
209 agg.update(12.0).unwrap();
210 agg.update(14.0).unwrap();
211 agg.update(16.0).unwrap();
212 agg.update(18.0).unwrap();
213
214 assert!((agg.variance().unwrap() - 8.0).abs() < 0.01);
217 assert!((agg.std_dev().unwrap() - 2.828).abs() < 0.01);
218 }
219
220 #[test]
221 fn test_stddev_batch() {
222 let mut agg = StandardDeviationAggregator::<f64>::new();
223 agg.update_batch(&[2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]).unwrap();
224
225 assert_eq!(agg.count(), 8);
226 assert!((agg.mean().unwrap() - 5.0).abs() < 0.01);
227 assert!((agg.std_dev().unwrap() - 2.138).abs() < 0.01);
228 }
229
230 #[test]
231 fn test_stddev_merge() {
232 let mut agg1 = StandardDeviationAggregator::<f64>::new();
233 agg1.update(10.0).unwrap();
234 agg1.update(12.0).unwrap();
235 agg1.update(14.0).unwrap();
236
237 let mut agg2 = StandardDeviationAggregator::<f64>::new();
238 agg2.update(16.0).unwrap();
239 agg2.update(18.0).unwrap();
240
241 let acc2 = agg2.accumulator();
242 agg1.merge(acc2).unwrap();
243
244 assert_eq!(agg1.count(), 5);
245 assert!((agg1.mean().unwrap() - 14.0).abs() < 0.01);
246 assert!((agg1.std_dev().unwrap() - 3.162).abs() < 0.01);
247 }
248
249 #[test]
250 fn test_stddev_reset() {
251 let mut agg = StandardDeviationAggregator::<f64>::new();
252 agg.update(10.0).unwrap();
253 agg.update(20.0).unwrap();
254 agg.update(30.0).unwrap();
255
256 assert_eq!(agg.count(), 3);
257
258 agg.reset();
259 assert_eq!(agg.count(), 0);
260 assert!(agg.mean().is_none());
261 assert!(agg.variance().is_none());
262 assert!(agg.std_dev().is_none());
263 }
264
265 #[test]
266 fn test_stddev_serialization() {
267 let mut agg = StandardDeviationAggregator::<f64>::new();
268 agg.update(10.0).unwrap();
269 agg.update(20.0).unwrap();
270
271 let acc = agg.accumulator();
272 let serialized = serde_json::to_string(&acc).unwrap();
273 let deserialized: StdDevAccumulator = serde_json::from_str(&serialized).unwrap();
274
275 assert_eq!(acc, deserialized);
276 }
277
278 #[test]
279 fn test_stddev_empty() {
280 let agg = StandardDeviationAggregator::<f64>::new();
281 assert!(agg.finalize().is_err());
282 assert!(agg.mean().is_none());
283 assert!(agg.variance().is_none());
284 }
285
286 #[test]
287 fn test_stddev_single_value() {
288 let mut agg = StandardDeviationAggregator::<f64>::new();
289 agg.update(42.0).unwrap();
290
291 assert!(agg.finalize().is_err());
293 assert_eq!(agg.mean().unwrap(), 42.0);
294 }
295
296 #[test]
297 fn test_stddev_two_values() {
298 let mut agg = StandardDeviationAggregator::<f64>::new();
299 agg.update(10.0).unwrap();
300 agg.update(20.0).unwrap();
301
302 assert!((agg.mean().unwrap() - 15.0).abs() < 0.01);
304 assert!((agg.variance().unwrap() - 50.0).abs() < 0.01);
305 assert!((agg.std_dev().unwrap() - 7.071).abs() < 0.01);
306 }
307
308 #[test]
309 fn test_stddev_zero_variance() {
310 let mut agg = StandardDeviationAggregator::<f64>::new();
311 agg.update(5.0).unwrap();
312 agg.update(5.0).unwrap();
313 agg.update(5.0).unwrap();
314
315 assert_eq!(agg.mean().unwrap(), 5.0);
316 assert_eq!(agg.variance().unwrap(), 0.0);
317 assert_eq!(agg.std_dev().unwrap(), 0.0);
318 }
319
320 #[test]
321 fn test_stddev_negative_values() {
322 let mut agg = StandardDeviationAggregator::<f64>::new();
323 agg.update(-10.0).unwrap();
324 agg.update(-5.0).unwrap();
325 agg.update(0.0).unwrap();
326 agg.update(5.0).unwrap();
327 agg.update(10.0).unwrap();
328
329 assert!((agg.mean().unwrap() - 0.0).abs() < 0.01);
330 assert!((agg.std_dev().unwrap() - 7.905).abs() < 0.01);
331 }
332
333 #[test]
334 fn test_stddev_with_integer_types() {
335 let mut agg = StandardDeviationAggregator::<i32>::new();
336 agg.update(10).unwrap();
337 agg.update(12).unwrap();
338 agg.update(14).unwrap();
339 agg.update(16).unwrap();
340 agg.update(18).unwrap();
341
342 assert!((agg.mean().unwrap() - 14.0).abs() < 0.01);
343 assert!((agg.std_dev().unwrap() - 3.162).abs() < 0.01);
344 }
345}