1pub fn fft(re: &mut [f64], im: &mut [f64]) {
38 transform_1d(re, im, false);
39}
40
41pub fn ifft(re: &mut [f64], im: &mut [f64]) {
43 transform_1d(re, im, true);
44}
45
46pub fn fft2(re: &mut [f64], im: &mut [f64], n: usize) {
48 transform_2d(re, im, n, false);
49}
50
51pub fn ifft2(re: &mut [f64], im: &mut [f64], n: usize) {
53 transform_2d(re, im, n, true);
54}
55
56pub fn fftshift(data: &[f64], n: usize) -> Vec<f64> {
61 let half = n / 2;
62 let mut out = vec![0.0; n * n];
63 for y in 0..n {
64 for x in 0..n {
65 let sx = (x + half) % n;
66 let sy = (y + half) % n;
67 out[y * n + x] = data[sy * n + sx];
68 }
69 }
70 out
71}
72
73fn transform_1d(re: &mut [f64], im: &mut [f64], inverse: bool) {
74 let n = re.len();
75 assert_eq!(
76 n,
77 im.len(),
78 "the real and imaginary parts must be the same length"
79 );
80 assert!(
81 n.is_power_of_two(),
82 "the transform is radix two, so the length must be a power of two, got {n}"
83 );
84 if n < 2 {
85 return;
86 }
87
88 let mut j = 0usize;
90 for i in 1..n {
91 let mut bit = n >> 1;
92 while j & bit != 0 {
93 j ^= bit;
94 bit >>= 1;
95 }
96 j |= bit;
97 if i < j {
98 re.swap(i, j);
99 im.swap(i, j);
100 }
101 }
102
103 let sign = if inverse { 1.0 } else { -1.0 };
104 let mut len = 2;
105 while len <= n {
106 let step = sign * std::f64::consts::TAU / len as f64;
107 let mut base = 0;
108 while base < n {
109 for k in 0..len / 2 {
110 let angle = step * k as f64;
111 let (wr, wi) = (angle.cos(), angle.sin());
112 let (a, b) = (base + k, base + k + len / 2);
113 let (ur, ui) = (re[a], im[a]);
114 let vr = re[b] * wr - im[b] * wi;
115 let vi = re[b] * wi + im[b] * wr;
116 re[a] = ur + vr;
117 im[a] = ui + vi;
118 re[b] = ur - vr;
119 im[b] = ui - vi;
120 }
121 base += len;
122 }
123 len <<= 1;
124 }
125
126 if inverse {
127 let scale = 1.0 / n as f64;
128 for v in re.iter_mut() {
129 *v *= scale;
130 }
131 for v in im.iter_mut() {
132 *v *= scale;
133 }
134 }
135}
136
137fn transform_2d(re: &mut [f64], im: &mut [f64], n: usize, inverse: bool) {
140 assert_eq!(re.len(), n * n, "the array must be n by n");
141 assert_eq!(im.len(), n * n, "the array must be n by n");
142 let mut row_re = vec![0.0; n];
143 let mut row_im = vec![0.0; n];
144 for y in 0..n {
145 row_re.copy_from_slice(&re[y * n..(y + 1) * n]);
146 row_im.copy_from_slice(&im[y * n..(y + 1) * n]);
147 transform_1d(&mut row_re, &mut row_im, inverse);
148 re[y * n..(y + 1) * n].copy_from_slice(&row_re);
149 im[y * n..(y + 1) * n].copy_from_slice(&row_im);
150 }
151 for x in 0..n {
152 for y in 0..n {
153 row_re[y] = re[y * n + x];
154 row_im[y] = im[y * n + x];
155 }
156 transform_1d(&mut row_re, &mut row_im, inverse);
157 for y in 0..n {
158 re[y * n + x] = row_re[y];
159 im[y * n + x] = row_im[y];
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::f64::consts::TAU;
168
169 #[test]
175 fn one_frequency_lands_in_one_bin() {
176 const N: usize = 64;
177 for m in [0usize, 1, 7, 31, 63] {
178 let mut re: Vec<f64> = (0..N)
179 .map(|k| (TAU * m as f64 * k as f64 / N as f64).cos())
180 .collect();
181 let mut im: Vec<f64> = (0..N)
182 .map(|k| (TAU * m as f64 * k as f64 / N as f64).sin())
183 .collect();
184 fft(&mut re, &mut im);
185 for j in 0..N {
186 let magnitude = (re[j] * re[j] + im[j] * im[j]).sqrt();
187 let expected = if j == m { N as f64 } else { 0.0 };
188 assert!(
189 (magnitude - expected).abs() < 1e-10,
190 "frequency {m}: bin {j} has {magnitude}, expected {expected}"
191 );
192 }
193 }
194 }
195
196 #[test]
198 fn a_delta_transforms_to_a_flat_spectrum() {
199 const N: usize = 32;
200 let mut re = vec![0.0; N];
201 let mut im = vec![0.0; N];
202 re[0] = 1.0;
203 fft(&mut re, &mut im);
204 for j in 0..N {
205 assert!((re[j] - 1.0).abs() < 1e-12, "bin {j} real part {}", re[j]);
206 assert!(im[j].abs() < 1e-12, "bin {j} imaginary part {}", im[j]);
207 }
208 }
209
210 #[test]
215 fn the_inverse_undoes_the_forward() {
216 const N: usize = 64;
217 let re0: Vec<f64> = (0..N).map(|i| ((i * 37) % 101) as f64 / 101.0).collect();
218 let im0: Vec<f64> = (0..N).map(|i| ((i * 53) % 97) as f64 / 97.0).collect();
219
220 let (mut re, mut im) = (re0.clone(), im0.clone());
221 fft(&mut re, &mut im);
222 ifft(&mut re, &mut im);
223 for i in 0..N {
224 assert!((re[i] - re0[i]).abs() < 1e-12, "1D real at {i}");
225 assert!((im[i] - im0[i]).abs() < 1e-12, "1D imaginary at {i}");
226 }
227
228 const M: usize = 32;
229 let re0: Vec<f64> = (0..M * M).map(|i| ((i * 29) % 89) as f64 / 89.0).collect();
230 let im0: Vec<f64> = (0..M * M).map(|i| ((i * 41) % 83) as f64 / 83.0).collect();
231 let (mut re, mut im) = (re0.clone(), im0.clone());
232 fft2(&mut re, &mut im, M);
233 ifft2(&mut re, &mut im, M);
234 for i in 0..M * M {
235 assert!((re[i] - re0[i]).abs() < 1e-12, "2D real at {i}");
236 assert!((im[i] - im0[i]).abs() < 1e-12, "2D imaginary at {i}");
237 }
238 }
239
240 #[test]
243 fn the_transform_conserves_energy() {
244 const N: usize = 128;
245 let re0: Vec<f64> = (0..N)
246 .map(|i| ((i * 17) % 71) as f64 / 71.0 - 0.5)
247 .collect();
248 let im0: Vec<f64> = (0..N)
249 .map(|i| ((i * 23) % 59) as f64 / 59.0 - 0.5)
250 .collect();
251 let before: f64 = re0.iter().zip(im0.iter()).map(|(a, b)| a * a + b * b).sum();
252
253 let (mut re, mut im) = (re0.clone(), im0.clone());
254 fft(&mut re, &mut im);
255 let after: f64 = re.iter().zip(im.iter()).map(|(a, b)| a * a + b * b).sum();
256 assert!(
257 (after / (N as f64 * before) - 1.0).abs() < 1e-12,
258 "1D: {after} against {} ",
259 N as f64 * before
260 );
261
262 const M: usize = 32;
263 let re0: Vec<f64> = (0..M * M).map(|i| ((i * 13) % 61) as f64 / 61.0).collect();
264 let im0 = vec![0.0; M * M];
265 let before: f64 = re0.iter().map(|a| a * a).sum();
266 let (mut re, mut im) = (re0, im0);
267 fft2(&mut re, &mut im, M);
268 let after: f64 = re.iter().zip(im.iter()).map(|(a, b)| a * a + b * b).sum();
269 let n2 = (M * M) as f64;
270 assert!((after / (n2 * before) - 1.0).abs() < 1e-12, "2D");
271 }
272
273 #[test]
280 fn the_two_dimensional_transform_is_separable() {
281 const N: usize = 16;
282 let a: Vec<f64> = (0..N).map(|i| ((i * 7) % 13) as f64).collect();
283 let b: Vec<f64> = (0..N).map(|i| ((i * 5) % 11) as f64).collect();
284
285 let (mut ar, mut ai) = (a.clone(), vec![0.0; N]);
287 let (mut br, mut bi) = (b.clone(), vec![0.0; N]);
288 fft(&mut ar, &mut ai);
289 fft(&mut br, &mut bi);
290
291 let mut re = vec![0.0; N * N];
293 let im0 = vec![0.0; N * N];
294 for y in 0..N {
295 for x in 0..N {
296 re[y * N + x] = a[x] * b[y];
297 }
298 }
299 let mut im = im0;
300 fft2(&mut re, &mut im, N);
301
302 for v in 0..N {
303 for u in 0..N {
304 let want_re = ar[u] * br[v] - ai[u] * bi[v];
306 let want_im = ar[u] * bi[v] + ai[u] * br[v];
307 let i = v * N + u;
308 assert!(
309 (re[i] - want_re).abs() < 1e-9 && (im[i] - want_im).abs() < 1e-9,
310 "at ({u},{v}): got ({}, {}), expected ({want_re}, {want_im})",
311 re[i],
312 im[i]
313 );
314 }
315 }
316 }
317
318 #[test]
321 fn a_real_even_signal_has_a_real_spectrum() {
322 const N: usize = 64;
323 let mut re: Vec<f64> = (0..N)
324 .map(|i| {
325 let k = if i <= N / 2 { i } else { N - i };
326 (-(k as f64) * 0.3).exp()
327 })
328 .collect();
329 let mut im = vec![0.0; N];
330 fft(&mut re, &mut im);
331 for (j, imaginary) in im.iter().enumerate() {
332 assert!(
333 imaginary.abs() < 1e-12,
334 "bin {j} should be real, imaginary part {imaginary}"
335 );
336 }
337 }
338
339 #[test]
342 fn the_transform_is_linear() {
343 const N: usize = 32;
344 let x: Vec<f64> = (0..N).map(|i| ((i * 11) % 17) as f64).collect();
345 let y: Vec<f64> = (0..N).map(|i| ((i * 3) % 7) as f64).collect();
346
347 let (mut xr, mut xi) = (x.clone(), vec![0.0; N]);
348 let (mut yr, mut yi) = (y.clone(), vec![0.0; N]);
349 fft(&mut xr, &mut xi);
350 fft(&mut yr, &mut yi);
351
352 let mut sr: Vec<f64> = x
353 .iter()
354 .zip(y.iter())
355 .map(|(a, b)| 2.0 * a + 3.0 * b)
356 .collect();
357 let mut si = vec![0.0; N];
358 fft(&mut sr, &mut si);
359
360 for j in 0..N {
361 let want = 2.0 * xr[j] + 3.0 * yr[j];
362 assert!(
363 (sr[j] - want).abs() < 1e-9,
364 "bin {j}: {} against {want}",
365 sr[j]
366 );
367 let want = 2.0 * xi[j] + 3.0 * yi[j];
368 assert!((si[j] - want).abs() < 1e-9);
369 }
370 }
371
372 #[test]
375 fn shifting_twice_returns_the_original() {
376 const N: usize = 8;
377 let data: Vec<f64> = (0..N * N).map(|i| i as f64).collect();
378 let once = fftshift(&data, N);
379 let twice = fftshift(&once, N);
380 assert_eq!(data, twice);
381 assert_eq!(once[(N / 2) * N + N / 2], data[0]);
383 }
384
385 #[test]
387 fn the_transform_is_bit_reproducible() {
388 const N: usize = 64;
389 let build = || {
390 let mut re: Vec<f64> = (0..N * N).map(|i| ((i * 19) % 43) as f64).collect();
391 let mut im: Vec<f64> = (0..N * N).map(|i| ((i * 31) % 37) as f64).collect();
392 fft2(&mut re, &mut im, N);
393 (re, im)
394 };
395 let (a_re, a_im) = build();
396 let (b_re, b_im) = build();
397 for i in 0..N * N {
398 assert_eq!(a_re[i].to_bits(), b_re[i].to_bits());
399 assert_eq!(a_im[i].to_bits(), b_im[i].to_bits());
400 }
401 }
402
403 #[test]
406 #[should_panic(expected = "power of two")]
407 fn a_non_power_of_two_length_is_refused() {
408 let mut re = vec![0.0; 12];
409 let mut im = vec![0.0; 12];
410 fft(&mut re, &mut im);
411 }
412
413 #[test]
414 #[should_panic(expected = "same length")]
415 fn mismatched_halves_are_refused() {
416 let mut re = vec![0.0; 8];
417 let mut im = vec![0.0; 4];
418 fft(&mut re, &mut im);
419 }
420
421 #[test]
424 fn a_single_sample_transforms_to_itself() {
425 let mut re = vec![3.0];
426 let mut im = vec![-1.0];
427 fft(&mut re, &mut im);
428 assert_eq!(re, vec![3.0]);
429 assert_eq!(im, vec![-1.0]);
430 }
431}