1use ferray_core::Array;
7use ferray_core::dimension::Ix1;
8use ferray_core::error::{FerrayError, FerrayResult};
9
10use std::f64::consts::PI;
11
12fn bessel_i0_scalar(x: f64) -> f64 {
17 let ax = x.abs();
18
19 if ax <= 3.75 {
20 let t = (ax / 3.75).powi(2);
21 1.0 + t
22 * (3.5156229
23 + t * (3.0899424
24 + t * (1.2067492 + t * (0.2659732 + t * (0.0360768 + t * 0.0045813)))))
25 } else {
26 let t = 3.75 / ax;
27 let poly = 0.39894228
28 + t * (0.01328592
29 + t * (0.00225319
30 + t * (-0.00157565
31 + t * (0.00916281
32 + t * (-0.02057706
33 + t * (0.02635537 + t * (-0.01647633 + t * 0.00392377)))))));
34 poly * ax.exp() / ax.sqrt()
35 }
36}
37
38pub fn bartlett(m: usize) -> FerrayResult<Array<f64, Ix1>> {
52 if m == 0 {
53 return Array::from_vec(Ix1::new([0]), vec![]);
54 }
55 if m == 1 {
56 return Array::from_vec(Ix1::new([1]), vec![1.0]);
57 }
58
59 let half = (m - 1) as f64 / 2.0;
60 let mut data = Vec::with_capacity(m);
61 for n in 0..m {
62 let val = 1.0 - ((n as f64 - half) / half).abs();
63 data.push(val);
64 }
65 Array::from_vec(Ix1::new([m]), data)
66}
67
68pub fn blackman(m: usize) -> FerrayResult<Array<f64, Ix1>> {
82 if m == 0 {
83 return Array::from_vec(Ix1::new([0]), vec![]);
84 }
85 if m == 1 {
86 return Array::from_vec(Ix1::new([1]), vec![1.0]);
87 }
88
89 let denom = (m - 1) as f64;
90 let mut data = Vec::with_capacity(m);
91 for n in 0..m {
92 let x = n as f64;
93 let val = 0.42 - 0.5 * (2.0 * PI * x / denom).cos() + 0.08 * (4.0 * PI * x / denom).cos();
94 data.push(val);
95 }
96 Array::from_vec(Ix1::new([m]), data)
97}
98
99pub fn hamming(m: usize) -> FerrayResult<Array<f64, Ix1>> {
113 if m == 0 {
114 return Array::from_vec(Ix1::new([0]), vec![]);
115 }
116 if m == 1 {
117 return Array::from_vec(Ix1::new([1]), vec![1.0]);
118 }
119
120 let denom = (m - 1) as f64;
121 let mut data = Vec::with_capacity(m);
122 for n in 0..m {
123 let val = 0.54 - 0.46 * (2.0 * PI * n as f64 / denom).cos();
124 data.push(val);
125 }
126 Array::from_vec(Ix1::new([m]), data)
127}
128
129pub fn hanning(m: usize) -> FerrayResult<Array<f64, Ix1>> {
143 if m == 0 {
144 return Array::from_vec(Ix1::new([0]), vec![]);
145 }
146 if m == 1 {
147 return Array::from_vec(Ix1::new([1]), vec![1.0]);
148 }
149
150 let denom = (m - 1) as f64;
151 let mut data = Vec::with_capacity(m);
152 for n in 0..m {
153 let val = 0.5 * (1.0 - (2.0 * PI * n as f64 / denom).cos());
154 data.push(val);
155 }
156 Array::from_vec(Ix1::new([m]), data)
157}
158
159pub fn kaiser(m: usize, beta: f64) -> FerrayResult<Array<f64, Ix1>> {
175 if beta.is_nan() {
176 return Err(FerrayError::invalid_value(
177 "kaiser: beta must not be NaN",
178 ));
179 }
180 let beta = beta.abs();
183
184 if m == 0 {
185 return Array::from_vec(Ix1::new([0]), vec![]);
186 }
187 if m == 1 {
188 return Array::from_vec(Ix1::new([1]), vec![1.0]);
189 }
190
191 let i0_beta = bessel_i0_scalar(beta);
192 let alpha = (m as f64 - 1.0) / 2.0;
193 let mut data = Vec::with_capacity(m);
194 for n in 0..m {
195 let t = (n as f64 - alpha) / alpha;
196 let arg = beta * (1.0 - t * t).max(0.0).sqrt();
197 data.push(bessel_i0_scalar(arg) / i0_beta);
198 }
199 Array::from_vec(Ix1::new([m]), data)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 fn assert_close(actual: &[f64], expected: &[f64], tol: f64) {
208 assert_eq!(
209 actual.len(),
210 expected.len(),
211 "length mismatch: {} vs {}",
212 actual.len(),
213 expected.len()
214 );
215 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
216 assert!(
217 (a - e).abs() <= tol,
218 "element {i}: {a} vs {e} (diff = {})",
219 (a - e).abs()
220 );
221 }
222 }
223
224 #[test]
229 fn bartlett_m0() {
230 let w = bartlett(0).unwrap();
231 assert_eq!(w.shape(), &[0]);
232 }
233
234 #[test]
235 fn bartlett_m1() {
236 let w = bartlett(1).unwrap();
237 assert_eq!(w.as_slice().unwrap(), &[1.0]);
238 }
239
240 #[test]
241 fn blackman_m0() {
242 let w = blackman(0).unwrap();
243 assert_eq!(w.shape(), &[0]);
244 }
245
246 #[test]
247 fn blackman_m1() {
248 let w = blackman(1).unwrap();
249 assert_eq!(w.as_slice().unwrap(), &[1.0]);
250 }
251
252 #[test]
253 fn hamming_m0() {
254 let w = hamming(0).unwrap();
255 assert_eq!(w.shape(), &[0]);
256 }
257
258 #[test]
259 fn hamming_m1() {
260 let w = hamming(1).unwrap();
261 assert_eq!(w.as_slice().unwrap(), &[1.0]);
262 }
263
264 #[test]
265 fn hanning_m0() {
266 let w = hanning(0).unwrap();
267 assert_eq!(w.shape(), &[0]);
268 }
269
270 #[test]
271 fn hanning_m1() {
272 let w = hanning(1).unwrap();
273 assert_eq!(w.as_slice().unwrap(), &[1.0]);
274 }
275
276 #[test]
277 fn kaiser_m0() {
278 let w = kaiser(0, 14.0).unwrap();
279 assert_eq!(w.shape(), &[0]);
280 }
281
282 #[test]
283 fn kaiser_m1() {
284 let w = kaiser(1, 14.0).unwrap();
285 assert_eq!(w.as_slice().unwrap(), &[1.0]);
286 }
287
288 #[test]
289 fn kaiser_negative_beta() {
290 let pos = kaiser(5, 1.0).unwrap();
292 let neg = kaiser(5, -1.0).unwrap();
293 assert_eq!(pos.as_slice().unwrap(), neg.as_slice().unwrap());
294 }
295
296 #[test]
297 fn kaiser_nan_beta() {
298 assert!(kaiser(5, f64::NAN).is_err());
299 }
300
301 #[test]
306 fn bartlett_5_ac1() {
307 let w = bartlett(5).unwrap();
308 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
309 assert_close(w.as_slice().unwrap(), &expected, 1e-14);
310 }
311
312 #[test]
317 fn kaiser_5_14_ac2() {
318 let w = kaiser(5, 14.0).unwrap();
319 let s = w.as_slice().unwrap();
320 assert_eq!(s.len(), 5);
321 let expected: [f64; 5] = [
323 7.72686684e-06,
324 1.64932188e-01,
325 1.0,
326 1.64932188e-01,
327 7.72686684e-06,
328 ];
329 for (i, (&a, &e)) in s.iter().zip(expected.iter()).enumerate() {
331 let tol = if e.abs() < 1e-4 { 1e-8 } else { 1e-6 };
332 assert!(
333 (a - e).abs() <= tol,
334 "kaiser element {i}: {a} vs {e} (diff = {})",
335 (a - e).abs()
336 );
337 }
338 }
339
340 #[test]
346 fn blackman_5() {
347 let w = blackman(5).unwrap();
348 assert_eq!(w.shape(), &[5]);
349 let s = w.as_slice().unwrap();
350 let expected = [
351 -1.38777878e-17,
352 3.40000000e-01,
353 1.00000000e+00,
354 3.40000000e-01,
355 -1.38777878e-17,
356 ];
357 assert_close(s, &expected, 1e-10);
358 }
359
360 #[test]
362 fn hamming_5() {
363 let w = hamming(5).unwrap();
364 assert_eq!(w.shape(), &[5]);
365 let s = w.as_slice().unwrap();
366 let expected = [0.08, 0.54, 1.0, 0.54, 0.08];
367 assert_close(s, &expected, 1e-14);
368 }
369
370 #[test]
372 fn hanning_5() {
373 let w = hanning(5).unwrap();
374 assert_eq!(w.shape(), &[5]);
375 let s = w.as_slice().unwrap();
376 let expected = [0.0, 0.5, 1.0, 0.5, 0.0];
377 assert_close(s, &expected, 1e-14);
378 }
379
380 #[test]
382 fn bartlett_12() {
383 let w = bartlett(12).unwrap();
384 assert_eq!(w.shape(), &[12]);
385 let s = w.as_slice().unwrap();
386 assert!((s[0] - 0.0).abs() < 1e-14);
388 assert!((s[11] - 0.0).abs() < 1e-14);
389 for i in 0..6 {
391 assert!((s[i] - s[11 - i]).abs() < 1e-14, "symmetry at {i}");
392 }
393 }
394
395 #[test]
397 fn all_windows_symmetric() {
398 let m = 7;
399 let windows: Vec<Array<f64, Ix1>> = vec![
400 bartlett(m).unwrap(),
401 blackman(m).unwrap(),
402 hamming(m).unwrap(),
403 hanning(m).unwrap(),
404 kaiser(m, 5.0).unwrap(),
405 ];
406 for (idx, w) in windows.iter().enumerate() {
407 let s = w.as_slice().unwrap();
408 for i in 0..m / 2 {
409 assert!(
410 (s[i] - s[m - 1 - i]).abs() < 1e-12,
411 "window {idx} not symmetric at {i}"
412 );
413 }
414 }
415 }
416
417 #[test]
419 fn all_windows_peak_at_center() {
420 let m = 9;
421 let windows: Vec<Array<f64, Ix1>> = vec![
422 bartlett(m).unwrap(),
423 blackman(m).unwrap(),
424 hamming(m).unwrap(),
425 hanning(m).unwrap(),
426 kaiser(m, 5.0).unwrap(),
427 ];
428 for (idx, w) in windows.iter().enumerate() {
429 let s = w.as_slice().unwrap();
430 let center = s[m / 2];
431 assert!(
432 (center - 1.0).abs() < 1e-10,
433 "window {idx} center = {center}, expected 1.0"
434 );
435 }
436 }
437
438 #[test]
440 fn kaiser_beta_zero() {
441 let w = kaiser(5, 0.0).unwrap();
442 let s = w.as_slice().unwrap();
443 for &v in s {
444 assert!((v - 1.0).abs() < 1e-10, "expected 1.0, got {v}");
445 }
446 }
447
448 #[test]
449 fn bessel_i0_scalar_zero() {
450 assert!((bessel_i0_scalar(0.0) - 1.0).abs() < 1e-6);
451 }
452
453 #[test]
454 fn bessel_i0_scalar_known() {
455 assert!((bessel_i0_scalar(1.0) - 1.2660658).abs() < 1e-4);
457 assert!((bessel_i0_scalar(5.0) - 27.2398718).abs() < 1e-2);
459 }
460}