velora_ta/utils/
circular_buffer.rs

1//! Efficient circular buffer for windowed calculations.
2//!
3//! A circular buffer is a fixed-size buffer that overwrites old data when full.
4//! This is ideal for indicators that only need a sliding window of recent data.
5
6use std::ops::Add;
7
8/// Fixed-size circular buffer optimized for indicator calculations.
9///
10/// This buffer maintains a fixed capacity and automatically overwrites the oldest
11/// data when new data is added. It's more memory-efficient than keeping all
12/// historical data when only a fixed window is needed.
13///
14/// # Example
15///
16/// ```ignore
17/// let mut buffer = CircularBuffer::new(3);
18/// buffer.push(10.0);
19/// buffer.push(20.0);
20/// buffer.push(30.0);  // Buffer is now full: [10, 20, 30]
21///
22/// assert_eq!(buffer.sum(), 60.0);
23/// assert_eq!(buffer.mean(), Some(20.0));
24///
25/// buffer.push(40.0);  // Overwrites 10: [20, 30, 40]
26/// assert_eq!(buffer.sum(), 90.0);
27/// ```
28#[derive(Debug, Clone)]
29pub struct CircularBuffer<T> {
30    data: Vec<T>,
31    capacity: usize,
32    head: usize,
33    size: usize,
34}
35
36impl<T: Copy + Default> CircularBuffer<T> {
37    /// Create a new circular buffer with the specified capacity.
38    ///
39    /// # Arguments
40    ///
41    /// * `capacity` - Maximum number of elements the buffer can hold
42    ///
43    /// # Panics
44    ///
45    /// Panics if capacity is 0.
46    pub fn new(capacity: usize) -> Self {
47        assert!(capacity > 0, "Capacity must be greater than 0");
48        Self {
49            data: vec![T::default(); capacity],
50            capacity,
51            head: 0,
52            size: 0,
53        }
54    }
55
56    /// Add a new value to the buffer.
57    ///
58    /// If the buffer is full, this overwrites the oldest value.
59    pub fn push(&mut self, value: T) {
60        self.data[self.head] = value;
61        self.head = (self.head + 1) % self.capacity;
62        if self.size < self.capacity {
63            self.size += 1;
64        }
65    }
66
67    /// Get a value at a specific index.
68    ///
69    /// Index 0 is the oldest value, index `size - 1` is the newest.
70    /// Returns `None` if the index is out of bounds.
71    pub fn get(&self, index: usize) -> Option<T> {
72        if index >= self.size {
73            return None;
74        }
75        let actual_index = (self.head + self.capacity - self.size + index) % self.capacity;
76        Some(self.data[actual_index])
77    }
78
79    /// Get the most recent value (last pushed).
80    pub fn last(&self) -> Option<T> {
81        if self.size == 0 {
82            return None;
83        }
84        let index = (self.head + self.capacity - 1) % self.capacity;
85        Some(self.data[index])
86    }
87
88    /// Get the oldest value in the buffer.
89    pub fn first(&self) -> Option<T> {
90        if self.size == 0 {
91            return None;
92        }
93        self.get(0)
94    }
95
96    /// Check if the buffer is full.
97    pub fn is_full(&self) -> bool {
98        self.size == self.capacity
99    }
100
101    /// Check if the buffer is empty.
102    pub fn is_empty(&self) -> bool {
103        self.size == 0
104    }
105
106    /// Get the current number of elements in the buffer.
107    pub fn len(&self) -> usize {
108        self.size
109    }
110
111    /// Get the maximum capacity of the buffer.
112    pub fn capacity(&self) -> usize {
113        self.capacity
114    }
115
116    /// Clear all elements from the buffer.
117    pub fn clear(&mut self) {
118        self.head = 0;
119        self.size = 0;
120    }
121
122    /// Iterate over all values in the buffer (oldest to newest).
123    pub fn iter(&self) -> CircularBufferIter<'_, T> {
124        CircularBufferIter {
125            buffer: self,
126            index: 0,
127        }
128    }
129
130    /// Get a slice view of the buffer's data in insertion order.
131    ///
132    /// Note: This returns the values in chronological order (oldest to newest),
133    /// which may not match the internal storage order.
134    pub fn as_slice(&self) -> Vec<T> {
135        self.iter().copied().collect()
136    }
137}
138
139// Specialized methods for numeric types
140impl<T> CircularBuffer<T>
141where
142    T: Copy + Default + Add<Output = T> + PartialOrd,
143{
144    /// Calculate the sum of all values in the buffer.
145    pub fn sum(&self) -> T
146    where
147        T: Add<Output = T> + Default,
148    {
149        self.iter().fold(T::default(), |acc, &x| acc + x)
150    }
151
152    /// Find the maximum value in the buffer.
153    ///
154    /// Returns `None` if the buffer is empty.
155    pub fn max(&self) -> Option<T> {
156        if self.is_empty() {
157            return None;
158        }
159        self.iter()
160            .copied()
161            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
162    }
163
164    /// Find the minimum value in the buffer.
165    ///
166    /// Returns `None` if the buffer is empty.
167    pub fn min(&self) -> Option<T> {
168        if self.is_empty() {
169            return None;
170        }
171        self.iter()
172            .copied()
173            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
174    }
175}
176
177impl CircularBuffer<f64> {
178    /// Calculate the mean (average) of all values in the buffer.
179    ///
180    /// Returns `None` if the buffer is empty.
181    pub fn mean(&self) -> Option<f64> {
182        if self.is_empty() {
183            return None;
184        }
185        Some(self.sum() / self.size as f64)
186    }
187
188    /// Calculate the variance of all values in the buffer.
189    ///
190    /// Returns `None` if the buffer is empty.
191    pub fn variance(&self) -> Option<f64> {
192        if self.is_empty() {
193            return None;
194        }
195        let mean = self.mean()?;
196        let sum_squared_diff: f64 = self.iter().map(|&x| (x - mean).powi(2)).sum();
197        Some(sum_squared_diff / self.size as f64)
198    }
199
200    /// Calculate the standard deviation of all values in the buffer.
201    ///
202    /// Returns `None` if the buffer is empty.
203    pub fn std_dev(&self) -> Option<f64> {
204        self.variance().map(|v| v.sqrt())
205    }
206}
207
208/// Iterator over circular buffer elements (oldest to newest).
209#[derive(Debug)]
210pub struct CircularBufferIter<'a, T> {
211    buffer: &'a CircularBuffer<T>,
212    index: usize,
213}
214
215impl<'a, T: Copy> Iterator for CircularBufferIter<'a, T> {
216    type Item = &'a T;
217
218    fn next(&mut self) -> Option<Self::Item> {
219        if self.index >= self.buffer.size {
220            return None;
221        }
222        let actual_index = (self.buffer.head + self.buffer.capacity - self.buffer.size
223            + self.index)
224            % self.buffer.capacity;
225        self.index += 1;
226        Some(&self.buffer.data[actual_index])
227    }
228
229    fn size_hint(&self) -> (usize, Option<usize>) {
230        let remaining = self.buffer.size - self.index;
231        (remaining, Some(remaining))
232    }
233}
234
235impl<'a, T: Copy> ExactSizeIterator for CircularBufferIter<'a, T> {}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_new_buffer() {
243        let buffer: CircularBuffer<f64> = CircularBuffer::new(5);
244        assert_eq!(buffer.capacity(), 5);
245        assert_eq!(buffer.len(), 0);
246        assert!(buffer.is_empty());
247        assert!(!buffer.is_full());
248    }
249
250    #[test]
251    #[should_panic(expected = "Capacity must be greater than 0")]
252    fn test_zero_capacity_panics() {
253        let _buffer: CircularBuffer<f64> = CircularBuffer::new(0);
254    }
255
256    #[test]
257    fn test_push_and_get() {
258        let mut buffer = CircularBuffer::new(3);
259        buffer.push(10.0);
260        buffer.push(20.0);
261        buffer.push(30.0);
262
263        assert_eq!(buffer.len(), 3);
264        assert!(buffer.is_full());
265        assert_eq!(buffer.get(0), Some(10.0));
266        assert_eq!(buffer.get(1), Some(20.0));
267        assert_eq!(buffer.get(2), Some(30.0));
268        assert_eq!(buffer.get(3), None);
269    }
270
271    #[test]
272    fn test_circular_overwrite() {
273        let mut buffer = CircularBuffer::new(3);
274        buffer.push(10.0);
275        buffer.push(20.0);
276        buffer.push(30.0);
277        buffer.push(40.0); // Overwrites 10.0
278
279        assert_eq!(buffer.len(), 3);
280        assert_eq!(buffer.get(0), Some(20.0));
281        assert_eq!(buffer.get(1), Some(30.0));
282        assert_eq!(buffer.get(2), Some(40.0));
283    }
284
285    #[test]
286    fn test_first_and_last() {
287        let mut buffer = CircularBuffer::new(3);
288        assert_eq!(buffer.first(), None);
289        assert_eq!(buffer.last(), None);
290
291        buffer.push(10.0);
292        assert_eq!(buffer.first(), Some(10.0));
293        assert_eq!(buffer.last(), Some(10.0));
294
295        buffer.push(20.0);
296        buffer.push(30.0);
297        assert_eq!(buffer.first(), Some(10.0));
298        assert_eq!(buffer.last(), Some(30.0));
299
300        buffer.push(40.0); // Overwrites 10.0
301        assert_eq!(buffer.first(), Some(20.0));
302        assert_eq!(buffer.last(), Some(40.0));
303    }
304
305    #[test]
306    fn test_sum() {
307        let mut buffer = CircularBuffer::new(3);
308        buffer.push(10.0);
309        buffer.push(20.0);
310        buffer.push(30.0);
311
312        assert_eq!(buffer.sum(), 60.0);
313
314        buffer.push(40.0); // Overwrites 10.0, sum = 20 + 30 + 40 = 90
315        assert_eq!(buffer.sum(), 90.0);
316    }
317
318    #[test]
319    fn test_mean() {
320        let mut buffer = CircularBuffer::new(3);
321        assert_eq!(buffer.mean(), None);
322
323        buffer.push(10.0);
324        buffer.push(20.0);
325        buffer.push(30.0);
326        assert_eq!(buffer.mean(), Some(20.0));
327
328        buffer.push(40.0); // Overwrites 10.0, mean = (20+30+40)/3 = 30
329        assert_eq!(buffer.mean(), Some(30.0));
330    }
331
332    #[test]
333    fn test_max_min() {
334        let mut buffer = CircularBuffer::new(3);
335        assert_eq!(buffer.max(), None);
336        assert_eq!(buffer.min(), None);
337
338        buffer.push(20.0);
339        buffer.push(10.0);
340        buffer.push(30.0);
341
342        assert_eq!(buffer.max(), Some(30.0));
343        assert_eq!(buffer.min(), Some(10.0));
344    }
345
346    #[test]
347    fn test_variance_std_dev() {
348        let mut buffer = CircularBuffer::new(3);
349        buffer.push(2.0);
350        buffer.push(4.0);
351        buffer.push(6.0);
352
353        // Mean = 4.0
354        // Variance = ((2-4)^2 + (4-4)^2 + (6-4)^2) / 3 = (4 + 0 + 4) / 3 = 8/3 ≈ 2.666...
355        let variance = buffer.variance().unwrap();
356        assert!((variance - 2.666666).abs() < 0.001);
357
358        let std_dev = buffer.std_dev().unwrap();
359        assert!((std_dev - 1.632993).abs() < 0.001);
360    }
361
362    #[test]
363    fn test_clear() {
364        let mut buffer = CircularBuffer::new(3);
365        buffer.push(10.0);
366        buffer.push(20.0);
367        buffer.push(30.0);
368
369        assert_eq!(buffer.len(), 3);
370
371        buffer.clear();
372        assert_eq!(buffer.len(), 0);
373        assert!(buffer.is_empty());
374        assert_eq!(buffer.first(), None);
375        assert_eq!(buffer.last(), None);
376    }
377
378    #[test]
379    fn test_iterator() {
380        let mut buffer = CircularBuffer::new(3);
381        buffer.push(10.0);
382        buffer.push(20.0);
383        buffer.push(30.0);
384
385        let values: Vec<f64> = buffer.iter().copied().collect();
386        assert_eq!(values, vec![10.0, 20.0, 30.0]);
387
388        buffer.push(40.0); // Overwrites 10.0
389        let values: Vec<f64> = buffer.iter().copied().collect();
390        assert_eq!(values, vec![20.0, 30.0, 40.0]);
391    }
392
393    #[test]
394    fn test_iterator_size_hint() {
395        let mut buffer = CircularBuffer::new(5);
396        buffer.push(1.0);
397        buffer.push(2.0);
398        buffer.push(3.0);
399
400        let iter = buffer.iter();
401        assert_eq!(iter.size_hint(), (3, Some(3)));
402    }
403}