1use ferray_core::Array;
7use ferray_core::dimension::Ix1;
8use ferray_core::error::{FerrayError, FerrayResult};
9
10use std::f64::consts::PI;
11
12use ferray_ufunc::ops::special::bessel_i0_scalar;
18
19#[inline]
24fn gen_window<F: FnMut(usize) -> f64>(m: usize, mut f: F) -> FerrayResult<Array<f64, Ix1>> {
25 if m == 0 {
26 return Array::from_vec(Ix1::new([0]), vec![]);
27 }
28 if m == 1 {
29 return Array::from_vec(Ix1::new([1]), vec![1.0]);
30 }
31 let mut data = Vec::with_capacity(m);
32 for n in 0..m {
33 data.push(f(n));
34 }
35 Array::from_vec(Ix1::new([m]), data)
36}
37
38pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
48 let half = (m.saturating_sub(1)) as f64 / 2.0;
49 gen_window(m, |n| 1.0 - ((n as f64 - half) / half).abs())
50}
51
52pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
62 let denom = (m.saturating_sub(1)) as f64;
63 gen_window(m, |n| {
64 let x = n as f64;
65 0.42 - 0.5 * (2.0 * PI * x / denom).cos() + 0.08 * (4.0 * PI * x / denom).cos()
66 })
67}
68
69pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
79 let denom = (m.saturating_sub(1)) as f64;
80 gen_window(m, |n| 0.54 - 0.46 * (2.0 * PI * n as f64 / denom).cos())
81}
82
83pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
93 let denom = (m.saturating_sub(1)) as f64;
94 gen_window(m, |n| 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos()))
95}
96
97pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
118 if beta.is_nan() {
119 return Err(FerrayError::invalid_value("kaiser: beta must not be NaN"));
120 }
121 let beta = beta.abs();
124 const BETA_OVERFLOW_THRESHOLD: f64 = 708.0;
129 if beta > BETA_OVERFLOW_THRESHOLD {
130 return Err(FerrayError::invalid_value(format!(
131 "kaiser: |beta| = {beta} exceeds the safe range ({BETA_OVERFLOW_THRESHOLD}) \
132 for f64 I_0; the window would be NaN everywhere"
133 )));
134 }
135 let i0_beta = bessel_i0_scalar::<f64>(beta);
136 let alpha = (m.saturating_sub(1)) as f64 / 2.0;
137 gen_window(m, |n| {
138 let t = (n as f64 - alpha) / alpha;
139 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
140 bessel_i0_scalar::<f64>(arg) / i0_beta
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
150 assert_eq!(
151 actual.len(),
152 expected.len(),
153 "length mismatch: {} vs {}",
154 actual.len(),
155 expected.len()
156 );
157 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
158 assert!(
159 (a - e).abs() <= tol,
160 "element {i}: {a} vs {e} (diff = {})",
161 (a - e).abs()
162 );
163 }
164 }
165
166 #[test]
171 fn bartlett_m0() {
172 let w = bartlett(0).unwrap();
173 assert_eq!(w.shape(), &[0]);
174 }
175
176 #[test]
177 fn bartlett_m1() {
178 let w = bartlett(1).unwrap();
179 assert_eq!(w.as_slice().unwrap(), &[1.0]);
180 }
181
182 #[test]
183 fn blackman_m0() {
184 let w = blackman(0).unwrap();
185 assert_eq!(w.shape(), &[0]);
186 }
187
188 #[test]
189 fn blackman_m1() {
190 let w = blackman(1).unwrap();
191 assert_eq!(w.as_slice().unwrap(), &[1.0]);
192 }
193
194 #[test]
195 fn hamming_m0() {
196 let w = hamming(0).unwrap();
197 assert_eq!(w.shape(), &[0]);
198 }
199
200 #[test]
201 fn hamming_m1() {
202 let w = hamming(1).unwrap();
203 assert_eq!(w.as_slice().unwrap(), &[1.0]);
204 }
205
206 #[test]
207 fn hanning_m0() {
208 let w = hanning(0).unwrap();
209 assert_eq!(w.shape(), &[0]);
210 }
211
212 #[test]
213 fn hanning_m1() {
214 let w = hanning(1).unwrap();
215 assert_eq!(w.as_slice().unwrap(), &[1.0]);
216 }
217
218 #[test]
219 fn kaiser_m0() {
220 let w = kaiser(0, 14.0).unwrap();
221 assert_eq!(w.shape(), &[0]);
222 }
223
224 #[test]
225 fn kaiser_m1() {
226 let w = kaiser(1, 14.0).unwrap();
227 assert_eq!(w.as_slice().unwrap(), &[1.0]);
228 }
229
230 #[test]
231 fn kaiser_negative_beta() {
232 let pos = kaiser(5, 1.0).unwrap();
234 let neg = kaiser(5, -1.0).unwrap();
235 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
236 }
237
238 #[test]
239 fn kaiser_nan_beta() {
240 assert!(kaiser(5, f64::NAN).is_err());
241 }
242
243 #[test]
248 fn bartlett_5_ac1() {
249 let w = bartlett(5).unwrap();
250 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
251 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
252 }
253
254 #[test]
259 fn kaiser_5_14_ac2() {
260 let w = kaiser(5, 14.0).unwrap();
261 let s = w.as_slice().unwrap();
262 assert_eq!(s.len(), 5);
263 let expected: [f64; 5] = [
265 7.72686684e-06,
266 1.64932188e-01,
267 1.0,
268 1.64932188e-01,
269 7.72686684e-06,
270 ];
271 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
273 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
274 assert!(
275 (a - e).abs() <= tol,
276 "kaiser element {i}: {a} vs {e} (diff = {})",
277 (a - e).abs()
278 );
279 }
280 }
281
282 #[test]
288 fn blackman_5() {
289 let w = blackman(5).unwrap();
290 assert_eq!(w.shape(), &[5]);
291 let s = w.as_slice().unwrap();
292 let expected = [
293 -1.38777878e-17,
294 3.40000000e-01,
295 1.00000000e+00,
296 3.40000000e-01,
297 -1.38777878e-17,
298 ];
299 assert_close(s, &expected, 1e-10);
300 }
301
302 #[test]
304 fn hamming_5() {
305 let w = hamming(5).unwrap();
306 assert_eq!(w.shape(), &[5]);
307 let s = w.as_slice().unwrap();
308 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
309 assert_close(s, &expected, 1e-14);
310 }
311
312 #[test]
314 fn hanning_5() {
315 let w = hanning(5).unwrap();
316 assert_eq!(w.shape(), &[5]);
317 let s = w.as_slice().unwrap();
318 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
319 assert_close(s, &expected, 1e-14);
320 }
321
322 #[test]
324 fn bartlett_12() {
325 let w = bartlett(12).unwrap();
326 assert_eq!(w.shape(), &[12]);
327 let s = w.as_slice().unwrap();
328 assert!((s[0] - 0.0).abs() < 1e-14);
330 assert!((s[11] - 0.0).abs() < 1e-14);
331 for i in 0..6 {
333 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
334 }
335 }
336
337 #[test]
339 fn all_windows_symmetric() {
340 let m = 7;
341 let windows: Vec<Array<f64, Ix1>> = vec![
342 bartlett(m).unwrap(),
343 blackman(m).unwrap(),
344 hamming(m).unwrap(),
345 hanning(m).unwrap(),
346 kaiser(m, 5.0).unwrap(),
347 ];
348 for (idx, w) in windows.iter().enumerate() {
349 let s = w.as_slice().unwrap();
350 for i in 0..m / 2 {
351 assert!(
352 (s[i] - s[m - 1 - i]).abs() < 1e-12,
353 "window {idx} not symmetric at {i}"
354 );
355 }
356 }
357 }
358
359 #[test]
361 fn all_windows_peak_at_center() {
362 let m = 9;
363 let windows: Vec<Array<f64, Ix1>> = vec![
364 bartlett(m).unwrap(),
365 blackman(m).unwrap(),
366 hamming(m).unwrap(),
367 hanning(m).unwrap(),
368 kaiser(m, 5.0).unwrap(),
369 ];
370 for (idx, w) in windows.iter().enumerate() {
371 let s = w.as_slice().unwrap();
372 let center = s[m / 2];
373 assert!(
374 (center - 1.0).abs() < 1e-10,
375 "window {idx} center = {center}, expected 1.0"
376 );
377 }
378 }
379
380 #[test]
382 fn kaiser_beta_zero() {
383 let w = kaiser(5, 0.0).unwrap();
384 let s = w.as_slice().unwrap();
385 for &v in s {
386 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
387 }
388 }
389
390 #[test]
391 fn bessel_i0_scalar_zero() {
392 assert!((bessel_i0_scalar::<f64>(0.0) - 1.0).abs() < 1e-6);
393 }
394
395 #[test]
396 fn bessel_i0_scalar_known() {
397 assert!((bessel_i0_scalar::<f64>(1.0) - 1.266_065_877_752_008_2).abs() < 1e-7);
402 assert!((bessel_i0_scalar::<f64>(5.0) - 27.239_871_823_604_443).abs() < 1e-5);
404 let expected_i0_10 = 2_815.716_628_466_254;
406 assert!((bessel_i0_scalar::<f64>(10.0) - expected_i0_10).abs() / expected_i0_10 < 1e-5);
407 }
408
409 #[test]
412 fn bartlett_m2_is_zeros() {
413 let w = bartlett(2).unwrap();
417 assert_eq!(w.as_slice().unwrap(), &[0.0, 0.0]);
418 }
419
420 #[test]
421 fn hanning_m2_is_zeros() {
422 let w = hanning(2).unwrap();
426 let s = w.as_slice().unwrap();
427 assert!(s[0].abs() < 1e-14);
428 assert!(s[1].abs() < 1e-14);
429 }
430
431 #[test]
432 fn bartlett_even_length_is_symmetric() {
433 for &m in &[4usize, 6, 8, 10] {
436 let w = bartlett(m).unwrap();
437 let s = w.as_slice().unwrap();
438 for i in 0..m / 2 {
439 assert!(
440 (s[i] - s[m - 1 - i]).abs() < 1e-14,
441 "bartlett({m}) not symmetric at {i}"
442 );
443 }
444 }
445 }
446
447 #[test]
448 fn blackman_even_length_is_symmetric() {
449 for &m in &[4usize, 6, 8, 10] {
450 let w = blackman(m).unwrap();
451 let s = w.as_slice().unwrap();
452 for i in 0..m / 2 {
453 assert!(
454 (s[i] - s[m - 1 - i]).abs() < 1e-14,
455 "blackman({m}) not symmetric at {i}"
456 );
457 }
458 }
459 }
460
461 #[test]
462 fn kaiser_large_beta_errors() {
463 assert!(kaiser(8, 800.0).is_err());
466 assert!(kaiser(8, 1000.0).is_err());
467 assert!(kaiser(8, 700.0).is_ok());
469 }
470}