lau_harmonic_analysis/
ztransform.rs1use num_complex::Complex64;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ZTransform;
9
10impl ZTransform {
11 pub fn transform(signal: &[f64], z: Complex64) -> Complex64 {
13 signal.iter().enumerate().map(|(n, &x)| {
14 x * z.powi(-(n as i32))
15 }).sum()
16 }
17
18 pub fn dtft(signal: &[f64], omega: f64) -> Complex64 {
20 let z = Complex64::from_polar(1.0, omega);
21 Self::transform(signal, z)
22 }
23
24 pub fn frequency_response(signal: &[f64], frequencies: &[f64]) -> Vec<Complex64> {
26 frequencies.iter().map(|&omega| Self::dtft(signal, omega)).collect()
27 }
28
29 pub fn magnitude_response_db(signal: &[f64], frequencies: &[f64]) -> Vec<f64> {
31 Self::frequency_response(signal, frequencies)
32 .iter()
33 .map(|h| 20.0 * h.norm().log10())
34 .collect()
35 }
36
37 pub fn phase_response(signal: &[f64], frequencies: &[f64]) -> Vec<f64> {
39 Self::frequency_response(signal, frequencies)
40 .iter()
41 .map(|h| h.arg())
42 .collect()
43 }
44
45 pub fn zeros(signal: &[f64]) -> Vec<Complex64> {
48 let n = signal.len();
49 if n <= 1 {
50 return vec![];
51 }
52 let mut coeffs: Vec<f64> = signal.iter().rev().cloned().collect();
55 Self::find_roots(&coeffs)
56 }
57
58 fn find_roots(coeffs: &[f64]) -> Vec<Complex64> {
60 let n = coeffs.len();
61 if n <= 1 {
62 return vec![];
63 }
64
65 let a0 = coeffs[0];
67 if a0.abs() < 1e-30 {
68 return vec![];
69 }
70
71 let size = n - 1;
73 use nalgebra::DMatrix;
74 let mut companion = DMatrix::<f64>::zeros(size, size);
75
76 for i in 0..size {
78 companion[(i, size - 1)] = -coeffs[n - 1 - i] / a0;
79 }
80 for i in 1..size {
82 companion[(i, i - 1)] = 1.0;
83 }
84
85 let eigenvalues = companion.complex_eigenvalues();
86 eigenvalues.iter().map(|c| Complex64::new(c.re, c.im)).collect()
87 }
88
89 pub fn first_order_transfer(z: Complex64, b0: f64, b1: f64, a1: f64) -> Complex64 {
91 let num = b0 + b1 * z.powi(-1);
92 let den = 1.0 + a1 * z.powi(-1);
93 num / den
94 }
95
96 pub fn group_delay(signal: &[f64], omega: f64, d_omega: f64) -> f64 {
98 let h1 = Self::dtft(signal, omega - d_omega / 2.0);
99 let h2 = Self::dtft(signal, omega + d_omega / 2.0);
100 let phase_diff = h2.arg() - h1.arg();
101 -phase_diff / d_omega
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_ztransform_delta() {
111 let signal = vec![1.0];
113 let z = Complex64::new(2.0, 0.0);
114 let result = ZTransform::transform(&signal, z);
115 assert!((result.re - 1.0).abs() < 1e-10);
116 }
117
118 #[test]
119 fn test_ztransform_unit_step() {
120 let signal = vec![1.0, 1.0, 1.0, 1.0];
122 let z = Complex64::new(2.0, 0.0);
123 let result = ZTransform::transform(&signal, z);
124 let expected = 1.0 + 0.5 + 0.25 + 0.125;
125 assert!((result.re - expected).abs() < 1e-10);
126 }
127
128 #[test]
129 fn test_ztransform_geometric() {
130 let a = 0.5;
132 let signal = vec![a, a * a, a * a * a];
133 let z = Complex64::new(1.0, 0.0);
134 let result = ZTransform::transform(&signal, z);
135 let expected = a + a * a + a * a * a;
136 assert!((result.re - expected).abs() < 1e-10);
137 }
138
139 #[test]
140 fn test_dtft_dc() {
141 let signal = vec![1.0, 1.0, 1.0, 1.0];
142 let result = ZTransform::dtft(&signal, 0.0);
143 assert!((result.re - 4.0).abs() < 1e-10);
144 }
145
146 #[test]
147 fn test_dtft_nyquist() {
148 let signal = vec![1.0, -1.0, 1.0, -1.0];
149 let result = ZTransform::dtft(&signal, std::f64::consts::PI);
150 assert!((result.re - 4.0).abs() < 1e-10);
151 }
152
153 #[test]
154 fn test_magnitude_response_db() {
155 let signal = vec![1.0, 0.0, 0.0, 0.0];
156 let freqs = vec![0.0, std::f64::consts::PI / 4.0];
157 let db = ZTransform::magnitude_response_db(&signal, &freqs);
158 assert!((db[0] - 0.0).abs() < 1e-10);
160 }
161
162 #[test]
163 fn test_phase_response() {
164 let signal = vec![1.0, 0.0, 0.0, 0.0];
165 let freqs = vec![0.0];
166 let phase = ZTransform::phase_response(&signal, &freqs);
167 assert!(phase[0].abs() < 1e-10);
168 }
169
170 #[test]
171 fn test_first_order_transfer() {
172 let z = Complex64::new(1.0, 0.0);
173 let h = ZTransform::first_order_transfer(z, 1.0, 0.5, -0.3);
174 let expected = (1.0 + 0.5) / (1.0 - 0.3);
175 assert!((h.re - expected).abs() < 1e-10);
176 }
177
178 #[test]
179 fn test_group_delay() {
180 let signal = vec![1.0, 0.0, 0.0, 0.0];
181 let gd = ZTransform::group_delay(&signal, 0.5, 0.01);
182 assert!(gd.abs() < 0.5, "Group delay of delta should be ~0: {gd}");
184 }
185}