1#![allow(clippy::unreadable_literal)]
10
11use ferray_core::Array;
12use ferray_core::dimension::Ix1;
13use ferray_core::error::{FerrayError, FerrayResult};
14
15use std::f64::consts::PI;
16
17use ferray_ufunc::ops::special::bessel_i0_scalar;
23
24#[inline]
29fn gen_window<F: FnMut(usize) -> f64>(m: usize, mut f: F) -> FerrayResult<Array<f64, Ix1>> {
30 if m == 0 {
31 return Array::from_vec(Ix1::new([0]), vec![]);
32 }
33 if m == 1 {
34 return Array::from_vec(Ix1::new([1]), vec![1.0]);
35 }
36 let mut data = Vec::with_capacity(m);
37 for n in 0..m {
38 data.push(f(n));
39 }
40 Array::from_vec(Ix1::new([m]), data)
41}
42
43pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
53 let half = (m.saturating_sub(1)) as f64 / 2.0;
54 gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
55}
56
57pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
67 let denom = (m.saturating_sub(1)) as f64;
68 gen_window(m, |n| {
69 let x = n as f64;
70 0.08f64.mul_add(
71 (4.0 * PI * x / denom).cos(),
72 0.5f64.mul_add(-(2.0 * PI * x / denom).cos(), 0.42),
73 )
74 })
75}
76
77pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
87 let denom = (m.saturating_sub(1)) as f64;
88 gen_window(m, |n| {
89 0.46f64.mul_add(-(2.0 * PI * n as f64 / denom).cos(), 0.54)
90 })
91}
92
93pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
103 let denom = (m.saturating_sub(1)) as f64;
104 gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
105}
106
107pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
128 if beta.is_nan() {
129 return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
130 }
131 let beta = beta.abs();
134 const BETA_OVERFLOW_THRESHOLD: f64 = 708.0;
139 if beta > BETA_OVERFLOW_THRESHOLD {
140 return Err(FerrayError::invalid_value(format!(
141 "kaiser: |beta| = {beta} exceeds the safe range ({BETA_OVERFLOW_THRESHOLD}) \
142 for f64 I_0; the window would be NaN everywhere"
143 )));
144 }
145 let i0_beta = bessel_i0_scalar::<f64>(beta);
146 let alpha = (m.saturating_sub(1)) as f64 / 2.0;
147 gen_window(m, |n| {
148 let t = (n as f64 - alpha) / alpha;
149 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
150 bessel_i0_scalar::<f64>(arg) / i0_beta
151 })
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
160 assert_eq!(
161 actual.len(),
162 expected.len(),
163 "length mismatch: {} vs {}",
164 actual.len(),
165 expected.len()
166 );
167 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
168 assert!(
169 (a - e).abs() <= tol,
170 "element {i}: {a} vs {e} (diff = {})",
171 (a - e).abs()
172 );
173 }
174 }
175
176 #[test]
181 fn bartlett_m0() {
182 let w = bartlett(0).unwrap();
183 assert_eq!(w.shape(), &[0]);
184 }
185
186 #[test]
187 fn bartlett_m1() {
188 let w = bartlett(1).unwrap();
189 assert_eq!(w.as_slice().unwrap(), &[1.0]);
190 }
191
192 #[test]
193 fn blackman_m0() {
194 let w = blackman(0).unwrap();
195 assert_eq!(w.shape(), &[0]);
196 }
197
198 #[test]
199 fn blackman_m1() {
200 let w = blackman(1).unwrap();
201 assert_eq!(w.as_slice().unwrap(), &[1.0]);
202 }
203
204 #[test]
205 fn hamming_m0() {
206 let w = hamming(0).unwrap();
207 assert_eq!(w.shape(), &[0]);
208 }
209
210 #[test]
211 fn hamming_m1() {
212 let w = hamming(1).unwrap();
213 assert_eq!(w.as_slice().unwrap(), &[1.0]);
214 }
215
216 #[test]
217 fn hanning_m0() {
218 let w = hanning(0).unwrap();
219 assert_eq!(w.shape(), &[0]);
220 }
221
222 #[test]
223 fn hanning_m1() {
224 let w = hanning(1).unwrap();
225 assert_eq!(w.as_slice().unwrap(), &[1.0]);
226 }
227
228 #[test]
229 fn kaiser_m0() {
230 let w = kaiser(0, 14.0).unwrap();
231 assert_eq!(w.shape(), &[0]);
232 }
233
234 #[test]
235 fn kaiser_m1() {
236 let w = kaiser(1, 14.0).unwrap();
237 assert_eq!(w.as_slice().unwrap(), &[1.0]);
238 }
239
240 #[test]
241 fn kaiser_negative_beta() {
242 let pos = kaiser(5, 1.0).unwrap();
244 let neg = kaiser(5, -1.0).unwrap();
245 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
246 }
247
248 #[test]
249 fn kaiser_nan_beta() {
250 assert!(kaiser(5, f64::NAN).is_err());
251 }
252
253 #[test]
258 fn bartlett_5_ac1() {
259 let w = bartlett(5).unwrap();
260 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
261 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
262 }
263
264 #[test]
269 fn kaiser_5_14_ac2() {
270 let w = kaiser(5, 14.0).unwrap();
271 let s = w.as_slice().unwrap();
272 assert_eq!(s.len(), 5);
273 let expected: [f64; 5] = [
275 7.72686684e-06,
276 1.64932188e-01,
277 1.0,
278 1.64932188e-01,
279 7.72686684e-06,
280 ];
281 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
283 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
284 assert!(
285 (a - e).abs() <= tol,
286 "kaiser element {i}: {a} vs {e} (diff = {})",
287 (a - e).abs()
288 );
289 }
290 }
291
292 #[test]
298 fn blackman_5() {
299 let w = blackman(5).unwrap();
300 assert_eq!(w.shape(), &[5]);
301 let s = w.as_slice().unwrap();
302 let expected = [
303 -1.38777878e-17,
304 3.40000000e-01,
305 1.00000000e+00,
306 3.40000000e-01,
307 -1.38777878e-17,
308 ];
309 assert_close(s, &expected, 1e-10);
310 }
311
312 #[test]
314 fn hamming_5() {
315 let w = hamming(5).unwrap();
316 assert_eq!(w.shape(), &[5]);
317 let s = w.as_slice().unwrap();
318 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
319 assert_close(s, &expected, 1e-14);
320 }
321
322 #[test]
324 fn hanning_5() {
325 let w = hanning(5).unwrap();
326 assert_eq!(w.shape(), &[5]);
327 let s = w.as_slice().unwrap();
328 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
329 assert_close(s, &expected, 1e-14);
330 }
331
332 #[test]
334 fn bartlett_12() {
335 let w = bartlett(12).unwrap();
336 assert_eq!(w.shape(), &[12]);
337 let s = w.as_slice().unwrap();
338 assert!((s[0] - 0.0).abs() < 1e-14);
340 assert!((s[11] - 0.0).abs() < 1e-14);
341 for i in 0..6 {
343 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
344 }
345 }
346
347 #[test]
349 fn all_windows_symmetric() {
350 let m = 7;
351 let windows: Vec<Array<f64, Ix1>> = vec![
352 bartlett(m).unwrap(),
353 blackman(m).unwrap(),
354 hamming(m).unwrap(),
355 hanning(m).unwrap(),
356 kaiser(m, 5.0).unwrap(),
357 ];
358 for (idx, w) in windows.iter().enumerate() {
359 let s = w.as_slice().unwrap();
360 for i in 0..m / 2 {
361 assert!(
362 (s[i] - s[m - 1 - i]).abs() < 1e-12,
363 "window {idx} not symmetric at {i}"
364 );
365 }
366 }
367 }
368
369 #[test]
371 fn all_windows_peak_at_center() {
372 let m = 9;
373 let windows: Vec<Array<f64, Ix1>> = vec![
374 bartlett(m).unwrap(),
375 blackman(m).unwrap(),
376 hamming(m).unwrap(),
377 hanning(m).unwrap(),
378 kaiser(m, 5.0).unwrap(),
379 ];
380 for (idx, w) in windows.iter().enumerate() {
381 let s = w.as_slice().unwrap();
382 let center = s[m / 2];
383 assert!(
384 (center - 1.0).abs() < 1e-10,
385 "window {idx} center = {center}, expected 1.0"
386 );
387 }
388 }
389
390 #[test]
392 fn kaiser_beta_zero() {
393 let w = kaiser(5, 0.0).unwrap();
394 let s = w.as_slice().unwrap();
395 for &v in s {
396 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
397 }
398 }
399
400 #[test]
401 fn bessel_i0_scalar_zero() {
402 assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-6);
403 }
404
405 #[test]
406 fn bessel_i0_scalar_known() {
407 assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-7);
412 assert!((bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_443).abs() < 1e-5);
414 let expected_i0_10 = 2_815.716_628_466_254;
416 assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-5);
417 }
418
419 #[test]
422 fn bartlett_m2_is_zeros() {
423 let w = bartlett(2).unwrap();
427 assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
428 }
429
430 #[test]
431 fn hanning_m2_is_zeros() {
432 let w = hanning(2).unwrap();
436 let s = w.as_slice().unwrap();
437 assert!(s[0].abs() < 1e-14);
438 assert!(s[1].abs() < 1e-14);
439 }
440
441 #[test]
442 fn bartlett_even_length_is_symmetric() {
443 for &m in &[4usize, 6, 8, 10] {
446 let w = bartlett(m).unwrap();
447 let s = w.as_slice().unwrap();
448 for i in 0..m / 2 {
449 assert!(
450 (s[i] - s[m - 1 - i]).abs() < 1e-14,
451 "bartlett({m}) not symmetric at {i}"
452 );
453 }
454 }
455 }
456
457 #[test]
458 fn blackman_even_length_is_symmetric() {
459 for &m in &[4usize, 6, 8, 10] {
460 let w = blackman(m).unwrap();
461 let s = w.as_slice().unwrap();
462 for i in 0..m / 2 {
463 assert!(
464 (s[i] - s[m - 1 - i]).abs() < 1e-14,
465 "blackman({m}) not symmetric at {i}"
466 );
467 }
468 }
469 }
470
471 #[test]
472 fn kaiser_large_beta_errors() {
473 assert!(kaiser(8, 800.0).is_err());
476 assert!(kaiser(8, 1000.0).is_err());
477 assert!(kaiser(8, 700.0).is_ok());
479 }
480}