ThreadAbort's picture
Refactor: Remove internationalization (i18n) support and related files
e3e7558
//! Exponential Moving Average (EMA) for smooth tracking
//!
//! EMA smooths noisy measurements while maintaining responsiveness.
//! Used to track period and amplitude patterns in Marine algorithm.
#![cfg_attr(not(feature = "std"), no_std)]
/// Exponential Moving Average tracker
///
/// EMA formula: value = alpha * new + (1 - alpha) * old
/// - Higher alpha = faster response, more noise
/// - Lower alpha = slower response, smoother
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Ema {
/// Smoothing factor (0..1)
alpha: f32,
/// Current smoothed value
value: f32,
/// Whether we've received at least one sample
initialized: bool,
}
impl Ema {
/// Create new EMA with given smoothing factor
///
/// # Arguments
/// * `alpha` - Smoothing factor (0..1). Higher = faster adaptation.
///
/// # Example
/// ```
/// use marine_salience::ema::Ema;
/// let mut ema = Ema::new(0.1); // 10% new, 90% old
/// ema.update(100.0);
/// assert_eq!(ema.get(), 100.0); // First value becomes baseline
/// ema.update(200.0);
/// assert!((ema.get() - 110.0).abs() < 0.01); // 0.1*200 + 0.9*100
/// ```
pub const fn new(alpha: f32) -> Self {
Self {
alpha,
value: 0.0,
initialized: false,
}
}
/// Update EMA with new measurement
pub fn update(&mut self, x: f32) {
if !self.initialized {
// First value becomes the baseline
self.value = x;
self.initialized = true;
} else {
// EMA update: new = alpha * x + (1 - alpha) * old
self.value = self.alpha * x + (1.0 - self.alpha) * self.value;
}
}
/// Get current smoothed value
pub fn get(&self) -> f32 {
self.value
}
/// Check if EMA has been initialized (received at least one sample)
pub fn is_ready(&self) -> bool {
self.initialized
}
/// Reset EMA to uninitialized state
pub fn reset(&mut self) {
self.value = 0.0;
self.initialized = false;
}
/// Get the smoothing factor
pub fn alpha(&self) -> f32 {
self.alpha
}
/// Set a new smoothing factor
pub fn set_alpha(&mut self, alpha: f32) {
self.alpha = alpha.clamp(0.0, 1.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first_value_becomes_baseline() {
let mut ema = Ema::new(0.1);
assert!(!ema.is_ready());
ema.update(42.0);
assert!(ema.is_ready());
assert_eq!(ema.get(), 42.0);
}
#[test]
fn test_ema_smoothing() {
let mut ema = Ema::new(0.1);
ema.update(100.0);
ema.update(200.0);
// 0.1 * 200 + 0.9 * 100 = 20 + 90 = 110
assert!((ema.get() - 110.0).abs() < 0.001);
}
#[test]
fn test_high_alpha_fast_response() {
let mut ema = Ema::new(0.9);
ema.update(100.0);
ema.update(200.0);
// 0.9 * 200 + 0.1 * 100 = 180 + 10 = 190
assert!((ema.get() - 190.0).abs() < 0.001);
}
#[test]
fn test_reset() {
let mut ema = Ema::new(0.1);
ema.update(100.0);
assert!(ema.is_ready());
ema.reset();
assert!(!ema.is_ready());
assert_eq!(ema.get(), 0.0);
}
}