mod biquad;
mod butterworth;
mod chebyshev;
mod lead_lag;
mod pid;
mod pid_tune;
#[cfg(test)]
mod tests;
pub use biquad::{Biquad, BiquadCascade};
pub use butterworth::{butterworth_highpass, butterworth_lowpass};
pub use chebyshev::{chebyshev1_highpass, chebyshev1_lowpass};
pub use lead_lag::{lead_compensator, lag_compensator};
pub use pid::Pid;
pub use pid_tune::{FopdtModel, PidGains, ziegler_nichols_ultimate};
use crate::traits::FloatScalar;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ControlError {
InvalidOrder,
InvalidFrequency,
InvalidRipple,
NearZeroDenominator,
}
impl core::fmt::Display for ControlError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ControlError::InvalidOrder => write!(f, "invalid filter order or section count"),
ControlError::InvalidFrequency => {
write!(f, "cutoff frequency must be in (0, sample_rate/2)")
}
ControlError::InvalidRipple => write!(f, "passband ripple must be positive"),
ControlError::NearZeroDenominator => {
write!(f, "denominator leading coefficient a[0] is near zero")
}
}
}
}
pub(super) fn validate_design_params<T: FloatScalar, const N: usize>(
order: usize,
cutoff: T,
sample_rate: T,
) -> Result<(), ControlError> {
if order == 0 {
return Err(ControlError::InvalidOrder);
}
let expected_sections = (order + 1) / 2;
if N != expected_sections {
return Err(ControlError::InvalidOrder);
}
let zero = T::zero();
let two = T::one() + T::one();
let nyquist = sample_rate / two;
if cutoff <= zero || cutoff >= nyquist || !cutoff.is_finite() {
return Err(ControlError::InvalidFrequency);
}
if !sample_rate.is_finite() || sample_rate <= zero {
return Err(ControlError::InvalidFrequency);
}
Ok(())
}