1#![warn(clippy::std_instead_of_core, clippy::std_instead_of_alloc)]
2#![no_std]
3
4mod num;
5pub use num::{Float, Num};
6
7pub struct SmoothBuffer<const CAP: usize, T: Num> {
9 data: [T; CAP],
10 head: usize,
11 sum: Option<T>,
12 max: Option<T>,
13 min: Option<T>,
14 filled_len: usize,
15 dirty_minmax: bool,
16}
17
18impl<const CAP: usize, T: Num> Default for SmoothBuffer<CAP, T> {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl<const CAP: usize, T: Num> SmoothBuffer<CAP, T> {
25 pub fn new() -> Self {
27 assert!(
28 CAP > 0,
29 "SmoothBuffer Error: Capacity must be larger than zero"
30 );
31 SmoothBuffer {
32 data: [T::default(); CAP],
33 head: 0,
34 sum: None,
35 max: None,
36 min: None,
37 filled_len: 0,
38 dirty_minmax: false,
39 }
40 }
41
42 pub fn pre_filled(value: T) -> Self {
44 assert!(
45 CAP > 0,
46 "SmoothBuffer Error: Capacity must be larger than zero"
47 );
48 SmoothBuffer {
49 data: [value; CAP],
50 head: CAP - 1,
51 sum: Some(value * T::from_usize(CAP)),
52 max: Some(value),
53 min: Some(value),
54 filled_len: CAP,
55 dirty_minmax: false,
56 }
57 }
58
59 pub fn average(&self) -> T {
61 if self.filled_len > 0 {
62 return self.sum.unwrap_or(T::zero()) / T::from_usize(self.filled_len);
63 }
64 T::zero()
65 }
66
67 pub fn clear(&mut self) {
69 for n in 0..self.data.len() {
70 self.data[n] = T::zero();
71 }
72 self.sum = None;
73 self.max = None;
74 self.min = None;
75 self.filled_len = 0;
76 self.head = 0;
77 }
78
79 pub fn is_empty(&self) -> bool {
81 self.filled_len == 0
82 }
83
84 pub fn max(&mut self) -> T {
86 if self.dirty_minmax {
87 self.recalculate_minmax();
89 }
90 self.max.unwrap_or(T::zero())
91 }
92
93 pub fn min(&mut self) -> T {
95 if self.dirty_minmax {
96 self.recalculate_minmax();
98 }
99 self.min.unwrap_or(T::zero())
100 }
101
102 fn recalculate_minmax(&mut self) {
103 if self.filled_len == 0 {
104 self.min = None;
105 self.max = None;
106 return;
107 }
108
109 let start_idx = if self.filled_len < CAP {
111 0
112 } else {
113 (self.head + CAP - self.filled_len) % CAP
114 };
115
116 let mut new_min = self.data[start_idx];
118 let mut new_max = self.data[start_idx];
119
120 for i in 1..self.filled_len {
122 let idx = (start_idx + i) % CAP;
123 new_min = T::get_min(new_min, self.data[idx]);
124 new_max = T::get_max(new_max, self.data[idx]);
125 }
126
127 self.min = Some(new_min);
128 self.max = Some(new_max);
129 self.dirty_minmax = false;
130 }
131
132 pub fn capacity(&self) -> usize {
135 CAP
136 }
137
138 pub fn len(&self) -> usize {
140 self.filled_len
141 }
142
143 pub fn push(&mut self, value: T) {
145 if self.filled_len < CAP {
147 self.sum = match self.sum {
148 None => Some(value),
149 Some(sum) => Some(sum + value),
150 };
151 } else {
152 self.sum = Some(self.sum.unwrap_or(T::zero()) - self.data[self.head] + value);
154 }
155
156 self.data[self.head] = value;
158 self.head = (self.head + 1) % CAP; if self.filled_len < CAP {
160 self.filled_len += 1;
161 }
162
163 if self.filled_len == CAP {
165 self.dirty_minmax = true;
167 } else {
168 match self.max {
169 None => self.max = Some(value),
170 Some(max) if value > max => self.max = Some(value),
171 _ => {}
172 }
173
174 match self.min {
175 None => self.min = Some(value),
176 Some(min) if value < min => self.min = Some(value),
177 _ => {}
178 }
179 }
180 }
181
182 pub fn push_slice(&mut self, slice: &[T]) {
184 for item in slice {
185 self.push(*item);
186 }
187 }
188
189 pub fn iter(&self) -> impl Iterator<Item = &T> {
191 let head = self.head;
192 let len = self.filled_len;
193 let cap = CAP;
194
195 (0..len).map(move |i| &self.data[(head + cap - len + i) % cap])
196 }
197}
198
199impl<const CAP: usize, T: Float> SmoothBuffer<CAP, T> {
200 pub fn gaussian_filter(&self) -> T {
203 if self.filled_len == 0 {
204 return T::zero();
205 }
206
207 let sigma = T::from_usize(self.filled_len) / T::four();
209 let center = T::from_usize(self.filled_len - 1) / T::two();
210
211 let mut weights = [T::zero(); CAP]; let mut total_weight = T::zero();
214
215 for i in 0..self.filled_len {
216 let distance = T::from_usize(i) - center;
217 let exp_term = -distance * distance / (T::two() * sigma * sigma);
218 let weight = T::exp(exp_term);
219 weights[i] = weight;
220 total_weight += weight;
221 }
222
223 let mut sum = T::zero();
225 for i in 0..self.filled_len {
226 let idx = if self.filled_len < CAP {
228 i
229 } else {
230 (self.head + CAP - self.filled_len + i) % CAP
231 };
232
233 let normalized_weight = weights[i] / total_weight;
235 sum += self.data[idx] * normalized_weight;
236 }
237
238 sum
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 const MARGIN: f64 = 0.000001;
246
247 #[test]
248 fn create_and_push() {
249 const CAP: usize = 10;
250 let mut buf = SmoothBuffer::<CAP, f32>::new();
251 for _ in 0..5 {
252 buf.push(10.0);
253 }
254
255 assert_eq!(buf.capacity(), CAP);
256 assert_eq!(buf.len(), 5);
257 assert_eq!(buf.average(), 10.0);
258
259 for _ in 0..10 {
260 buf.push(5.0);
261 }
262 assert_eq!(buf.len(), CAP);
263 assert_eq!(buf.average(), 5.0);
264 }
265
266 #[test]
267 fn clearing() {
268 let mut buf = SmoothBuffer::<10, f32>::new();
269 for n in 0..buf.capacity() {
270 buf.push(n as f32);
271 }
272 buf.clear();
273 assert_eq!(buf.capacity(), 10);
274 assert_eq!(buf.len(), 0);
275 assert_eq!(buf.average(), 0.0);
276 assert_eq!(buf.iter().next(), None);
277 }
278
279 #[test]
280 fn iteration() {
281 let mut buf = SmoothBuffer::<10, f64>::new();
282 let len = 7;
283 for n in 0..len {
284 buf.push(n as f64);
285 }
286
287 for (i, value) in buf.iter().enumerate() {
288 assert_eq!(i as f64, *value);
289 }
290 }
291
292 #[test]
293 fn gaussian_smoothing_simple() {
294 let mut buf = SmoothBuffer::<10, f64>::new();
295 for _ in 0..100 {
296 buf.push(3.0);
297 }
298 assert!(buf.gaussian_filter() - 3.0 < MARGIN);
301 }
302
303 #[test]
304 fn gaussian_smoothing_with_negative_values() {
305 let mut buf = SmoothBuffer::<10, f64>::new();
306 let mid = buf.len() / 2;
307 for v in 0..buf.len() {
308 buf.push(if v < mid {
309 1.0
310 } else if v > mid {
311 -1.0
312 } else {
313 0.0
314 });
315 }
316 assert!(buf.gaussian_filter().abs() < MARGIN);
318 }
319
320 #[test]
321 fn gaussian_smoothing_slice() {
322 let mut buf = SmoothBuffer::<10, f64>::new();
323 buf.push_slice(&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]);
324 assert!(buf.gaussian_filter() - 0.55 < MARGIN);
325 }
326
327 #[test]
328 fn pre_filled_buffer() {
329 fn test_value(x: f64) {
330 let buf = SmoothBuffer::<10, f64>::pre_filled(x);
332 assert!(buf.len() == 10);
333 assert!((buf.gaussian_filter() - x).abs() < MARGIN);
334 assert!((buf.average() - x).abs() < MARGIN);
335 }
336
337 for n in 0..=10 {
338 test_value(n as f64 / 10.0);
339 }
340 }
341
342 #[test]
343 fn progressive_fill() {
344 let mut buf = SmoothBuffer::<10, f64>::pre_filled(0.0);
345 assert!(buf.gaussian_filter() < MARGIN);
347 for _n in 0..10 {
348 buf.push(1.0);
349 }
351 assert!(buf.gaussian_filter() - 1.0 < MARGIN);
352 }
353
354 #[test]
355 fn test_min_max_recalculation() {
356 let mut buf = SmoothBuffer::<5, f64>::new();
357
358 buf.push(1.0);
360 buf.push(2.0);
361 buf.push(3.0);
362 buf.push(4.0);
363 buf.push(5.0);
364
365 assert_eq!(buf.min(), 1.0);
367 assert_eq!(buf.max(), 5.0);
368
369 buf.push(2.5); assert_eq!(buf.min(), 2.0);
374 assert_eq!(buf.max(), 5.0);
375
376 buf.push(3.5); buf.push(4.5); buf.push(3.0); buf.push(2.0); assert_eq!(buf.min(), 2.0);
384 assert_eq!(buf.max(), 4.5);
385 }
386
387 #[test]
388 fn test_buffer_wrapping() {
389 let mut buf = SmoothBuffer::<3, i32>::new();
390
391 buf.push(1);
393 buf.push(2);
394 buf.push(3);
395
396 assert_eq!(buf.average(), 2);
398
399 buf.push(4); assert_eq!(buf.average(), 3);
402
403 buf.push(5); assert_eq!(buf.average(), 4);
405
406 buf.push(6); assert_eq!(buf.average(), 5);
408
409 let mut collected = [0; 3];
411 let mut count = 0;
412
413 for &val in buf.iter() {
414 if count < 3 {
415 collected[count] = val;
416 count += 1;
417 }
418 }
419
420 assert_eq!(count, 3);
421 assert_eq!(collected, [4, 5, 6]);
422 }
423
424 #[test]
425 fn test_dirty_flag_behavior() {
426 let mut buf = SmoothBuffer::<5, f64>::new();
427
428 for i in 0..5 {
430 buf.push(i as f64);
431 }
432
433 assert_eq!(buf.min(), 0.0);
435 assert_eq!(buf.max(), 4.0);
436
437 buf.push(2.0); assert_eq!(buf.min(), 1.0);
442
443 buf.push(3.0); buf.push(2.0); buf.push(1.0); buf.push(0.5); assert_eq!(buf.max(), 3.0);
451 }
452
453 #[test]
454 fn test_pre_filled_edge_cases() {
455 let buf_large = SmoothBuffer::<5, f64>::pre_filled(1e10);
457 assert!((buf_large.average() - 1e10).abs() < MARGIN);
458
459 let buf_small = SmoothBuffer::<5, f64>::pre_filled(1e-10);
461 assert!((buf_small.average() - 1e-10).abs() < MARGIN);
462
463 let mut buf_neg = SmoothBuffer::<5, f64>::pre_filled(-5.0);
465 assert!((buf_neg.average() - (-5.0)).abs() < MARGIN);
466 assert!((buf_neg.min() - (-5.0)).abs() < MARGIN);
467 assert!((buf_neg.max() - (-5.0)).abs() < MARGIN);
468 }
469
470 #[test]
471 fn test_single_element_buffer() {
472 let mut buf = SmoothBuffer::<1, i32>::new();
473
474 buf.push(42);
476 assert_eq!(buf.average(), 42);
477 assert_eq!(buf.min(), 42);
478 assert_eq!(buf.max(), 42);
479
480 buf.push(17);
482 assert_eq!(buf.average(), 17);
483 assert_eq!(buf.min(), 17);
484 assert_eq!(buf.max(), 17);
485
486 let mut has_value = false;
488
489 for &val in buf.iter() {
490 assert_eq!(val, 17);
491 has_value = true;
492 }
493
494 assert!(has_value);
495 }
496
497 #[test]
498 #[should_panic(expected = "Capacity must be larger than zero")]
499 fn test_zero_capacity() {
500 let _buf = SmoothBuffer::<0, f32>::new();
501 }
503
504 #[test]
505 fn test_gaussian_filter_with_spikes() {
506 let mut buf = SmoothBuffer::<10, f64>::new();
507
508 for _ in 0..8 {
510 buf.push(1.0);
511 }
512
513 buf.push(10.0);
515 buf.push(1.0);
516
517 let avg = buf.average();
519 let gaussian = buf.gaussian_filter();
520
521 assert!(gaussian < avg);
523 assert!(gaussian > 1.0);
524
525 let mut buf2 = SmoothBuffer::<5, f64>::new();
527 buf2.push(1.0);
528 buf2.push(10.0);
529 buf2.push(5.0);
530 buf2.push(5.0);
531 buf2.push(1.0);
532
533 let gaussian2 = buf2.gaussian_filter();
535 assert!(gaussian2 > 4.0); assert!(gaussian2 < 5.5); }
538}