use super::FilterError;
pub const DEFAULT_SETTLING_TOLERANCE: f64 = 1e-3;
const SETTLING_HORIZON_MARGIN: usize = 64;
const A0_TOLERANCE: f64 = 1e-12;
const NEGLIGIBLE_POLE_RADIUS: f64 = 1e-12;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Section {
pub b: [f64; 3],
pub a: [f64; 3],
}
impl Section {
pub fn pole_radius(&self) -> f64 {
let (a1, a2) = (self.a[1], self.a[2]);
let disc = a1 * a1 - 4.0 * a2;
if disc < 0.0 {
a2.sqrt()
} else {
let sq = disc.sqrt();
((-a1 + sq) / 2.0).abs().max(((-a1 - sq) / 2.0).abs())
}
}
fn step_zi(&self) -> [f64; 2] {
let [b0, b1, b2] = self.b;
let [_, a1, a2] = self.a;
let r0 = b1 - a1 * b0;
let r1 = b2 - a2 * b0;
let z0 = (r0 + r1) / (1.0 + a1 + a2);
[z0, r1 - a2 * z0]
}
fn dc_gain(&self) -> f64 {
self.b.iter().sum::<f64>() / self.a.iter().sum::<f64>()
}
pub fn svf(&self) -> [f64; 6] {
let [b0, b1, b2] = self.b;
let [_, a1, a2] = self.a;
let (n0, n1, n2) = (b0 + b1 + b2, 2.0 * (b0 - b2), b0 - b1 + b2);
let (d0, d1, d2) = (1.0 + a1 + a2, 2.0 * (1.0 - a2), 1.0 - a1 + a2);
let g = (d0 / d2).sqrt();
let k = d1 / (d2 * g);
let m0 = n2 / d2;
let m1 = n1 / (d2 * g) - m0 * k;
let m2 = n0 / (d2 * g * g) - m0;
let c1 = 1.0 / (1.0 + g * (g + k));
let c2 = g * c1;
[c1, c2, g * c2, m0, m1, m2]
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sos {
pub sections: Vec<Section>,
}
impl Sos {
pub fn new(sections: Vec<Section>) -> Self {
Self { sections }
}
pub fn len(&self) -> usize {
self.sections.len()
}
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
pub fn validate(&self) -> Result<(), FilterError> {
let ok = !self.sections.is_empty()
&& self.sections.iter().all(|s| {
s.b.iter().chain(s.a.iter()).all(|c| c.is_finite())
&& (s.a[0] - 1.0).abs() < A0_TOLERANCE
&& s.pole_radius() < 1.0
});
if ok { Ok(()) } else { Err(FilterError::InvalidSections) }
}
pub fn zi(&self) -> Vec<[f64; 2]> {
let mut scale = 1.0;
self.sections
.iter()
.map(|s| {
let [z0, z1] = s.step_zi();
let zi = [scale * z0, scale * z1];
scale *= s.dc_gain();
zi
})
.collect()
}
pub fn settling_samples(&self, tol: f64) -> usize {
let horizon = 2 * self.pole_settling_bound(tol) + SETTLING_HORIZON_MARGIN;
let mut impulse = vec![0.0; horizon];
impulse[0] = 1.0;
let h = self.filter(&impulse, false);
let total: f64 = h.iter().map(|v| v.abs()).sum();
let mut tail = 0.0;
for (t, v) in h.iter().enumerate().rev() {
tail += v.abs();
if tail > tol * total {
return t + 1;
}
}
0
}
pub fn pole_settling_bound(&self, tol: f64) -> usize {
self.sections
.iter()
.map(|s| {
let r = s.pole_radius();
if r < NEGLIGIBLE_POLE_RADIUS {
2
} else {
(tol.ln() / r.ln()).ceil().max(2.0) as usize
}
})
.sum()
}
pub fn dc_gain(&self) -> f64 {
self.sections.iter().map(Section::dc_gain).product()
}
pub fn filter(&self, x: &[f64], steady_state: bool) -> Vec<f64> {
let x0 = x.first().copied().unwrap_or(0.0);
let mut state: Vec<[f64; 2]> = if steady_state {
self.zi().into_iter().map(|[a, b]| [a * x0, b * x0]).collect()
} else {
vec![[0.0; 2]; self.sections.len()]
};
x.iter()
.map(|&xi| {
let mut v = xi;
for (s, z) in self.sections.iter().zip(state.iter_mut()) {
let y = s.b[0] * v + z[0];
z[0] = s.b[1] * v - s.a[1] * y + z[1];
z[1] = s.b[2] * v - s.a[2] * y;
v = y;
}
v
})
.collect()
}
pub fn filtfilt(&self, x: &[f64], padlen: usize) -> Vec<f64> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let p = padlen.min(n - 1);
let mut ext = Vec::with_capacity(n + 2 * p);
ext.extend((1..=p).rev().map(|k| 2.0 * x[0] - x[k]));
ext.extend_from_slice(x);
ext.extend((1..=p).map(|k| 2.0 * x[n - 1] - x[n - 1 - k]));
let mut y = self.filter(&ext, true);
y.reverse();
let mut y = self.filter(&y, true);
y.reverse();
y[p..p + n].to_vec()
}
}