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
46pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
56 let half = (m.saturating_sub(1)) as f64 / 2.0;
57 gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
58}
59
60pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
70 let denom = (m.saturating_sub(1)) as f64;
71 gen_window(m, |n| {
72 let x = n as f64;
73 0.08f64.mul_add(
74 (4.0 * PI * x / denom).cos(),
75 0.5f64.mul_add(-(2.0 * PI * x / denom).cos(), 0.42),
76 )
77 })
78}
79
80pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
90 let denom = (m.saturating_sub(1)) as f64;
91 gen_window(m, |n| {
92 0.46f64.mul_add(-(2.0 * PI * n as f64 / denom).cos(), 0.54)
93 })
94}
95
96pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
106 let denom = (m.saturating_sub(1)) as f64;
107 gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
108}
109
110pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
131 if beta.is_nan() {
132 return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
133 }
134 let beta = beta.abs();
137 const BETA_OVERFLOW_THRESHOLD: f64 = 708.0;
142 if beta > BETA_OVERFLOW_THRESHOLD {
143 return Err(FerrayError::invalid_value(format!(
144 "kaiser: |beta| = {beta} exceeds the safe range ({BETA_OVERFLOW_THRESHOLD}) \
145 for f64 I_0; the window would be NaN everywhere"
146 )));
147 }
148 let i0_beta = bessel_i0_scalar::<f64>(beta);
149 let alpha = (m.saturating_sub(1)) as f64 / 2.0;
150 gen_window(m, |n| {
151 let t = (n as f64 - alpha) / alpha;
152 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
153 bessel_i0_scalar::<f64>(arg) / i0_beta
154 })
155}
156
157pub fn cosine(m: usize) -> FerrayResult<Array<f64, Ix1>> {
173 if m == 0 {
174 return Array::from_vec(Ix1::new([0]), vec![]);
175 }
176 if m == 1 {
177 return Array::from_vec(Ix1::new([1]), vec![1.0]);
178 }
179 let mf = m as f64;
180 gen_window(m, |n| (PI * (n as f64 + 0.5) / mf).sin())
181}
182
183pub fn exponential(m: usize, center: Option<f64>, tau: f64) -> FerrayResult<Array<f64, Ix1>> {
191 if !tau.is_finite() || tau <= 0.0 {
192 return Err(FerrayError::invalid_value(format!(
193 "exponential: tau must be positive and finite, got {tau}"
194 )));
195 }
196 let centre = center.unwrap_or((m.saturating_sub(1)) as f64 / 2.0);
197 gen_window(m, |n| (-((n as f64) - centre).abs() / tau).exp())
198}
199
200pub fn gaussian(m: usize, std: f64) -> FerrayResult<Array<f64, Ix1>> {
207 if !std.is_finite() || std <= 0.0 {
208 return Err(FerrayError::invalid_value(format!(
209 "gaussian: std must be positive and finite, got {std}"
210 )));
211 }
212 let centre = (m.saturating_sub(1)) as f64 / 2.0;
213 gen_window(m, |n| {
214 let z = ((n as f64) - centre) / std;
215 (-0.5 * z * z).exp()
216 })
217}
218
219pub fn general_cosine(m: usize, coeffs: &[f64]) -> FerrayResult<Array<f64, Ix1>> {
227 if coeffs.is_empty() {
228 return Err(FerrayError::invalid_value(
229 "general_cosine: coeffs must not be empty",
230 ));
231 }
232 let denom = (m.saturating_sub(1)) as f64;
233 let coeffs = coeffs.to_vec();
234 gen_window(m, |n| {
235 let nf = n as f64;
236 let mut sum = 0.0_f64;
237 for (k, &a) in coeffs.iter().enumerate() {
238 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
239 sum += sign * a * (2.0 * PI * (k as f64) * nf / denom).cos();
240 }
241 sum
242 })
243}
244
245pub fn general_hamming(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
253 if !alpha.is_finite() {
254 return Err(FerrayError::invalid_value(format!(
255 "general_hamming: alpha must be finite, got {alpha}"
256 )));
257 }
258 let denom = (m.saturating_sub(1)) as f64;
259 gen_window(m, |n| {
260 alpha - (1.0 - alpha) * (2.0 * PI * (n as f64) / denom).cos()
261 })
262}
263
264pub fn nuttall(m: usize) -> FerrayResult<Array<f64, Ix1>> {
272 general_cosine(m, &[0.3635819, 0.4891775, 0.1365995, 0.0106411])
273}
274
275pub fn parzen(m: usize) -> FerrayResult<Array<f64, Ix1>> {
279 if m == 0 {
280 return Array::from_vec(Ix1::new([0]), vec![]);
281 }
282 if m == 1 {
283 return Array::from_vec(Ix1::new([1]), vec![1.0]);
284 }
285 let half = m as f64 / 2.0;
288 gen_window(m, |n| {
289 let x = (n as f64) - (m as f64 - 1.0) / 2.0;
290 let r = x.abs() / half;
291 if r <= 0.5 {
292 1.0 - 6.0 * r * r + 6.0 * r * r * r
293 } else if r <= 1.0 {
294 let one_minus_r = 1.0 - r;
295 2.0 * one_minus_r * one_minus_r * one_minus_r
296 } else {
297 0.0
298 }
299 })
300}
301
302pub fn taylor(
317 m: usize,
318 nbar: usize,
319 sll: f64,
320 norm: bool,
321) -> FerrayResult<Array<f64, Ix1>> {
322 if m == 0 {
323 return Array::from_vec(Ix1::new([0]), vec![]);
324 }
325 if m == 1 {
326 return Array::from_vec(Ix1::new([1]), vec![1.0]);
327 }
328 if nbar == 0 {
329 return Err(FerrayError::invalid_value("taylor: nbar must be >= 1"));
330 }
331 if !sll.is_finite() {
332 return Err(FerrayError::invalid_value("taylor: sll must be finite"));
333 }
334 let r = 10.0_f64.powf(sll / 20.0);
336 let b = r.acosh() / PI;
337 let nbar_f = nbar as f64;
338 let sigma2 = (nbar_f * nbar_f) / (b * b + (nbar_f - 0.5) * (nbar_f - 0.5));
341
342 let mut f_coeffs = Vec::with_capacity(nbar.saturating_sub(1));
344 for mm in 1..nbar {
345 let mmf = mm as f64;
346 let mut num = 1.0_f64;
349 for n in 1..nbar {
350 let nf = n as f64;
351 num *= 1.0 - mmf * mmf / (sigma2 * (b * b + (nf - 0.5) * (nf - 0.5)));
352 }
353 let sign = if mm % 2 == 0 { -1.0 } else { 1.0 };
354 let mut den = 1.0_f64;
355 for n in 1..nbar {
356 if n == mm {
357 continue;
358 }
359 let nf = n as f64;
360 den *= 1.0 - mmf * mmf / (nf * nf);
361 }
362 f_coeffs.push(0.5 * sign * num / den);
366 }
367
368 let denom = (m - 1) as f64;
369 let arr = gen_window(m, |n| {
370 let xn = (n as f64) - denom / 2.0;
371 let mut w = 1.0_f64;
372 for (idx, &fk) in f_coeffs.iter().enumerate() {
373 let kk = (idx + 1) as f64;
374 w += 2.0 * fk * (2.0 * PI * kk * xn / m as f64).cos();
375 }
376 w
377 })?;
378
379 if !norm {
380 return Ok(arr);
381 }
382 let s = arr.as_slice().unwrap().to_vec();
384 let centre_val = if m % 2 == 1 {
385 s[m / 2]
386 } else {
387 0.5 * (s[m / 2 - 1] + s[m / 2])
389 };
390 if centre_val == 0.0 {
391 return Ok(arr); }
393 let normed: Vec<f64> = s.into_iter().map(|v| v / centre_val).collect();
394 Array::from_vec(Ix1::new([m]), normed)
395}
396
397pub fn tukey(m: usize, alpha: f64) -> FerrayResult<Array<f64, Ix1>> {
405 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
406 return Err(FerrayError::invalid_value(format!(
407 "tukey: alpha must be in [0, 1], got {alpha}"
408 )));
409 }
410 if m == 0 {
411 return Array::from_vec(Ix1::new([0]), vec![]);
412 }
413 if m == 1 {
414 return Array::from_vec(Ix1::new([1]), vec![1.0]);
415 }
416 if alpha == 0.0 {
417 return Array::from_vec(Ix1::new([m]), vec![1.0; m]);
418 }
419 let denom = (m - 1) as f64;
420 let width = alpha * denom / 2.0;
421 gen_window(m, |n| {
422 let nf = n as f64;
423 if nf < width {
424 0.5 * (1.0 + (PI * (nf / width - 1.0)).cos())
425 } else if nf <= denom - width {
426 1.0
427 } else {
428 0.5 * (1.0 + (PI * ((denom - nf) / width - 1.0)).cos())
429 }
430 })
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
439 assert_eq!(
440 actual.len(),
441 expected.len(),
442 "length mismatch: {} vs {}",
443 actual.len(),
444 expected.len()
445 );
446 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
447 assert!(
448 (a - e).abs() <= tol,
449 "element {i}: {a} vs {e} (diff = {})",
450 (a - e).abs()
451 );
452 }
453 }
454
455 #[test]
460 fn bartlett_m0() {
461 let w = bartlett(0).unwrap();
462 assert_eq!(w.shape(), &[0]);
463 }
464
465 #[test]
466 fn bartlett_m1() {
467 let w = bartlett(1).unwrap();
468 assert_eq!(w.as_slice().unwrap(), &[1.0]);
469 }
470
471 #[test]
472 fn blackman_m0() {
473 let w = blackman(0).unwrap();
474 assert_eq!(w.shape(), &[0]);
475 }
476
477 #[test]
478 fn blackman_m1() {
479 let w = blackman(1).unwrap();
480 assert_eq!(w.as_slice().unwrap(), &[1.0]);
481 }
482
483 #[test]
484 fn hamming_m0() {
485 let w = hamming(0).unwrap();
486 assert_eq!(w.shape(), &[0]);
487 }
488
489 #[test]
490 fn hamming_m1() {
491 let w = hamming(1).unwrap();
492 assert_eq!(w.as_slice().unwrap(), &[1.0]);
493 }
494
495 #[test]
496 fn hanning_m0() {
497 let w = hanning(0).unwrap();
498 assert_eq!(w.shape(), &[0]);
499 }
500
501 #[test]
502 fn hanning_m1() {
503 let w = hanning(1).unwrap();
504 assert_eq!(w.as_slice().unwrap(), &[1.0]);
505 }
506
507 #[test]
508 fn kaiser_m0() {
509 let w = kaiser(0, 14.0).unwrap();
510 assert_eq!(w.shape(), &[0]);
511 }
512
513 #[test]
514 fn kaiser_m1() {
515 let w = kaiser(1, 14.0).unwrap();
516 assert_eq!(w.as_slice().unwrap(), &[1.0]);
517 }
518
519 #[test]
520 fn kaiser_negative_beta() {
521 let pos = kaiser(5, 1.0).unwrap();
523 let neg = kaiser(5, -1.0).unwrap();
524 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
525 }
526
527 #[test]
528 fn kaiser_nan_beta() {
529 assert!(kaiser(5, f64::NAN).is_err());
530 }
531
532 #[test]
537 fn bartlett_5_ac1() {
538 let w = bartlett(5).unwrap();
539 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
540 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
541 }
542
543 #[test]
548 fn kaiser_5_14_ac2() {
549 let w = kaiser(5, 14.0).unwrap();
550 let s = w.as_slice().unwrap();
551 assert_eq!(s.len(), 5);
552 let expected: [f64; 5] = [
554 7.72686684e-06,
555 1.64932188e-01,
556 1.0,
557 1.64932188e-01,
558 7.72686684e-06,
559 ];
560 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
562 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
563 assert!(
564 (a - e).abs() <= tol,
565 "kaiser element {i}: {a} vs {e} (diff = {})",
566 (a - e).abs()
567 );
568 }
569 }
570
571 #[test]
577 fn blackman_5() {
578 let w = blackman(5).unwrap();
579 assert_eq!(w.shape(), &[5]);
580 let s = w.as_slice().unwrap();
581 let expected = [
582 -1.38777878e-17,
583 3.40000000e-01,
584 1.00000000e+00,
585 3.40000000e-01,
586 -1.38777878e-17,
587 ];
588 assert_close(s, &expected, 1e-10);
589 }
590
591 #[test]
593 fn hamming_5() {
594 let w = hamming(5).unwrap();
595 assert_eq!(w.shape(), &[5]);
596 let s = w.as_slice().unwrap();
597 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
598 assert_close(s, &expected, 1e-14);
599 }
600
601 #[test]
603 fn hanning_5() {
604 let w = hanning(5).unwrap();
605 assert_eq!(w.shape(), &[5]);
606 let s = w.as_slice().unwrap();
607 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
608 assert_close(s, &expected, 1e-14);
609 }
610
611 #[test]
613 fn bartlett_12() {
614 let w = bartlett(12).unwrap();
615 assert_eq!(w.shape(), &[12]);
616 let s = w.as_slice().unwrap();
617 assert!((s[0] - 0.0).abs() < 1e-14);
619 assert!((s[11] - 0.0).abs() < 1e-14);
620 for i in 0..6 {
622 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
623 }
624 }
625
626 #[test]
628 fn all_windows_symmetric() {
629 let m = 7;
630 let windows: Vec<Array<f64, Ix1>> = vec![
631 bartlett(m).unwrap(),
632 blackman(m).unwrap(),
633 hamming(m).unwrap(),
634 hanning(m).unwrap(),
635 kaiser(m, 5.0).unwrap(),
636 ];
637 for (idx, w) in windows.iter().enumerate() {
638 let s = w.as_slice().unwrap();
639 for i in 0..m / 2 {
640 assert!(
641 (s[i] - s[m - 1 - i]).abs() < 1e-12,
642 "window {idx} not symmetric at {i}"
643 );
644 }
645 }
646 }
647
648 #[test]
650 fn all_windows_peak_at_center() {
651 let m = 9;
652 let windows: Vec<Array<f64, Ix1>> = vec![
653 bartlett(m).unwrap(),
654 blackman(m).unwrap(),
655 hamming(m).unwrap(),
656 hanning(m).unwrap(),
657 kaiser(m, 5.0).unwrap(),
658 ];
659 for (idx, w) in windows.iter().enumerate() {
660 let s = w.as_slice().unwrap();
661 let center = s[m / 2];
662 assert!(
663 (center - 1.0).abs() < 1e-10,
664 "window {idx} center = {center}, expected 1.0"
665 );
666 }
667 }
668
669 #[test]
671 fn kaiser_beta_zero() {
672 let w = kaiser(5, 0.0).unwrap();
673 let s = w.as_slice().unwrap();
674 for &v in s {
675 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
676 }
677 }
678
679 #[test]
680 fn bessel_i0_scalar_zero() {
681 assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-6);
682 }
683
684 #[test]
685 fn bessel_i0_scalar_known() {
686 assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-7);
691 assert!((bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_443).abs() < 1e-5);
693 let expected_i0_10 = 2_815.716_628_466_254;
695 assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-5);
696 }
697
698 #[test]
701 fn bartlett_m2_is_zeros() {
702 let w = bartlett(2).unwrap();
706 assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
707 }
708
709 #[test]
710 fn hanning_m2_is_zeros() {
711 let w = hanning(2).unwrap();
715 let s = w.as_slice().unwrap();
716 assert!(s[0].abs() < 1e-14);
717 assert!(s[1].abs() < 1e-14);
718 }
719
720 #[test]
721 fn bartlett_even_length_is_symmetric() {
722 for &m in &[4usize, 6, 8, 10] {
725 let w = bartlett(m).unwrap();
726 let s = w.as_slice().unwrap();
727 for i in 0..m / 2 {
728 assert!(
729 (s[i] - s[m - 1 - i]).abs() < 1e-14,
730 "bartlett({m}) not symmetric at {i}"
731 );
732 }
733 }
734 }
735
736 #[test]
737 fn blackman_even_length_is_symmetric() {
738 for &m in &[4usize, 6, 8, 10] {
739 let w = blackman(m).unwrap();
740 let s = w.as_slice().unwrap();
741 for i in 0..m / 2 {
742 assert!(
743 (s[i] - s[m - 1 - i]).abs() < 1e-14,
744 "blackman({m}) not symmetric at {i}"
745 );
746 }
747 }
748 }
749
750 #[test]
751 fn kaiser_large_beta_errors() {
752 assert!(kaiser(8, 800.0).is_err());
755 assert!(kaiser(8, 1000.0).is_err());
756 assert!(kaiser(8, 700.0).is_ok());
758 }
759
760 fn close(a: f64, b: f64, tol: f64) -> bool {
766 (a - b).abs() < tol
767 }
768
769 #[test]
772 fn cosine_length_and_endpoints() {
773 let w = cosine(8).unwrap();
776 let s = w.as_slice().unwrap();
777 assert_eq!(s.len(), 8);
778 for i in 0..4 {
780 assert!(close(s[i], s[7 - i], 1e-14));
781 }
782 }
783
784 #[test]
785 fn cosine_m1_and_m0() {
786 assert_eq!(cosine(0).unwrap().shape(), &[0]);
787 assert_eq!(cosine(1).unwrap().as_slice().unwrap(), &[1.0]);
788 }
789
790 #[test]
793 fn exponential_centred_default() {
794 let w = exponential(8, None, 1.0).unwrap();
797 let s = w.as_slice().unwrap();
798 for i in 0..4 {
800 assert!(close(s[i], s[7 - i], 1e-14));
801 }
802 let centre_max = s[3].max(s[4]);
804 for &v in s {
805 assert!(v <= centre_max + 1e-14);
806 }
807 }
808
809 #[test]
810 fn exponential_rejects_nonpositive_tau() {
811 assert!(exponential(8, None, 0.0).is_err());
812 assert!(exponential(8, None, -1.0).is_err());
813 assert!(exponential(8, None, f64::NAN).is_err());
814 }
815
816 #[test]
819 fn gaussian_centre_is_one() {
820 let w = gaussian(11, 2.0).unwrap();
822 let s = w.as_slice().unwrap();
823 assert!(close(s[5], 1.0, 1e-14));
824 }
825
826 #[test]
827 fn gaussian_known_value() {
828 let w = gaussian(7, 1.0).unwrap();
830 let s = w.as_slice().unwrap();
831 assert!(close(s[4], (-0.5_f64).exp(), 1e-14));
832 }
833
834 #[test]
835 fn gaussian_rejects_nonpositive_std() {
836 assert!(gaussian(8, 0.0).is_err());
837 assert!(gaussian(8, -1.0).is_err());
838 }
839
840 #[test]
843 fn general_cosine_with_hann_coeffs_matches_hann() {
844 let m = 9;
846 let gc = general_cosine(m, &[0.5, 0.5]).unwrap();
847 let hn = hanning(m).unwrap();
848 for i in 0..m {
849 assert!(close(
850 gc.as_slice().unwrap()[i],
851 hn.as_slice().unwrap()[i],
852 1e-14,
853 ));
854 }
855 }
856
857 #[test]
858 fn general_cosine_with_blackman_coeffs_matches_blackman() {
859 let m = 9;
861 let gc = general_cosine(m, &[0.42, 0.5, 0.08]).unwrap();
862 let bk = blackman(m).unwrap();
863 for i in 0..m {
864 assert!(close(
865 gc.as_slice().unwrap()[i],
866 bk.as_slice().unwrap()[i],
867 1e-12,
868 ));
869 }
870 }
871
872 #[test]
873 fn general_cosine_rejects_empty_coeffs() {
874 assert!(general_cosine(8, &[]).is_err());
875 }
876
877 #[test]
880 fn general_hamming_alpha_half_matches_hann() {
881 let m = 9;
882 let gh = general_hamming(m, 0.5).unwrap();
883 let hn = hanning(m).unwrap();
884 for i in 0..m {
885 assert!(close(
886 gh.as_slice().unwrap()[i],
887 hn.as_slice().unwrap()[i],
888 1e-14,
889 ));
890 }
891 }
892
893 #[test]
894 fn general_hamming_alpha_054_matches_hamming() {
895 let m = 9;
896 let gh = general_hamming(m, 0.54).unwrap();
897 let hm = hamming(m).unwrap();
898 for i in 0..m {
899 assert!(close(
900 gh.as_slice().unwrap()[i],
901 hm.as_slice().unwrap()[i],
902 1e-14,
903 ));
904 }
905 }
906
907 #[test]
910 fn nuttall_endpoints_are_small() {
911 let w = nuttall(64).unwrap();
914 let s = w.as_slice().unwrap();
915 assert!(s[0].abs() < 1e-2);
916 assert!(s[s.len() - 1].abs() < 1e-2);
917 }
918
919 #[test]
920 fn nuttall_is_symmetric() {
921 let m = 33;
922 let w = nuttall(m).unwrap();
923 let s = w.as_slice().unwrap();
924 for i in 0..m / 2 {
925 assert!(close(s[i], s[m - 1 - i], 1e-14));
926 }
927 }
928
929 #[test]
932 fn parzen_endpoints_are_zero() {
933 let w = parzen(16).unwrap();
934 let s = w.as_slice().unwrap();
935 assert!(s[0].abs() < 1e-2);
937 assert!(s[s.len() - 1].abs() < 1e-2);
938 }
939
940 #[test]
941 fn parzen_centre_is_one() {
942 let w = parzen(13).unwrap();
944 let s = w.as_slice().unwrap();
945 assert!(close(s[6], 1.0, 1e-14));
946 }
947
948 #[test]
949 fn parzen_is_symmetric() {
950 let m = 21;
951 let w = parzen(m).unwrap();
952 let s = w.as_slice().unwrap();
953 for i in 0..m / 2 {
954 assert!(close(s[i], s[m - 1 - i], 1e-14));
955 }
956 }
957
958 #[test]
961 fn taylor_default_normalised_centre_is_one() {
962 let w = taylor(33, 4, 30.0, true).unwrap();
964 let s = w.as_slice().unwrap();
965 assert!(close(s[16], 1.0, 1e-12));
966 }
967
968 #[test]
969 fn taylor_is_symmetric() {
970 let m = 33;
971 let w = taylor(m, 4, 30.0, true).unwrap();
972 let s = w.as_slice().unwrap();
973 for i in 0..m / 2 {
974 assert!(close(s[i], s[m - 1 - i], 1e-12));
975 }
976 }
977
978 #[test]
979 fn taylor_rejects_nbar_zero() {
980 assert!(taylor(8, 0, 30.0, true).is_err());
981 assert!(taylor(8, 4, f64::NAN, true).is_err());
982 }
983
984 #[test]
987 fn tukey_alpha_zero_is_rectangular() {
988 let w = tukey(8, 0.0).unwrap();
989 for &v in w.as_slice().unwrap() {
990 assert!(close(v, 1.0, 1e-14));
991 }
992 }
993
994 #[test]
995 fn tukey_alpha_one_matches_hann() {
996 let m = 9;
998 let tk = tukey(m, 1.0).unwrap();
999 let hn = hanning(m).unwrap();
1000 for i in 0..m {
1001 assert!(close(
1002 tk.as_slice().unwrap()[i],
1003 hn.as_slice().unwrap()[i],
1004 1e-12,
1005 ));
1006 }
1007 }
1008
1009 #[test]
1010 fn tukey_centre_is_one() {
1011 let m = 21;
1012 let w = tukey(m, 0.5).unwrap();
1013 assert!(close(w.as_slice().unwrap()[m / 2], 1.0, 1e-14));
1015 }
1016
1017 #[test]
1018 fn tukey_rejects_invalid_alpha() {
1019 assert!(tukey(8, -0.1).is_err());
1020 assert!(tukey(8, 1.1).is_err());
1021 assert!(tukey(8, f64::NAN).is_err());
1022 }
1023}