#![forbid(unsafe_code)]
use crate::dsfb::features::Features;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlewSignal {
None,
DensityJump,
HistJump,
}
#[derive(Debug, Clone)]
pub struct SlewDetector {
density_ema: f64,
hist_ema: f64,
density_factor: f64,
hist_factor: f64,
samples: u64,
}
impl Default for SlewDetector {
fn default() -> Self {
Self {
density_ema: 0.0,
hist_ema: 0.0,
density_factor: 4.0,
hist_factor: 4.0,
samples: 0,
}
}
}
impl SlewDetector {
pub fn new(density_factor: f64, hist_factor: f64) -> Self {
Self {
density_factor,
hist_factor,
..Self::default()
}
}
pub fn observe(&mut self, f: &Features) -> SlewSignal {
self.samples += 1;
let rho = 0.8f64;
let mut sig = SlewSignal::None;
if self.samples > 5 {
let d_jump = self.density_ema >= 0.005
&& f.diff_density > self.density_ema * self.density_factor
&& f.diff_density - self.density_ema > 0.2;
let h_jump = self.hist_ema >= 0.005
&& f.hist_change > self.hist_ema * self.hist_factor
&& f.hist_change - self.hist_ema > 0.2;
if d_jump && h_jump {
sig = SlewSignal::DensityJump;
} else if h_jump {
sig = SlewSignal::HistJump;
} else if d_jump {
sig = SlewSignal::DensityJump;
}
}
self.density_ema = rho * self.density_ema + (1.0 - rho) * f.diff_density;
self.hist_ema = rho * self.hist_ema + (1.0 - rho) * f.hist_change;
sig
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsfb::features::Channel;
fn feats(density: f64, hist: f64) -> Features {
Features {
channel: Channel::PrevVersion,
residual_ratio: density,
diff_density: density,
diff_runs: 1,
diff_positions: (density * 100.0) as u32,
hist_change: hist,
exact_match: false,
}
}
#[test]
fn detects_jump() {
let mut d = SlewDetector::default();
for _ in 0..10 {
assert_eq!(d.observe(&feats(0.01, 0.01)), SlewSignal::None);
}
assert_ne!(d.observe(&feats(0.9, 0.9)), SlewSignal::None);
}
#[test]
fn ignores_gradual_change() {
let mut d = SlewDetector::default();
let mut v = 0.01f64;
for _ in 0..40 {
d.observe(&feats(v, v));
v += 0.005;
}
assert_eq!(d.observe(&feats(v, v)), SlewSignal::None);
}
}