1#![allow(clippy::needless_range_loop)]
6use super::types::{Complex, ComplexMat2};
7use std::f64::consts::PI;
8
9pub 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}
26pub fn joukowski(z: Complex) -> Complex {
30 z + Complex::one() / z
31}
32pub 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}
51pub fn exponential_conformal(z: Complex) -> Complex {
55 z.exp()
56}
57pub fn logarithmic_conformal(z: Complex) -> Complex {
61 z.ln()
62}
63pub fn cayley_transform(z: Complex) -> Complex {
67 let i = Complex::new(0.0, 1.0);
68 (z - i) / (z + i)
69}
70pub 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}
77pub 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}
87pub 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}
136pub 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}
143pub 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}
153pub 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}
166pub 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[allow(dead_code)]
1281pub fn rectangular_window(n: usize) -> Vec<f64> {
1282 vec![1.0; n]
1283}
1284#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}