#[derive(Clone, Copy, Debug)]
pub struct Complementary {
estimate: f32,
alpha: f32,
}
impl Complementary {
pub fn new(alpha: f32, initial: f32) -> Self {
Self {
estimate: initial,
alpha: unit_interval(alpha),
}
}
pub fn update(&mut self, rate: f32, absolute: f32, dt: f32) -> f32 {
let integrated = self.estimate + rate * dt;
self.estimate = self.alpha * integrated + (1.0 - self.alpha) * absolute;
self.estimate
}
pub fn estimate(&self) -> f32 {
self.estimate
}
}
#[allow(clippy::manual_clamp)]
fn unit_interval(value: f32) -> f32 {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alpha_one_integrates_the_rate_only() {
let mut filter = Complementary::new(1.0, 0.0);
assert!((filter.update(10.0, 999.0, 1.0) - 10.0).abs() < 1e-6); assert!((filter.update(10.0, 999.0, 1.0) - 20.0).abs() < 1e-6);
}
#[test]
fn alpha_zero_follows_the_absolute_reading() {
let mut filter = Complementary::new(0.0, 0.0);
assert!((filter.update(10.0, 3.0, 1.0) - 3.0).abs() < 1e-6);
}
#[test]
fn it_blends_rate_and_absolute() {
let mut filter = Complementary::new(0.9, 0.0);
assert!((filter.update(2.0, 0.0, 1.0) - 1.8).abs() < 1e-6);
}
#[test]
fn alpha_is_clamped_to_the_unit_interval() {
let mut filter = Complementary::new(5.0, 0.0); assert!((filter.update(4.0, 100.0, 1.0) - 4.0).abs() < 1e-6);
}
}