Skip to main content

oxiphysics_core/complex/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#![allow(clippy::needless_range_loop)]
6use super::types::{Complex, ComplexMat2};
7use std::f64::consts::PI;
8
9/// Check whether a function `f: ℂ → ℂ` satisfies the Cauchy-Riemann equations
10/// at point `z` using finite differences with step `h`.
11///
12/// The C-R equations are: ∂u/∂x = ∂v/∂y  and  ∂u/∂y = -∂v/∂x,
13/// where f(z) = u + iv.
14pub fn cauchy_riemann_check(f: &impl Fn(Complex) -> Complex, z: Complex, h: f64) -> bool {
15    let fxp = f(Complex::new(z.re + h, z.im));
16    let fxm = f(Complex::new(z.re - h, z.im));
17    let fyp = f(Complex::new(z.re, z.im + h));
18    let fym = f(Complex::new(z.re, z.im - h));
19    let du_dx = (fxp.re - fxm.re) / (2.0 * h);
20    let du_dy = (fyp.re - fym.re) / (2.0 * h);
21    let dv_dx = (fxp.im - fxm.im) / (2.0 * h);
22    let dv_dy = (fyp.im - fym.im) / (2.0 * h);
23    let tol = h.sqrt().max(1e-4);
24    (du_dx - dv_dy).abs() < tol && (du_dy + dv_dx).abs() < tol
25}
26/// Joukowski conformal map: `w = z + 1/z`.
27///
28/// Maps circles to airfoil-like shapes (Joukowski profiles).
29pub fn joukowski(z: Complex) -> Complex {
30    z + Complex::one() / z
31}
32/// Schwarz-Christoffel map approximation for a rectangle.
33///
34/// Maps the upper half-plane to a rectangle-like region.
35/// Uses the simplified integral `∫ dz / sqrt((1-z²)(1-k²z²))`.
36/// Computed numerically using midpoint quadrature.
37pub fn schwarz_christoffel_rectangle(z: Complex, k: f64, n_steps: usize) -> Complex {
38    let mut sum = Complex::zero();
39    let n = n_steps as f64;
40    for i in 0..n_steps {
41        let t_mid = z * Complex::new((i as f64 + 0.5) / n, 0.0);
42        let dz = z * Complex::new(1.0 / n, 0.0);
43        let t2 = t_mid * t_mid;
44        let k2 = Complex::new(k * k, 0.0);
45        let factor = (Complex::one() - t2) * (Complex::one() - k2 * t2);
46        let integrand = Complex::one() / factor.sqrt();
47        sum = sum + integrand * dz;
48    }
49    sum
50}
51/// Exponential conformal map: `w = exp(z)`.
52///
53/// Maps horizontal strips of height 2Ï€ to annular sectors.
54pub fn exponential_conformal(z: Complex) -> Complex {
55    z.exp()
56}
57/// Logarithmic conformal map: `w = ln(z)` (principal branch).
58///
59/// Inverse of the exponential map; maps annular sectors to strips.
60pub fn logarithmic_conformal(z: Complex) -> Complex {
61    z.ln()
62}
63/// Cayley transform: maps the upper half-plane to the unit disk.
64///
65/// `w = (z - i) / (z + i)`
66pub fn cayley_transform(z: Complex) -> Complex {
67    let i = Complex::new(0.0, 1.0);
68    (z - i) / (z + i)
69}
70/// Inverse Cayley transform: maps the unit disk to the upper half-plane.
71///
72/// `w = i (1 + z) / (1 - z)`
73pub fn cayley_transform_inverse(z: Complex) -> Complex {
74    let i = Complex::new(0.0, 1.0);
75    i * (Complex::one() + z) / (Complex::one() - z)
76}
77/// Cooley-Tukey Radix-2 FFT (in-place, decimation-in-time).
78///
79/// Input length must be a power of two. If not, the input is zero-padded.
80pub fn fft_radix2(xs: &[Complex]) -> Vec<Complex> {
81    let n = xs.len().next_power_of_two();
82    let mut a: Vec<Complex> = xs.to_vec();
83    a.resize(n, Complex::zero());
84    fft_inplace(&mut a, false);
85    a
86}
87/// Inverse FFT (Cooley-Tukey Radix-2).
88pub fn ifft_radix2(xs: &[Complex]) -> Vec<Complex> {
89    let n = xs.len().next_power_of_two();
90    let mut a: Vec<Complex> = xs.to_vec();
91    a.resize(n, Complex::zero());
92    fft_inplace(&mut a, true);
93    let inv_n = 1.0 / n as f64;
94    a.iter_mut()
95        .for_each(|z| *z = Complex::new(z.re * inv_n, z.im * inv_n));
96    a
97}
98pub(super) fn fft_inplace(a: &mut [Complex], inverse: bool) {
99    let n = a.len();
100    if n <= 1 {
101        return;
102    }
103    let mut j = 0usize;
104    for i in 1..n {
105        let mut bit = n >> 1;
106        while j & bit != 0 {
107            j ^= bit;
108            bit >>= 1;
109        }
110        j ^= bit;
111        if i < j {
112            a.swap(i, j);
113        }
114    }
115    let mut len = 2usize;
116    while len <= n {
117        use std::f64::consts::PI;
118        let sign = if inverse { 1.0 } else { -1.0 };
119        let ang = sign * 2.0 * PI / len as f64;
120        let wlen = Complex::from_polar(1.0, ang);
121        let mut i = 0;
122        while i < n {
123            let mut w = Complex::one();
124            for k in 0..(len / 2) {
125                let u = a[i + k];
126                let v = a[i + k + len / 2] * w;
127                a[i + k] = u + v;
128                a[i + k + len / 2] = u - v;
129                w = w * wlen;
130            }
131            i += len;
132        }
133        len <<= 1;
134    }
135}
136/// Compute the DFT of a real-valued signal (returns N/2+1 complex frequencies).
137pub fn rfft(xs: &[f64]) -> Vec<Complex> {
138    let complex_in: Vec<Complex> = xs.iter().map(|&x| Complex::new(x, 0.0)).collect();
139    let out = fft_radix2(&complex_in);
140    let half = out.len() / 2 + 1;
141    out[..half].to_vec()
142}
143/// Taylor series approximation of exp(z) = Σ z^k / k! up to `terms` terms.
144pub fn complex_exp_taylor(z: Complex, terms: usize) -> Complex {
145    let mut result = Complex::one();
146    let mut term = Complex::one();
147    for k in 1..=terms {
148        term = term * z * Complex::new(1.0 / k as f64, 0.0);
149        result = result + term;
150    }
151    result
152}
153/// Taylor series approximation of sin(z) = Σ (-1)^k z^(2k+1) / (2k+1)!
154pub fn complex_sin_taylor(z: Complex, terms: usize) -> Complex {
155    let mut result = Complex::zero();
156    let mut term = z;
157    let z2 = z * z;
158    for k in 0..terms {
159        let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
160        result = result + term * Complex::new(sign, 0.0);
161        let denom = (2 * k + 2) as f64 * (2 * k + 3) as f64;
162        term = term * z2 * Complex::new(1.0 / denom, 0.0);
163    }
164    result
165}
166/// Numerically differentiate `f` at `z` using the complex-step method.
167///
168/// For analytic functions, `f'(z) ≈ Im[f(z + ih)] / h` with high accuracy.
169pub fn complex_step_derivative(f: &impl Fn(Complex) -> Complex, z: Complex, h: f64) -> Complex {
170    let fz_ih = f(Complex::new(z.re, z.im + h));
171    let fz = f(z);
172    let dz = Complex::new(0.0, h);
173    (fz_ih - fz) / dz
174}
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::complex::Complex;
179    use crate::complex::ComplexMat2;
180
181    use crate::complex::MobiusTransform;
182    use crate::complex::QuatAlgebra;
183
184    use crate::complex::fft_radix2;
185
186    use crate::complex::joukowski;
187
188    use std::f64::consts::SQRT_2;
189    pub(super) const EPS: f64 = 1e-10;
190    pub(super) const EPS_6: f64 = 1e-6;
191    fn approx(a: f64, b: f64, eps: f64) -> bool {
192        (a - b).abs() < eps
193    }
194    fn approx_quat(a: QuatAlgebra, b: QuatAlgebra, eps: f64) -> bool {
195        approx(a.w, b.w, eps)
196            && approx(a.x, b.x, eps)
197            && approx(a.y, b.y, eps)
198            && approx(a.z, b.z, eps)
199    }
200    #[test]
201    fn complex_add() {
202        let a = Complex::new(1.0, 2.0);
203        let b = Complex::new(3.0, -1.0);
204        let c = a + b;
205        assert!(approx(c.re, 4.0, EPS) && approx(c.im, 1.0, EPS));
206    }
207    #[test]
208    fn complex_sub() {
209        let a = Complex::new(5.0, 3.0);
210        let b = Complex::new(2.0, 1.0);
211        let c = a - b;
212        assert!(approx(c.re, 3.0, EPS) && approx(c.im, 2.0, EPS));
213    }
214    #[test]
215    fn complex_mul() {
216        let a = Complex::new(1.0, 2.0);
217        let b = Complex::new(3.0, 4.0);
218        let c = a * b;
219        assert!(approx(c.re, -5.0, EPS) && approx(c.im, 10.0, EPS));
220    }
221    #[test]
222    fn complex_div() {
223        let a = Complex::new(1.0, 2.0);
224        let c = a / a;
225        assert!(approx(c.re, 1.0, EPS) && approx(c.im, 0.0, EPS));
226    }
227    #[test]
228    fn complex_neg() {
229        let a = Complex::new(3.0, -4.0);
230        let c = -a;
231        assert!(approx(c.re, -3.0, EPS) && approx(c.im, 4.0, EPS));
232    }
233    #[test]
234    fn complex_conj() {
235        let a = Complex::new(3.0, 4.0);
236        let c = a.conj();
237        assert!(approx(c.re, 3.0, EPS) && approx(c.im, -4.0, EPS));
238    }
239    #[test]
240    fn complex_norm() {
241        let a = Complex::new(3.0, 4.0);
242        assert!(approx(a.norm(), 5.0, EPS));
243    }
244    #[test]
245    fn complex_norm_sq() {
246        let a = Complex::new(3.0, 4.0);
247        assert!(approx(a.norm_sq(), 25.0, EPS));
248    }
249    #[test]
250    fn complex_arg() {
251        let a = Complex::new(0.0, 1.0);
252        assert!(approx(a.arg(), PI / 2.0, EPS));
253    }
254    #[test]
255    fn complex_from_polar() {
256        let c = Complex::from_polar(1.0, PI / 2.0);
257        assert!(approx(c.re, 0.0, EPS_6) && approx(c.im, 1.0, EPS_6));
258    }
259    #[test]
260    fn complex_exp_euler() {
261        let c = Complex::new(0.0, PI).exp();
262        assert!(approx(c.re, -1.0, EPS_6) && approx(c.im, 0.0, EPS_6));
263    }
264    #[test]
265    fn complex_ln_then_exp() {
266        let z = Complex::new(2.0, 3.0);
267        let recovered = z.ln().exp();
268        assert!(approx(recovered.re, z.re, EPS_6) && approx(recovered.im, z.im, EPS_6));
269    }
270    #[test]
271    fn complex_sqrt_of_minus_one() {
272        let z = Complex::new(-1.0, 0.0);
273        let s = z.sqrt();
274        assert!(approx(s.re, 0.0, EPS_6) && approx(s.im, 1.0, EPS_6));
275    }
276    #[test]
277    fn complex_sqrt_of_two() {
278        let z = Complex::new(2.0, 0.0);
279        let s = z.sqrt();
280        assert!(approx(s.re, SQRT_2, EPS_6) && approx(s.im, 0.0, EPS_6));
281    }
282    #[test]
283    fn complex_pow_square() {
284        let z = Complex::new(1.0, 1.0);
285        let p = z.pow_f64(2.0);
286        assert!(approx(p.re, 0.0, EPS_6) && approx(p.im, 2.0, EPS_6));
287    }
288    #[test]
289    fn complex_sin_real() {
290        let z = Complex::new(PI / 6.0, 0.0);
291        let s = z.sin();
292        assert!(approx(s.re, 0.5, EPS_6) && approx(s.im, 0.0, EPS_6));
293    }
294    #[test]
295    fn complex_cos_real() {
296        let z = Complex::new(0.0, 0.0);
297        let c = z.cos();
298        assert!(approx(c.re, 1.0, EPS) && approx(c.im, 0.0, EPS));
299    }
300    #[test]
301    fn complex_pythagorean_identity() {
302        let z = Complex::new(1.0, 0.5);
303        let sin2 = z.sin() * z.sin();
304        let cos2 = z.cos() * z.cos();
305        let sum = sin2 + cos2;
306        assert!(approx(sum.re, 1.0, EPS_6) && approx(sum.im, 0.0, EPS_6));
307    }
308    #[test]
309    fn complex_zero_one_i() {
310        assert_eq!(Complex::zero(), Complex::new(0.0, 0.0));
311        assert_eq!(Complex::one(), Complex::new(1.0, 0.0));
312        assert_eq!(Complex::i(), Complex::new(0.0, 1.0));
313    }
314    #[test]
315    fn complex_mul_associative() {
316        let a = Complex::new(1.0, 2.0);
317        let b = Complex::new(3.0, -1.0);
318        let c = Complex::new(-2.0, 5.0);
319        let lhs = (a * b) * c;
320        let rhs = a * (b * c);
321        assert!(approx(lhs.re, rhs.re, EPS_6) && approx(lhs.im, rhs.im, EPS_6));
322    }
323    #[test]
324    fn quat_identity_mul() {
325        let q = QuatAlgebra::new(0.5, 0.5, 0.5, 0.5);
326        let id = QuatAlgebra::identity();
327        assert!(approx_quat(q * id, q, EPS));
328        assert!(approx_quat(id * q, q, EPS));
329    }
330    #[test]
331    fn quat_mul_not_commutative() {
332        let i = QuatAlgebra::new(0.0, 1.0, 0.0, 0.0);
333        let j = QuatAlgebra::new(0.0, 0.0, 1.0, 0.0);
334        let k = QuatAlgebra::new(0.0, 0.0, 0.0, 1.0);
335        let ij = i * j;
336        assert!(approx_quat(ij, k, EPS));
337        let ji = j * i;
338        assert!(approx_quat(ji, QuatAlgebra::new(0.0, 0.0, 0.0, -1.0), EPS));
339    }
340    #[test]
341    fn quat_mul_associative() {
342        let a = QuatAlgebra::new(1.0, 2.0, -1.0, 0.5).normalize();
343        let b = QuatAlgebra::new(0.5, -0.5, 1.0, 2.0).normalize();
344        let c = QuatAlgebra::new(-1.0, 1.0, 1.0, -1.0).normalize();
345        let lhs = (a * b) * c;
346        let rhs = a * (b * c);
347        assert!(approx_quat(lhs, rhs, EPS_6));
348    }
349    #[test]
350    fn quat_norm_of_unit() {
351        let q = QuatAlgebra::from_axis_angle([0.0, 1.0, 0.0], PI / 3.0);
352        assert!(approx(q.norm(), 1.0, EPS_6));
353    }
354    #[test]
355    fn quat_inverse() {
356        let q = QuatAlgebra::new(1.0, 2.0, 3.0, 4.0);
357        let qi = q.inverse();
358        let prod = q * qi;
359        assert!(approx_quat(prod, QuatAlgebra::identity(), EPS_6));
360    }
361    #[test]
362    fn quat_conj_times_self_is_norm_sq() {
363        let q = QuatAlgebra::new(1.0, 2.0, 3.0, 4.0);
364        let qc = q.conj();
365        let prod = q * qc;
366        assert!(approx(prod.w, q.norm_sq(), EPS_6));
367        assert!(approx(prod.x, 0.0, EPS_6));
368        assert!(approx(prod.y, 0.0, EPS_6));
369        assert!(approx(prod.z, 0.0, EPS_6));
370    }
371    #[test]
372    fn quat_rotate_90_around_z() {
373        let q = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI / 2.0);
374        let v = [1.0_f64, 0.0, 0.0];
375        let r = q.rotate_vector(v);
376        assert!(approx(r[0], 0.0, EPS_6));
377        assert!(approx(r[1], 1.0, EPS_6));
378        assert!(approx(r[2], 0.0, EPS_6));
379    }
380    #[test]
381    fn quat_rotation_composition() {
382        let q90 = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI / 2.0);
383        let q180 = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI);
384        let q_composed = q90 * q90;
385        let v = [1.0_f64, 0.0, 0.0];
386        let r1 = q_composed.rotate_vector(v);
387        let r2 = q180.rotate_vector(v);
388        assert!(approx(r1[0], r2[0], EPS_6));
389        assert!(approx(r1[1], r2[1], EPS_6));
390        assert!(approx(r1[2], r2[2], EPS_6));
391    }
392    #[test]
393    fn quat_to_rotation_matrix_orthogonal() {
394        let q = QuatAlgebra::from_axis_angle([1.0, 1.0, 0.0], PI / 4.0);
395        let r = q.to_rotation_matrix();
396        for i in 0..3 {
397            for j in 0..3 {
398                let dot: f64 = (0..3).map(|k| r[i][k] * r[j][k]).sum();
399                let expected = if i == j { 1.0 } else { 0.0 };
400                assert!(
401                    approx(dot, expected, EPS_6),
402                    "R R^T [{i}][{j}] = {dot}, expected {expected}"
403                );
404            }
405        }
406    }
407    #[test]
408    fn quat_exp_ln_roundtrip() {
409        let q = QuatAlgebra::from_axis_angle([1.0, 0.0, 0.0], PI / 4.0);
410        let recovered = q.ln().exp();
411        assert!(approx_quat(recovered, q, EPS_6));
412    }
413    #[test]
414    fn quat_pow_half_is_half_angle() {
415        let q = QuatAlgebra::from_axis_angle([0.0, 1.0, 0.0], PI / 2.0);
416        let q_half = q.pow(0.5);
417        let expected = QuatAlgebra::from_axis_angle([0.0, 1.0, 0.0], PI / 4.0);
418        assert!(approx_quat(q_half, expected, EPS_6));
419    }
420    #[test]
421    fn quat_slerp_endpoints() {
422        let q0 = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], 0.0);
423        let q1 = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI / 2.0);
424        let s0 = q0.slerp(&q1, 0.0);
425        let s1 = q0.slerp(&q1, 1.0);
426        assert!(approx_quat(s0, q0, EPS_6));
427        assert!(approx_quat(s1, q1, EPS_6));
428    }
429    #[test]
430    fn quat_slerp_midpoint() {
431        let q0 = QuatAlgebra::identity();
432        let q1 = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI / 2.0);
433        let mid = q0.slerp(&q1, 0.5);
434        let expected = QuatAlgebra::from_axis_angle([0.0, 0.0, 1.0], PI / 4.0);
435        assert!(approx_quat(mid, expected, EPS_6));
436    }
437    #[test]
438    fn quat_dot_self_is_norm_sq() {
439        let q = QuatAlgebra::new(1.0, 2.0, 3.0, 4.0);
440        assert!(approx(q.dot(&q), q.norm_sq(), EPS));
441    }
442    #[test]
443    fn quat_normalize_gives_unit() {
444        let q = QuatAlgebra::new(1.0, 2.0, 3.0, 4.0);
445        assert!(approx(q.normalize().norm(), 1.0, EPS_6));
446    }
447    #[test]
448    fn quat_zero() {
449        let z = QuatAlgebra::zero();
450        assert!(approx(z.norm(), 0.0, EPS));
451    }
452    #[test]
453    fn test_complex_mat2_identity_mul() {
454        let id = ComplexMat2::identity();
455        let m = ComplexMat2::new(
456            Complex::new(1.0, 2.0),
457            Complex::new(3.0, -1.0),
458            Complex::new(-2.0, 0.5),
459            Complex::new(4.0, 1.0),
460        );
461        let r = id.mul(&m);
462        assert!(approx(r.a.re, m.a.re, EPS_6) && approx(r.d.im, m.d.im, EPS_6));
463    }
464    #[test]
465    fn test_complex_mat2_det_identity() {
466        let id = ComplexMat2::identity();
467        let det = id.det();
468        assert!(approx(det.re, 1.0, EPS) && approx(det.im, 0.0, EPS));
469    }
470    #[test]
471    fn test_complex_mat2_inverse() {
472        let m = ComplexMat2::new(
473            Complex::new(2.0, 0.0),
474            Complex::new(1.0, 0.0),
475            Complex::new(1.0, 0.0),
476            Complex::new(1.0, 0.0),
477        );
478        if let Some(inv) = m.inverse() {
479            let prod = m.mul(&inv);
480            let id = ComplexMat2::identity();
481            assert!(approx(prod.a.re, id.a.re, EPS_6));
482            assert!(approx(prod.d.re, id.d.re, EPS_6));
483        }
484    }
485    #[test]
486    fn test_complex_mat2_eigenvalues() {
487        let m = ComplexMat2::new(
488            Complex::new(3.0, 0.0),
489            Complex::new(1.0, 0.0),
490            Complex::new(1.0, 0.0),
491            Complex::new(2.0, 0.0),
492        );
493        let (l1, l2) = m.eigenvalues();
494        let trace = 5.0_f64;
495        let product = 5.0_f64;
496        let disc = (trace * trace - 4.0 * product).sqrt();
497        let expected_l1 = (trace + disc) / 2.0;
498        let expected_l2 = (trace - disc) / 2.0;
499        assert!(approx(l1.re, expected_l1, 1e-6), "l1={}", l1.re);
500        assert!(approx(l2.re, expected_l2, 1e-6), "l2={}", l2.re);
501    }
502    #[test]
503    fn test_cauchy_riemann_exp() {
504        let z = Complex::new(1.0, 0.5);
505        let satisfied = cauchy_riemann_check(&|z| z.exp(), z, 1e-6);
506        assert!(satisfied, "exp(z) should satisfy Cauchy-Riemann");
507    }
508    #[test]
509    fn test_cauchy_riemann_z_squared() {
510        let z = Complex::new(2.0, 1.0);
511        let satisfied = cauchy_riemann_check(&|z| z * z, z, 1e-5);
512        assert!(satisfied, "z^2 should satisfy Cauchy-Riemann");
513    }
514    #[test]
515    fn test_cauchy_riemann_conj_fails() {
516        let z = Complex::new(1.0, 1.0);
517        let satisfied = cauchy_riemann_check(&|z: Complex| z.conj(), z, 1e-5);
518        assert!(!satisfied, "conj(z) should NOT satisfy Cauchy-Riemann");
519    }
520    #[test]
521    fn test_mobius_identity() {
522        let id = MobiusTransform::identity();
523        let z = Complex::new(2.0, 3.0);
524        let fz = id.apply(z);
525        assert!(approx(fz.re, z.re, EPS_6) && approx(fz.im, z.im, EPS_6));
526    }
527    #[test]
528    fn test_mobius_inversion_z() {
529        let f = MobiusTransform::new(
530            Complex::zero(),
531            Complex::one(),
532            Complex::one(),
533            Complex::zero(),
534        );
535        let z = Complex::new(2.0, 0.0);
536        let fz = f.apply(z);
537        assert!(approx(fz.re, 0.5, EPS_6), "1/2 = 0.5, got {}", fz.re);
538    }
539    #[test]
540    fn test_mobius_compose_with_inverse() {
541        let f = MobiusTransform::new(
542            Complex::new(1.0, 0.0),
543            Complex::new(2.0, 0.0),
544            Complex::new(3.0, 0.0),
545            Complex::new(4.0, 0.0),
546        );
547        if let Some(inv) = f.inverse() {
548            let composed = f.compose(&inv);
549            let z = Complex::new(1.5, 0.7);
550            let fz = composed.apply(z);
551            let id_z = MobiusTransform::identity().apply(z);
552            assert!(approx(fz.re, id_z.re, 1e-8) && approx(fz.im, id_z.im, 1e-8));
553        }
554    }
555    #[test]
556    fn test_joukowski_symmetry() {
557        let z = Complex::new(2.0, 1.0);
558        let w1 = joukowski(z);
559        let w2 = joukowski(z.conj()).conj();
560        assert!(approx(w1.re, w2.re, EPS_6) && approx(w1.im, w2.im, EPS_6));
561    }
562    #[test]
563    fn test_joukowski_real_axis_maps_real() {
564        let z = Complex::new(3.0, 0.0);
565        let w = joukowski(z);
566        assert!(w.im.abs() < EPS_6, "im={}", w.im);
567    }
568    #[test]
569    fn test_fft_size_1() {
570        let xs = vec![Complex::new(5.0, 3.0)];
571        let out = fft_radix2(&xs);
572        assert_eq!(out.len(), 1);
573        assert!(approx(out[0].re, 5.0, EPS_6) && approx(out[0].im, 3.0, EPS_6));
574    }
575    #[test]
576    fn test_fft_size_4_dc() {
577        let val = Complex::new(1.0, 0.0);
578        let xs = vec![val; 4];
579        let out = fft_radix2(&xs);
580        assert!(approx(out[0].re, 4.0, EPS_6), "DC={}", out[0].re);
581        assert!(approx(out[1].re, 0.0, EPS_6) && approx(out[1].im, 0.0, EPS_6));
582    }
583    #[test]
584    fn test_fft_parseval() {
585        let xs = vec![
586            Complex::new(1.0, 0.0),
587            Complex::new(0.0, 1.0),
588            Complex::new(-1.0, 0.0),
589            Complex::new(0.0, -1.0),
590        ];
591        let out = fft_radix2(&xs);
592        let energy_time: f64 = xs.iter().map(|z| z.norm_sq()).sum();
593        let energy_freq: f64 = out.iter().map(|z| z.norm_sq()).sum::<f64>() / xs.len() as f64;
594        assert!(
595            approx(energy_time, energy_freq, 1e-9),
596            "Parseval: {} vs {}",
597            energy_time,
598            energy_freq
599        );
600    }
601    #[test]
602    fn test_complex_power_series_exp() {
603        let z = Complex::new(0.5, 0.3);
604        let approx_exp = complex_exp_taylor(z, 15);
605        let exact = z.exp();
606        assert!(
607            approx(approx_exp.re, exact.re, 1e-8),
608            "re diff={}",
609            (approx_exp.re - exact.re).abs()
610        );
611        assert!(
612            approx(approx_exp.im, exact.im, 1e-8),
613            "im diff={}",
614            (approx_exp.im - exact.im).abs()
615        );
616    }
617}
618/// Compute the N×N DFT matrix W where W\[j\]\[k\] = exp(-2πi·j·k/N).
619///
620/// The matrix form of the DFT: X = W·x.
621#[allow(dead_code)]
622pub fn dft_matrix(n: usize) -> Vec<Vec<Complex>> {
623    let mut mat = vec![vec![Complex::zero(); n]; n];
624    for j in 0..n {
625        for k in 0..n {
626            let angle = -2.0 * PI * (j * k) as f64 / n as f64;
627            mat[j][k] = Complex::from_polar(1.0, angle);
628        }
629    }
630    mat
631}
632/// Apply the DFT matrix to a vector (O(N²) naive DFT).
633///
634/// Returns X\[k\] = Σ x\[n\] · exp(-2πi·k·n/N).
635#[allow(dead_code)]
636pub fn naive_dft(xs: &[Complex]) -> Vec<Complex> {
637    let n = xs.len();
638    (0..n)
639        .map(|k| {
640            let mut sum = Complex::zero();
641            for (j, &x) in xs.iter().enumerate() {
642                let angle = -2.0 * PI * (k * j) as f64 / n as f64;
643                sum = sum + x * Complex::from_polar(1.0, angle);
644            }
645            sum
646        })
647        .collect()
648}
649/// Returns the N-th roots of unity: exp(2πi·k/N) for k=0,…,N-1.
650#[allow(dead_code)]
651pub fn roots_of_unity(n: usize) -> Vec<Complex> {
652    (0..n)
653        .map(|k| Complex::from_polar(1.0, 2.0 * PI * k as f64 / n as f64))
654        .collect()
655}
656/// Returns the primitive N-th root of unity: exp(2Ï€i/N).
657#[allow(dead_code)]
658pub fn primitive_root_of_unity(n: usize) -> Complex {
659    Complex::from_polar(1.0, 2.0 * PI / n as f64)
660}
661/// Numerically integrate `f(z)` along a circular contour of radius `r`
662/// centered at `center`, using `n_points` quadrature points.
663///
664/// Returns ∮_C f(z) dz ≈ Σ f(z_k) · Δz_k.
665#[allow(dead_code)]
666pub fn contour_integral_circle(
667    f: &impl Fn(Complex) -> Complex,
668    center: Complex,
669    r: f64,
670    n_points: usize,
671) -> Complex {
672    let n = n_points.max(4);
673    let mut sum = Complex::zero();
674    for k in 0..n {
675        let theta = 2.0 * PI * k as f64 / n as f64;
676        let theta_next = 2.0 * PI * (k + 1) as f64 / n as f64;
677        let z_mid = center + Complex::from_polar(r, (theta + theta_next) / 2.0);
678        let dz = Complex::from_polar(r, theta_next) - Complex::from_polar(r, theta);
679        sum = sum + f(z_mid) * dz;
680    }
681    sum
682}
683/// Numerically compute the residue of `f` at `pole` using the contour integral.
684///
685/// For a simple pole: Res(f, z0) = (1/(2πi)) ∮_{|z-z0|=r} f(z) dz.
686#[allow(dead_code)]
687pub fn residue_by_contour(
688    f: &impl Fn(Complex) -> Complex,
689    pole: Complex,
690    r: f64,
691    n_points: usize,
692) -> Complex {
693    let integral = contour_integral_circle(f, pole, r, n_points);
694    let two_pi_i = Complex::new(0.0, 2.0 * PI);
695    integral / two_pi_i
696}
697/// Find all roots of a monic polynomial using the Durand-Kerner (Weierstrass) method.
698///
699/// The polynomial is given as `coeffs[0] + coeffs[1]*z + ... + coeffs[n]*z^n`
700/// with the leading coefficient being monic (implicit 1).
701///
702/// `coeffs` should have length `n` (for a degree-n polynomial with leading coeff 1).
703/// Returns `n` approximate roots.
704#[allow(dead_code)]
705pub fn durand_kerner_roots(coeffs: &[Complex], max_iter: usize) -> Vec<Complex> {
706    let n = coeffs.len();
707    if n == 0 {
708        return vec![];
709    }
710    let omega = Complex::from_polar(0.4, 2.0 * PI / n as f64);
711    let mut roots: Vec<Complex> = (0..n)
712        .map(|k| omega.pow_f64(k as f64) * Complex::new(0.4, 0.0))
713        .collect();
714    let r0 = (coeffs[0].norm() + 1.0).powf(1.0 / n as f64).max(0.5);
715    for k in 0..n {
716        let angle = 2.0 * PI * k as f64 / n as f64 + 0.1;
717        roots[k] = Complex::from_polar(r0, angle);
718    }
719    let eval_poly = |z: Complex| -> Complex {
720        let mut result = Complex::one();
721        let mut p = Complex::zero();
722        for c in coeffs.iter() {
723            p = p + *c * result;
724            result = result * z;
725        }
726        p + result
727    };
728    for _ in 0..max_iter {
729        let old_roots = roots.clone();
730        for i in 0..n {
731            let pz = eval_poly(old_roots[i]);
732            let mut denom = Complex::one();
733            for j in 0..n {
734                if i != j {
735                    denom = denom * (old_roots[i] - old_roots[j]);
736                }
737            }
738            if denom.norm_sq() > 1e-30 {
739                roots[i] = old_roots[i] - pz / denom;
740            }
741        }
742    }
743    roots
744}
745/// Solve the complex linear system `A·x = b` using Gaussian elimination
746/// with partial pivoting.
747///
748/// `a` is an N×N matrix in row-major format (Vec of rows).
749/// Returns the solution vector `x`, or `None` if the system is singular.
750#[allow(dead_code)]
751pub fn complex_gaussian_elimination(a: &[Vec<Complex>], b: &[Complex]) -> Option<Vec<Complex>> {
752    let n = b.len();
753    assert_eq!(a.len(), n, "complex_gaussian_elimination: A must be n×n");
754    for row in a {
755        assert_eq!(row.len(), n, "complex_gaussian_elimination: A must be n×n");
756    }
757    let mut mat: Vec<Vec<Complex>> = a
758        .iter()
759        .zip(b.iter())
760        .map(|(row, &bi)| {
761            let mut r = row.clone();
762            r.push(bi);
763            r
764        })
765        .collect();
766    for col in 0..n {
767        let (pivot_row, _) = (col..n)
768            .map(|r| (r, mat[r][col].norm_sq()))
769            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))?;
770        if mat[pivot_row][col].norm_sq() < 1e-30 {
771            return None;
772        }
773        mat.swap(col, pivot_row);
774        let pivot = mat[col][col];
775        for j in col..=n {
776            let v = mat[col][j];
777            mat[col][j] = v / pivot;
778        }
779        for row in 0..n {
780            if row != col {
781                let factor = mat[row][col];
782                for j in col..=n {
783                    let v = mat[col][j] * factor;
784                    let old = mat[row][j];
785                    mat[row][j] = old - v;
786                }
787            }
788        }
789    }
790    Some((0..n).map(|i| mat[i][n]).collect())
791}
792#[cfg(test)]
793mod tests_new_complex {
794
795    use crate::complex::Complex;
796
797    use crate::complex::ComplexMatN;
798
799    use crate::complex::cayley_transform;
800    use crate::complex::cayley_transform_inverse;
801    use crate::complex::complex_gaussian_elimination;
802    use crate::complex::complex_sin_taylor;
803    use crate::complex::complex_step_derivative;
804    use crate::complex::contour_integral_circle;
805
806    use crate::complex::dft_matrix;
807    use crate::complex::durand_kerner_roots;
808
809    use crate::complex::fft_radix2;
810    use crate::complex::functions::PI;
811
812    use crate::complex::ifft_radix2;
813    use crate::complex::joukowski;
814
815    use crate::complex::naive_dft;
816
817    use crate::complex::primitive_root_of_unity;
818
819    use crate::complex::residue_by_contour;
820    use crate::complex::rfft;
821    use crate::complex::roots_of_unity;
822
823    use crate::complex::schwarz_christoffel_rectangle;
824
825    fn approx(a: f64, b: f64, eps: f64) -> bool {
826        (a - b).abs() < eps
827    }
828    fn capprox(a: Complex, b: Complex, eps: f64) -> bool {
829        approx(a.re, b.re, eps) && approx(a.im, b.im, eps)
830    }
831    #[test]
832    fn test_dft_matrix_size() {
833        let m = dft_matrix(4);
834        assert_eq!(m.len(), 4);
835        assert_eq!(m[0].len(), 4);
836    }
837    #[test]
838    fn test_dft_matrix_unit_entries() {
839        let m = dft_matrix(4);
840        for row in &m {
841            for entry in row {
842                assert!(
843                    approx(entry.norm(), 1.0, 1e-10),
844                    "DFT matrix entries should be on unit circle"
845                );
846            }
847        }
848    }
849    #[test]
850    fn test_naive_dft_matches_fft() {
851        let xs = vec![
852            Complex::new(1.0, 0.0),
853            Complex::new(2.0, 0.0),
854            Complex::new(3.0, 0.0),
855            Complex::new(4.0, 0.0),
856        ];
857        let fft_out = fft_radix2(&xs);
858        let dft_out = naive_dft(&xs);
859        for (f, d) in fft_out.iter().zip(dft_out.iter()) {
860            assert!(
861                approx(f.re, d.re, 1e-8),
862                "FFT re={} vs DFT re={}",
863                f.re,
864                d.re
865            );
866            assert!(
867                approx(f.im, d.im, 1e-8),
868                "FFT im={} vs DFT im={}",
869                f.im,
870                d.im
871            );
872        }
873    }
874    #[test]
875    fn test_naive_dft_dc_component() {
876        let xs = vec![Complex::new(1.0, 0.0); 4];
877        let out = naive_dft(&xs);
878        assert!(
879            approx(out[0].re, 4.0, 1e-10),
880            "DC component should be N*value"
881        );
882        assert!(
883            approx(out[1].norm(), 0.0, 1e-8),
884            "non-DC should be zero for constant input"
885        );
886    }
887    #[test]
888    fn test_complex_mat_n_identity_mul() {
889        let id = ComplexMatN::identity(3);
890        let mut m = ComplexMatN::zeros(3);
891        for i in 0..3 {
892            for j in 0..3 {
893                m.set(i, j, Complex::new((i * 3 + j) as f64, 0.0));
894            }
895        }
896        let result = id.mul(&m);
897        for i in 0..3 {
898            for j in 0..3 {
899                assert!(approx(result.get(i, j).re, m.get(i, j).re, 1e-10));
900            }
901        }
902    }
903    #[test]
904    fn test_complex_mat_n_trace_identity() {
905        let id = ComplexMatN::identity(4);
906        let tr = id.trace();
907        assert!(approx(tr.re, 4.0, 1e-10) && approx(tr.im, 0.0, 1e-10));
908    }
909    #[test]
910    fn test_complex_mat_n_frobenius_identity() {
911        let id = ComplexMatN::identity(3);
912        assert!(approx(id.frobenius_norm(), 3.0_f64.sqrt(), 1e-10));
913    }
914    #[test]
915    fn test_complex_mat_n_conj_transpose() {
916        let mut m = ComplexMatN::zeros(2);
917        m.set(0, 0, Complex::new(1.0, 2.0));
918        m.set(0, 1, Complex::new(3.0, -1.0));
919        m.set(1, 0, Complex::new(0.0, 4.0));
920        m.set(1, 1, Complex::new(5.0, 0.0));
921        let mh = m.conj_transpose();
922        assert!(capprox(mh.get(0, 1), m.get(1, 0).conj(), 1e-10));
923    }
924    #[test]
925    fn test_complex_mat_n_apply_identity() {
926        let id = ComplexMatN::identity(3);
927        let v = vec![
928            Complex::new(1.0, 2.0),
929            Complex::new(-1.0, 0.5),
930            Complex::new(0.0, 3.0),
931        ];
932        let result = id.apply(&v);
933        for (a, b) in result.iter().zip(v.iter()) {
934            assert!(capprox(*a, *b, 1e-10));
935        }
936    }
937    #[test]
938    fn test_roots_of_unity_count() {
939        let roots = roots_of_unity(6);
940        assert_eq!(roots.len(), 6);
941    }
942    #[test]
943    fn test_roots_of_unity_on_unit_circle() {
944        let roots = roots_of_unity(8);
945        for r in &roots {
946            assert!(
947                approx(r.norm(), 1.0, 1e-10),
948                "root should be on unit circle, norm={}",
949                r.norm()
950            );
951        }
952    }
953    #[test]
954    fn test_roots_of_unity_sum_to_zero() {
955        let roots = roots_of_unity(5);
956        let sum = roots.iter().fold(Complex::zero(), |acc, &r| acc + r);
957        assert!(
958            approx(sum.norm(), 0.0, 1e-10),
959            "sum of roots of unity should be 0, norm={}",
960            sum.norm()
961        );
962    }
963    #[test]
964    fn test_roots_of_unity_first_is_one() {
965        let roots = roots_of_unity(4);
966        assert!(approx(roots[0].re, 1.0, 1e-10) && approx(roots[0].im, 0.0, 1e-10));
967    }
968    #[test]
969    fn test_primitive_root_of_unity_order() {
970        let n = 6usize;
971        let omega = primitive_root_of_unity(n);
972        let mut w = omega;
973        for _ in 1..n {
974            w = w * omega;
975        }
976        assert!(
977            approx(w.re, 1.0, 1e-10) && approx(w.im, 0.0, 1e-10),
978            "omega^n should be 1, got ({}, {})",
979            w.re,
980            w.im
981        );
982    }
983    #[test]
984    fn test_contour_integral_analytic_function_zero() {
985        let center = Complex::new(0.0, 0.0);
986        let integral = contour_integral_circle(&|z: Complex| z * z, center, 1.0, 1000);
987        assert!(
988            approx(integral.norm(), 0.0, 1e-6),
989            "integral of analytic fn should be 0, got {}",
990            integral.norm()
991        );
992    }
993    #[test]
994    fn test_contour_integral_1_over_z() {
995        let center = Complex::new(0.0, 0.0);
996        let integral =
997            contour_integral_circle(&|z: Complex| Complex::one() / z, center, 1.0, 10000);
998        assert!(
999            approx(integral.re, 0.0, 1e-3),
1000            "real part should be 0, got {}",
1001            integral.re
1002        );
1003        assert!(
1004            approx(integral.im, 2.0 * PI, 1e-3),
1005            "imag part should be 2Ï€, got {}",
1006            integral.im
1007        );
1008    }
1009    #[test]
1010    fn test_residue_1_over_z() {
1011        let center = Complex::new(0.0, 0.0);
1012        let res = residue_by_contour(&|z: Complex| Complex::one() / z, center, 1.0, 10000);
1013        assert!(
1014            approx(res.re, 1.0, 1e-3),
1015            "residue should be 1, got {}",
1016            res.re
1017        );
1018    }
1019    #[test]
1020    fn test_complex_gauss_identity_system() {
1021        let n = 3;
1022        let a: Vec<Vec<Complex>> = (0..n)
1023            .map(|i| {
1024                (0..n)
1025                    .map(|j| {
1026                        if i == j {
1027                            Complex::one()
1028                        } else {
1029                            Complex::zero()
1030                        }
1031                    })
1032                    .collect()
1033            })
1034            .collect();
1035        let b = vec![
1036            Complex::new(1.0, 0.0),
1037            Complex::new(2.0, 0.0),
1038            Complex::new(3.0, 0.0),
1039        ];
1040        let x = complex_gaussian_elimination(&a, &b).unwrap();
1041        for (xi, bi) in x.iter().zip(b.iter()) {
1042            assert!(capprox(*xi, *bi, 1e-10), "I·x=b should give x=b");
1043        }
1044        let _ = a.len();
1045    }
1046    #[test]
1047    fn test_complex_gauss_2x2_real() {
1048        let a = vec![
1049            vec![Complex::new(2.0, 0.0), Complex::new(1.0, 0.0)],
1050            vec![Complex::new(1.0, 0.0), Complex::new(3.0, 0.0)],
1051        ];
1052        let b = vec![Complex::new(5.0, 0.0), Complex::new(10.0, 0.0)];
1053        let x = complex_gaussian_elimination(&a, &b).unwrap();
1054        assert!(
1055            approx(x[0].re, 1.0, 1e-8) && approx(x[0].im, 0.0, 1e-8),
1056            "x[0]={}",
1057            x[0].re
1058        );
1059        assert!(
1060            approx(x[1].re, 3.0, 1e-8) && approx(x[1].im, 0.0, 1e-8),
1061            "x[1]={}",
1062            x[1].re
1063        );
1064    }
1065    #[test]
1066    fn test_complex_gauss_singular_returns_none() {
1067        let a = vec![
1068            vec![Complex::new(1.0, 0.0), Complex::new(2.0, 0.0)],
1069            vec![Complex::new(2.0, 0.0), Complex::new(4.0, 0.0)],
1070        ];
1071        let b = vec![Complex::new(1.0, 0.0), Complex::new(2.0, 0.0)];
1072        assert!(
1073            complex_gaussian_elimination(&a, &b).is_none(),
1074            "singular system should return None"
1075        );
1076    }
1077    #[test]
1078    fn test_complex_gauss_complex_rhs() {
1079        let a = vec![vec![Complex::new(1.0, 1.0)]];
1080        let b = vec![Complex::new(2.0, 2.0)];
1081        let x = complex_gaussian_elimination(&a, &b).unwrap();
1082        assert!(
1083            approx(x[0].re, 2.0, 1e-8) && approx(x[0].im, 0.0, 1e-8),
1084            "x = ({}, {})",
1085            x[0].re,
1086            x[0].im
1087        );
1088    }
1089    #[test]
1090    fn test_fft_then_ifft_roundtrip() {
1091        let xs = vec![
1092            Complex::new(1.0, 0.5),
1093            Complex::new(-1.0, 2.0),
1094            Complex::new(0.5, -0.5),
1095            Complex::new(2.0, 1.0),
1096        ];
1097        let freq = fft_radix2(&xs);
1098        let recovered = ifft_radix2(&freq);
1099        for (orig, rec) in xs.iter().zip(recovered.iter()) {
1100            assert!(
1101                approx(orig.re, rec.re, 1e-8),
1102                "re: {} vs {}",
1103                orig.re,
1104                rec.re
1105            );
1106            assert!(
1107                approx(orig.im, rec.im, 1e-8),
1108                "im: {} vs {}",
1109                orig.im,
1110                rec.im
1111            );
1112        }
1113    }
1114    #[test]
1115    fn test_rfft_length() {
1116        let xs = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1117        let out = rfft(&xs);
1118        assert_eq!(
1119            out.len(),
1120            xs.len() / 2 + 1,
1121            "rfft should return N/2+1 components"
1122        );
1123    }
1124    #[test]
1125    fn test_complex_sin_taylor_matches_exact() {
1126        let z = Complex::new(0.3, 0.2);
1127        let approx_sin = complex_sin_taylor(z, 12);
1128        let exact = z.sin();
1129        assert!(approx(approx_sin.re, exact.re, 1e-8));
1130        assert!(approx(approx_sin.im, exact.im, 1e-8));
1131    }
1132    #[test]
1133    fn test_complex_step_derivative_for_polynomial() {
1134        let z = Complex::new(2.0, 0.0);
1135        let deriv = complex_step_derivative(&|z| z * z * z, z, 1e-8);
1136        let expected = Complex::new(12.0, 0.0);
1137        assert!(
1138            approx(deriv.re, expected.re, 1e-4),
1139            "derivative re={} expected {}",
1140            deriv.re,
1141            expected.re
1142        );
1143    }
1144    #[test]
1145    fn test_cayley_transform_unit_disk() {
1146        let z = Complex::new(0.0, 1.0);
1147        let w = cayley_transform(z);
1148        assert!(
1149            approx(w.norm(), 0.0, 1e-10),
1150            "cayley(i) should be 0, got |w|={}",
1151            w.norm()
1152        );
1153    }
1154    #[test]
1155    fn test_cayley_inverse_roundtrip() {
1156        let z = Complex::new(1.5, 2.0);
1157        let w = cayley_transform(z);
1158        let z_back = cayley_transform_inverse(w);
1159        assert!(
1160            approx(z_back.re, z.re, 1e-8) && approx(z_back.im, z.im, 1e-8),
1161            "cayley inverse roundtrip failed: ({}, {})",
1162            z_back.re,
1163            z_back.im
1164        );
1165    }
1166    #[test]
1167    fn test_joukowski_unit_circle_chord() {
1168        let z = Complex::from_polar(1.0, PI / 3.0);
1169        let w = joukowski(z);
1170        let expected_re = 2.0 * (PI / 3.0).cos();
1171        assert!(approx(w.re, expected_re, 1e-10));
1172        assert!(
1173            approx(w.im, 0.0, 1e-10),
1174            "unit circle joukowski should be real, got im={}",
1175            w.im
1176        );
1177    }
1178    #[test]
1179    fn test_schwarz_christoffel_at_origin_is_zero() {
1180        let result = schwarz_christoffel_rectangle(Complex::zero(), 0.5, 100);
1181        assert!(approx(result.norm(), 0.0, 1e-10));
1182    }
1183    #[test]
1184    fn test_durand_kerner_quadratic() {
1185        let coeffs = vec![Complex::new(-1.0, 0.0), Complex::new(0.0, 0.0)];
1186        let roots = durand_kerner_roots(&coeffs, 200);
1187        assert_eq!(roots.len(), 2);
1188        let norms: Vec<f64> = roots.iter().map(|r| (r.norm() - 1.0).abs()).collect();
1189        for n in &norms {
1190            assert!(*n < 0.2, "root magnitude should be ~1, error={n}");
1191        }
1192    }
1193    #[test]
1194    fn test_dft_matrix_row0_all_ones() {
1195        let m = dft_matrix(4);
1196        for entry in &m[0] {
1197            assert!(approx(entry.re, 1.0, 1e-10) && approx(entry.im, 0.0, 1e-10));
1198        }
1199    }
1200}
1201/// Compute the Short-Time Fourier Transform of a real signal.
1202///
1203/// # Arguments
1204/// * `signal` – real-valued input signal.
1205/// * `window_size` – number of samples per window (power of two recommended).
1206/// * `hop_size` – step between successive windows (≤ window_size).
1207/// * `window_fn` – window function applied to each frame (e.g., Hann window).
1208///
1209/// Returns a `Vec<Vec`Complex`>` where each inner Vec is the FFT of one frame.
1210/// The outer dimension is time (frames), inner is frequency.
1211#[allow(dead_code)]
1212pub fn stft(
1213    signal: &[f64],
1214    window_size: usize,
1215    hop_size: usize,
1216    window_fn: &[f64],
1217) -> Vec<Vec<Complex>> {
1218    if signal.is_empty() || window_size == 0 || hop_size == 0 {
1219        return Vec::new();
1220    }
1221    let hop = hop_size.max(1);
1222    let wlen = window_fn.len().min(window_size);
1223    let mut frames = Vec::new();
1224    let mut start = 0;
1225    while start < signal.len() {
1226        let mut frame: Vec<Complex> = (0..window_size)
1227            .map(|i| {
1228                let sig_i = start + i;
1229                let sig_val = if sig_i < signal.len() {
1230                    signal[sig_i]
1231                } else {
1232                    0.0
1233                };
1234                let win_val = if i < wlen { window_fn[i] } else { 1.0 };
1235                Complex::new(sig_val * win_val, 0.0)
1236            })
1237            .collect();
1238        let n = frame.len().next_power_of_two();
1239        frame.resize(n, Complex::zero());
1240        fft_inplace(&mut frame, false);
1241        frames.push(frame);
1242        start += hop;
1243    }
1244    frames
1245}
1246/// Generate a Hann window of length `n`.
1247///
1248/// `w[k] = sin²(π k / (n-1))` for k = 0..n-1.
1249#[allow(dead_code)]
1250pub fn hann_window(n: usize) -> Vec<f64> {
1251    if n == 0 {
1252        return Vec::new();
1253    }
1254    if n == 1 {
1255        return vec![1.0];
1256    }
1257    (0..n)
1258        .map(|k| {
1259            let x = std::f64::consts::PI * k as f64 / (n - 1) as f64;
1260            x.sin().powi(2)
1261        })
1262        .collect()
1263}
1264/// Generate a Hamming window of length `n`.
1265///
1266/// `w[k] = 0.54 - 0.46 cos(2Ï€ k / (n-1))`
1267#[allow(dead_code)]
1268pub fn hamming_window(n: usize) -> Vec<f64> {
1269    if n == 0 {
1270        return Vec::new();
1271    }
1272    if n == 1 {
1273        return vec![1.0];
1274    }
1275    (0..n)
1276        .map(|k| 0.54 - 0.46 * (2.0 * std::f64::consts::PI * k as f64 / (n - 1) as f64).cos())
1277        .collect()
1278}
1279/// Generate a rectangular (boxcar) window of length `n`.
1280#[allow(dead_code)]
1281pub fn rectangular_window(n: usize) -> Vec<f64> {
1282    vec![1.0; n]
1283}
1284/// Compute the inverse STFT (overlap-add reconstruction).
1285///
1286/// # Arguments
1287/// * `frames` – STFT frames from [`stft`] (each of length window_size).
1288/// * `hop_size` – hop size used when computing the STFT.
1289/// * `window_fn` – the same window used for analysis (for proper reconstruction).
1290///
1291/// Returns the reconstructed real signal.
1292#[allow(dead_code)]
1293pub fn istft(frames: &[Vec<Complex>], hop_size: usize, window_fn: &[f64]) -> Vec<f64> {
1294    if frames.is_empty() {
1295        return Vec::new();
1296    }
1297    let window_size = frames[0].len();
1298    let hop = hop_size.max(1);
1299    let n_out = hop * frames.len() + window_size;
1300    let mut signal = vec![0.0f64; n_out];
1301    let mut weight = vec![0.0f64; n_out];
1302    let wlen = window_fn.len().min(window_size);
1303    for (fi, frame) in frames.iter().enumerate() {
1304        let mut f = frame.clone();
1305        fft_inplace(&mut f, true);
1306        let inv_n = 1.0 / f.len() as f64;
1307        let start = fi * hop;
1308        for k in 0..window_size.min(f.len()) {
1309            let win = if k < wlen { window_fn[k] } else { 1.0 };
1310            if start + k < signal.len() {
1311                signal[start + k] += f[k].re * inv_n * win;
1312                weight[start + k] += win * win;
1313            }
1314        }
1315    }
1316    for i in 0..signal.len() {
1317        if weight[i] > 1e-12 {
1318            signal[i] /= weight[i];
1319        }
1320    }
1321    signal
1322}
1323/// Compute the DCT-II of a real signal (the "standard" DCT used in JPEG/MP3).
1324///
1325/// `X[k] = 2 Σ_{n=0}^{N-1} x[n] cos(π(2n+1)k / (2N))`
1326///
1327/// Uses a naive O(N²) implementation.
1328#[allow(dead_code)]
1329pub fn dct_ii(xs: &[f64]) -> Vec<f64> {
1330    let n = xs.len();
1331    if n == 0 {
1332        return Vec::new();
1333    }
1334    let pi = std::f64::consts::PI;
1335    (0..n)
1336        .map(|k| {
1337            let sum: f64 = xs
1338                .iter()
1339                .enumerate()
1340                .map(|(m, &x)| x * (pi * (2 * m + 1) as f64 * k as f64 / (2.0 * n as f64)).cos())
1341                .sum();
1342            2.0 * sum
1343        })
1344        .collect()
1345}
1346/// Compute the inverse DCT-II (DCT-III).
1347///
1348/// `x[n] = (1/N) (X[0]/2 + Σ_{k=1}^{N-1} X[k] cos(π(2n+1)k / (2N)))`
1349#[allow(dead_code)]
1350pub fn idct_ii(xs: &[f64]) -> Vec<f64> {
1351    let n = xs.len();
1352    if n == 0 {
1353        return Vec::new();
1354    }
1355    let pi = std::f64::consts::PI;
1356    (0..n)
1357        .map(|m| {
1358            let dc = xs[0] / 2.0;
1359            let sum: f64 = (1..n)
1360                .map(|k| xs[k] * (pi * (2 * m + 1) as f64 * k as f64 / (2.0 * n as f64)).cos())
1361                .sum();
1362            (dc + sum) / n as f64
1363        })
1364        .collect()
1365}
1366/// Compute the DCT-I of a real signal.
1367///
1368/// `X[k] = x[0] + (-1)^k x[N-1] + 2 Σ_{n=1}^{N-2} x[n] cos(π n k / (N-1))`
1369///
1370/// The DCT-I is its own inverse (when normalized).
1371#[allow(dead_code)]
1372pub fn dct_i(xs: &[f64]) -> Vec<f64> {
1373    let n = xs.len();
1374    if n < 2 {
1375        return xs.to_vec();
1376    }
1377    let pi = std::f64::consts::PI;
1378    let nm1 = (n - 1) as f64;
1379    (0..n)
1380        .map(|k| {
1381            let mut sum = xs[0] + if k % 2 == 0 { xs[n - 1] } else { -xs[n - 1] };
1382            for m in 1..(n - 1) {
1383                sum += 2.0 * xs[m] * (pi * m as f64 * k as f64 / nm1).cos();
1384            }
1385            sum
1386        })
1387        .collect()
1388}
1389/// Schur-like decomposition of a 2×2 complex matrix.
1390///
1391/// Returns `(T, U)` where:
1392/// - `T` is upper-triangular (quasi-Schur form)
1393/// - `U` is unitary such that `A = U T U†`
1394///
1395/// For 2×2 matrices the decomposition is computed directly from eigenvalues.
1396/// Returns `None` if the matrix is degenerate.
1397#[allow(dead_code)]
1398pub fn schur_2x2(m: &ComplexMat2) -> Option<(ComplexMat2, ComplexMat2)> {
1399    let (l1, l2) = m.eigenvalues();
1400    let al1 = ComplexMat2::new(m.a - l1, m.b, m.c, m.d - l1);
1401    let (u0, u1) = if m.b.norm() > 1e-12 {
1402        (m.b, l1 - m.a)
1403    } else if m.c.norm() > 1e-12 {
1404        (l1 - m.d, m.c)
1405    } else {
1406        let u = ComplexMat2::identity();
1407        let t = ComplexMat2::new(l1, m.b, Complex::zero(), l2);
1408        return Some((t, u));
1409    };
1410    let _ = al1;
1411    let norm = (u0.norm_sq() + u1.norm_sq()).sqrt();
1412    if norm < 1e-12 {
1413        return None;
1414    }
1415    let v0 = Complex::new(u0.re / norm, u0.im / norm);
1416    let v1 = Complex::new(u1.re / norm, u1.im / norm);
1417    let u = ComplexMat2::new(v0, -v1.conj(), v1, v0.conj());
1418    let uh = u.conjugate_transpose();
1419    let t = uh.mul(m).mul(&u);
1420    Some((t, u))
1421}
1422/// Compute the power spectral density (PSD) of a real signal.
1423///
1424/// Returns `|X[k]|²` for k = 0..N/2+1.
1425#[allow(dead_code)]
1426pub fn power_spectral_density(xs: &[f64]) -> Vec<f64> {
1427    let freq = rfft(xs);
1428    freq.iter().map(|z| z.norm_sq()).collect()
1429}
1430/// Compute the magnitude spectrum of a real signal.
1431///
1432/// Returns `|X[k]|` for k = 0..N/2+1.
1433#[allow(dead_code)]
1434pub fn magnitude_spectrum(xs: &[f64]) -> Vec<f64> {
1435    let freq = rfft(xs);
1436    freq.iter().map(|z| z.norm()).collect()
1437}
1438/// Compute the phase spectrum of a real signal.
1439///
1440/// Returns `arg(X[k])` for k = 0..N/2+1.
1441#[allow(dead_code)]
1442pub fn phase_spectrum(xs: &[f64]) -> Vec<f64> {
1443    let freq = rfft(xs);
1444    freq.iter().map(|z| z.arg()).collect()
1445}
1446/// Convolve two real signals using FFT.
1447///
1448/// Returns the linear convolution `a * b` of length `a.len() + b.len() - 1`.
1449#[allow(dead_code)]
1450pub fn fft_convolve(a: &[f64], b: &[f64]) -> Vec<f64> {
1451    if a.is_empty() || b.is_empty() {
1452        return Vec::new();
1453    }
1454    let out_len = a.len() + b.len() - 1;
1455    let n = out_len.next_power_of_two();
1456    let mut fa: Vec<Complex> = a.iter().map(|&x| Complex::new(x, 0.0)).collect();
1457    fa.resize(n, Complex::zero());
1458    let mut fb: Vec<Complex> = b.iter().map(|&x| Complex::new(x, 0.0)).collect();
1459    fb.resize(n, Complex::zero());
1460    fft_inplace(&mut fa, false);
1461    fft_inplace(&mut fb, false);
1462    let mut fc: Vec<Complex> = fa.iter().zip(fb.iter()).map(|(&ai, &bi)| ai * bi).collect();
1463    fft_inplace(&mut fc, true);
1464    let inv_n = 1.0 / n as f64;
1465    fc[..out_len].iter().map(|z| z.re * inv_n).collect()
1466}
1467/// Compute Parseval's theorem check: sum of |x\[n\]|² == (1/N) sum |X\[k\]|².
1468///
1469/// Returns `(time_energy, freq_energy)`.  These should be approximately equal.
1470#[allow(dead_code)]
1471pub fn parseval_check(xs: &[f64]) -> (f64, f64) {
1472    let time_energy: f64 = xs.iter().map(|&x| x * x).sum();
1473    let complex_in: Vec<Complex> = xs.iter().map(|&x| Complex::new(x, 0.0)).collect();
1474    let freq = fft_radix2(&complex_in);
1475    let n = freq.len() as f64;
1476    let freq_energy: f64 = freq.iter().map(|z| z.norm_sq()).sum::<f64>() / n;
1477    (time_energy, freq_energy)
1478}
1479/// Evaluate a polynomial with complex coefficients at complex point `z`.
1480///
1481/// Coefficients are in descending order: `coeffs[0] z^n + ... + coeffs[n]`.
1482#[allow(dead_code)]
1483pub fn poly_eval(coeffs: &[Complex], z: Complex) -> Complex {
1484    if coeffs.is_empty() {
1485        return Complex::zero();
1486    }
1487    let mut result = coeffs[0];
1488    for &c in &coeffs[1..] {
1489        result = result * z + c;
1490    }
1491    result
1492}
1493/// Compute the derivative of a polynomial with complex coefficients.
1494///
1495/// Returns the derivative coefficients (one fewer term).
1496#[allow(dead_code)]
1497pub fn poly_derivative(coeffs: &[Complex]) -> Vec<Complex> {
1498    if coeffs.len() <= 1 {
1499        return Vec::new();
1500    }
1501    let n = coeffs.len() - 1;
1502    (0..n)
1503        .map(|i| coeffs[i] * Complex::new((n - i) as f64, 0.0))
1504        .collect()
1505}
1506/// Multiply two polynomials with complex coefficients.
1507///
1508/// Coefficients in descending order.
1509#[allow(dead_code)]
1510pub fn poly_multiply(a: &[Complex], b: &[Complex]) -> Vec<Complex> {
1511    if a.is_empty() || b.is_empty() {
1512        return Vec::new();
1513    }
1514    let na = a.len();
1515    let nb = b.len();
1516    let mut result = vec![Complex::zero(); na + nb - 1];
1517    for i in 0..na {
1518        for j in 0..nb {
1519            result[i + j] = result[i + j] + a[i] * b[j];
1520        }
1521    }
1522    result
1523}
1524/// Evaluate the Z-transform of a finite-length sequence at complex frequency `z`.
1525///
1526/// `X(z) = Σ_{n=0}^{N-1} x[n] z^{-n}`
1527#[allow(dead_code)]
1528pub fn z_transform_eval(xs: &[f64], z: Complex) -> Complex {
1529    let mut result = Complex::zero();
1530    let mut zn = Complex::one();
1531    let inv_z = if z.norm_sq() > 1e-24 {
1532        Complex::one() / z
1533    } else {
1534        Complex::zero()
1535    };
1536    for &x in xs {
1537        result = result + Complex::new(x, 0.0) * zn;
1538        zn = zn * inv_z;
1539    }
1540    result
1541}
1542/// Evaluate the Laplace transform of an exponentially decaying signal
1543/// `f(t) = x * exp(-alpha * t)` at complex frequency `s`.
1544///
1545/// `F(s) = x / (s + alpha)` for `Re(s + alpha) > 0`.
1546#[allow(dead_code)]
1547pub fn laplace_exp_decay(amplitude: f64, alpha: f64, s: Complex) -> Complex {
1548    let denom = s + Complex::new(alpha, 0.0);
1549    if denom.norm_sq() < 1e-24 {
1550        return Complex::zero();
1551    }
1552    Complex::new(amplitude, 0.0) / denom
1553}
1554#[cfg(test)]
1555mod tests_new_complex2 {
1556
1557    use crate::complex::Complex;
1558    use crate::complex::ComplexMat2;
1559    use crate::complex::ComplexMatN;
1560
1561    use crate::complex::dct_i;
1562    use crate::complex::dct_ii;
1563
1564    use crate::complex::fft_convolve;
1565
1566    use crate::complex::functions::PI;
1567    use crate::complex::hamming_window;
1568    use crate::complex::hann_window;
1569    use crate::complex::idct_ii;
1570
1571    use crate::complex::laplace_exp_decay;
1572    use crate::complex::magnitude_spectrum;
1573
1574    use crate::complex::parseval_check;
1575    use crate::complex::phase_spectrum;
1576    use crate::complex::poly_derivative;
1577    use crate::complex::poly_eval;
1578    use crate::complex::poly_multiply;
1579    use crate::complex::power_spectral_density;
1580
1581    use crate::complex::rectangular_window;
1582
1583    use crate::complex::schur_2x2;
1584
1585    use crate::complex::stft;
1586    use crate::complex::z_transform_eval;
1587    fn approx(a: f64, b: f64, eps: f64) -> bool {
1588        (a - b).abs() < eps
1589    }
1590    #[test]
1591    fn test_stft_produces_frames() {
1592        let signal: Vec<f64> = (0..64).map(|i| (i as f64 * 0.1).sin()).collect();
1593        let window = hann_window(16);
1594        let frames = stft(&signal, 16, 8, &window);
1595        assert!(!frames.is_empty(), "STFT should produce at least one frame");
1596    }
1597    #[test]
1598    fn test_stft_frame_count() {
1599        let signal: Vec<f64> = vec![0.0; 64];
1600        let window = rectangular_window(16);
1601        let frames = stft(&signal, 16, 16, &window);
1602        assert!(
1603            frames.len() >= 4,
1604            "should have at least 4 frames, got {}",
1605            frames.len()
1606        );
1607    }
1608    #[test]
1609    fn test_stft_dc_signal() {
1610        let signal: Vec<f64> = vec![1.0; 16];
1611        let window = rectangular_window(16);
1612        let frames = stft(&signal, 16, 16, &window);
1613        assert!(!frames.is_empty());
1614        let frame = &frames[0];
1615        let dc = frame[0].norm();
1616        let max_ac = frame[1..frame.len() / 2]
1617            .iter()
1618            .map(|z| z.norm())
1619            .fold(0.0f64, f64::max);
1620        assert!(dc > max_ac, "DC should dominate: dc={dc}, max_ac={max_ac}");
1621    }
1622    #[test]
1623    fn test_hann_window_zero_endpoints() {
1624        let w = hann_window(8);
1625        assert!((w[0]).abs() < 1e-10, "Hann window should start at 0");
1626        assert!(!w.is_empty());
1627    }
1628    #[test]
1629    fn test_hann_window_length() {
1630        let w = hann_window(16);
1631        assert_eq!(w.len(), 16);
1632    }
1633    #[test]
1634    fn test_hamming_window_nonzero_endpoints() {
1635        let w = hamming_window(8);
1636        assert!(w[0] > 0.0 && w[0] < 0.2, "Hamming endpoint = {}", w[0]);
1637    }
1638    #[test]
1639    fn test_rectangular_window_all_ones() {
1640        let w = rectangular_window(8);
1641        for &wi in &w {
1642            assert!((wi - 1.0).abs() < 1e-12);
1643        }
1644    }
1645    #[test]
1646    fn test_dct_ii_dc_component() {
1647        let n = 8;
1648        let xs = vec![1.0f64; n];
1649        let out = dct_ii(&xs);
1650        assert!(
1651            approx(out[0], 2.0 * n as f64, 1e-8),
1652            "DCT-II[0] of constant 1 should be 2N={}, got {}",
1653            2 * n,
1654            out[0]
1655        );
1656    }
1657    #[test]
1658    fn test_dct_ii_idct_roundtrip() {
1659        let xs = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1660        let dct = dct_ii(&xs);
1661        let recovered = idct_ii(&dct);
1662        assert_eq!(recovered.len(), xs.len());
1663        for (a, b) in xs.iter().zip(recovered.iter()) {
1664            assert!(approx(*a, *b, 1e-8), "DCT-II roundtrip: {} vs {}", a, b);
1665        }
1666    }
1667    #[test]
1668    fn test_dct_ii_orthogonality() {
1669        let n = 8;
1670        let xs1: Vec<f64> = (0..n)
1671            .map(|m| (PI * (2 * m + 1) as f64 * 1.0 / (2.0 * n as f64)).cos())
1672            .collect();
1673        let xs2: Vec<f64> = (0..n)
1674            .map(|m| (PI * (2 * m + 1) as f64 * 2.0 / (2.0 * n as f64)).cos())
1675            .collect();
1676        let dot: f64 = xs1.iter().zip(xs2.iter()).map(|(a, b)| a * b).sum();
1677        assert!(
1678            dot.abs() < 1e-8,
1679            "DCT basis vectors should be orthogonal, dot={dot}"
1680        );
1681    }
1682    #[test]
1683    fn test_dct_ii_single_element() {
1684        let xs = vec![3.0];
1685        let out = dct_ii(&xs);
1686        assert_eq!(out.len(), 1);
1687        assert!(
1688            approx(out[0], 6.0, 1e-10),
1689            "DCT-II of [3] should be [6], got {}",
1690            out[0]
1691        );
1692    }
1693    #[test]
1694    fn test_dct_i_self_inverse() {
1695        let xs = vec![1.0, 2.0, 3.0, 2.0, 1.0];
1696        let dct1 = dct_i(&xs);
1697        let dct2 = dct_i(&dct1);
1698        let scale = 2.0 * (xs.len() - 1) as f64;
1699        for (a, b) in xs.iter().zip(dct2.iter()) {
1700            assert!(
1701                approx(*a * scale, *b, 1e-6),
1702                "DCT-I self-inverse: {} vs {}",
1703                a * scale,
1704                b
1705            );
1706        }
1707    }
1708    #[test]
1709    fn test_schur_2x2_upper_triangular() {
1710        let m = ComplexMat2::new(
1711            Complex::new(3.0, 0.0),
1712            Complex::new(1.0, 0.0),
1713            Complex::new(1.0, 0.0),
1714            Complex::new(2.0, 0.0),
1715        );
1716        if let Some((t, _u)) = schur_2x2(&m) {
1717            assert!(
1718                t.c.norm() < 0.1,
1719                "Schur form should have near-zero lower-left, got {}",
1720                t.c.norm()
1721            );
1722        }
1723    }
1724    #[test]
1725    fn test_schur_2x2_diagonal_matrix() {
1726        let m = ComplexMat2::new(
1727            Complex::new(2.0, 0.0),
1728            Complex::zero(),
1729            Complex::zero(),
1730            Complex::new(3.0, 0.0),
1731        );
1732        let result = schur_2x2(&m);
1733        assert!(result.is_some(), "diagonal matrix Schur should succeed");
1734    }
1735    #[test]
1736    fn test_schur_2x2_eigenvalue_trace() {
1737        let m = ComplexMat2::new(
1738            Complex::new(4.0, 0.0),
1739            Complex::new(1.0, 0.0),
1740            Complex::new(2.0, 0.0),
1741            Complex::new(3.0, 0.0),
1742        );
1743        if let Some((t, _u)) = schur_2x2(&m) {
1744            let tr_m = m.trace();
1745            let tr_t = t.trace();
1746            assert!(
1747                approx(tr_m.re, tr_t.re, 1e-6) && approx(tr_m.im, tr_t.im, 1e-6),
1748                "traces should match: ({},{}) vs ({},{})",
1749                tr_m.re,
1750                tr_m.im,
1751                tr_t.re,
1752                tr_t.im
1753            );
1754        }
1755    }
1756    #[test]
1757    fn test_complex_matn_identity_mul() {
1758        let id = ComplexMatN::identity(3);
1759        let m = ComplexMatN::identity(3);
1760        let result = id.matmul(&m);
1761        assert_eq!(result.n, 3);
1762        for i in 0..3 {
1763            assert!(approx(result.get(i, i).re, 1.0, 1e-12));
1764        }
1765    }
1766    #[test]
1767    fn test_complex_matn_trace() {
1768        let mut m = ComplexMatN::zeros(3);
1769        for i in 0..3 {
1770            m[(i, i)] = Complex::new(i as f64 + 1.0, 0.0);
1771        }
1772        let tr = m.trace();
1773        assert!(
1774            approx(tr.re, 6.0, 1e-12),
1775            "trace should be 6, got {}",
1776            tr.re
1777        );
1778    }
1779    #[test]
1780    fn test_complex_matn_frobenius_identity() {
1781        let id = ComplexMatN::identity(4);
1782        let frob = id.frobenius_norm();
1783        assert!(
1784            approx(frob, 2.0, 1e-10),
1785            "Frobenius norm of 4x4 identity should be 2, got {frob}"
1786        );
1787    }
1788    #[test]
1789    fn test_complex_matn_matvec() {
1790        let id = ComplexMatN::identity(3);
1791        let v = vec![
1792            Complex::new(1.0, 0.0),
1793            Complex::new(2.0, 0.0),
1794            Complex::new(3.0, 0.0),
1795        ];
1796        let result = id.matvec(&v);
1797        for (a, b) in v.iter().zip(result.iter()) {
1798            assert!(approx(a.re, b.re, 1e-12));
1799        }
1800    }
1801    #[test]
1802    fn test_complex_matn_conjugate_transpose() {
1803        let mut m = ComplexMatN::zeros(2);
1804        m[(0, 1)] = Complex::new(1.0, 2.0);
1805        let mh = m.conjugate_transpose();
1806        assert!(approx(mh.get(1, 0).re, 1.0, 1e-12));
1807        assert!(approx(mh.get(1, 0).im, -2.0, 1e-12));
1808    }
1809    #[test]
1810    fn test_parseval_theorem() {
1811        let xs: Vec<f64> = (0..8).map(|i| (i as f64).sin()).collect();
1812        let (te, fe) = parseval_check(&xs);
1813        assert!(
1814            approx(te, fe, 1e-8),
1815            "Parseval: time_energy={te}, freq_energy={fe}"
1816        );
1817    }
1818    #[test]
1819    fn test_power_spectral_density_dc() {
1820        let xs = vec![1.0f64; 8];
1821        let psd = power_spectral_density(&xs);
1822        assert!(!psd.is_empty());
1823        let max_val = *psd.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
1824        assert!((max_val - psd[0]).abs() < 1e-8, "DC should dominate PSD");
1825    }
1826    #[test]
1827    fn test_magnitude_spectrum_length() {
1828        let xs = vec![0.0f64; 16];
1829        let mag = magnitude_spectrum(&xs);
1830        assert_eq!(mag.len(), 9, "rfft of 16 samples should have 16/2+1=9 bins");
1831    }
1832    #[test]
1833    fn test_fft_convolve_unit_impulse() {
1834        let xs = vec![1.0, 2.0, 3.0];
1835        let impulse = vec![1.0];
1836        let result = fft_convolve(&xs, &impulse);
1837        assert_eq!(result.len(), xs.len());
1838        for (a, b) in xs.iter().zip(result.iter()) {
1839            assert!(
1840                approx(*a, *b, 1e-8),
1841                "convolve with impulse: {} vs {}",
1842                a,
1843                b
1844            );
1845        }
1846    }
1847    #[test]
1848    fn test_fft_convolve_commutativity() {
1849        let a = vec![1.0, 2.0, 3.0];
1850        let b = vec![0.5, 1.0];
1851        let ab = fft_convolve(&a, &b);
1852        let ba = fft_convolve(&b, &a);
1853        assert_eq!(ab.len(), ba.len());
1854        for (x, y) in ab.iter().zip(ba.iter()) {
1855            assert!(
1856                approx(*x, *y, 1e-8),
1857                "convolution should be commutative: {} vs {}",
1858                x,
1859                y
1860            );
1861        }
1862    }
1863    #[test]
1864    fn test_poly_eval_constant() {
1865        let coeffs = vec![Complex::new(5.0, 0.0)];
1866        let v = poly_eval(&coeffs, Complex::new(3.0, 0.0));
1867        assert!(approx(v.re, 5.0, 1e-12));
1868    }
1869    #[test]
1870    fn test_poly_eval_linear() {
1871        let coeffs = vec![Complex::new(2.0, 0.0), Complex::new(3.0, 0.0)];
1872        let v = poly_eval(&coeffs, Complex::new(1.0, 0.0));
1873        assert!(approx(v.re, 5.0, 1e-12));
1874    }
1875    #[test]
1876    fn test_poly_eval_quadratic() {
1877        let coeffs = vec![
1878            Complex::new(1.0, 0.0),
1879            Complex::new(0.0, 0.0),
1880            Complex::new(-1.0, 0.0),
1881        ];
1882        let v1 = poly_eval(&coeffs, Complex::new(1.0, 0.0));
1883        let v2 = poly_eval(&coeffs, Complex::new(-1.0, 0.0));
1884        assert!(approx(v1.re, 0.0, 1e-12));
1885        assert!(approx(v2.re, 0.0, 1e-12));
1886    }
1887    #[test]
1888    fn test_poly_derivative_constant() {
1889        let coeffs = vec![Complex::new(5.0, 0.0)];
1890        let deriv = poly_derivative(&coeffs);
1891        assert!(deriv.is_empty(), "derivative of constant should be empty");
1892    }
1893    #[test]
1894    fn test_poly_derivative_linear() {
1895        let coeffs = vec![Complex::new(3.0, 0.0), Complex::new(2.0, 0.0)];
1896        let deriv = poly_derivative(&coeffs);
1897        assert_eq!(deriv.len(), 1);
1898        assert!(approx(deriv[0].re, 3.0, 1e-12));
1899    }
1900    #[test]
1901    fn test_poly_multiply_monomial() {
1902        let a = vec![Complex::new(1.0, 0.0), Complex::new(1.0, 0.0)];
1903        let b = vec![Complex::new(1.0, 0.0), Complex::new(-1.0, 0.0)];
1904        let product = poly_multiply(&a, &b);
1905        assert_eq!(product.len(), 3);
1906        assert!(approx(product[0].re, 1.0, 1e-12));
1907        assert!(approx(product[1].re, 0.0, 1e-12));
1908        assert!(approx(product[2].re, -1.0, 1e-12));
1909    }
1910    #[test]
1911    fn test_z_transform_impulse() {
1912        let xs = vec![1.0, 0.0, 0.0, 0.0];
1913        let result = z_transform_eval(&xs, Complex::new(2.0, 0.0));
1914        assert!(approx(result.re, 1.0, 1e-12), "Z{{delta}} should be 1");
1915    }
1916    #[test]
1917    fn test_z_transform_delay() {
1918        let xs = vec![0.0, 1.0, 0.0];
1919        let result = z_transform_eval(&xs, Complex::new(2.0, 0.0));
1920        assert!(
1921            approx(result.re, 0.5, 1e-12),
1922            "Z{{delta[n-1]}} at z=2 should be 0.5, got {}",
1923            result.re
1924        );
1925    }
1926    #[test]
1927    fn test_laplace_exp_decay_at_zero() {
1928        let alpha = 2.0;
1929        let result = laplace_exp_decay(1.0, alpha, Complex::zero());
1930        let expected = 1.0 / alpha;
1931        assert!(
1932            approx(result.re, expected, 1e-12),
1933            "Laplace F(0) should be 1/alpha={expected}"
1934        );
1935    }
1936    #[test]
1937    fn test_laplace_exp_decay_pole_returns_zero() {
1938        let alpha = 1.0;
1939        let result = laplace_exp_decay(1.0, alpha, Complex::new(-alpha, 0.0));
1940        assert!(result.norm() == 0.0, "at pole should return zero");
1941    }
1942    #[test]
1943    fn test_phase_spectrum_length() {
1944        let xs: Vec<f64> = (0..8).map(|i| i as f64).collect();
1945        let phase = phase_spectrum(&xs);
1946        assert_eq!(phase.len(), 5, "phase of 8 samples should have 5 bins");
1947    }
1948}