Skip to content

Commit ed969eb

Browse files
committed
Add general cosine window
1 parent 82d166d commit ed969eb

File tree

2 files changed

+233
-5
lines changed

2 files changed

+233
-5
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use super::{extend, len_guard, truncate};
2+
use nalgebra::RealField;
3+
use num_traits::{real::Real, Float};
4+
5+
#[cfg(feature = "alloc")]
6+
use super::GetWindow;
7+
#[cfg(feature = "alloc")]
8+
use alloc::{vec, vec::Vec};
9+
10+
/// Collection of arguments for window `GeneralCosine` for use in [GetWindow].
11+
#[cfg(feature = "alloc")]
12+
#[derive(Debug, Clone, PartialEq)]
13+
pub struct GeneralCosine<A>
14+
where
15+
A: Real + Float,
16+
{
17+
/// Number of points in the output window. If zero, an empty array is returned in [GetWindow].
18+
pub m: usize,
19+
/// Sequence of weighting coefficients.
20+
pub a: Vec<A>, // Is there a better type, such as impl Into?
21+
/// Whether the window is symmetric.
22+
///
23+
/// When true, generates a symmetric window, for use in filter design.
24+
/// When false, generates a periodic window, for use in spectral analysis.
25+
pub sym: bool,
26+
}
27+
28+
impl<A> GeneralCosine<A>
29+
where
30+
A: Real + Float,
31+
{
32+
/// Returns a GeneralCosine struct.
33+
///
34+
/// # Parameters
35+
/// * `m`:
36+
/// Number of points in the output window. If zero, an empty array is returned.
37+
/// * `a`:
38+
/// Sequence of weighting coefficients. This uses the convention of being centered on the
39+
/// origin, so these will typically all be positive numbers, not alternating sign.
40+
/// * `sym`:
41+
/// When true, generates a symmetric window, for use in filter design.
42+
/// When false, generates a periodic window, for use in spectral analysis.
43+
pub fn new(m: usize, a: Vec<A>, sym: bool) -> Self {
44+
GeneralCosine { m, a, sym }
45+
}
46+
}
47+
48+
#[cfg(feature = "alloc")]
49+
impl<A> GetWindow for GeneralCosine<A>
50+
where
51+
A: Real + Float,
52+
{
53+
/// Return a window of type: GeneralCosine.
54+
///
55+
/// The general cosine window is a generic weighted sum of cosine terms.
56+
///
57+
/// # Parameters
58+
/// `self`: [GeneralCosine]
59+
///
60+
/// # Returns
61+
/// `w`: `vec<F>`
62+
/// The window, with the maximum value normalized to 1 (though the value 1 does not appear
63+
/// if `M` is even and `sym` is True).
64+
///
65+
/// # Example
66+
/// We can create a `flat-top` window named "HFT90D" as following:
67+
/// ```
68+
/// use sci_rs::signal::windows::{GeneralCosine, GetWindow};
69+
///
70+
/// let hfd90 = [1., 1.942604, 1.340318, 0.440811, 0.043097].into();
71+
/// let window: Vec<f64> = GeneralCosine::new(30, hfd90, true).get_window();
72+
/// ```
73+
///
74+
/// This is equivalent to the Python code:
75+
/// ```custom,{class=language-python}
76+
/// from scipy.signal.windows import general_cosine
77+
/// HFT90D = [1, 1.942604, 1.340318, 0.440811, 0.043097]
78+
/// window = general_cosine(30, HFT90D, sym=False)
79+
/// ```
80+
///
81+
/// # References
82+
/// <https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.general_cosine.html>
83+
#[cfg(feature = "alloc")]
84+
fn get_window<F>(&self) -> Vec<F>
85+
where
86+
F: Real + Float + RealField,
87+
{
88+
if len_guard(self.m) {
89+
return Vec::<F>::new();
90+
}
91+
let (m, needs_trunc) = extend(self.m, self.sym);
92+
93+
let linspace = (0..m).map(|i| F::from(i).unwrap());
94+
let fac: _ = linspace.map(|i| F::two_pi() * i / F::from(m - 1).unwrap() - F::pi());
95+
let w: Vec<_> = self
96+
.a
97+
.iter()
98+
.enumerate()
99+
.map(|(k, a)| {
100+
fac.clone()
101+
.map(move |f| Float::cos(f * F::from(k).unwrap()) * F::from(*a).unwrap())
102+
})
103+
.fold(
104+
vec![F::from(0).unwrap(); fac.clone().count()],
105+
// Would this have made more sense if using ndarray::Array1? No, Array1 has no pop.
106+
|acc, x| acc.iter().zip(x).map(|(&a, b)| a + b).collect(),
107+
)
108+
.into_iter()
109+
.collect();
110+
111+
return truncate(w, needs_trunc);
112+
}
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use super::*;
118+
use approx::assert_abs_diff_eq;
119+
120+
#[test]
121+
fn general_cosine_scipy_eg() {
122+
// Created with
123+
// >>> from scipy.signal.windows import general_cosine
124+
// >>> HFT90D = [1, 1.942604, 1.340318, 0.440811, 0.043097]
125+
// >>> window = general_cosine(30, HFT90D, sym=False)
126+
let expected = vec![
127+
-1.04083409e-16,
128+
-3.49808964e-03,
129+
-1.85322176e-02,
130+
-5.60667246e-02,
131+
-1.25488810e-01,
132+
-2.22198500e-01,
133+
-3.14696393e-01,
134+
-3.38497088e-01,
135+
-2.04818447e-01,
136+
1.72651725e-01,
137+
8.38783500e-01,
138+
1.76097559e+00,
139+
2.81469639e+00,
140+
3.80321808e+00,
141+
4.51005597e+00,
142+
4.76683000e+00,
143+
4.51005597e+00,
144+
3.80321808e+00,
145+
2.81469639e+00,
146+
1.76097559e+00,
147+
8.38783500e-01,
148+
1.72651725e-01,
149+
-2.04818447e-01,
150+
-3.38497088e-01,
151+
-3.14696393e-01,
152+
-2.22198500e-01,
153+
-1.25488810e-01,
154+
-5.60667246e-02,
155+
-1.85322176e-02,
156+
-3.49808964e-03,
157+
];
158+
159+
let hfd90 = [1., 1.942604, 1.340318, 0.440811, 0.043097].into();
160+
let gc = GeneralCosine::new(30, hfd90, false);
161+
assert_vec_eq(expected, gc.get_window());
162+
}
163+
164+
#[test]
165+
fn constant() {
166+
let x = 0.42;
167+
let n = 6;
168+
let expected = vec![x; n];
169+
170+
assert_eq!(
171+
expected,
172+
GeneralCosine::new(n, vec![x], true).get_window::<f64>()
173+
)
174+
}
175+
176+
#[test]
177+
fn case_b() {
178+
// Created with
179+
// >>> from scipy.signal.windows import general_cosine
180+
// >>> n = 20
181+
// >>> a = [0.42, 0.50]
182+
// >>> window = general_cosine(30, a, sym=False)
183+
let n = 20;
184+
let a = vec![0.42, 0.50];
185+
let expected = vec![
186+
-0.08,
187+
-0.05552826,
188+
0.0154915,
189+
0.12610737,
190+
0.2654915,
191+
0.42,
192+
0.5745085,
193+
0.71389263,
194+
0.8245085,
195+
0.89552826,
196+
0.92,
197+
0.89552826,
198+
0.8245085,
199+
0.71389263,
200+
0.5745085,
201+
0.42,
202+
0.2654915,
203+
0.12610737,
204+
0.0154915,
205+
-0.05552826,
206+
];
207+
208+
assert_vec_eq(expected, GeneralCosine::new(n, a, false).get_window());
209+
}
210+
211+
#[track_caller]
212+
fn assert_vec_eq(a: Vec<f64>, b: Vec<f64>) {
213+
for (a, b) in a.into_iter().zip(b) {
214+
assert_abs_diff_eq!(a, b, epsilon = 1e-6);
215+
}
216+
}
217+
}

sci-rs/src/signal/windows/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(feature = "alloc")]
22
use alloc::vec::Vec;
3+
use nalgebra::RealField;
34
use num_traits::{real::Real, Float};
45

56
/// Corresponding window representation for tuple-structs of [Window] variants.
@@ -25,7 +26,7 @@ pub trait GetWindow {
2526
#[cfg(feature = "alloc")]
2627
fn get_window<W>(&self) -> Vec<W>
2728
where
28-
W: Real + Float;
29+
W: Real + Float + RealField;
2930
}
3031

3132
/// Private function for windows implementing [GetWindow]
@@ -57,14 +58,23 @@ fn truncate<'a, W>(mut w: Vec<W>, needed: bool) -> Vec<W> {
5758
}
5859

5960
mod boxcar;
61+
mod general_cosine;
6062
mod triangle;
6163
pub use boxcar::Boxcar;
64+
pub use general_cosine::GeneralCosine;
6265
pub use triangle::Triangle;
6366

64-
/// todo
67+
/// This collects all structs that implement the [GetWindow] trait.
68+
/// This allows for running .get_window() on the struct, which can then be, for example, used in
69+
/// Firwin.
6570
// Ordering is as in accordance with
6671
// https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.get_window.html.
67-
pub enum Window {
72+
// Is it possible for the enums which wraps the structs to only require the generic that the struct
73+
// implements GetWindow?
74+
pub enum Window<A>
75+
where
76+
A: Float,
77+
{
6878
/// Boxcar window, also known as a rectangular window or Dirichlet window; This is equivalent
6979
/// to no window at all.
7080
Boxcar(Boxcar),
@@ -89,8 +99,9 @@ pub enum Window {
8999
// Kaiser, // Needs Beta
90100
// KaiserBesselDerived, // Needs Beta
91101
// Gaussian, // Needs Standard Deviation
92-
// Generic weighted sum of cosine term windows.
93-
// GenericCosine(GenericCosine<T>), // Needs Weighting Coefficients
102+
/// Generic weighted sum of cosine term windows.
103+
// Needs Weighting Coefficients
104+
GeneralCosine(GeneralCosine<A>),
94105
// GeneralGaussian, // Needs Power, Width
95106
// GeneralHamming, // Needs Window Coefficients.
96107
// Dpss, // Needs Normalized Half-Bandwidth.

0 commit comments

Comments
 (0)