use num_traits::Float;
pub fn zoh_discretize<T: Float>(a: T, b: T, delta: T) -> (T, T) {
let a_bar = (delta * a).exp();
let b_bar = if a.abs() > T::epsilon() {
(a_bar - T::one()) / a * b
} else {
delta * b
};
(a_bar, b_bar)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn handles_zero_a_with_linear_approximation() {
let (a_bar, b_bar) = zoh_discretize(0.0_f64, 2.0, 0.5);
assert!((a_bar - 1.0).abs() < 1e-12);
assert!((b_bar - 1.0).abs() < 1e-12); }
#[test]
fn matches_euler_integration_for_nonzero_a() {
let a = -1.0_f64;
let b = 1.0_f64;
let delta = 0.1_f64;
let steps = 200_000;
let dt = delta / steps as f64;
let mut h = 0.0_f64;
for _ in 0..steps {
h += (a * h + b) * dt;
}
let (a_bar, b_bar) = zoh_discretize(a, b, delta);
let h_zoh = a_bar * 0.0 + b_bar;
assert!((h - h_zoh).abs() < 1e-6, "euler={h}, zoh={h_zoh}");
}
}