use std::{
f64::consts::PI,
num::NonZeroUsize,
};
use getset::CopyGetters;
use num::Float;
use crate::View;
#[derive(Debug, Clone, CopyGetters)]
pub struct SuperSmoother<T, V> {
view: V,
#[getset(get_copy = "pub")]
window_len: NonZeroUsize,
i: usize,
c1: T,
c2: T,
c3: T,
filt: T,
filt_1: T,
filt_2: T,
last_val: T,
}
impl<T, V> SuperSmoother<T, V>
where
V: View<T>,
T: Float,
{
pub fn new(view: V, window_len: NonZeroUsize) -> Self {
let wl = T::from(window_len.get()).expect("can convert");
let a1 =
(-T::from(1.414).expect("can convert") * T::from(PI).expect("can convert") / wl).exp();
let b1 = T::from(2.0).expect("can convert")
* a1
* (T::from(4.4422).expect("can convert") / wl).cos();
let c2 = b1;
let c3 = -a1 * a1;
Self {
view,
window_len,
i: 0,
c1: T::one() - c2 - c3,
c2,
c3,
filt: T::zero(),
filt_1: T::zero(),
filt_2: T::zero(),
last_val: T::zero(),
}
}
}
impl<T, V> View<T> for SuperSmoother<T, V>
where
V: View<T>,
T: Float,
{
fn update(&mut self, val: T) {
debug_assert!(val.is_finite(), "value must be finite");
self.view.update(val);
let Some(val) = self.view.last() else { return };
debug_assert!(val.is_finite(), "value must be finite");
self.filt = self.c1 * (val + self.last_val) / T::from(2.0).expect("can convert")
+ (self.c2 * self.filt_1)
+ (self.c3 * self.filt_2);
self.filt_2 = self.filt_1;
self.filt_1 = self.filt;
self.last_val = val;
self.i += 1;
}
#[inline]
fn last(&self) -> Option<T> {
if self.i < self.window_len.get() {
None
} else {
let out = self.filt;
debug_assert!(out.is_finite(), "value must be finite");
Some(self.filt)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
plot::plot_values,
pure_functions::Echo,
test_data::TEST_DATA,
};
#[test]
fn super_smoother_plot() {
let mut ss = SuperSmoother::new(Echo::new(), NonZeroUsize::new(20).unwrap());
let mut out: Vec<f64> = Vec::with_capacity(TEST_DATA.len());
for v in &TEST_DATA {
ss.update(*v);
if let Some(val) = ss.last() {
out.push(val);
}
}
let filename = "img/super_smoother.png";
plot_values(out, filename).unwrap();
}
}