Skip to main content

entrenar/train/tui/
buffer.rs

1//! MetricsBuffer - Ring Buffer for streaming metrics (ENT-055)
2//!
3//! Fixed-size O(1) ring buffer for training metric visualization.
4
5/// Fixed-size ring buffer for streaming metrics.
6///
7/// Provides O(1) push and O(n) iteration for visualization.
8#[derive(Debug, Clone)]
9pub struct MetricsBuffer {
10    data: Vec<f32>,
11    capacity: usize,
12    write_idx: usize,
13    len: usize,
14}
15
16impl MetricsBuffer {
17    /// Create a new metrics buffer with given capacity.
18    pub fn new(capacity: usize) -> Self {
19        Self { data: vec![0.0; capacity], capacity, write_idx: 0, len: 0 }
20    }
21
22    /// Push a new value, overwriting oldest if full.
23    pub fn push(&mut self, value: f32) {
24        self.data[self.write_idx] = value;
25        self.write_idx = (self.write_idx + 1) % self.capacity;
26        if self.len < self.capacity {
27            self.len += 1;
28        }
29    }
30
31    /// Get the number of values in the buffer.
32    pub fn len(&self) -> usize {
33        self.len
34    }
35
36    /// Check if buffer is empty.
37    pub fn is_empty(&self) -> bool {
38        self.len == 0
39    }
40
41    /// Get the capacity of the buffer.
42    pub fn capacity(&self) -> usize {
43        self.capacity
44    }
45
46    /// Get the last N values in chronological order.
47    pub fn last_n(&self, n: usize) -> Vec<f32> {
48        let n = n.min(self.len);
49        if n == 0 {
50            return Vec::new();
51        }
52
53        let mut result = Vec::with_capacity(n);
54        let start_idx = if self.len == self.capacity {
55            (self.write_idx + self.capacity - n) % self.capacity
56        } else {
57            self.len.saturating_sub(n)
58        };
59
60        for i in 0..n {
61            let idx = (start_idx + i) % self.capacity;
62            result.push(self.data[idx]);
63        }
64        result
65    }
66
67    /// Get all values in chronological order.
68    pub fn values(&self) -> Vec<f32> {
69        self.last_n(self.len)
70    }
71
72    /// Get the most recent value.
73    pub fn last(&self) -> Option<f32> {
74        if self.len == 0 {
75            None
76        } else {
77            let idx = (self.write_idx + self.capacity - 1) % self.capacity;
78            Some(self.data[idx])
79        }
80    }
81
82    /// Get min value.
83    pub fn min(&self) -> Option<f32> {
84        if self.len == 0 {
85            return None;
86        }
87        self.values().into_iter().reduce(f32::min)
88    }
89
90    /// Get max value.
91    pub fn max(&self) -> Option<f32> {
92        if self.len == 0 {
93            return None;
94        }
95        self.values().into_iter().reduce(f32::max)
96    }
97
98    /// Get mean value.
99    pub fn mean(&self) -> Option<f32> {
100        if self.len == 0 {
101            return None;
102        }
103        let sum: f32 = self.values().iter().sum();
104        Some(sum / self.len as f32)
105    }
106
107    /// Clear all values.
108    pub fn clear(&mut self) {
109        self.write_idx = 0;
110        self.len = 0;
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_metrics_buffer_new() {
120        let buf = MetricsBuffer::new(10);
121        assert_eq!(buf.capacity(), 10);
122        assert_eq!(buf.len(), 0);
123        assert!(buf.is_empty());
124    }
125
126    #[test]
127    fn test_metrics_buffer_push() {
128        let mut buf = MetricsBuffer::new(5);
129        buf.push(1.0);
130        buf.push(2.0);
131        buf.push(3.0);
132
133        assert_eq!(buf.len(), 3);
134        assert_eq!(buf.values(), vec![1.0, 2.0, 3.0]);
135    }
136
137    #[test]
138    fn test_metrics_buffer_wraparound() {
139        let mut buf = MetricsBuffer::new(3);
140        buf.push(1.0);
141        buf.push(2.0);
142        buf.push(3.0);
143        buf.push(4.0); // Overwrites 1.0
144        buf.push(5.0); // Overwrites 2.0
145
146        assert_eq!(buf.len(), 3);
147        assert_eq!(buf.values(), vec![3.0, 4.0, 5.0]);
148    }
149
150    #[test]
151    fn test_metrics_buffer_last_n() {
152        let mut buf = MetricsBuffer::new(10);
153        for i in 0..10 {
154            buf.push(i as f32);
155        }
156
157        assert_eq!(buf.last_n(3), vec![7.0, 8.0, 9.0]);
158        assert_eq!(buf.last_n(1), vec![9.0]);
159        assert_eq!(buf.last_n(0), Vec::<f32>::new());
160    }
161
162    #[test]
163    fn test_metrics_buffer_last() {
164        let mut buf = MetricsBuffer::new(5);
165        assert_eq!(buf.last(), None);
166
167        buf.push(1.0);
168        assert_eq!(buf.last(), Some(1.0));
169
170        buf.push(2.0);
171        assert_eq!(buf.last(), Some(2.0));
172    }
173
174    #[test]
175    fn test_metrics_buffer_min_max_mean() {
176        let mut buf = MetricsBuffer::new(10);
177        assert_eq!(buf.min(), None);
178        assert_eq!(buf.max(), None);
179        assert_eq!(buf.mean(), None);
180
181        buf.push(1.0);
182        buf.push(5.0);
183        buf.push(3.0);
184
185        assert_eq!(buf.min(), Some(1.0));
186        assert_eq!(buf.max(), Some(5.0));
187        assert_eq!(buf.mean(), Some(3.0));
188    }
189
190    #[test]
191    fn test_metrics_buffer_clear() {
192        let mut buf = MetricsBuffer::new(5);
193        buf.push(1.0);
194        buf.push(2.0);
195        buf.clear();
196
197        assert!(buf.is_empty());
198        assert_eq!(buf.len(), 0);
199    }
200
201    #[test]
202    fn test_metrics_buffer_last_n_wraparound() {
203        let mut buf = MetricsBuffer::new(4);
204        for i in 0..6 {
205            buf.push(i as f32);
206        }
207        // Buffer contains [4, 5, 2, 3] with write_idx at 2
208        // Chronological order: 2, 3, 4, 5
209        assert_eq!(buf.last_n(2), vec![4.0, 5.0]);
210        assert_eq!(buf.last_n(4), vec![2.0, 3.0, 4.0, 5.0]);
211    }
212
213    #[test]
214    fn test_metrics_buffer_last_n_more_than_len() {
215        let mut buf = MetricsBuffer::new(10);
216        buf.push(1.0);
217        buf.push(2.0);
218
219        assert_eq!(buf.last_n(5), vec![1.0, 2.0]);
220    }
221}