1#![allow(clippy::unreadable_literal)]
13
14use ferray_core::Array;
15use ferray_core::dimension::Ix1;
16use ferray_core::error::{FerrayError, FerrayResult};
17
18use std::f64::consts::PI;
19
20use ferray_ufunc::ops::special::bessel_i0_scalar;
26
27#[inline]
32fn gen_window<F: FnMut(usize) -> f64>(m: usize, mut f: F) -> FerrayResult<Array<f64, Ix1>> {
33 if m == 0 {
34 return Array::from_vec(Ix1::new([0]), vec![]);
35 }
36 if m == 1 {
37 return Array::from_vec(Ix1::new([1]), vec![1.0]);
38 }
39 let mut data = Vec::with_capacity(m);
40 for n in 0..m {
41 data.push(f(n));
42 }
43 Array::from_vec(Ix1::new([m]), data)
44}
45
46fn cast_to_f32(arr: &Array<f64, Ix1>) -> FerrayResult<Array<f32, Ix1>> {
51 let data: Vec<f32> = arr.iter().map(|&x| x as f32).collect();
52 Array::<f32, Ix1>::from_vec(Ix1::new([data.len()]), data)
53}
54
55pub fn bartlett_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
60 cast_to_f32(&bartlett(m)?)
61}
62
63pub fn blackman_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
68 cast_to_f32(&blackman(m)?)
69}
70
71pub fn hamming_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
76 cast_to_f32(&hamming(m)?)
77}
78
79pub fn hanning_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
84 cast_to_f32(&hanning(m)?)
85}
86
87pub fn kaiser_f32(m: usize, beta: f64) -> FerrayResult<Array<f32, Ix1>> {
92 cast_to_f32(&kaiser(m, beta)?)
93}
94
95pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
105 let half = (m.saturating_sub(1)) as f64 / 2.0;
106 gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
107}
108
109pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
119 let denom = (m.saturating_sub(1)) as f64;
120 gen_window(m, |n| {
121 let x = n as f64;
122 0.08f64.mul_add(
123 (4.0 * PI * x / denom).cos(),
124 0.5f64.mul_add(-(2.0 * PI * x / denom).cos(), 0.42),
125 )
126 })
127}
128
129pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
139 let denom = (m.saturating_sub(1)) as f64;
140 gen_window(m, |n| {
141 0.46f64.mul_add(-(2.0 * PI * n as f64 / denom).cos(), 0.54)
142 })
143}
144
145pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
155 let denom = (m.saturating_sub(1)) as f64;
156 gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
157}
158
159pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
180 if beta.is_nan() {
181 return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
182 }
183 let beta = beta.abs();
186 const BETA_OVERFLOW_THRESHOLD: f64 = 708.0;
191 if beta > BETA_OVERFLOW_THRESHOLD {
192 return Err(FerrayError::invalid_value(format!(
193 "kaiser: |beta| = {beta} exceeds the safe range ({BETA_OVERFLOW_THRESHOLD}) \
194 for f64 I_0; the window would be NaN everywhere"
195 )));
196 }
197 let i0_beta = bessel_i0_scalar::<f64>(beta);
198 let alpha = (m.saturating_sub(1)) as f64 / 2.0;
199 gen_window(m, |n| {
200 let t = (n as f64 - alpha) / alpha;
201 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
202 bessel_i0_scalar::<f64>(arg) / i0_beta
203 })
204}
205
206pub fn cosine(m: usize) -> FerrayResult<Array<f64, Ix1>> {
222 if m == 0 {
223 return Array::from_vec(Ix1::new([0]), vec![]);
224 }
225 if m == 1 {
226 return Array::from_vec(Ix1::new([1]), vec![1.0]);
227 }
228 let mf = m as f64;
229 gen_window(m, |n| (PI * (n as f64 + 0.5) / mf).sin())
230}
231
232pub fn exponential(m: usize, center: Option<f64>, tau: f64) -> FerrayResult<Array<f64, Ix1>> {
240 if !tau.is_finite() || tau <= 0.0 {
241 return Err(FerrayError::invalid_value(format!(
242 "exponential: tau must be positive and finite, got {tau}"
243 )));
244 }
245 let centre = center.unwrap_or((m.saturating_sub(1)) as f64 / 2.0);
246 gen_window(m, |n| (-((n as f64) - centre).abs() / tau).exp())
247}
248
249pub fn gaussian(m: usize, std: f64) -> FerrayResult<Array<f64, Ix1>> {
256 if !std.is_finite() || std <= 0.0 {
257 return Err(FerrayError::invalid_value(format!(
258 "gaussian: std must be positive and finite, got {std}"
259 )));
260 }
261 let centre = (m.saturating_sub(1)) as f64 / 2.0;
262 gen_window(m, |n| {
263 let z = ((n as f64) - centre) / std;
264 (-0.5 * z * z).exp()
265 })
266}
267
268pub fn general_cosine(m: usize, coeffs: &[f64]) -> FerrayResult<Array<f64, Ix1>> {
276 if coeffs.is_empty() {
277 return Err(FerrayError::invalid_value(
278 "general_cosine: coeffs must not be empty",
279 ));
280 }
281 let denom = (m.saturating_sub(1)) as f64;
282 let coeffs = coeffs.to_vec();
283 gen_window(m, |n| {
284 let nf = n as f64;
285 let mut sum = 0.0_f64;
286 for (k, &a) in coeffs.iter().enumerate() {
287 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
288 sum += sign * a * (2.0 * PI * (k as f64) * nf / denom).cos();
289 }
290 sum
291 })
292}
293
294pub fn general_hamming(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
302 if !alpha.is_finite() {
303 return Err(FerrayError::invalid_value(format!(
304 "general_hamming: alpha must be finite, got {alpha}"
305 )));
306 }
307 let denom = (m.saturating_sub(1)) as f64;
308 gen_window(m, |n| {
309 alpha - (1.0 - alpha) * (2.0 * PI * (n as f64) / denom).cos()
310 })
311}
312
313pub fn nuttall(m: usize) -> FerrayResult<Array<f64, Ix1>> {
321 general_cosine(m, &[0.3635819, 0.4891775, 0.1365995, 0.0106411])
322}
323
324pub fn parzen(m: usize) -> FerrayResult<Array<f64, Ix1>> {
328 if m == 0 {
329 return Array::from_vec(Ix1::new([0]), vec![]);
330 }
331 if m == 1 {
332 return Array::from_vec(Ix1::new([1]), vec![1.0]);
333 }
334 let half = m as f64 / 2.0;
337 gen_window(m, |n| {
338 let x = (n as f64) - (m as f64 - 1.0) / 2.0;
339 let r = x.abs() / half;
340 if r <= 0.5 {
341 1.0 - 6.0 * r * r + 6.0 * r * r * r
342 } else if r <= 1.0 {
343 let one_minus_r = 1.0 - r;
344 2.0 * one_minus_r * one_minus_r * one_minus_r
345 } else {
346 0.0
347 }
348 })
349}
350
351pub fn chebwin(m: usize, at: f64) -> FerrayResult<Array<f64, Ix1>> {
367 if !at.is_finite() || at <= 0.0 {
368 return Err(FerrayError::invalid_value(format!(
369 "chebwin: at must be a positive finite dB attenuation, got {at}"
370 )));
371 }
372 if m == 0 {
373 return Array::from_vec(Ix1::new([0]), vec![]);
374 }
375 if m == 1 {
376 return Array::from_vec(Ix1::new([1]), vec![1.0]);
377 }
378
379 let order = (m - 1) as f64;
380 let r = 10.0_f64.powf(at / 20.0);
381 let beta = (r.acosh() / order).cosh();
382
383 let mut spectrum = Vec::with_capacity(m);
385 for k in 0..m {
386 let x = beta * (PI * k as f64 / m as f64).cos();
387 spectrum.push(chebyshev_t_extended(x, order));
388 }
389
390 let mf = m as f64;
402 let half_dft = |idx: usize| -> f64 {
403 if m % 2 == 1 {
404 spectrum
405 .iter()
406 .enumerate()
407 .map(|(k, &pk)| pk * (-2.0 * PI * k as f64 * idx as f64 / mf).cos())
408 .sum()
409 } else {
410 spectrum
414 .iter()
415 .enumerate()
416 .map(|(k, &pk)| {
417 let phase = PI * k as f64 * (1.0 - 2.0 * idx as f64) / mf;
418 pk * phase.cos()
419 })
420 .sum()
421 }
422 };
423
424 let half_len = m / 2 + 1;
425 let mut half: Vec<f64> = (0..half_len).map(half_dft).collect();
426 let denom = if m % 2 == 1 { half[0] } else { half[1] };
427 if denom != 0.0 {
428 for v in &mut half {
429 *v /= denom;
430 }
431 }
432
433 let mut w = Vec::with_capacity(m);
434 if m % 2 == 1 {
435 for &v in half.iter().skip(1).rev() {
437 w.push(v);
438 }
439 w.extend_from_slice(&half);
440 } else {
441 let n = m / 2 + 1;
444 for &v in half[1..n].iter().rev() {
445 w.push(v);
446 }
447 w.extend_from_slice(&half[1..n]);
448 }
449
450 Array::from_vec(Ix1::new([m]), w)
451}
452
453pub fn dpss(m: usize, nw: f64) -> FerrayResult<Array<f64, Ix1>> {
485 if !nw.is_finite() || nw <= 0.0 {
486 return Err(FerrayError::invalid_value(format!(
487 "dpss: nw must be a positive finite half bandwidth, got {nw}"
488 )));
489 }
490 if m == 0 {
491 return Array::from_vec(Ix1::new([0]), vec![]);
492 }
493 if m == 1 {
494 return Array::from_vec(Ix1::new([1]), vec![1.0]);
495 }
496 if nw >= (m as f64) / 2.0 {
497 return Err(FerrayError::invalid_value(format!(
498 "dpss: nw = {nw} must be < M/2 = {} for a non-degenerate band",
499 (m as f64) / 2.0
500 )));
501 }
502
503 let mf = m as f64;
504 let cos_w = (2.0 * PI * nw / mf).cos();
505 let diag: Vec<f64> = (0..m)
507 .map(|i| {
508 let centre = (mf - 1.0) / 2.0 - i as f64;
509 centre * centre * cos_w
510 })
511 .collect();
512 let off: Vec<f64> = (0..m.saturating_sub(1))
514 .map(|i| (i as f64 + 1.0) * (mf - i as f64 - 1.0) / 2.0)
515 .collect();
516
517 let mut v: Vec<f64> = (0..m)
520 .map(|i| {
521 let centred = (i as f64 - (mf - 1.0) / 2.0) / mf;
522 (-2.0 * centred * centred).exp()
523 })
524 .collect();
525 let n0 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
526 for x in &mut v {
527 *x /= n0.max(f64::MIN_POSITIVE);
528 }
529
530 let mut prev_eig = 0.0_f64;
531 for _ in 0..500 {
532 let mut next = vec![0.0_f64; m];
533 for i in 0..m {
534 next[i] = diag[i] * v[i];
535 if i > 0 {
536 next[i] += off[i - 1] * v[i - 1];
537 }
538 if i + 1 < m {
539 next[i] += off[i] * v[i + 1];
540 }
541 }
542 let eig: f64 = v.iter().zip(next.iter()).map(|(a, b)| a * b).sum();
544 let norm = next.iter().map(|x| x * x).sum::<f64>().sqrt();
545 if norm < f64::MIN_POSITIVE {
546 return Err(FerrayError::invalid_value(
547 "dpss: power iteration produced a zero vector",
548 ));
549 }
550 for x in &mut next {
551 *x /= norm;
552 }
553 v = next;
554 if (eig - prev_eig).abs() < 1e-12 * eig.abs().max(1.0) {
555 break;
556 }
557 prev_eig = eig;
558 }
559
560 let centre_val = if m % 2 == 1 {
563 v[m / 2]
564 } else {
565 v[m / 2 - 1] + v[m / 2]
566 };
567 if centre_val < 0.0 {
568 for x in &mut v {
569 *x = -*x;
570 }
571 }
572
573 let peak = v.iter().copied().fold(f64::NEG_INFINITY, f64::max);
578 if peak > 0.0 {
579 for x in &mut v {
580 *x /= peak;
581 }
582 }
583
584 Array::from_vec(Ix1::new([m]), v)
585}
586
587fn chebyshev_t_extended(x: f64, n: f64) -> f64 {
592 if x.abs() <= 1.0 {
593 (n * x.acos()).cos()
594 } else {
595 let mag = (n * x.abs().acosh()).cosh();
596 if x < 0.0 && (n as i64) % 2 != 0 {
597 -mag
598 } else {
599 mag
600 }
601 }
602}
603
604pub fn boxcar(m: usize) -> FerrayResult<Array<f64, Ix1>> {
611 gen_window(m, |_| 1.0)
612}
613
614pub fn triang(m: usize) -> FerrayResult<Array<f64, Ix1>> {
623 if m == 0 {
624 return Array::from_vec(Ix1::new([0]), vec![]);
625 }
626 if m == 1 {
627 return Array::from_vec(Ix1::new([1]), vec![1.0]);
628 }
629 let mf = m as f64;
630 let centre = (mf - 1.0) / 2.0;
631 if m % 2 == 0 {
632 gen_window(m, |n| 1.0 - (2.0 * n as f64 - centre - centre).abs() / mf)
634 } else {
635 gen_window(m, |n| {
637 1.0 - (2.0 * n as f64 - centre - centre).abs() / (mf + 1.0)
638 })
639 }
640}
641
642pub fn bohman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
651 if m == 0 {
652 return Array::from_vec(Ix1::new([0]), vec![]);
653 }
654 if m == 1 {
655 return Array::from_vec(Ix1::new([1]), vec![1.0]);
656 }
657 let mf = (m - 1) as f64;
659 let mut data = Vec::with_capacity(m);
660 for n in 0..m {
661 if n == 0 || n == m - 1 {
662 data.push(0.0);
663 continue;
664 }
665 let r = (2.0 * n as f64 / mf - 1.0).abs();
666 let v = (1.0 - r) * (PI * r).cos() + (PI * r).sin() / PI;
667 data.push(v);
668 }
669 Array::from_vec(Ix1::new([m]), data)
670}
671
672pub fn flattop(m: usize) -> FerrayResult<Array<f64, Ix1>> {
681 const COEFFS: [f64; 5] = [
683 0.215_578_95,
684 0.416_631_58,
685 0.277_263_158,
686 0.083_578_95,
687 0.006_947_368,
688 ];
689 if m == 0 {
690 return Array::from_vec(Ix1::new([0]), vec![]);
691 }
692 let denom = (m - 1).max(1) as f64;
693 gen_window(m, |n| {
694 let phase = 2.0 * PI * n as f64 / denom;
695 let mut acc = COEFFS[0];
696 for (k, &c) in COEFFS.iter().enumerate().skip(1) {
697 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
698 acc += sign * c * (k as f64 * phase).cos();
699 }
700 acc
701 })
702}
703
704pub fn lanczos(m: usize) -> FerrayResult<Array<f64, Ix1>> {
712 if m == 0 {
713 return Array::from_vec(Ix1::new([0]), vec![]);
714 }
715 if m == 1 {
716 return Array::from_vec(Ix1::new([1]), vec![1.0]);
717 }
718 let denom = (m - 1) as f64;
719 gen_window(m, |n| {
720 let x = 2.0 * n as f64 / denom - 1.0;
721 if x.abs() < f64::EPSILON {
722 1.0
723 } else {
724 let pi_x = PI * x;
725 pi_x.sin() / pi_x
726 }
727 })
728}
729
730pub fn taylor(m: usize, nbar: usize, sll: f64, norm: bool) -> FerrayResult<Array<f64, Ix1>> {
745 if m == 0 {
746 return Array::from_vec(Ix1::new([0]), vec![]);
747 }
748 if m == 1 {
749 return Array::from_vec(Ix1::new([1]), vec![1.0]);
750 }
751 if nbar == 0 {
752 return Err(FerrayError::invalid_value("taylor: nbar must be >= 1"));
753 }
754 if !sll.is_finite() {
755 return Err(FerrayError::invalid_value("taylor: sll must be finite"));
756 }
757 let r = 10.0_f64.powf(sll / 20.0);
759 let b = r.acosh() / PI;
760 let nbar_f = nbar as f64;
761 let sigma2 = (nbar_f * nbar_f) / (b * b + (nbar_f - 0.5) * (nbar_f - 0.5));
764
765 let mut f_coeffs = Vec::with_capacity(nbar.saturating_sub(1));
767 for mm in 1..nbar {
768 let mmf = mm as f64;
769 let mut num = 1.0_f64;
772 for n in 1..nbar {
773 let nf = n as f64;
774 num *= 1.0 - mmf * mmf / (sigma2 * (b * b + (nf - 0.5) * (nf - 0.5)));
775 }
776 let sign = if mm % 2 == 0 { -1.0 } else { 1.0 };
777 let mut den = 1.0_f64;
778 for n in 1..nbar {
779 if n == mm {
780 continue;
781 }
782 let nf = n as f64;
783 den *= 1.0 - mmf * mmf / (nf * nf);
784 }
785 f_coeffs.push(0.5 * sign * num / den);
789 }
790
791 let denom = (m - 1) as f64;
792 let arr = gen_window(m, |n| {
793 let xn = (n as f64) - denom / 2.0;
794 let mut w = 1.0_f64;
795 for (idx, &fk) in f_coeffs.iter().enumerate() {
796 let kk = (idx + 1) as f64;
797 w += 2.0 * fk * (2.0 * PI * kk * xn / m as f64).cos();
798 }
799 w
800 })?;
801
802 if !norm {
803 return Ok(arr);
804 }
805 let s = arr.as_slice().unwrap().to_vec();
807 let centre_val = if m % 2 == 1 {
808 s[m / 2]
809 } else {
810 0.5 * (s[m / 2 - 1] + s[m / 2])
812 };
813 if centre_val == 0.0 {
814 return Ok(arr); }
816 let normed: Vec<f64> = s.into_iter().map(|v| v / centre_val).collect();
817 Array::from_vec(Ix1::new([m]), normed)
818}
819
820pub fn tukey(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
828 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
829 return Err(FerrayError::invalid_value(format!(
830 "tukey: alpha must be in [0, 1], got {alpha}"
831 )));
832 }
833 if m == 0 {
834 return Array::from_vec(Ix1::new([0]), vec![]);
835 }
836 if m == 1 {
837 return Array::from_vec(Ix1::new([1]), vec![1.0]);
838 }
839 if alpha == 0.0 {
840 return Array::from_vec(Ix1::new([m]), vec![1.0; m]);
841 }
842 let denom = (m - 1) as f64;
843 let width = alpha * denom / 2.0;
844 gen_window(m, |n| {
845 let nf = n as f64;
846 if nf < width {
847 0.5 * (1.0 + (PI * (nf / width - 1.0)).cos())
848 } else if nf <= denom - width {
849 1.0
850 } else {
851 0.5 * (1.0 + (PI * ((denom - nf) / width - 1.0)).cos())
852 }
853 })
854}
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859
860 #[test]
863 fn dpss_max_one_and_symmetric() {
864 let w = dpss(64, 3.0).unwrap();
865 let s = w.as_slice().unwrap();
866 let max = s.iter().copied().fold(f64::NEG_INFINITY, f64::max);
868 assert!((max - 1.0).abs() < 1e-12, "max = {max}");
869 for i in 0..s.len() / 2 {
871 let mirror = s.len() - 1 - i;
872 assert!(
873 (s[i] - s[mirror]).abs() < 1e-7,
874 "asymmetric at i={i}: {} vs {}",
875 s[i],
876 s[mirror]
877 );
878 }
879 }
880
881 #[test]
882 fn dpss_centre_is_positive_and_peak() {
883 let w = dpss(48, 2.5).unwrap();
887 let s = w.as_slice().unwrap();
888 let mid = s.len() / 2;
889 assert!(s[mid] > 0.0);
890 assert!((s[mid] - 1.0).abs() < 1e-12);
891 }
892
893 #[test]
894 fn dpss_endpoints_smaller_than_centre() {
895 let w = dpss(64, 3.0).unwrap();
898 let s = w.as_slice().unwrap();
899 let mid = s.len() / 2;
900 assert!(s[mid] > s[0]);
901 assert!(s[mid] > s[s.len() - 1]);
902 assert!(s[0].abs() < 0.1 * s[mid]);
903 }
904
905 #[test]
906 fn dpss_rejects_invalid_nw() {
907 assert!(dpss(32, -1.0).is_err());
908 assert!(dpss(32, 0.0).is_err());
909 assert!(dpss(32, f64::NAN).is_err());
910 assert!(dpss(8, 4.0).is_err());
912 assert!(dpss(8, 5.0).is_err());
913 }
914
915 #[test]
916 fn dpss_matches_scipy_within_relative_tolerance() {
917 let want_first_half: [f64; 8] = [
924 0.00710856, 0.03620714, 0.10884379, 0.24276927, 0.43733689, 0.6634221, 0.86701952,
925 0.98841699,
926 ];
927 let got = dpss(16, 3.0).unwrap();
928 let s = got.as_slice().unwrap();
929 for (i, (&g, &w)) in s[..8].iter().zip(want_first_half.iter()).enumerate() {
930 let tol = 0.05 * w.abs().max(1e-3);
935 assert!((g - w).abs() <= tol, "i={i}: got={g}, want={w}, tol={tol}");
936 }
937 }
938
939 #[test]
940 fn dpss_m0_m1_edge_cases() {
941 let z = dpss(0, 2.0).unwrap();
942 assert_eq!(z.shape(), &[0]);
943 let one = dpss(1, 0.5).unwrap();
944 assert_eq!(one.as_slice().unwrap(), &[1.0]);
945 }
946
947 #[test]
950 fn chebwin_centre_is_one_and_symmetric() {
951 let w = chebwin(11, 50.0).unwrap();
952 let s = w.as_slice().unwrap();
953 let mid = s.len() / 2;
954 assert!((s[mid] - 1.0).abs() < 1e-6, "centre = {}", s[mid]);
955 for i in 0..s.len() / 2 {
956 assert!(
957 (s[i] - s[s.len() - 1 - i]).abs() < 1e-9,
958 "asymmetric at i={i}: {} vs {}",
959 s[i],
960 s[s.len() - 1 - i]
961 );
962 }
963 }
964
965 #[test]
966 fn chebwin_even_length_symmetric() {
967 let w = chebwin(8, 40.0).unwrap();
968 let s = w.as_slice().unwrap();
969 for i in 0..s.len() / 2 {
970 assert!(
971 (s[i] - s[s.len() - 1 - i]).abs() < 1e-9,
972 "asymmetric at i={i}"
973 );
974 }
975 let max = s.iter().copied().fold(f64::NEG_INFINITY, f64::max);
977 assert!((max - 1.0).abs() < 1e-6);
978 }
979
980 #[test]
981 fn chebwin_endpoints_smaller_than_centre() {
982 let w = chebwin(31, 80.0).unwrap();
984 let s = w.as_slice().unwrap();
985 let mid = s.len() / 2;
986 assert!(s[mid] > s[0]);
987 assert!(s[mid] > s[s.len() - 1]);
988 }
989
990 #[test]
991 fn chebwin_rejects_invalid_attenuation() {
992 assert!(chebwin(16, -10.0).is_err());
993 assert!(chebwin(16, 0.0).is_err());
994 assert!(chebwin(16, f64::NAN).is_err());
995 }
996
997 #[test]
998 fn chebwin_matches_scipy_known_values() {
999 let want = [
1001 0.06228583, 0.20113857, 0.42847525, 0.69573494, 0.91497506, 1.0, 0.91497506,
1002 0.69573494, 0.42847525, 0.20113857, 0.06228583,
1003 ];
1004 let got = chebwin(11, 50.0).unwrap();
1005 let s = got.as_slice().unwrap();
1006 for (i, (&g, &w)) in s.iter().zip(want.iter()).enumerate() {
1007 assert!((g - w).abs() < 1e-6, "i={i}: got={g}, want={w}");
1008 }
1009 }
1010
1011 #[test]
1012 fn chebwin_matches_scipy_even_length() {
1013 let want = [
1015 0.14609713, 0.41790422, 0.75944595, 1.0, 1.0, 0.75944595, 0.41790422, 0.14609713,
1016 ];
1017 let got = chebwin(8, 40.0).unwrap();
1018 let s = got.as_slice().unwrap();
1019 for (i, (&g, &w)) in s.iter().zip(want.iter()).enumerate() {
1020 assert!((g - w).abs() < 1e-6, "i={i}: got={g}, want={w}");
1021 }
1022 }
1023
1024 #[test]
1025 fn chebwin_m0_m1_edge_cases() {
1026 let z = chebwin(0, 50.0).unwrap();
1027 assert_eq!(z.shape(), &[0]);
1028 let one = chebwin(1, 50.0).unwrap();
1029 assert_eq!(one.as_slice().unwrap(), &[1.0]);
1030 }
1031
1032 #[test]
1035 fn boxcar_all_ones() {
1036 let w = boxcar(8).unwrap();
1037 let s = w.as_slice().unwrap();
1038 for &v in s {
1039 assert!((v - 1.0).abs() < 1e-15);
1040 }
1041 }
1042
1043 #[test]
1044 fn triang_centre_is_max_and_endpoints_smaller() {
1045 let w = triang(7).unwrap();
1046 let s = w.as_slice().unwrap();
1047 for i in 0..s.len() / 2 {
1049 assert!((s[i] - s[s.len() - 1 - i]).abs() < 1e-12);
1050 }
1051 let mid = s.len() / 2;
1053 for i in 0..s.len() {
1054 if i != mid {
1055 assert!(s[i] <= s[mid] + 1e-12);
1056 }
1057 }
1058 }
1059
1060 #[test]
1061 fn bohman_zero_at_endpoints_and_one_at_centre() {
1062 let w = bohman(5).unwrap();
1063 let s = w.as_slice().unwrap();
1064 assert!(s[0].abs() < 1e-12);
1065 assert!(s[s.len() - 1].abs() < 1e-12);
1066 let mid = s.len() / 2;
1068 assert!((s[mid] - 1.0).abs() < 1e-6);
1069 }
1070
1071 #[test]
1072 fn flattop_centre_around_one_and_negative_lobes_present() {
1073 let w = flattop(11).unwrap();
1076 let s = w.as_slice().unwrap();
1077 let mid = s.len() / 2;
1078 assert!((s[mid] - 1.0).abs() < 0.01);
1079 let any_negative = s.iter().any(|&v| v < -0.001);
1082 assert!(any_negative, "flattop should have negative side lobes");
1083 }
1084
1085 #[test]
1086 fn lanczos_centre_is_one_and_endpoints_zero() {
1087 let w = lanczos(9).unwrap();
1088 let s = w.as_slice().unwrap();
1089 let mid = s.len() / 2;
1090 assert!((s[mid] - 1.0).abs() < 1e-12);
1091 assert!(s[0].abs() < 1e-12);
1092 assert!(s[s.len() - 1].abs() < 1e-12);
1093 }
1094
1095 #[test]
1096 fn small_m_edge_cases_handled() {
1097 for f in [triang as fn(usize) -> _, bohman, lanczos] {
1099 let z = f(0).unwrap();
1100 assert_eq!(z.shape(), &[0]);
1101 let one = f(1).unwrap();
1102 assert_eq!(one.shape(), &[1]);
1103 assert_eq!(one.as_slice().unwrap(), &[1.0]);
1104 }
1105 }
1106
1107 #[test]
1110 fn f32_windows_match_f64_within_f32_eps() {
1111 for m in [1usize, 5, 16, 64] {
1112 for (name, got, want) in [
1113 ("bartlett", bartlett_f32(m).unwrap(), bartlett(m).unwrap()),
1114 ("blackman", blackman_f32(m).unwrap(), blackman(m).unwrap()),
1115 ("hamming", hamming_f32(m).unwrap(), hamming(m).unwrap()),
1116 ("hanning", hanning_f32(m).unwrap(), hanning(m).unwrap()),
1117 ] {
1118 assert_eq!(got.shape(), want.shape(), "{name} m={m} shape");
1119 let g = got.as_slice().unwrap();
1120 let w = want.as_slice().unwrap();
1121 for (i, (&a, &b)) in g.iter().zip(w.iter()).enumerate() {
1122 let bf = b as f32;
1123 assert!(
1124 (a - bf).abs() < 1e-6,
1125 "{name} m={m} idx={i}: f32 {a} vs f64 {b}"
1126 );
1127 }
1128 }
1129 }
1130 }
1131
1132 #[test]
1133 fn kaiser_f32_matches_kaiser() {
1134 let m = 20;
1135 let beta = 8.6;
1136 let f32_arr = kaiser_f32(m, beta).unwrap();
1137 let f64_arr = kaiser(m, beta).unwrap();
1138 for (a, b) in f32_arr.iter().zip(f64_arr.iter()) {
1139 assert!((a - *b as f32).abs() < 1e-5);
1140 }
1141 }
1142
1143 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
1145 assert_eq!(
1146 actual.len(),
1147 expected.len(),
1148 "length mismatch: {} vs {}",
1149 actual.len(),
1150 expected.len()
1151 );
1152 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
1153 assert!(
1154 (a - e).abs() <= tol,
1155 "element {i}: {a} vs {e} (diff = {})",
1156 (a - e).abs()
1157 );
1158 }
1159 }
1160
1161 #[test]
1166 fn bartlett_m0() {
1167 let w = bartlett(0).unwrap();
1168 assert_eq!(w.shape(), &[0]);
1169 }
1170
1171 #[test]
1172 fn bartlett_m1() {
1173 let w = bartlett(1).unwrap();
1174 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1175 }
1176
1177 #[test]
1178 fn blackman_m0() {
1179 let w = blackman(0).unwrap();
1180 assert_eq!(w.shape(), &[0]);
1181 }
1182
1183 #[test]
1184 fn blackman_m1() {
1185 let w = blackman(1).unwrap();
1186 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1187 }
1188
1189 #[test]
1190 fn hamming_m0() {
1191 let w = hamming(0).unwrap();
1192 assert_eq!(w.shape(), &[0]);
1193 }
1194
1195 #[test]
1196 fn hamming_m1() {
1197 let w = hamming(1).unwrap();
1198 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1199 }
1200
1201 #[test]
1202 fn hanning_m0() {
1203 let w = hanning(0).unwrap();
1204 assert_eq!(w.shape(), &[0]);
1205 }
1206
1207 #[test]
1208 fn hanning_m1() {
1209 let w = hanning(1).unwrap();
1210 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1211 }
1212
1213 #[test]
1214 fn kaiser_m0() {
1215 let w = kaiser(0, 14.0).unwrap();
1216 assert_eq!(w.shape(), &[0]);
1217 }
1218
1219 #[test]
1220 fn kaiser_m1() {
1221 let w = kaiser(1, 14.0).unwrap();
1222 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1223 }
1224
1225 #[test]
1226 fn kaiser_negative_beta() {
1227 let pos = kaiser(5, 1.0).unwrap();
1229 let neg = kaiser(5, -1.0).unwrap();
1230 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
1231 }
1232
1233 #[test]
1234 fn kaiser_nan_beta() {
1235 assert!(kaiser(5, f64::NAN).is_err());
1236 }
1237
1238 #[test]
1243 fn bartlett_5_ac1() {
1244 let w = bartlett(5).unwrap();
1245 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
1246 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
1247 }
1248
1249 #[test]
1254 fn kaiser_5_14_ac2() {
1255 let w = kaiser(5, 14.0).unwrap();
1256 let s = w.as_slice().unwrap();
1257 assert_eq!(s.len(), 5);
1258 let expected: [f64; 5] = [
1260 7.72686684e-06,
1261 1.64932188e-01,
1262 1.0,
1263 1.64932188e-01,
1264 7.72686684e-06,
1265 ];
1266 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
1268 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
1269 assert!(
1270 (a - e).abs() <= tol,
1271 "kaiser element {i}: {a} vs {e} (diff = {})",
1272 (a - e).abs()
1273 );
1274 }
1275 }
1276
1277 #[test]
1283 fn blackman_5() {
1284 let w = blackman(5).unwrap();
1285 assert_eq!(w.shape(), &[5]);
1286 let s = w.as_slice().unwrap();
1287 let expected = [
1288 -1.38777878e-17,
1289 3.40000000e-01,
1290 1.00000000e+00,
1291 3.40000000e-01,
1292 -1.38777878e-17,
1293 ];
1294 assert_close(s, &expected, 1e-10);
1295 }
1296
1297 #[test]
1299 fn hamming_5() {
1300 let w = hamming(5).unwrap();
1301 assert_eq!(w.shape(), &[5]);
1302 let s = w.as_slice().unwrap();
1303 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
1304 assert_close(s, &expected, 1e-14);
1305 }
1306
1307 #[test]
1309 fn hanning_5() {
1310 let w = hanning(5).unwrap();
1311 assert_eq!(w.shape(), &[5]);
1312 let s = w.as_slice().unwrap();
1313 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
1314 assert_close(s, &expected, 1e-14);
1315 }
1316
1317 #[test]
1319 fn bartlett_12() {
1320 let w = bartlett(12).unwrap();
1321 assert_eq!(w.shape(), &[12]);
1322 let s = w.as_slice().unwrap();
1323 assert!((s[0] - 0.0).abs() < 1e-14);
1325 assert!((s[11] - 0.0).abs() < 1e-14);
1326 for i in 0..6 {
1328 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
1329 }
1330 }
1331
1332 #[test]
1334 fn all_windows_symmetric() {
1335 let m = 7;
1336 let windows: Vec<Array<f64, Ix1>> = vec![
1337 bartlett(m).unwrap(),
1338 blackman(m).unwrap(),
1339 hamming(m).unwrap(),
1340 hanning(m).unwrap(),
1341 kaiser(m, 5.0).unwrap(),
1342 ];
1343 for (idx, w) in windows.iter().enumerate() {
1344 let s = w.as_slice().unwrap();
1345 for i in 0..m / 2 {
1346 assert!(
1347 (s[i] - s[m - 1 - i]).abs() < 1e-12,
1348 "window {idx} not symmetric at {i}"
1349 );
1350 }
1351 }
1352 }
1353
1354 #[test]
1356 fn all_windows_peak_at_center() {
1357 let m = 9;
1358 let windows: Vec<Array<f64, Ix1>> = vec![
1359 bartlett(m).unwrap(),
1360 blackman(m).unwrap(),
1361 hamming(m).unwrap(),
1362 hanning(m).unwrap(),
1363 kaiser(m, 5.0).unwrap(),
1364 ];
1365 for (idx, w) in windows.iter().enumerate() {
1366 let s = w.as_slice().unwrap();
1367 let center = s[m / 2];
1368 assert!(
1369 (center - 1.0).abs() < 1e-10,
1370 "window {idx} center = {center}, expected 1.0"
1371 );
1372 }
1373 }
1374
1375 #[test]
1377 fn kaiser_beta_zero() {
1378 let w = kaiser(5, 0.0).unwrap();
1379 let s = w.as_slice().unwrap();
1380 for &v in s {
1381 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
1382 }
1383 }
1384
1385 #[test]
1386 fn bessel_i0_scalar_zero() {
1387 assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-6);
1388 }
1389
1390 #[test]
1391 fn bessel_i0_scalar_known() {
1392 assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-7);
1397 assert!((bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_443).abs() < 1e-5);
1399 let expected_i0_10 = 2_815.716_628_466_254;
1401 assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-5);
1402 }
1403
1404 #[test]
1407 fn bartlett_m2_is_zeros() {
1408 let w = bartlett(2).unwrap();
1412 assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
1413 }
1414
1415 #[test]
1416 fn hanning_m2_is_zeros() {
1417 let w = hanning(2).unwrap();
1421 let s = w.as_slice().unwrap();
1422 assert!(s[0].abs() < 1e-14);
1423 assert!(s[1].abs() < 1e-14);
1424 }
1425
1426 #[test]
1427 fn bartlett_even_length_is_symmetric() {
1428 for &m in &[4usize, 6, 8, 10] {
1431 let w = bartlett(m).unwrap();
1432 let s = w.as_slice().unwrap();
1433 for i in 0..m / 2 {
1434 assert!(
1435 (s[i] - s[m - 1 - i]).abs() < 1e-14,
1436 "bartlett({m}) not symmetric at {i}"
1437 );
1438 }
1439 }
1440 }
1441
1442 #[test]
1443 fn blackman_even_length_is_symmetric() {
1444 for &m in &[4usize, 6, 8, 10] {
1445 let w = blackman(m).unwrap();
1446 let s = w.as_slice().unwrap();
1447 for i in 0..m / 2 {
1448 assert!(
1449 (s[i] - s[m - 1 - i]).abs() < 1e-14,
1450 "blackman({m}) not symmetric at {i}"
1451 );
1452 }
1453 }
1454 }
1455
1456 #[test]
1457 fn kaiser_large_beta_errors() {
1458 assert!(kaiser(8, 800.0).is_err());
1461 assert!(kaiser(8, 1000.0).is_err());
1462 assert!(kaiser(8, 700.0).is_ok());
1464 }
1465
1466 fn close(a: f64, b: f64, tol: f64) -> bool {
1472 (a - b).abs() < tol
1473 }
1474
1475 #[test]
1478 fn cosine_length_and_endpoints() {
1479 let w = cosine(8).unwrap();
1482 let s = w.as_slice().unwrap();
1483 assert_eq!(s.len(), 8);
1484 for i in 0..4 {
1486 assert!(close(s[i], s[7 - i], 1e-14));
1487 }
1488 }
1489
1490 #[test]
1491 fn cosine_m1_and_m0() {
1492 assert_eq!(cosine(0).unwrap().shape(), &[0]);
1493 assert_eq!(cosine(1).unwrap().as_slice().unwrap(), &[1.0]);
1494 }
1495
1496 #[test]
1499 fn exponential_centred_default() {
1500 let w = exponential(8, None, 1.0).unwrap();
1503 let s = w.as_slice().unwrap();
1504 for i in 0..4 {
1506 assert!(close(s[i], s[7 - i], 1e-14));
1507 }
1508 let centre_max = s[3].max(s[4]);
1510 for &v in s {
1511 assert!(v <= centre_max + 1e-14);
1512 }
1513 }
1514
1515 #[test]
1516 fn exponential_rejects_nonpositive_tau() {
1517 assert!(exponential(8, None, 0.0).is_err());
1518 assert!(exponential(8, None, -1.0).is_err());
1519 assert!(exponential(8, None, f64::NAN).is_err());
1520 }
1521
1522 #[test]
1525 fn gaussian_centre_is_one() {
1526 let w = gaussian(11, 2.0).unwrap();
1528 let s = w.as_slice().unwrap();
1529 assert!(close(s[5], 1.0, 1e-14));
1530 }
1531
1532 #[test]
1533 fn gaussian_known_value() {
1534 let w = gaussian(7, 1.0).unwrap();
1536 let s = w.as_slice().unwrap();
1537 assert!(close(s[4], (-0.5_f64).exp(), 1e-14));
1538 }
1539
1540 #[test]
1541 fn gaussian_rejects_nonpositive_std() {
1542 assert!(gaussian(8, 0.0).is_err());
1543 assert!(gaussian(8, -1.0).is_err());
1544 }
1545
1546 #[test]
1549 fn general_cosine_with_hann_coeffs_matches_hann() {
1550 let m = 9;
1552 let gc = general_cosine(m, &[0.5, 0.5]).unwrap();
1553 let hn = hanning(m).unwrap();
1554 for i in 0..m {
1555 assert!(close(
1556 gc.as_slice().unwrap()[i],
1557 hn.as_slice().unwrap()[i],
1558 1e-14,
1559 ));
1560 }
1561 }
1562
1563 #[test]
1564 fn general_cosine_with_blackman_coeffs_matches_blackman() {
1565 let m = 9;
1567 let gc = general_cosine(m, &[0.42, 0.5, 0.08]).unwrap();
1568 let bk = blackman(m).unwrap();
1569 for i in 0..m {
1570 assert!(close(
1571 gc.as_slice().unwrap()[i],
1572 bk.as_slice().unwrap()[i],
1573 1e-12,
1574 ));
1575 }
1576 }
1577
1578 #[test]
1579 fn general_cosine_rejects_empty_coeffs() {
1580 assert!(general_cosine(8, &[]).is_err());
1581 }
1582
1583 #[test]
1586 fn general_hamming_alpha_half_matches_hann() {
1587 let m = 9;
1588 let gh = general_hamming(m, 0.5).unwrap();
1589 let hn = hanning(m).unwrap();
1590 for i in 0..m {
1591 assert!(close(
1592 gh.as_slice().unwrap()[i],
1593 hn.as_slice().unwrap()[i],
1594 1e-14,
1595 ));
1596 }
1597 }
1598
1599 #[test]
1600 fn general_hamming_alpha_054_matches_hamming() {
1601 let m = 9;
1602 let gh = general_hamming(m, 0.54).unwrap();
1603 let hm = hamming(m).unwrap();
1604 for i in 0..m {
1605 assert!(close(
1606 gh.as_slice().unwrap()[i],
1607 hm.as_slice().unwrap()[i],
1608 1e-14,
1609 ));
1610 }
1611 }
1612
1613 #[test]
1616 fn nuttall_endpoints_are_small() {
1617 let w = nuttall(64).unwrap();
1620 let s = w.as_slice().unwrap();
1621 assert!(s[0].abs() < 1e-2);
1622 assert!(s[s.len() - 1].abs() < 1e-2);
1623 }
1624
1625 #[test]
1626 fn nuttall_is_symmetric() {
1627 let m = 33;
1628 let w = nuttall(m).unwrap();
1629 let s = w.as_slice().unwrap();
1630 for i in 0..m / 2 {
1631 assert!(close(s[i], s[m - 1 - i], 1e-14));
1632 }
1633 }
1634
1635 #[test]
1638 fn parzen_endpoints_are_zero() {
1639 let w = parzen(16).unwrap();
1640 let s = w.as_slice().unwrap();
1641 assert!(s[0].abs() < 1e-2);
1643 assert!(s[s.len() - 1].abs() < 1e-2);
1644 }
1645
1646 #[test]
1647 fn parzen_centre_is_one() {
1648 let w = parzen(13).unwrap();
1650 let s = w.as_slice().unwrap();
1651 assert!(close(s[6], 1.0, 1e-14));
1652 }
1653
1654 #[test]
1655 fn parzen_is_symmetric() {
1656 let m = 21;
1657 let w = parzen(m).unwrap();
1658 let s = w.as_slice().unwrap();
1659 for i in 0..m / 2 {
1660 assert!(close(s[i], s[m - 1 - i], 1e-14));
1661 }
1662 }
1663
1664 #[test]
1667 fn taylor_default_normalised_centre_is_one() {
1668 let w = taylor(33, 4, 30.0, true).unwrap();
1670 let s = w.as_slice().unwrap();
1671 assert!(close(s[16], 1.0, 1e-12));
1672 }
1673
1674 #[test]
1675 fn taylor_is_symmetric() {
1676 let m = 33;
1677 let w = taylor(m, 4, 30.0, true).unwrap();
1678 let s = w.as_slice().unwrap();
1679 for i in 0..m / 2 {
1680 assert!(close(s[i], s[m - 1 - i], 1e-12));
1681 }
1682 }
1683
1684 #[test]
1685 fn taylor_rejects_nbar_zero() {
1686 assert!(taylor(8, 0, 30.0, true).is_err());
1687 assert!(taylor(8, 4, f64::NAN, true).is_err());
1688 }
1689
1690 #[test]
1693 fn tukey_alpha_zero_is_rectangular() {
1694 let w = tukey(8, 0.0).unwrap();
1695 for &v in w.as_slice().unwrap() {
1696 assert!(close(v, 1.0, 1e-14));
1697 }
1698 }
1699
1700 #[test]
1701 fn tukey_alpha_one_matches_hann() {
1702 let m = 9;
1704 let tk = tukey(m, 1.0).unwrap();
1705 let hn = hanning(m).unwrap();
1706 for i in 0..m {
1707 assert!(close(
1708 tk.as_slice().unwrap()[i],
1709 hn.as_slice().unwrap()[i],
1710 1e-12,
1711 ));
1712 }
1713 }
1714
1715 #[test]
1716 fn tukey_centre_is_one() {
1717 let m = 21;
1718 let w = tukey(m, 0.5).unwrap();
1719 assert!(close(w.as_slice().unwrap()[m / 2], 1.0, 1e-14));
1721 }
1722
1723 #[test]
1724 fn tukey_rejects_invalid_alpha() {
1725 assert!(tukey(8, -0.1).is_err());
1726 assert!(tukey(8, 1.1).is_err());
1727 assert!(tukey(8, f64::NAN).is_err());
1728 }
1729}
1730
1731#[cfg(test)]
1732mod debug_test_removed {
1733 }