#![allow(clippy::unreadable_literal)]
use ferray_core::Array;
use ferray_core::dimension::Ix1;
use ferray_core::error::{FerrayError, FerrayResult};
use std::f64::consts::PI;
use ferray_ufunc::ops::special::bessel_i0_scalar;
#[inline]
fn gen_window<F: FnMut(usize) -> f64>(m: usize, mut f: F) -> FerrayResult<Array<f64, Ix1>> {
if m == 0 {
return Array::from_vec(Ix1::new([0]), vec![]);
}
if m == 1 {
return Array::from_vec(Ix1::new([1]), vec![1.0]);
}
let mut data = Vec::with_capacity(m);
for n in 0..m {
data.push(f(n));
}
Array::from_vec(Ix1::new([m]), data)
}
pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
let half = (m.saturating_sub(1)) as f64 / 2.0;
gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
}
pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
let denom = (m.saturating_sub(1)) as f64;
gen_window(m, |n| {
let x = n as f64;
0.08f64.mul_add(
(4.0 * PI * x / denom).cos(),
0.5f64.mul_add(-(2.0 * PI * x / denom).cos(), 0.42),
)
})
}
pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
let denom = (m.saturating_sub(1)) as f64;
gen_window(m, |n| {
0.46f64.mul_add(-(2.0 * PI * n as f64 / denom).cos(), 0.54)
})
}
pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
let denom = (m.saturating_sub(1)) as f64;
gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
}
pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
if beta.is_nan() {
return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
}
let beta = beta.abs();
const BETA_OVERFLOW_THRESHOLD: f64 = 708.0;
if beta > BETA_OVERFLOW_THRESHOLD {
return Err(FerrayError::invalid_value(format!(
"kaiser: |beta| = {beta} exceeds the safe range ({BETA_OVERFLOW_THRESHOLD}) \
for f64 I_0; the window would be NaN everywhere"
)));
}
let i0_beta = bessel_i0_scalar::<f64>(beta);
let alpha = (m.saturating_sub(1)) as f64 / 2.0;
gen_window(m, |n| {
let t = (n as f64 - alpha) / alpha;
let arg = beta * (1.0 - t * t).max(0.0).sqrt();
bessel_i0_scalar::<f64>(arg) / i0_beta
})
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
assert_eq!(
actual.len(),
expected.len(),
"length mismatch: {} vs {}",
actual.len(),
expected.len()
);
for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!(
(a - e).abs() <= tol,
"element {i}: {a} vs {e} (diff = {})",
(a - e).abs()
);
}
}
#[test]
fn bartlett_m0() {
let w = bartlett(0).unwrap();
assert_eq!(w.shape(), &[0]);
}
#[test]
fn bartlett_m1() {
let w = bartlett(1).unwrap();
assert_eq!(w.as_slice().unwrap(), &[1.0]);
}
#[test]
fn blackman_m0() {
let w = blackman(0).unwrap();
assert_eq!(w.shape(), &[0]);
}
#[test]
fn blackman_m1() {
let w = blackman(1).unwrap();
assert_eq!(w.as_slice().unwrap(), &[1.0]);
}
#[test]
fn hamming_m0() {
let w = hamming(0).unwrap();
assert_eq!(w.shape(), &[0]);
}
#[test]
fn hamming_m1() {
let w = hamming(1).unwrap();
assert_eq!(w.as_slice().unwrap(), &[1.0]);
}
#[test]
fn hanning_m0() {
let w = hanning(0).unwrap();
assert_eq!(w.shape(), &[0]);
}
#[test]
fn hanning_m1() {
let w = hanning(1).unwrap();
assert_eq!(w.as_slice().unwrap(), &[1.0]);
}
#[test]
fn kaiser_m0() {
let w = kaiser(0, 14.0).unwrap();
assert_eq!(w.shape(), &[0]);
}
#[test]
fn kaiser_m1() {
let w = kaiser(1, 14.0).unwrap();
assert_eq!(w.as_slice().unwrap(), &[1.0]);
}
#[test]
fn kaiser_negative_beta() {
let pos = kaiser(5, 1.0).unwrap();
let neg = kaiser(5, -1.0).unwrap();
assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
}
#[test]
fn kaiser_nan_beta() {
assert!(kaiser(5, f64::NAN).is_err());
}
#[test]
fn bartlett_5_ac1() {
let w = bartlett(5).unwrap();
let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
assert_close(w.as_slice().unwrap(), &expected, 1e-14);
}
#[test]
fn kaiser_5_14_ac2() {
let w = kaiser(5, 14.0).unwrap();
let s = w.as_slice().unwrap();
assert_eq!(s.len(), 5);
let expected: [f64; 5] = [
7.72686684e-06,
1.64932188e-01,
1.0,
1.64932188e-01,
7.72686684e-06,
];
for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
assert!(
(a - e).abs() <= tol,
"kaiser element {i}: {a} vs {e} (diff = {})",
(a - e).abs()
);
}
}
#[test]
fn blackman_5() {
let w = blackman(5).unwrap();
assert_eq!(w.shape(), &[5]);
let s = w.as_slice().unwrap();
let expected = [
-1.38777878e-17,
3.40000000e-01,
1.00000000e+00,
3.40000000e-01,
-1.38777878e-17,
];
assert_close(s, &expected, 1e-10);
}
#[test]
fn hamming_5() {
let w = hamming(5).unwrap();
assert_eq!(w.shape(), &[5]);
let s = w.as_slice().unwrap();
let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
assert_close(s, &expected, 1e-14);
}
#[test]
fn hanning_5() {
let w = hanning(5).unwrap();
assert_eq!(w.shape(), &[5]);
let s = w.as_slice().unwrap();
let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
assert_close(s, &expected, 1e-14);
}
#[test]
fn bartlett_12() {
let w = bartlett(12).unwrap();
assert_eq!(w.shape(), &[12]);
let s = w.as_slice().unwrap();
assert!((s[0] - 0.0).abs() < 1e-14);
assert!((s[11] - 0.0).abs() < 1e-14);
for i in 0..6 {
assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
}
}
#[test]
fn all_windows_symmetric() {
let m = 7;
let windows: Vec<Array<f64, Ix1>> = vec![
bartlett(m).unwrap(),
blackman(m).unwrap(),
hamming(m).unwrap(),
hanning(m).unwrap(),
kaiser(m, 5.0).unwrap(),
];
for (idx, w) in windows.iter().enumerate() {
let s = w.as_slice().unwrap();
for i in 0..m / 2 {
assert!(
(s[i] - s[m - 1 - i]).abs() < 1e-12,
"window {idx} not symmetric at {i}"
);
}
}
}
#[test]
fn all_windows_peak_at_center() {
let m = 9;
let windows: Vec<Array<f64, Ix1>> = vec![
bartlett(m).unwrap(),
blackman(m).unwrap(),
hamming(m).unwrap(),
hanning(m).unwrap(),
kaiser(m, 5.0).unwrap(),
];
for (idx, w) in windows.iter().enumerate() {
let s = w.as_slice().unwrap();
let center = s[m / 2];
assert!(
(center - 1.0).abs() < 1e-10,
"window {idx} center = {center}, expected 1.0"
);
}
}
#[test]
fn kaiser_beta_zero() {
let w = kaiser(5, 0.0).unwrap();
let s = w.as_slice().unwrap();
for &v in s {
assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
}
}
#[test]
fn bessel_i0_scalar_zero() {
assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-6);
}
#[test]
fn bessel_i0_scalar_known() {
assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-7);
assert!((bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_443).abs() < 1e-5);
let expected_i0_10 = 2_815.716_628_466_254;
assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-5);
}
#[test]
fn bartlett_m2_is_zeros() {
let w = bartlett(2).unwrap();
assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
}
#[test]
fn hanning_m2_is_zeros() {
let w = hanning(2).unwrap();
let s = w.as_slice().unwrap();
assert!(s[0].abs() < 1e-14);
assert!(s[1].abs() < 1e-14);
}
#[test]
fn bartlett_even_length_is_symmetric() {
for &m in &[4usize, 6, 8, 10] {
let w = bartlett(m).unwrap();
let s = w.as_slice().unwrap();
for i in 0..m / 2 {
assert!(
(s[i] - s[m - 1 - i]).abs() < 1e-14,
"bartlett({m}) not symmetric at {i}"
);
}
}
}
#[test]
fn blackman_even_length_is_symmetric() {
for &m in &[4usize, 6, 8, 10] {
let w = blackman(m).unwrap();
let s = w.as_slice().unwrap();
for i in 0..m / 2 {
assert!(
(s[i] - s[m - 1 - i]).abs() < 1e-14,
"blackman({m}) not symmetric at {i}"
);
}
}
}
#[test]
fn kaiser_large_beta_errors() {
assert!(kaiser(8, 800.0).is_err());
assert!(kaiser(8, 1000.0).is_err());
assert!(kaiser(8, 700.0).is_ok());
}
}