1use std::f64::consts::PI;
22use std::ops::{Add, Mul, Sub};
23
24#[derive(Debug, Clone, Copy, PartialEq, Default)]
26pub struct Complex {
27 pub re: f64,
29 pub im: f64,
31}
32
33impl Complex {
34 pub const fn new(re: f64, im: f64) -> Self {
36 Self { re, im }
37 }
38
39 pub const fn real(re: f64) -> Self {
41 Self { re, im: 0.0 }
42 }
43
44 pub fn from_polar_unit(theta: f64) -> Self {
46 Self {
47 re: theta.cos(),
48 im: theta.sin(),
49 }
50 }
51
52 pub fn conj(self) -> Self {
54 Self {
55 re: self.re,
56 im: -self.im,
57 }
58 }
59
60 pub fn norm(self) -> f64 {
62 self.re.hypot(self.im)
63 }
64
65 pub fn norm_sqr(self) -> f64 {
67 self.re * self.re + self.im * self.im
68 }
69
70 pub fn arg(self) -> f64 {
72 self.im.atan2(self.re)
73 }
74
75 pub fn scale(self, k: f64) -> Self {
77 Self {
78 re: self.re * k,
79 im: self.im * k,
80 }
81 }
82}
83
84impl Add for Complex {
85 type Output = Self;
86 fn add(self, rhs: Self) -> Self {
87 Self {
88 re: self.re + rhs.re,
89 im: self.im + rhs.im,
90 }
91 }
92}
93
94impl Sub for Complex {
95 type Output = Self;
96 fn sub(self, rhs: Self) -> Self {
97 Self {
98 re: self.re - rhs.re,
99 im: self.im - rhs.im,
100 }
101 }
102}
103
104impl Mul for Complex {
105 type Output = Self;
106 fn mul(self, rhs: Self) -> Self {
107 Self {
108 re: self.re * rhs.re - self.im * rhs.im,
109 im: self.re * rhs.im + self.im * rhs.re,
110 }
111 }
112}
113
114pub fn fft(input: &[Complex]) -> Vec<Complex> {
134 transform(input, false)
135}
136
137pub fn ifft(input: &[Complex]) -> Vec<Complex> {
153 let n = input.len();
154 if n == 0 {
155 return Vec::new();
156 }
157 let scale = 1.0 / n as f64;
158 transform(input, true)
159 .into_iter()
160 .map(|z| z.scale(scale))
161 .collect()
162}
163
164pub fn rfft(input: &[f64]) -> Vec<Complex> {
182 let complex: Vec<Complex> = input.iter().map(|&x| Complex::real(x)).collect();
183 fft(&complex)
184}
185
186fn transform(input: &[Complex], inverse: bool) -> Vec<Complex> {
188 let n = input.len();
189 if n <= 1 {
190 return input.to_vec();
191 }
192 let mut data = input.to_vec();
193 if n.is_power_of_two() {
194 radix2_in_place(&mut data, inverse);
195 data
196 } else {
197 bluestein(&data, inverse)
198 }
199}
200
201fn radix2_in_place(data: &mut [Complex], inverse: bool) {
203 let n = data.len();
204 debug_assert!(n.is_power_of_two());
205
206 let bits = n.trailing_zeros();
208 for i in 0..n {
209 let j = i.reverse_bits() >> (usize::BITS - bits);
210 if j > i {
211 data.swap(i, j);
212 }
213 }
214
215 let sign = if inverse { 1.0 } else { -1.0 };
216 let mut len = 2;
217 while len <= n {
218 let step = Complex::from_polar_unit(sign * 2.0 * PI / len as f64);
219 for start in (0..n).step_by(len) {
220 let mut w = Complex::real(1.0);
221 for k in 0..len / 2 {
222 let a = data[start + k];
223 let b = data[start + k + len / 2] * w;
224 data[start + k] = a + b;
225 data[start + k + len / 2] = a - b;
226 w = w * step;
227 }
228 }
229 len <<= 1;
230 }
231}
232
233fn bluestein(input: &[Complex], inverse: bool) -> Vec<Complex> {
239 let n = input.len();
240 let m = (2 * n - 1).next_power_of_two();
241 let sign = if inverse { 1.0 } else { -1.0 };
242
243 let chirp: Vec<Complex> = (0..n)
246 .map(|j| {
247 let jj = (j * j) % (2 * n);
248 Complex::from_polar_unit(sign * PI * jj as f64 / n as f64)
249 })
250 .collect();
251
252 let mut a = vec![Complex::default(); m];
253 for j in 0..n {
254 a[j] = input[j] * chirp[j];
255 }
256 let mut b = vec![Complex::default(); m];
257 b[0] = chirp[0].conj();
258 for j in 1..n {
259 let c = chirp[j].conj();
260 b[j] = c;
261 b[m - j] = c;
262 }
263
264 radix2_in_place(&mut a, false);
265 radix2_in_place(&mut b, false);
266 let mut c: Vec<Complex> = a.iter().zip(&b).map(|(&x, &y)| x * y).collect();
267 radix2_in_place(&mut c, true);
268 let scale = 1.0 / m as f64;
269
270 (0..n).map(|k| c[k].scale(scale) * chirp[k]).collect()
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use proptest::prelude::*;
277
278 fn naive_dft(x: &[Complex], inverse: bool) -> Vec<Complex> {
279 let n = x.len();
280 let sign = if inverse { 1.0 } else { -1.0 };
281 (0..n)
282 .map(|k| {
283 let mut acc = Complex::default();
284 for (j, &xj) in x.iter().enumerate() {
285 let angle = sign * 2.0 * PI * (j * k) as f64 / n as f64;
286 acc = acc + xj * Complex::from_polar_unit(angle);
287 }
288 if inverse {
289 acc.scale(1.0 / n as f64)
290 } else {
291 acc
292 }
293 })
294 .collect()
295 }
296
297 fn assert_close(a: &[Complex], b: &[Complex], tol: f64) {
298 assert_eq!(a.len(), b.len());
299 for (i, (x, y)) in a.iter().zip(b).enumerate() {
300 assert!(
301 (*x - *y).norm() < tol,
302 "bin {i}: {x:?} vs {y:?} (|Δ| = {})",
303 (*x - *y).norm()
304 );
305 }
306 }
307
308 #[test]
309 fn empty_and_singleton() {
310 assert!(fft(&[]).is_empty());
311 assert!(ifft(&[]).is_empty());
312 let one = [Complex::new(2.0, -3.0)];
313 assert_eq!(fft(&one), one.to_vec());
314 assert_eq!(ifft(&one), one.to_vec());
315 }
316
317 #[test]
318 fn impulse_is_flat() {
319 let mut x = vec![Complex::default(); 7];
320 x[0] = Complex::real(1.0);
321 for z in fft(&x) {
322 assert!((z.re - 1.0).abs() < 1e-12 && z.im.abs() < 1e-12);
323 }
324 }
325
326 #[test]
327 fn sine_lands_in_its_bin_for_every_length_kind() {
328 for n in [8usize, 12, 30, 64, 97] {
329 let x: Vec<f64> = (0..n)
330 .map(|j| (2.0 * PI * 3.0 * j as f64 / n as f64).sin())
331 .collect();
332 let spectrum = rfft(&x);
333 let power: Vec<f64> = spectrum.iter().map(|z| z.norm_sqr()).collect();
334 let (argmax, _) = power[..n / 2 + 1]
335 .iter()
336 .enumerate()
337 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
338 .unwrap();
339 assert_eq!(argmax, 3, "n = {n}");
340 assert!((spectrum[3].im + n as f64 / 2.0).abs() < 1e-9, "n = {n}");
341 }
342 }
343
344 proptest! {
345 #![proptest_config(ProptestConfig::with_cases(128))]
346
347 #[test]
348 fn matches_the_naive_dft_for_any_length(
349 data in prop::collection::vec((-100.0f64..100.0, -100.0f64..100.0), 1..=64)
350 ) {
351 let x: Vec<Complex> = data.iter().map(|&(re, im)| Complex::new(re, im)).collect();
352 let n = x.len() as f64;
353 let tol = 1e-9 * n * 100.0;
354 let forward = fft(&x);
355 assert_close(&forward, &naive_dft(&x, false), tol);
356 assert_close(&ifft(&forward), &x, tol);
357 assert_close(&ifft(&x), &naive_dft(&x, true), tol);
358 }
359
360 #[test]
362 fn parseval_holds(
363 data in prop::collection::vec(-100.0f64..100.0, 1..=100)
364 ) {
365 let n = data.len() as f64;
366 let time: f64 = data.iter().map(|x| x * x).sum();
367 let freq: f64 = rfft(&data).iter().map(|z| z.norm_sqr()).sum::<f64>() / n;
368 prop_assert!((time - freq).abs() < 1e-7 * (1.0 + time), "{time} vs {freq}");
369 }
370
371 #[test]
373 fn real_input_has_hermitian_spectrum(
374 data in prop::collection::vec(-100.0f64..100.0, 2..=100)
375 ) {
376 let n = data.len();
377 let spectrum = rfft(&data);
378 for k in 1..n {
379 let mirror = spectrum[n - k].conj();
380 prop_assert!((spectrum[k] - mirror).norm() < 1e-7 * (1.0 + spectrum[k].norm()));
381 }
382 prop_assert!(spectrum[0].im.abs() < 1e-9);
383 }
384 }
385}