use num_complex::Complex64;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZTransform;
impl ZTransform {
pub fn transform(signal: &[f64], z: Complex64) -> Complex64 {
signal.iter().enumerate().map(|(n, &x)| {
x * z.powi(-(n as i32))
}).sum()
}
pub fn dtft(signal: &[f64], omega: f64) -> Complex64 {
let z = Complex64::from_polar(1.0, omega);
Self::transform(signal, z)
}
pub fn frequency_response(signal: &[f64], frequencies: &[f64]) -> Vec<Complex64> {
frequencies.iter().map(|&omega| Self::dtft(signal, omega)).collect()
}
pub fn magnitude_response_db(signal: &[f64], frequencies: &[f64]) -> Vec<f64> {
Self::frequency_response(signal, frequencies)
.iter()
.map(|h| 20.0 * h.norm().log10())
.collect()
}
pub fn phase_response(signal: &[f64], frequencies: &[f64]) -> Vec<f64> {
Self::frequency_response(signal, frequencies)
.iter()
.map(|h| h.arg())
.collect()
}
pub fn zeros(signal: &[f64]) -> Vec<Complex64> {
let n = signal.len();
if n <= 1 {
return vec![];
}
let mut coeffs: Vec<f64> = signal.iter().rev().cloned().collect();
Self::find_roots(&coeffs)
}
fn find_roots(coeffs: &[f64]) -> Vec<Complex64> {
let n = coeffs.len();
if n <= 1 {
return vec![];
}
let a0 = coeffs[0];
if a0.abs() < 1e-30 {
return vec![];
}
let size = n - 1;
use nalgebra::DMatrix;
let mut companion = DMatrix::<f64>::zeros(size, size);
for i in 0..size {
companion[(i, size - 1)] = -coeffs[n - 1 - i] / a0;
}
for i in 1..size {
companion[(i, i - 1)] = 1.0;
}
let eigenvalues = companion.complex_eigenvalues();
eigenvalues.iter().map(|c| Complex64::new(c.re, c.im)).collect()
}
pub fn first_order_transfer(z: Complex64, b0: f64, b1: f64, a1: f64) -> Complex64 {
let num = b0 + b1 * z.powi(-1);
let den = 1.0 + a1 * z.powi(-1);
num / den
}
pub fn group_delay(signal: &[f64], omega: f64, d_omega: f64) -> f64 {
let h1 = Self::dtft(signal, omega - d_omega / 2.0);
let h2 = Self::dtft(signal, omega + d_omega / 2.0);
let phase_diff = h2.arg() - h1.arg();
-phase_diff / d_omega
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ztransform_delta() {
let signal = vec![1.0];
let z = Complex64::new(2.0, 0.0);
let result = ZTransform::transform(&signal, z);
assert!((result.re - 1.0).abs() < 1e-10);
}
#[test]
fn test_ztransform_unit_step() {
let signal = vec![1.0, 1.0, 1.0, 1.0];
let z = Complex64::new(2.0, 0.0);
let result = ZTransform::transform(&signal, z);
let expected = 1.0 + 0.5 + 0.25 + 0.125;
assert!((result.re - expected).abs() < 1e-10);
}
#[test]
fn test_ztransform_geometric() {
let a = 0.5;
let signal = vec![a, a * a, a * a * a];
let z = Complex64::new(1.0, 0.0);
let result = ZTransform::transform(&signal, z);
let expected = a + a * a + a * a * a;
assert!((result.re - expected).abs() < 1e-10);
}
#[test]
fn test_dtft_dc() {
let signal = vec![1.0, 1.0, 1.0, 1.0];
let result = ZTransform::dtft(&signal, 0.0);
assert!((result.re - 4.0).abs() < 1e-10);
}
#[test]
fn test_dtft_nyquist() {
let signal = vec![1.0, -1.0, 1.0, -1.0];
let result = ZTransform::dtft(&signal, std::f64::consts::PI);
assert!((result.re - 4.0).abs() < 1e-10);
}
#[test]
fn test_magnitude_response_db() {
let signal = vec![1.0, 0.0, 0.0, 0.0];
let freqs = vec![0.0, std::f64::consts::PI / 4.0];
let db = ZTransform::magnitude_response_db(&signal, &freqs);
assert!((db[0] - 0.0).abs() < 1e-10);
}
#[test]
fn test_phase_response() {
let signal = vec![1.0, 0.0, 0.0, 0.0];
let freqs = vec![0.0];
let phase = ZTransform::phase_response(&signal, &freqs);
assert!(phase[0].abs() < 1e-10);
}
#[test]
fn test_first_order_transfer() {
let z = Complex64::new(1.0, 0.0);
let h = ZTransform::first_order_transfer(z, 1.0, 0.5, -0.3);
let expected = (1.0 + 0.5) / (1.0 - 0.3);
assert!((h.re - expected).abs() < 1e-10);
}
#[test]
fn test_group_delay() {
let signal = vec![1.0, 0.0, 0.0, 0.0];
let gd = ZTransform::group_delay(&signal, 0.5, 0.01);
assert!(gd.abs() < 0.5, "Group delay of delta should be ~0: {gd}");
}
}