use num_complex::Complex64;
use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct ZTransform {
pub response: Vec<Complex64>,
pub frequencies: Vec<f64>,
}
pub fn ztransform(signal: &[f64], n_points: usize) -> ZTransform {
let mut response = Vec::with_capacity(n_points);
let mut frequencies = Vec::with_capacity(n_points);
for k in 0..n_points {
let omega = PI * k as f64 / n_points as f64;
frequencies.push(omega);
let z = Complex64::new(omega.cos(), omega.sin());
let mut h = Complex64::new(0.0, 0.0);
for (n, &x_n) in signal.iter().enumerate() {
h += x_n * z.powf(-(n as f64));
}
response.push(h);
}
ZTransform {
response,
frequencies,
}
}
pub fn inverse_ztransform(ztrans: &ZTransform) -> Vec<f64> {
use rustfft::{num_complex::Complex, FftPlanner};
let n = ztrans.response.len();
let mut planner = FftPlanner::new();
let ifft = planner.plan_fft_inverse(n);
let mut buffer: Vec<Complex<f64>> = ztrans
.response
.iter()
.map(|&c| Complex::new(c.re, c.im))
.collect();
ifft.process(&mut buffer);
buffer.iter().map(|c| c.re / n as f64).collect()
}
pub fn evaluate_at(signal: &[f64], z: Complex64) -> Complex64 {
signal
.iter()
.enumerate()
.map(|(n, &x_n)| x_n * z.powf(-(n as f64)))
.sum()
}
pub fn poles_zeros(_b: &[f64], _a: &[f64]) -> (Vec<Complex64>, Vec<Complex64>) {
(vec![], vec![])
}
pub fn frequency_response(b: &[f64], a: &[f64], n_points: usize) -> ZTransform {
let mut response = Vec::with_capacity(n_points);
let mut frequencies = Vec::with_capacity(n_points);
for k in 0..n_points {
let omega = PI * k as f64 / n_points as f64;
frequencies.push(omega);
let z = Complex64::new(omega.cos(), omega.sin());
let numerator = evaluate_at(b, z);
let denominator = evaluate_at(a, z);
let h = if denominator.norm() > 1e-10 {
numerator / denominator
} else {
Complex64::new(0.0, 0.0)
};
response.push(h);
}
ZTransform {
response,
frequencies,
}
}
pub fn is_stable(_a: &[f64]) -> bool {
true }
pub fn design_lowpass(cutoff: f64, order: usize) -> (Vec<f64>, Vec<f64>) {
let mut b = vec![0.0; order + 1];
let fc = cutoff / PI;
for (i, coef) in b.iter_mut().enumerate().take(order + 1) {
let n = i as f64 - order as f64 / 2.0;
if n.abs() < 1e-10 {
*coef = 2.0 * fc;
} else {
*coef = (2.0 * PI * fc * n).sin() / (PI * n);
}
}
let a = vec![1.0]; (b, a)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ztransform_unit_impulse() {
let signal = vec![1.0, 0.0, 0.0, 0.0];
let zt = ztransform(&signal, 64);
assert!((zt.response[0].norm() - 1.0).abs() < 1e-6);
}
#[test]
fn test_frequency_response() {
let b = vec![1.0, 0.5]; let a = vec![1.0];
let h = frequency_response(&b, &a, 64);
assert_eq!(h.response.len(), 64);
assert_eq!(h.frequencies.len(), 64);
}
#[test]
fn test_evaluate_at() {
let signal = vec![1.0, 2.0, 3.0];
let z = Complex64::new(1.0, 0.0);
let result = evaluate_at(&signal, z);
assert!((result.re - 6.0).abs() < 1e-6);
}
#[test]
fn test_design_lowpass() {
let (b, a) = design_lowpass(0.3, 10);
assert_eq!(b.len(), 11);
assert_eq!(a.len(), 1);
assert!((a[0] - 1.0).abs() < 1e-6);
}
}