use std::{
collections::VecDeque,
num::NonZeroUsize,
};
use getset::CopyGetters;
use num::Float;
use crate::View;
#[derive(Clone, Debug, CopyGetters)]
pub struct Alma<T, V> {
view: V,
#[getset(get_copy = "pub")]
window_len: NonZeroUsize,
wtd_sum: T,
cum_wt: T,
m: T,
s: T,
q_vals: VecDeque<T>,
q_wtd: VecDeque<T>,
q_out: VecDeque<T>,
}
impl<T, V> Alma<T, V>
where
V: View<T>,
T: Float,
{
pub fn new(view: V, window_len: NonZeroUsize) -> Self {
Alma::new_custom(
view,
window_len,
T::from(6.0).expect("Can convert"),
T::from(0.85).expect("Can convert"),
)
}
pub fn new_custom(view: V, window_len: NonZeroUsize, sigma: T, offset: T) -> Self {
let wl = T::from(window_len.get()).expect("can convert");
let m = offset * (wl + T::one());
let s = wl / sigma;
Alma {
view,
window_len,
m,
s,
wtd_sum: T::zero(),
cum_wt: T::zero(),
q_vals: VecDeque::with_capacity(window_len.get()),
q_wtd: VecDeque::with_capacity(window_len.get()),
q_out: VecDeque::with_capacity(window_len.get()),
}
}
}
impl<T, V> View<T> for Alma<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");
if self.q_vals.len() >= self.window_len.get() {
let old_val = self.q_vals.front().unwrap();
let old_wtd = self.q_wtd.front().unwrap();
self.wtd_sum = self.wtd_sum - *old_wtd * *old_val;
self.cum_wt = self.cum_wt - *old_wtd;
self.q_vals.pop_front();
self.q_wtd.pop_front();
self.q_out.pop_front();
}
let count = T::from(self.q_vals.len()).expect("can convert");
let wtd = (-(count - self.m).powi(2)
/ (T::from(2.0).expect("can convert") * self.s * self.s))
.exp();
self.wtd_sum = self.wtd_sum + wtd * val;
self.cum_wt = self.cum_wt + wtd;
self.q_vals.push_back(val);
self.q_wtd.push_back(wtd);
let ala = self.wtd_sum / self.cum_wt;
debug_assert!(ala.is_finite(), "value must be finite");
self.q_out.push_back(ala);
}
fn last(&self) -> Option<T> {
self.q_out.back().copied()
}
}
#[cfg(test)]
mod tests {
use rand::{
RngExt,
rng,
};
use super::*;
use crate::{
plot::plot_values,
pure_functions::Echo,
test_data::TEST_DATA,
};
#[test]
fn alma() {
let mut rng = rng();
let mut alma = Alma::new(Echo::new(), NonZeroUsize::new(16).unwrap());
for _ in 0..1_000_000 {
let v = rng.random::<f64>();
alma.update(v);
let last = alma.last().unwrap();
assert!(last >= 0.0);
assert!(last <= 1.0);
}
}
#[test]
fn alma_plot() {
let mut alma = Alma::new(Echo::new(), NonZeroUsize::new(16).unwrap());
let mut out: Vec<f64> = Vec::new();
for v in &TEST_DATA {
alma.update(*v);
out.push(alma.last().unwrap())
}
let filename = "img/alma.png";
plot_values(out, filename).unwrap();
}
}