Skip to main content

llm_optimizer_processor/aggregation/
avg.rs

1use super::trait_::{Aggregator, ToF64};
2use anyhow::anyhow;
3use serde::{Deserialize, Serialize};
4
5/// Accumulator for average aggregation
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct AverageAccumulator {
8    sum: f64,
9    count: u64,
10}
11
12/// Average aggregator - computes the mean of all values
13///
14/// This aggregator computes the running average using incremental mean calculation.
15/// Useful for tracking average latency, average cost per request, etc.
16///
17/// # Examples
18///
19/// ```
20/// use processor::aggregation::{Aggregator, AverageAggregator};
21///
22/// let mut agg = AverageAggregator::new();
23/// agg.update(10.0).unwrap();
24/// agg.update(20.0).unwrap();
25/// agg.update(30.0).unwrap();
26///
27/// assert_eq!(agg.finalize().unwrap(), 20.0);
28/// ```
29#[derive(Debug, Clone)]
30pub struct AverageAggregator<T> {
31    sum: f64,
32    count: u64,
33    _phantom: std::marker::PhantomData<T>,
34}
35
36impl<T: ToF64 + Clone> AverageAggregator<T> {
37    /// Create a new average aggregator
38    pub fn new() -> Self {
39        Self {
40            sum: 0.0,
41            count: 0,
42            _phantom: std::marker::PhantomData,
43        }
44    }
45
46    /// Get the current mean value
47    pub fn mean(&self) -> Option<f64> {
48        if self.count == 0 {
49            None
50        } else {
51            Some(self.sum / self.count as f64)
52        }
53    }
54}
55
56impl<T: ToF64 + Clone> Default for AverageAggregator<T> {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl<T: ToF64 + Clone + Send + Sync + std::fmt::Debug> Aggregator for AverageAggregator<T> {
63    type Input = T;
64    type Output = f64;
65    type Accumulator = AverageAccumulator;
66
67    fn new() -> Self {
68        Self::new()
69    }
70
71    fn update(&mut self, value: <Self as Aggregator>::Input) -> anyhow::Result<()> {
72        self.sum += value.to_f64();
73        self.count += 1;
74        Ok(())
75    }
76
77    fn finalize(&self) -> anyhow::Result<<Self as Aggregator>::Output> {
78        if self.count == 0 {
79            return Err(anyhow!("Cannot compute average of zero values"));
80        }
81        Ok(self.sum / self.count as f64)
82    }
83
84    fn accumulator(&self) -> <Self as Aggregator>::Accumulator {
85        AverageAccumulator {
86            sum: self.sum,
87            count: self.count,
88        }
89    }
90
91    fn merge(&mut self, other: <Self as Aggregator>::Accumulator) -> anyhow::Result<()> {
92        self.sum += other.sum;
93        self.count += other.count;
94        Ok(())
95    }
96
97    fn reset(&mut self) {
98        self.sum = 0.0;
99        self.count = 0;
100    }
101
102    fn count(&self) -> u64 {
103        self.count
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_avg_basic() {
113        let mut agg = AverageAggregator::<f64>::new();
114        assert_eq!(agg.count(), 0);
115        assert!(agg.is_empty());
116
117        agg.update(10.0).unwrap();
118        agg.update(20.0).unwrap();
119        agg.update(30.0).unwrap();
120
121        assert_eq!(agg.count(), 3);
122        assert_eq!(agg.finalize().unwrap(), 20.0);
123        assert_eq!(agg.mean().unwrap(), 20.0);
124    }
125
126    #[test]
127    fn test_avg_batch() {
128        let mut agg = AverageAggregator::<f64>::new();
129        agg.update_batch(&[10.0, 20.0, 30.0, 40.0, 50.0]).unwrap();
130
131        assert_eq!(agg.count(), 5);
132        assert_eq!(agg.finalize().unwrap(), 30.0);
133    }
134
135    #[test]
136    fn test_avg_merge() {
137        let mut agg1 = AverageAggregator::<f64>::new();
138        agg1.update(10.0).unwrap();
139        agg1.update(20.0).unwrap();
140        // avg1: (10 + 20) / 2 = 15
141
142        let mut agg2 = AverageAggregator::<f64>::new();
143        agg2.update(30.0).unwrap();
144        agg2.update(40.0).unwrap();
145        agg2.update(50.0).unwrap();
146        // avg2: (30 + 40 + 50) / 3 = 40
147
148        let acc2 = agg2.accumulator();
149        agg1.merge(acc2).unwrap();
150
151        // merged: (10 + 20 + 30 + 40 + 50) / 5 = 30
152        assert_eq!(agg1.finalize().unwrap(), 30.0);
153        assert_eq!(agg1.count(), 5);
154    }
155
156    #[test]
157    fn test_avg_reset() {
158        let mut agg = AverageAggregator::<f64>::new();
159        agg.update(10.0).unwrap();
160        agg.update(20.0).unwrap();
161
162        assert_eq!(agg.finalize().unwrap(), 15.0);
163
164        agg.reset();
165        assert_eq!(agg.count(), 0);
166        assert!(agg.mean().is_none());
167    }
168
169    #[test]
170    fn test_avg_serialization() {
171        let mut agg = AverageAggregator::<f64>::new();
172        agg.update(10.0).unwrap();
173        agg.update(20.0).unwrap();
174
175        let acc = agg.accumulator();
176        let serialized = serde_json::to_string(&acc).unwrap();
177        let deserialized: AverageAccumulator = serde_json::from_str(&serialized).unwrap();
178
179        assert_eq!(acc, deserialized);
180        assert_eq!(deserialized.sum, 30.0);
181        assert_eq!(deserialized.count, 2);
182    }
183
184    #[test]
185    fn test_avg_with_different_types() {
186        // Test with i64
187        let mut agg = AverageAggregator::<i64>::new();
188        agg.update(10).unwrap();
189        agg.update(20).unwrap();
190        agg.update(30).unwrap();
191        assert_eq!(agg.finalize().unwrap(), 20.0);
192
193        // Test with f32
194        let mut agg = AverageAggregator::<f32>::new();
195        agg.update(1.5).unwrap();
196        agg.update(2.5).unwrap();
197        assert_eq!(agg.finalize().unwrap(), 2.0);
198    }
199
200    #[test]
201    fn test_avg_empty() {
202        let agg = AverageAggregator::<f64>::new();
203        assert!(agg.finalize().is_err());
204        assert!(agg.mean().is_none());
205    }
206
207    #[test]
208    fn test_avg_single_value() {
209        let mut agg = AverageAggregator::<f64>::new();
210        agg.update(42.0).unwrap();
211        assert_eq!(agg.finalize().unwrap(), 42.0);
212    }
213
214    #[test]
215    fn test_avg_negative_values() {
216        let mut agg = AverageAggregator::<f64>::new();
217        agg.update(-10.0).unwrap();
218        agg.update(10.0).unwrap();
219        agg.update(20.0).unwrap();
220
221        assert_eq!(agg.finalize().unwrap(), 20.0 / 3.0);
222    }
223}