use rustfft::num_complex::Complex64;
use rustfft::{Fft, FftPlanner};
use std::sync::Arc;
pub(crate) struct Fft3d {
n: [usize; 3],
fwd: [Arc<dyn Fft<f64>>; 3],
inv: [Arc<dyn Fft<f64>>; 3],
}
impl Fft3d {
pub(crate) fn new(n: [usize; 3]) -> Self {
let mut planner = FftPlanner::new();
let fwd = [
planner.plan_fft_forward(n[0]),
planner.plan_fft_forward(n[1]),
planner.plan_fft_forward(n[2]),
];
let inv = [
planner.plan_fft_inverse(n[0]),
planner.plan_fft_inverse(n[1]),
planner.plan_fft_inverse(n[2]),
];
Self { n, fwd, inv }
}
pub(crate) fn forward(&self, data: &mut [Complex64]) {
for axis in 0..3 {
self.transform_axis(data, axis, self.fwd[axis].as_ref());
}
}
pub(crate) fn inverse(&self, data: &mut [Complex64]) {
for axis in 0..3 {
self.transform_axis(data, axis, self.inv[axis].as_ref());
}
}
fn transform_axis(&self, data: &mut [Complex64], axis: usize, plan: &dyn Fft<f64>) {
let [n0, n1, n2] = self.n;
debug_assert_eq!(data.len(), n0 * n1 * n2);
let len = self.n[axis];
let (stride, others): (usize, [(usize, usize); 2]) = match axis {
0 => (n1 * n2, [(n1, n2), (n2, 1)]),
1 => (n2, [(n0, n1 * n2), (n2, 1)]),
2 => (1, [(n0, n1 * n2), (n1, n2)]),
_ => unreachable!(),
};
let zero = Complex64::new(0.0, 0.0);
let mut scratch = vec![zero; plan.get_inplace_scratch_len()];
let mut line = vec![zero; len];
let (ea, sa) = others[0];
let (eb, sb) = others[1];
for a in 0..ea {
for b in 0..eb {
let start = a * sa + b * sb;
for (t, l) in line.iter_mut().enumerate() {
*l = data[start + t * stride];
}
plan.process_with_scratch(&mut line, &mut scratch);
for (t, l) in line.iter().enumerate() {
data[start + t * stride] = *l;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: &[Complex64], b: &[Complex64], tol: f64) -> bool {
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).norm() < tol)
}
#[test]
fn round_trip_recovers_input() {
let n = [4usize, 5, 6];
let npts = n[0] * n[1] * n[2];
let input: Vec<Complex64> = (0..npts)
.map(|i| Complex64::new((i as f64 * 0.37).sin(), (i as f64 * 0.11 + 1.0).cos()))
.collect();
let fft = Fft3d::new(n);
let mut data = input.clone();
fft.forward(&mut data);
fft.inverse(&mut data);
let nfac = npts as f64;
for d in &mut data {
*d /= nfac;
}
assert!(approx_eq(&data, &input, 1e-12));
}
#[test]
fn forward_of_constant_is_a_single_peak() {
let n = [3usize, 3, 3];
let npts = n[0] * n[1] * n[2];
let mut data = vec![Complex64::new(2.0, 0.0); npts];
Fft3d::new(n).forward(&mut data);
assert!((data[0] - Complex64::new(2.0 * npts as f64, 0.0)).norm() < 1e-10);
for d in &data[1..] {
assert!(d.norm() < 1e-10);
}
}
}