1#![allow(clippy::unreadable_literal)]
48
49use ferray_core::Array;
50use ferray_core::dimension::Ix1;
51use ferray_core::error::{FerrayError, FerrayResult};
52
53use std::f64::consts::PI;
54
55use ferray_ufunc::ops::special::bessel_i0_scalar;
61
62#[inline]
67fn gen_window<F: FnMut(usize) -> f64>(m: usize, mut f: F) -> FerrayResult<Array<f64, Ix1>> {
68 if m == 0 {
69 return Array::from_vec(Ix1::new([0]), vec![]);
70 }
71 if m == 1 {
72 return Array::from_vec(Ix1::new([1]), vec![1.0]);
73 }
74 let mut data = Vec::with_capacity(m);
75 for n in 0..m {
76 data.push(f(n));
77 }
78 Array::from_vec(Ix1::new([m]), data)
79}
80
81fn cast_to_f32(arr: &Array<f64, Ix1>) -> FerrayResult<Array<f32, Ix1>> {
86 let data: Vec<f32> = arr.iter().map(|&x| x as f32).collect();
87 Array::<f32, Ix1>::from_vec(Ix1::new([data.len()]), data)
88}
89
90pub fn bartlett_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
95 cast_to_f32(&bartlett(m)?)
96}
97
98pub fn blackman_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
103 cast_to_f32(&blackman(m)?)
104}
105
106pub fn hamming_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
111 cast_to_f32(&hamming(m)?)
112}
113
114pub fn hanning_f32(m: usize) -> FerrayResult<Array<f32, Ix1>> {
119 cast_to_f32(&hanning(m)?)
120}
121
122pub fn kaiser_f32(m: usize, beta: f64) -> FerrayResult<Array<f32, Ix1>> {
127 cast_to_f32(&kaiser(m, beta)?)
128}
129
130pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
140 let half = (m.saturating_sub(1)) as f64 / 2.0;
141 gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
142}
143
144pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
154 let denom = (m.saturating_sub(1)) as f64;
155 gen_window(m, |n| {
156 let x = n as f64;
157 0.08f64.mul_add(
158 (4.0 * PI * x / denom).cos(),
159 0.5f64.mul_add(-(2.0 * PI * x / denom).cos(), 0.42),
160 )
161 })
162}
163
164pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
174 let denom = (m.saturating_sub(1)) as f64;
175 gen_window(m, |n| {
176 0.46f64.mul_add(-(2.0 * PI * n as f64 / denom).cos(), 0.54)
177 })
178}
179
180pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
190 let denom = (m.saturating_sub(1)) as f64;
191 gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
192}
193
194pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
218 if beta.is_nan() {
219 return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
220 }
221 let beta = beta.abs();
224 let i0_beta = bessel_i0_scalar::<f64>(beta);
229 let alpha = (m.saturating_sub(1)) as f64 / 2.0;
230 gen_window(m, |n| {
231 let t = (n as f64 - alpha) / alpha;
232 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
233 bessel_i0_scalar::<f64>(arg) / i0_beta
234 })
235}
236
237pub fn cosine(m: usize) -> FerrayResult<Array<f64, Ix1>> {
253 if m == 0 {
254 return Array::from_vec(Ix1::new([0]), vec![]);
255 }
256 if m == 1 {
257 return Array::from_vec(Ix1::new([1]), vec![1.0]);
258 }
259 let mf = m as f64;
260 gen_window(m, |n| (PI * (n as f64 + 0.5) / mf).sin())
261}
262
263pub fn exponential(m: usize, center: Option<f64>, tau: f64) -> FerrayResult<Array<f64, Ix1>> {
271 if !tau.is_finite() || tau <= 0.0 {
272 return Err(FerrayError::invalid_value(format!(
273 "exponential: tau must be positive and finite, got {tau}"
274 )));
275 }
276 let centre = center.unwrap_or((m.saturating_sub(1)) as f64 / 2.0);
277 gen_window(m, |n| (-((n as f64) - centre).abs() / tau).exp())
278}
279
280pub fn gaussian(m: usize, std: f64) -> FerrayResult<Array<f64, Ix1>> {
287 if !std.is_finite() || std <= 0.0 {
288 return Err(FerrayError::invalid_value(format!(
289 "gaussian: std must be positive and finite, got {std}"
290 )));
291 }
292 let centre = (m.saturating_sub(1)) as f64 / 2.0;
293 gen_window(m, |n| {
294 let z = ((n as f64) - centre) / std;
295 (-0.5 * z * z).exp()
296 })
297}
298
299pub fn general_cosine(m: usize, coeffs: &[f64]) -> FerrayResult<Array<f64, Ix1>> {
307 if coeffs.is_empty() {
308 return Err(FerrayError::invalid_value(
309 "general_cosine: coeffs must not be empty",
310 ));
311 }
312 let denom = (m.saturating_sub(1)) as f64;
313 let coeffs = coeffs.to_vec();
314 gen_window(m, |n| {
315 let nf = n as f64;
316 let mut sum = 0.0_f64;
317 for (k, &a) in coeffs.iter().enumerate() {
318 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
319 sum += sign * a * (2.0 * PI * (k as f64) * nf / denom).cos();
320 }
321 sum
322 })
323}
324
325pub fn general_hamming(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
333 if !alpha.is_finite() {
334 return Err(FerrayError::invalid_value(format!(
335 "general_hamming: alpha must be finite, got {alpha}"
336 )));
337 }
338 let denom = (m.saturating_sub(1)) as f64;
339 gen_window(m, |n| {
340 alpha - (1.0 - alpha) * (2.0 * PI * (n as f64) / denom).cos()
341 })
342}
343
344pub fn nuttall(m: usize) -> FerrayResult<Array<f64, Ix1>> {
352 general_cosine(m, &[0.3635819, 0.4891775, 0.1365995, 0.0106411])
353}
354
355pub fn parzen(m: usize) -> FerrayResult<Array<f64, Ix1>> {
359 if m == 0 {
360 return Array::from_vec(Ix1::new([0]), vec![]);
361 }
362 if m == 1 {
363 return Array::from_vec(Ix1::new([1]), vec![1.0]);
364 }
365 let half = m as f64 / 2.0;
368 gen_window(m, |n| {
369 let x = (n as f64) - (m as f64 - 1.0) / 2.0;
370 let r = x.abs() / half;
371 if r <= 0.5 {
372 1.0 - 6.0 * r * r + 6.0 * r * r * r
373 } else if r <= 1.0 {
374 let one_minus_r = 1.0 - r;
375 2.0 * one_minus_r * one_minus_r * one_minus_r
376 } else {
377 0.0
378 }
379 })
380}
381
382pub fn chebwin(m: usize, at: f64) -> FerrayResult<Array<f64, Ix1>> {
398 if !at.is_finite() || at <= 0.0 {
399 return Err(FerrayError::invalid_value(format!(
400 "chebwin: at must be a positive finite dB attenuation, got {at}"
401 )));
402 }
403 if m == 0 {
404 return Array::from_vec(Ix1::new([0]), vec![]);
405 }
406 if m == 1 {
407 return Array::from_vec(Ix1::new([1]), vec![1.0]);
408 }
409
410 let order = (m - 1) as f64;
411 let r = 10.0_f64.powf(at / 20.0);
412 let beta = (r.acosh() / order).cosh();
413
414 let mut spectrum = Vec::with_capacity(m);
416 for k in 0..m {
417 let x = beta * (PI * k as f64 / m as f64).cos();
418 spectrum.push(chebyshev_t_extended(x, order));
419 }
420
421 let mf = m as f64;
433 let half_dft = |idx: usize| -> f64 {
434 if m % 2 == 1 {
435 spectrum
436 .iter()
437 .enumerate()
438 .map(|(k, &pk)| pk * (-2.0 * PI * k as f64 * idx as f64 / mf).cos())
439 .sum()
440 } else {
441 spectrum
445 .iter()
446 .enumerate()
447 .map(|(k, &pk)| {
448 let phase = PI * k as f64 * (1.0 - 2.0 * idx as f64) / mf;
449 pk * phase.cos()
450 })
451 .sum()
452 }
453 };
454
455 let half_len = m / 2 + 1;
456 let mut half: Vec<f64> = (0..half_len).map(half_dft).collect();
457 let denom = if m % 2 == 1 { half[0] } else { half[1] };
458 if denom != 0.0 {
459 for v in &mut half {
460 *v /= denom;
461 }
462 }
463
464 let mut w = Vec::with_capacity(m);
465 if m % 2 == 1 {
466 for &v in half.iter().skip(1).rev() {
468 w.push(v);
469 }
470 w.extend_from_slice(&half);
471 } else {
472 let n = m / 2 + 1;
475 for &v in half[1..n].iter().rev() {
476 w.push(v);
477 }
478 w.extend_from_slice(&half[1..n]);
479 }
480
481 Array::from_vec(Ix1::new([m]), w)
482}
483
484pub fn dpss(m: usize, nw: f64) -> FerrayResult<Array<f64, Ix1>> {
519 if !nw.is_finite() || nw <= 0.0 {
520 return Err(FerrayError::invalid_value(format!(
521 "dpss: nw must be a positive finite half bandwidth, got {nw}"
522 )));
523 }
524 if m == 0 {
525 return Array::from_vec(Ix1::new([0]), vec![]);
526 }
527 if m == 1 {
528 return Array::from_vec(Ix1::new([1]), vec![1.0]);
529 }
530 if nw >= (m as f64) / 2.0 {
531 return Err(FerrayError::invalid_value(format!(
532 "dpss: nw = {nw} must be < M/2 = {} for a non-degenerate band",
533 (m as f64) / 2.0
534 )));
535 }
536
537 let mf = m as f64;
538 let cos_w = (2.0 * PI * nw / mf).cos();
539 let diag: Vec<f64> = (0..m)
541 .map(|i| {
542 let centre = (mf - 1.0) / 2.0 - i as f64;
543 centre * centre * cos_w
544 })
545 .collect();
546 let off: Vec<f64> = (0..m.saturating_sub(1))
548 .map(|i| (i as f64 + 1.0) * (mf - i as f64 - 1.0) / 2.0)
549 .collect();
550
551 let mut v: Vec<f64> = (0..m)
554 .map(|i| {
555 let centred = (i as f64 - (mf - 1.0) / 2.0) / mf;
556 (-2.0 * centred * centred).exp()
557 })
558 .collect();
559 let n0 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
560 for x in &mut v {
561 *x /= n0.max(f64::MIN_POSITIVE);
562 }
563
564 let apply_t = |x: &[f64]| -> Vec<f64> {
566 let mut y = vec![0.0_f64; m];
567 for i in 0..m {
568 y[i] = diag[i] * x[i];
569 if i > 0 {
570 y[i] += off[i - 1] * x[i - 1];
571 }
572 if i + 1 < m {
573 y[i] += off[i] * x[i + 1];
574 }
575 }
576 y
577 };
578
579 for _ in 0..50 {
582 let mut next = apply_t(&v);
583 let norm = next.iter().map(|x| x * x).sum::<f64>().sqrt();
584 if norm < f64::MIN_POSITIVE {
585 return Err(FerrayError::invalid_value(
586 "dpss: power iteration produced a zero vector",
587 ));
588 }
589 for x in &mut next {
590 *x /= norm;
591 }
592 v = next;
593 }
594
595 let tv0 = apply_t(&v);
597 let mut sigma: f64 = v.iter().zip(tv0.iter()).map(|(a, b)| a * b).sum();
598
599 for _ in 0..50 {
604 let mut d_shift: Vec<f64> = diag.iter().map(|d| d - sigma).collect();
606 let sup: Vec<f64> = off.clone();
607 let mut rhs: Vec<f64> = v.clone();
608
609 let mut singular = false;
611 for i in 1..m {
612 if d_shift[i - 1].abs() < 1e-30 {
613 singular = true;
616 break;
617 }
618 let factor = off[i - 1] / d_shift[i - 1];
619 d_shift[i] -= factor * sup[i - 1];
620 rhs[i] -= factor * rhs[i - 1];
621 }
622 if singular {
623 break;
624 }
625 if d_shift[m - 1].abs() < 1e-30 {
626 break;
627 }
628
629 let mut w = vec![0.0_f64; m];
631 w[m - 1] = rhs[m - 1] / d_shift[m - 1];
632 for i in (0..m - 1).rev() {
633 w[i] = (rhs[i] - sup[i] * w[i + 1]) / d_shift[i];
634 }
635
636 let norm = w.iter().map(|x| x * x).sum::<f64>().sqrt();
637 if norm < f64::MIN_POSITIVE {
638 break;
639 }
640 for x in &mut w {
641 *x /= norm;
642 }
643
644 let tw = apply_t(&w);
645 let new_sigma: f64 = w.iter().zip(tw.iter()).map(|(a, b)| a * b).sum();
646
647 let resid: f64 = tw
649 .iter()
650 .zip(w.iter())
651 .map(|(t, x)| {
652 let r = t - new_sigma * x;
653 r * r
654 })
655 .sum::<f64>()
656 .sqrt();
657
658 v = w;
659 sigma = new_sigma;
660 if resid < 1e-14 {
661 break;
662 }
663 }
664 let _ = sigma;
668
669 let centre_val = if m % 2 == 1 {
672 v[m / 2]
673 } else {
674 v[m / 2 - 1] + v[m / 2]
675 };
676 if centre_val < 0.0 {
677 for x in &mut v {
678 *x = -*x;
679 }
680 }
681
682 let peak = v.iter().copied().fold(f64::NEG_INFINITY, f64::max);
687 if peak > 0.0 {
688 for x in &mut v {
689 *x /= peak;
690 }
691 }
692
693 Array::from_vec(Ix1::new([m]), v)
694}
695
696fn chebyshev_t_extended(x: f64, n: f64) -> f64 {
701 if x.abs() <= 1.0 {
702 (n * x.acos()).cos()
703 } else {
704 let mag = (n * x.abs().acosh()).cosh();
705 if x < 0.0 && (n as i64) % 2 != 0 {
706 -mag
707 } else {
708 mag
709 }
710 }
711}
712
713pub fn boxcar(m: usize) -> FerrayResult<Array<f64, Ix1>> {
720 gen_window(m, |_| 1.0)
721}
722
723pub fn triang(m: usize) -> FerrayResult<Array<f64, Ix1>> {
732 if m == 0 {
733 return Array::from_vec(Ix1::new([0]), vec![]);
734 }
735 if m == 1 {
736 return Array::from_vec(Ix1::new([1]), vec![1.0]);
737 }
738 let mf = m as f64;
739 let centre = (mf - 1.0) / 2.0;
740 if m % 2 == 0 {
741 gen_window(m, |n| 1.0 - (2.0 * n as f64 - centre - centre).abs() / mf)
743 } else {
744 gen_window(m, |n| {
746 1.0 - (2.0 * n as f64 - centre - centre).abs() / (mf + 1.0)
747 })
748 }
749}
750
751pub fn bohman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
760 if m == 0 {
761 return Array::from_vec(Ix1::new([0]), vec![]);
762 }
763 if m == 1 {
764 return Array::from_vec(Ix1::new([1]), vec![1.0]);
765 }
766 let mf = (m - 1) as f64;
768 let mut data = Vec::with_capacity(m);
769 for n in 0..m {
770 if n == 0 || n == m - 1 {
771 data.push(0.0);
772 continue;
773 }
774 let r = (2.0 * n as f64 / mf - 1.0).abs();
775 let v = (1.0 - r) * (PI * r).cos() + (PI * r).sin() / PI;
776 data.push(v);
777 }
778 Array::from_vec(Ix1::new([m]), data)
779}
780
781pub fn flattop(m: usize) -> FerrayResult<Array<f64, Ix1>> {
790 const COEFFS: [f64; 5] = [
793 0.215_578_95,
794 0.416_631_58,
795 0.277_263_158,
796 0.083_578_947,
797 0.006_947_368,
798 ];
799 if m == 0 {
800 return Array::from_vec(Ix1::new([0]), vec![]);
801 }
802 let denom = (m - 1).max(1) as f64;
803 gen_window(m, |n| {
804 let phase = 2.0 * PI * n as f64 / denom;
805 let mut acc = COEFFS[0];
806 for (k, &c) in COEFFS.iter().enumerate().skip(1) {
807 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
808 acc += sign * c * (k as f64 * phase).cos();
809 }
810 acc
811 })
812}
813
814pub fn lanczos(m: usize) -> FerrayResult<Array<f64, Ix1>> {
822 if m == 0 {
823 return Array::from_vec(Ix1::new([0]), vec![]);
824 }
825 if m == 1 {
826 return Array::from_vec(Ix1::new([1]), vec![1.0]);
827 }
828 let denom = (m - 1) as f64;
829 gen_window(m, |n| {
830 let x = 2.0 * n as f64 / denom - 1.0;
831 if x.abs() < f64::EPSILON {
832 1.0
833 } else {
834 let pi_x = PI * x;
835 pi_x.sin() / pi_x
836 }
837 })
838}
839
840pub fn taylor(m: usize, nbar: usize, sll: f64, norm: bool) -> FerrayResult<Array<f64, Ix1>> {
855 if m == 0 {
856 return Array::from_vec(Ix1::new([0]), vec![]);
857 }
858 if m == 1 {
859 return Array::from_vec(Ix1::new([1]), vec![1.0]);
860 }
861 if nbar == 0 {
862 return Err(FerrayError::invalid_value("taylor: nbar must be >= 1"));
863 }
864 if !sll.is_finite() {
865 return Err(FerrayError::invalid_value("taylor: sll must be finite"));
866 }
867 let r = 10.0_f64.powf(sll / 20.0);
869 let b = r.acosh() / PI;
870 let nbar_f = nbar as f64;
871 let sigma2 = (nbar_f * nbar_f) / (b * b + (nbar_f - 0.5) * (nbar_f - 0.5));
874
875 let mut f_coeffs = Vec::with_capacity(nbar.saturating_sub(1));
877 for mm in 1..nbar {
878 let mmf = mm as f64;
879 let mut num = 1.0_f64;
882 for n in 1..nbar {
883 let nf = n as f64;
884 num *= 1.0 - mmf * mmf / (sigma2 * (b * b + (nf - 0.5) * (nf - 0.5)));
885 }
886 let sign = if mm % 2 == 0 { -1.0 } else { 1.0 };
887 let mut den = 1.0_f64;
888 for n in 1..nbar {
889 if n == mm {
890 continue;
891 }
892 let nf = n as f64;
893 den *= 1.0 - mmf * mmf / (nf * nf);
894 }
895 f_coeffs.push(0.5 * sign * num / den);
899 }
900
901 let denom = (m - 1) as f64;
902 let arr = gen_window(m, |n| {
903 let xn = (n as f64) - denom / 2.0;
904 let mut w = 1.0_f64;
905 for (idx, &fk) in f_coeffs.iter().enumerate() {
906 let kk = (idx + 1) as f64;
907 w += 2.0 * fk * (2.0 * PI * kk * xn / m as f64).cos();
908 }
909 w
910 })?;
911
912 if !norm {
913 return Ok(arr);
914 }
915 let centre_val: f64 = 1.0 + 2.0 * f_coeffs.iter().sum::<f64>();
922 if centre_val == 0.0 {
923 return Ok(arr); }
925 let normed: Vec<f64> = arr
926 .as_slice()
927 .unwrap()
928 .iter()
929 .map(|&v| v / centre_val)
930 .collect();
931 Array::from_vec(Ix1::new([m]), normed)
932}
933
934pub fn tukey(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
942 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
943 return Err(FerrayError::invalid_value(format!(
944 "tukey: alpha must be in [0, 1], got {alpha}"
945 )));
946 }
947 if m == 0 {
948 return Array::from_vec(Ix1::new([0]), vec![]);
949 }
950 if m == 1 {
951 return Array::from_vec(Ix1::new([1]), vec![1.0]);
952 }
953 if alpha == 0.0 {
954 return Array::from_vec(Ix1::new([m]), vec![1.0; m]);
955 }
956 let denom = (m - 1) as f64;
957 let width = alpha * denom / 2.0;
958 gen_window(m, |n| {
959 let nf = n as f64;
960 if nf < width {
961 0.5 * (1.0 + (PI * (nf / width - 1.0)).cos())
962 } else if nf <= denom - width {
963 1.0
964 } else {
965 0.5 * (1.0 + (PI * ((denom - nf) / width - 1.0)).cos())
966 }
967 })
968}
969
970#[cfg(test)]
971mod tests {
972 use super::*;
973
974 #[test]
977 fn dpss_max_one_and_symmetric() {
978 let w = dpss(64, 3.0).unwrap();
979 let s = w.as_slice().unwrap();
980 let max = s.iter().copied().fold(f64::NEG_INFINITY, f64::max);
982 assert!((max - 1.0).abs() < 1e-12, "max = {max}");
983 for i in 0..s.len() / 2 {
985 let mirror = s.len() - 1 - i;
986 assert!(
987 (s[i] - s[mirror]).abs() < 1e-7,
988 "asymmetric at i={i}: {} vs {}",
989 s[i],
990 s[mirror]
991 );
992 }
993 }
994
995 #[test]
996 fn dpss_centre_is_positive_and_peak() {
997 let w = dpss(48, 2.5).unwrap();
1001 let s = w.as_slice().unwrap();
1002 let mid = s.len() / 2;
1003 assert!(s[mid] > 0.0);
1004 assert!((s[mid] - 1.0).abs() < 1e-12);
1005 }
1006
1007 #[test]
1008 fn dpss_endpoints_smaller_than_centre() {
1009 let w = dpss(64, 3.0).unwrap();
1012 let s = w.as_slice().unwrap();
1013 let mid = s.len() / 2;
1014 assert!(s[mid] > s[0]);
1015 assert!(s[mid] > s[s.len() - 1]);
1016 assert!(s[0].abs() < 0.1 * s[mid]);
1017 }
1018
1019 #[test]
1020 fn dpss_rejects_invalid_nw() {
1021 assert!(dpss(32, -1.0).is_err());
1022 assert!(dpss(32, 0.0).is_err());
1023 assert!(dpss(32, f64::NAN).is_err());
1024 assert!(dpss(8, 4.0).is_err());
1026 assert!(dpss(8, 5.0).is_err());
1027 }
1028
1029 #[test]
1030 fn dpss_matches_scipy_within_relative_tolerance() {
1031 let want_first_half: [f64; 8] = [
1038 0.00710856, 0.03620714, 0.10884379, 0.24276927, 0.43733689, 0.6634221, 0.86701952,
1039 0.98841699,
1040 ];
1041 let got = dpss(16, 3.0).unwrap();
1042 let s = got.as_slice().unwrap();
1043 for (i, (&g, &w)) in s[..8].iter().zip(want_first_half.iter()).enumerate() {
1044 let tol = 0.05 * w.abs().max(1e-3);
1049 assert!((g - w).abs() <= tol, "i={i}: got={g}, want={w}, tol={tol}");
1050 }
1051 }
1052
1053 #[test]
1054 fn dpss_m0_m1_edge_cases() {
1055 let z = dpss(0, 2.0).unwrap();
1056 assert_eq!(z.shape(), &[0]);
1057 let one = dpss(1, 0.5).unwrap();
1058 assert_eq!(one.as_slice().unwrap(), &[1.0]);
1059 }
1060
1061 #[test]
1064 fn chebwin_centre_is_one_and_symmetric() {
1065 let w = chebwin(11, 50.0).unwrap();
1066 let s = w.as_slice().unwrap();
1067 let mid = s.len() / 2;
1068 assert!((s[mid] - 1.0).abs() < 1e-6, "centre = {}", s[mid]);
1069 for i in 0..s.len() / 2 {
1070 assert!(
1071 (s[i] - s[s.len() - 1 - i]).abs() < 1e-9,
1072 "asymmetric at i={i}: {} vs {}",
1073 s[i],
1074 s[s.len() - 1 - i]
1075 );
1076 }
1077 }
1078
1079 #[test]
1080 fn chebwin_even_length_symmetric() {
1081 let w = chebwin(8, 40.0).unwrap();
1082 let s = w.as_slice().unwrap();
1083 for i in 0..s.len() / 2 {
1084 assert!(
1085 (s[i] - s[s.len() - 1 - i]).abs() < 1e-9,
1086 "asymmetric at i={i}"
1087 );
1088 }
1089 let max = s.iter().copied().fold(f64::NEG_INFINITY, f64::max);
1091 assert!((max - 1.0).abs() < 1e-6);
1092 }
1093
1094 #[test]
1095 fn chebwin_endpoints_smaller_than_centre() {
1096 let w = chebwin(31, 80.0).unwrap();
1098 let s = w.as_slice().unwrap();
1099 let mid = s.len() / 2;
1100 assert!(s[mid] > s[0]);
1101 assert!(s[mid] > s[s.len() - 1]);
1102 }
1103
1104 #[test]
1105 fn chebwin_rejects_invalid_attenuation() {
1106 assert!(chebwin(16, -10.0).is_err());
1107 assert!(chebwin(16, 0.0).is_err());
1108 assert!(chebwin(16, f64::NAN).is_err());
1109 }
1110
1111 #[test]
1112 fn chebwin_matches_scipy_known_values() {
1113 let want = [
1115 0.06228583, 0.20113857, 0.42847525, 0.69573494, 0.91497506, 1.0, 0.91497506,
1116 0.69573494, 0.42847525, 0.20113857, 0.06228583,
1117 ];
1118 let got = chebwin(11, 50.0).unwrap();
1119 let s = got.as_slice().unwrap();
1120 for (i, (&g, &w)) in s.iter().zip(want.iter()).enumerate() {
1121 assert!((g - w).abs() < 1e-6, "i={i}: got={g}, want={w}");
1122 }
1123 }
1124
1125 #[test]
1126 fn chebwin_matches_scipy_even_length() {
1127 let want = [
1129 0.14609713, 0.41790422, 0.75944595, 1.0, 1.0, 0.75944595, 0.41790422, 0.14609713,
1130 ];
1131 let got = chebwin(8, 40.0).unwrap();
1132 let s = got.as_slice().unwrap();
1133 for (i, (&g, &w)) in s.iter().zip(want.iter()).enumerate() {
1134 assert!((g - w).abs() < 1e-6, "i={i}: got={g}, want={w}");
1135 }
1136 }
1137
1138 #[test]
1139 fn chebwin_m0_m1_edge_cases() {
1140 let z = chebwin(0, 50.0).unwrap();
1141 assert_eq!(z.shape(), &[0]);
1142 let one = chebwin(1, 50.0).unwrap();
1143 assert_eq!(one.as_slice().unwrap(), &[1.0]);
1144 }
1145
1146 #[test]
1149 fn boxcar_all_ones() {
1150 let w = boxcar(8).unwrap();
1151 let s = w.as_slice().unwrap();
1152 for &v in s {
1153 assert!((v - 1.0).abs() < 1e-15);
1154 }
1155 }
1156
1157 #[test]
1158 fn triang_centre_is_max_and_endpoints_smaller() {
1159 let w = triang(7).unwrap();
1160 let s = w.as_slice().unwrap();
1161 for i in 0..s.len() / 2 {
1163 assert!((s[i] - s[s.len() - 1 - i]).abs() < 1e-12);
1164 }
1165 let mid = s.len() / 2;
1167 for i in 0..s.len() {
1168 if i != mid {
1169 assert!(s[i] <= s[mid] + 1e-12);
1170 }
1171 }
1172 }
1173
1174 #[test]
1175 fn bohman_zero_at_endpoints_and_one_at_centre() {
1176 let w = bohman(5).unwrap();
1177 let s = w.as_slice().unwrap();
1178 assert!(s[0].abs() < 1e-12);
1179 assert!(s[s.len() - 1].abs() < 1e-12);
1180 let mid = s.len() / 2;
1182 assert!((s[mid] - 1.0).abs() < 1e-6);
1183 }
1184
1185 #[test]
1186 fn flattop_centre_around_one_and_negative_lobes_present() {
1187 let w = flattop(11).unwrap();
1190 let s = w.as_slice().unwrap();
1191 let mid = s.len() / 2;
1192 assert!((s[mid] - 1.0).abs() < 0.01);
1193 let any_negative = s.iter().any(|&v| v < -0.001);
1196 assert!(any_negative, "flattop should have negative side lobes");
1197 }
1198
1199 #[test]
1200 fn lanczos_centre_is_one_and_endpoints_zero() {
1201 let w = lanczos(9).unwrap();
1202 let s = w.as_slice().unwrap();
1203 let mid = s.len() / 2;
1204 assert!((s[mid] - 1.0).abs() < 1e-12);
1205 assert!(s[0].abs() < 1e-12);
1206 assert!(s[s.len() - 1].abs() < 1e-12);
1207 }
1208
1209 #[test]
1210 fn small_m_edge_cases_handled() {
1211 for f in [triang as fn(usize) -> _, bohman, lanczos] {
1213 let z = f(0).unwrap();
1214 assert_eq!(z.shape(), &[0]);
1215 let one = f(1).unwrap();
1216 assert_eq!(one.shape(), &[1]);
1217 assert_eq!(one.as_slice().unwrap(), &[1.0]);
1218 }
1219 }
1220
1221 #[test]
1224 fn f32_windows_match_f64_within_f32_eps() {
1225 for m in [1usize, 5, 16, 64] {
1226 for (name, got, want) in [
1227 ("bartlett", bartlett_f32(m).unwrap(), bartlett(m).unwrap()),
1228 ("blackman", blackman_f32(m).unwrap(), blackman(m).unwrap()),
1229 ("hamming", hamming_f32(m).unwrap(), hamming(m).unwrap()),
1230 ("hanning", hanning_f32(m).unwrap(), hanning(m).unwrap()),
1231 ] {
1232 assert_eq!(got.shape(), want.shape(), "{name} m={m} shape");
1233 let g = got.as_slice().unwrap();
1234 let w = want.as_slice().unwrap();
1235 for (i, (&a, &b)) in g.iter().zip(w.iter()).enumerate() {
1236 let bf = b as f32;
1237 assert!(
1238 (a - bf).abs() < 1e-6,
1239 "{name} m={m} idx={i}: f32 {a} vs f64 {b}"
1240 );
1241 }
1242 }
1243 }
1244 }
1245
1246 #[test]
1247 fn kaiser_f32_matches_kaiser() {
1248 let m = 20;
1249 let beta = 8.6;
1250 let f32_arr = kaiser_f32(m, beta).unwrap();
1251 let f64_arr = kaiser(m, beta).unwrap();
1252 for (a, b) in f32_arr.iter().zip(f64_arr.iter()) {
1253 assert!((a - *b as f32).abs() < 1e-5);
1254 }
1255 }
1256
1257 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
1259 assert_eq!(
1260 actual.len(),
1261 expected.len(),
1262 "length mismatch: {} vs {}",
1263 actual.len(),
1264 expected.len()
1265 );
1266 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
1267 assert!(
1268 (a - e).abs() <= tol,
1269 "element {i}: {a} vs {e} (diff = {})",
1270 (a - e).abs()
1271 );
1272 }
1273 }
1274
1275 #[test]
1280 fn bartlett_m0() {
1281 let w = bartlett(0).unwrap();
1282 assert_eq!(w.shape(), &[0]);
1283 }
1284
1285 #[test]
1286 fn bartlett_m1() {
1287 let w = bartlett(1).unwrap();
1288 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1289 }
1290
1291 #[test]
1292 fn blackman_m0() {
1293 let w = blackman(0).unwrap();
1294 assert_eq!(w.shape(), &[0]);
1295 }
1296
1297 #[test]
1298 fn blackman_m1() {
1299 let w = blackman(1).unwrap();
1300 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1301 }
1302
1303 #[test]
1304 fn hamming_m0() {
1305 let w = hamming(0).unwrap();
1306 assert_eq!(w.shape(), &[0]);
1307 }
1308
1309 #[test]
1310 fn hamming_m1() {
1311 let w = hamming(1).unwrap();
1312 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1313 }
1314
1315 #[test]
1316 fn hanning_m0() {
1317 let w = hanning(0).unwrap();
1318 assert_eq!(w.shape(), &[0]);
1319 }
1320
1321 #[test]
1322 fn hanning_m1() {
1323 let w = hanning(1).unwrap();
1324 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1325 }
1326
1327 #[test]
1328 fn kaiser_m0() {
1329 let w = kaiser(0, 14.0).unwrap();
1330 assert_eq!(w.shape(), &[0]);
1331 }
1332
1333 #[test]
1334 fn kaiser_m1() {
1335 let w = kaiser(1, 14.0).unwrap();
1336 assert_eq!(w.as_slice().unwrap(), &[1.0]);
1337 }
1338
1339 #[test]
1340 fn kaiser_negative_beta() {
1341 let pos = kaiser(5, 1.0).unwrap();
1343 let neg = kaiser(5, -1.0).unwrap();
1344 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
1345 }
1346
1347 #[test]
1348 fn kaiser_nan_beta() {
1349 assert!(kaiser(5, f64::NAN).is_err());
1350 }
1351
1352 #[test]
1357 fn bartlett_5_ac1() {
1358 let w = bartlett(5).unwrap();
1359 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
1360 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
1361 }
1362
1363 #[test]
1368 fn kaiser_5_14_ac2() {
1369 let w = kaiser(5, 14.0).unwrap();
1370 let s = w.as_slice().unwrap();
1371 assert_eq!(s.len(), 5);
1372 let expected: [f64; 5] = [
1374 7.72686684e-06,
1375 1.64932188e-01,
1376 1.0,
1377 1.64932188e-01,
1378 7.72686684e-06,
1379 ];
1380 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
1382 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
1383 assert!(
1384 (a - e).abs() <= tol,
1385 "kaiser element {i}: {a} vs {e} (diff = {})",
1386 (a - e).abs()
1387 );
1388 }
1389 }
1390
1391 #[test]
1397 fn blackman_5() {
1398 let w = blackman(5).unwrap();
1399 assert_eq!(w.shape(), &[5]);
1400 let s = w.as_slice().unwrap();
1401 let expected = [
1402 -1.38777878e-17,
1403 3.40000000e-01,
1404 1.00000000e+00,
1405 3.40000000e-01,
1406 -1.38777878e-17,
1407 ];
1408 assert_close(s, &expected, 1e-10);
1409 }
1410
1411 #[test]
1413 fn hamming_5() {
1414 let w = hamming(5).unwrap();
1415 assert_eq!(w.shape(), &[5]);
1416 let s = w.as_slice().unwrap();
1417 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
1418 assert_close(s, &expected, 1e-14);
1419 }
1420
1421 #[test]
1423 fn hanning_5() {
1424 let w = hanning(5).unwrap();
1425 assert_eq!(w.shape(), &[5]);
1426 let s = w.as_slice().unwrap();
1427 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
1428 assert_close(s, &expected, 1e-14);
1429 }
1430
1431 #[test]
1433 fn bartlett_12() {
1434 let w = bartlett(12).unwrap();
1435 assert_eq!(w.shape(), &[12]);
1436 let s = w.as_slice().unwrap();
1437 assert!((s[0] - 0.0).abs() < 1e-14);
1439 assert!((s[11] - 0.0).abs() < 1e-14);
1440 for i in 0..6 {
1442 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
1443 }
1444 }
1445
1446 #[test]
1448 fn all_windows_symmetric() {
1449 let m = 7;
1450 let windows: Vec<Array<f64, Ix1>> = vec![
1451 bartlett(m).unwrap(),
1452 blackman(m).unwrap(),
1453 hamming(m).unwrap(),
1454 hanning(m).unwrap(),
1455 kaiser(m, 5.0).unwrap(),
1456 ];
1457 for (idx, w) in windows.iter().enumerate() {
1458 let s = w.as_slice().unwrap();
1459 for i in 0..m / 2 {
1460 assert!(
1461 (s[i] - s[m - 1 - i]).abs() < 1e-12,
1462 "window {idx} not symmetric at {i}"
1463 );
1464 }
1465 }
1466 }
1467
1468 #[test]
1470 fn all_windows_peak_at_center() {
1471 let m = 9;
1472 let windows: Vec<Array<f64, Ix1>> = vec![
1473 bartlett(m).unwrap(),
1474 blackman(m).unwrap(),
1475 hamming(m).unwrap(),
1476 hanning(m).unwrap(),
1477 kaiser(m, 5.0).unwrap(),
1478 ];
1479 for (idx, w) in windows.iter().enumerate() {
1480 let s = w.as_slice().unwrap();
1481 let center = s[m / 2];
1482 assert!(
1483 (center - 1.0).abs() < 1e-10,
1484 "window {idx} center = {center}, expected 1.0"
1485 );
1486 }
1487 }
1488
1489 #[test]
1491 fn kaiser_beta_zero() {
1492 let w = kaiser(5, 0.0).unwrap();
1493 let s = w.as_slice().unwrap();
1494 for &v in s {
1495 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
1496 }
1497 }
1498
1499 #[test]
1500 fn bessel_i0_scalar_zero() {
1501 assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-13);
1503 }
1504
1505 #[test]
1506 fn bessel_i0_scalar_known() {
1507 assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-13);
1512 assert!(
1514 (bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_442).abs() / 27.239_871_823_604_442
1515 < 1e-13
1516 );
1517 let expected_i0_10 = 2_815.716_628_466_254;
1519 assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-13);
1520 }
1521
1522 #[test]
1525 fn bartlett_m2_is_zeros() {
1526 let w = bartlett(2).unwrap();
1530 assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
1531 }
1532
1533 #[test]
1534 fn hanning_m2_is_zeros() {
1535 let w = hanning(2).unwrap();
1539 let s = w.as_slice().unwrap();
1540 assert!(s[0].abs() < 1e-14);
1541 assert!(s[1].abs() < 1e-14);
1542 }
1543
1544 #[test]
1545 fn bartlett_even_length_is_symmetric() {
1546 for &m in &[4usize, 6, 8, 10] {
1549 let w = bartlett(m).unwrap();
1550 let s = w.as_slice().unwrap();
1551 for i in 0..m / 2 {
1552 assert!(
1553 (s[i] - s[m - 1 - i]).abs() < 1e-14,
1554 "bartlett({m}) not symmetric at {i}"
1555 );
1556 }
1557 }
1558 }
1559
1560 #[test]
1561 fn blackman_even_length_is_symmetric() {
1562 for &m in &[4usize, 6, 8, 10] {
1563 let w = blackman(m).unwrap();
1564 let s = w.as_slice().unwrap();
1565 for i in 0..m / 2 {
1566 assert!(
1567 (s[i] - s[m - 1 - i]).abs() < 1e-14,
1568 "blackman({m}) not symmetric at {i}"
1569 );
1570 }
1571 }
1572 }
1573
1574 #[test]
1575 fn kaiser_large_beta_matches_numpy_degenerate() -> FerrayResult<()> {
1576 let not_contig = || FerrayError::invalid_value("kaiser window must be contiguous");
1583 for beta in [800.0, 1000.0] {
1584 let w = kaiser(8, beta)?;
1585 let s = w.as_slice().ok_or_else(not_contig)?;
1586 assert_eq!(s.len(), 8);
1587 let expected_nan = [false, false, true, true, true, true, false, false];
1588 for (i, (&v, &nan)) in s.iter().zip(expected_nan.iter()).enumerate() {
1589 if nan {
1590 assert!(
1591 v.is_nan(),
1592 "kaiser(8, {beta})[{i}] should be NaN, got {v:e}"
1593 );
1594 } else {
1595 assert_eq!(v, 0.0, "kaiser(8, {beta})[{i}] should be 0.0, got {v:e}");
1596 }
1597 }
1598 }
1599 let w700 = kaiser(8, 700.0)?;
1601 let s700 = w700.as_slice().ok_or_else(not_contig)?;
1602 assert!(s700.iter().all(|v| v.is_finite()));
1603 Ok(())
1604 }
1605
1606 fn close(a: f64, b: f64, tol: f64) -> bool {
1612 (a - b).abs() < tol
1613 }
1614
1615 #[test]
1618 fn cosine_length_and_endpoints() {
1619 let w = cosine(8).unwrap();
1622 let s = w.as_slice().unwrap();
1623 assert_eq!(s.len(), 8);
1624 for i in 0..4 {
1626 assert!(close(s[i], s[7 - i], 1e-14));
1627 }
1628 }
1629
1630 #[test]
1631 fn cosine_m1_and_m0() {
1632 assert_eq!(cosine(0).unwrap().shape(), &[0]);
1633 assert_eq!(cosine(1).unwrap().as_slice().unwrap(), &[1.0]);
1634 }
1635
1636 #[test]
1639 fn exponential_centred_default() {
1640 let w = exponential(8, None, 1.0).unwrap();
1643 let s = w.as_slice().unwrap();
1644 for i in 0..4 {
1646 assert!(close(s[i], s[7 - i], 1e-14));
1647 }
1648 let centre_max = s[3].max(s[4]);
1650 for &v in s {
1651 assert!(v <= centre_max + 1e-14);
1652 }
1653 }
1654
1655 #[test]
1656 fn exponential_rejects_nonpositive_tau() {
1657 assert!(exponential(8, None, 0.0).is_err());
1658 assert!(exponential(8, None, -1.0).is_err());
1659 assert!(exponential(8, None, f64::NAN).is_err());
1660 }
1661
1662 #[test]
1665 fn gaussian_centre_is_one() {
1666 let w = gaussian(11, 2.0).unwrap();
1668 let s = w.as_slice().unwrap();
1669 assert!(close(s[5], 1.0, 1e-14));
1670 }
1671
1672 #[test]
1673 fn gaussian_known_value() {
1674 let w = gaussian(7, 1.0).unwrap();
1676 let s = w.as_slice().unwrap();
1677 assert!(close(s[4], (-0.5_f64).exp(), 1e-14));
1678 }
1679
1680 #[test]
1681 fn gaussian_rejects_nonpositive_std() {
1682 assert!(gaussian(8, 0.0).is_err());
1683 assert!(gaussian(8, -1.0).is_err());
1684 }
1685
1686 #[test]
1689 fn general_cosine_with_hann_coeffs_matches_hann() {
1690 let m = 9;
1692 let gc = general_cosine(m, &[0.5, 0.5]).unwrap();
1693 let hn = hanning(m).unwrap();
1694 for i in 0..m {
1695 assert!(close(
1696 gc.as_slice().unwrap()[i],
1697 hn.as_slice().unwrap()[i],
1698 1e-14,
1699 ));
1700 }
1701 }
1702
1703 #[test]
1704 fn general_cosine_with_blackman_coeffs_matches_blackman() {
1705 let m = 9;
1707 let gc = general_cosine(m, &[0.42, 0.5, 0.08]).unwrap();
1708 let bk = blackman(m).unwrap();
1709 for i in 0..m {
1710 assert!(close(
1711 gc.as_slice().unwrap()[i],
1712 bk.as_slice().unwrap()[i],
1713 1e-12,
1714 ));
1715 }
1716 }
1717
1718 #[test]
1719 fn general_cosine_rejects_empty_coeffs() {
1720 assert!(general_cosine(8, &[]).is_err());
1721 }
1722
1723 #[test]
1726 fn general_hamming_alpha_half_matches_hann() {
1727 let m = 9;
1728 let gh = general_hamming(m, 0.5).unwrap();
1729 let hn = hanning(m).unwrap();
1730 for i in 0..m {
1731 assert!(close(
1732 gh.as_slice().unwrap()[i],
1733 hn.as_slice().unwrap()[i],
1734 1e-14,
1735 ));
1736 }
1737 }
1738
1739 #[test]
1740 fn general_hamming_alpha_054_matches_hamming() {
1741 let m = 9;
1742 let gh = general_hamming(m, 0.54).unwrap();
1743 let hm = hamming(m).unwrap();
1744 for i in 0..m {
1745 assert!(close(
1746 gh.as_slice().unwrap()[i],
1747 hm.as_slice().unwrap()[i],
1748 1e-14,
1749 ));
1750 }
1751 }
1752
1753 #[test]
1756 fn nuttall_endpoints_are_small() {
1757 let w = nuttall(64).unwrap();
1760 let s = w.as_slice().unwrap();
1761 assert!(s[0].abs() < 1e-2);
1762 assert!(s[s.len() - 1].abs() < 1e-2);
1763 }
1764
1765 #[test]
1766 fn nuttall_is_symmetric() {
1767 let m = 33;
1768 let w = nuttall(m).unwrap();
1769 let s = w.as_slice().unwrap();
1770 for i in 0..m / 2 {
1771 assert!(close(s[i], s[m - 1 - i], 1e-14));
1772 }
1773 }
1774
1775 #[test]
1778 fn parzen_endpoints_are_zero() {
1779 let w = parzen(16).unwrap();
1780 let s = w.as_slice().unwrap();
1781 assert!(s[0].abs() < 1e-2);
1783 assert!(s[s.len() - 1].abs() < 1e-2);
1784 }
1785
1786 #[test]
1787 fn parzen_centre_is_one() {
1788 let w = parzen(13).unwrap();
1790 let s = w.as_slice().unwrap();
1791 assert!(close(s[6], 1.0, 1e-14));
1792 }
1793
1794 #[test]
1795 fn parzen_is_symmetric() {
1796 let m = 21;
1797 let w = parzen(m).unwrap();
1798 let s = w.as_slice().unwrap();
1799 for i in 0..m / 2 {
1800 assert!(close(s[i], s[m - 1 - i], 1e-14));
1801 }
1802 }
1803
1804 #[test]
1807 fn taylor_default_normalised_centre_is_one() {
1808 let w = taylor(33, 4, 30.0, true).unwrap();
1810 let s = w.as_slice().unwrap();
1811 assert!(close(s[16], 1.0, 1e-12));
1812 }
1813
1814 #[test]
1815 fn taylor_is_symmetric() {
1816 let m = 33;
1817 let w = taylor(m, 4, 30.0, true).unwrap();
1818 let s = w.as_slice().unwrap();
1819 for i in 0..m / 2 {
1820 assert!(close(s[i], s[m - 1 - i], 1e-12));
1821 }
1822 }
1823
1824 #[test]
1825 fn taylor_rejects_nbar_zero() {
1826 assert!(taylor(8, 0, 30.0, true).is_err());
1827 assert!(taylor(8, 4, f64::NAN, true).is_err());
1828 }
1829
1830 #[test]
1831 fn taylor_even_m_matches_scipy_analytic_peak() {
1832 let w = taylor(16, 4, 30.0, true).unwrap();
1841 let s = w.as_slice().unwrap();
1842 let expected_first = 0.252_321_041_674_507_f64;
1843 assert!(
1844 (s[0] - expected_first).abs() < 1e-13,
1845 "taylor(16,4,30,true)[0] = {} expected {}",
1846 s[0],
1847 expected_first,
1848 );
1849 assert!(s[7] < 1.0 && s[8] < 1.0);
1854 assert!(s[7] > 0.99 && s[8] > 0.99);
1855 }
1856
1857 #[test]
1860 fn tukey_alpha_zero_is_rectangular() {
1861 let w = tukey(8, 0.0).unwrap();
1862 for &v in w.as_slice().unwrap() {
1863 assert!(close(v, 1.0, 1e-14));
1864 }
1865 }
1866
1867 #[test]
1868 fn tukey_alpha_one_matches_hann() {
1869 let m = 9;
1871 let tk = tukey(m, 1.0).unwrap();
1872 let hn = hanning(m).unwrap();
1873 for i in 0..m {
1874 assert!(close(
1875 tk.as_slice().unwrap()[i],
1876 hn.as_slice().unwrap()[i],
1877 1e-12,
1878 ));
1879 }
1880 }
1881
1882 #[test]
1883 fn tukey_centre_is_one() {
1884 let m = 21;
1885 let w = tukey(m, 0.5).unwrap();
1886 assert!(close(w.as_slice().unwrap()[m / 2], 1.0, 1e-14));
1888 }
1889
1890 #[test]
1891 fn tukey_rejects_invalid_alpha() {
1892 assert!(tukey(8, -0.1).is_err());
1893 assert!(tukey(8, 1.1).is_err());
1894 assert!(tukey(8, f64::NAN).is_err());
1895 }
1896}
1897
1898#[cfg(test)]
1899mod debug_test_removed {
1900 }