use std::ops::Neg;
pub trait Float: Copy + Default + Neg<Output = Self> {
fn from_f64(v: f64) -> Self;
fn mul_add(self, a: Self, b: Self) -> Self;
}
impl Float for f32 {
fn from_f64(v: f64) -> Self {
v as f32
}
#[inline(always)]
fn mul_add(self, a: Self, b: Self) -> Self {
f32::mul_add(self, a, b)
}
}
impl Float for f64 {
fn from_f64(v: f64) -> Self {
v
}
#[inline(always)]
fn mul_add(self, a: Self, b: Self) -> Self {
f64::mul_add(self, a, b)
}
}
pub struct Fft<T> {
leaf: usize,
perm: Vec<usize>,
leaf_trig: (Vec<T>, Vec<T>),
twiddles: (Vec<T>, Vec<T>),
}
impl<T: Float> Fft<T> {
pub fn new(n: usize) -> Self {
let leaf = n >> n.trailing_zeros();
let blocks = n / leaf;
let mut starts = vec![0];
while starts.len() < blocks {
starts = starts.iter().map(|s| 2 * s).chain(starts.iter().map(|s| 2 * s + 1)).collect();
}
let perm = (0..n).map(|i| starts[i % blocks] + i / blocks * blocks).collect();
let trig = |i: usize| {
let phase = std::f64::consts::TAU * i as f64 / n as f64;
(T::from_f64(phase.cos()), T::from_f64(phase.sin()))
};
let leaf_trig = (0..leaf * leaf).map(|i| trig(i / leaf * (i % leaf) * blocks % n)).unzip();
let sizes = std::iter::successors(Some(2 * leaf), |s| Some(2 * s)).take_while(|&s| s <= n);
let twiddles =
sizes.flat_map(|size| (0..n / 2).map(move |i| i / (n / size) * (n / size))).map(trig).unzip();
Self { leaf, perm, leaf_trig, twiddles }
}
pub fn forward<'a>(&self, x: &[T], buf: &'a mut [T]) -> (&'a [T], &'a [T]) {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("fma") {
unsafe { self.forward_fma(x, buf) };
return buf[..2 * self.perm.len()].split_at(self.perm.len());
}
self.run(x, buf);
buf[..2 * self.perm.len()].split_at(self.perm.len())
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "fma")]
fn forward_fma(&self, x: &[T], buf: &mut [T]) {
self.run(x, buf);
}
#[inline(always)]
fn run(&self, x: &[T], buf: &mut [T]) {
let n = self.perm.len();
let stages = self.twiddles.0.len() / (n / 2);
let (front, back) = buf[..4 * n].split_at_mut(2 * n);
let (mut src, mut dst) = if stages.is_multiple_of(2) { (back, front) } else { (front, back) };
for (v, &i) in src[..n].iter_mut().zip(&self.perm) {
*v = x[i];
}
let (re, im) = dst.split_at_mut(n);
leaves(&src[..n], re, im, &self.leaf_trig.0, &self.leaf_trig.1, self.leaf);
let twiddles = self.twiddles.0.chunks_exact(n / 2).zip(self.twiddles.1.chunks_exact(n / 2));
for (cos, sin) in twiddles {
std::mem::swap(&mut src, &mut dst);
let ((sr, si), (dr, di)) = (src.split_at(n), dst.split_at_mut(n));
stage(sr, si, dr, di, cos, sin);
}
}
}
#[inline(always)]
fn leaves<T: Float>(xs: &[T], re: &mut [T], im: &mut [T], cos: &[T], sin: &[T], leaf: usize) {
let blocks = xs.len() / leaf;
for (k, (re, im)) in re.chunks_exact_mut(blocks).zip(im.chunks_exact_mut(blocks)).enumerate() {
re.copy_from_slice(&xs[..blocks]);
im.fill(T::default());
for j in 1..leaf {
let (c, s, xs) = (cos[j * leaf + k], sin[j * leaf + k], &xs[j * blocks..][..blocks]);
for ((re, im), &v) in re.iter_mut().zip(im.iter_mut()).zip(xs) {
*re = v.mul_add(c, *re);
*im = (-v).mul_add(s, *im);
}
}
}
}
#[inline(always)]
fn stage<T: Float>(sr: &[T], si: &[T], dr: &mut [T], di: &mut [T], cos: &[T], sin: &[T]) {
let half = cos.len();
let (sr, si, sin) = (&sr[..2 * half], &si[..2 * half], &sin[..half]);
let (dr, di) = (&mut dr[..2 * half], &mut di[..2 * half]);
for i in 0..half {
let (c, s, a, x, b, y) = (cos[i], sin[i], sr[2 * i], sr[2 * i + 1], si[2 * i], si[2 * i + 1]);
dr[i] = s.mul_add(y, c.mul_add(x, a));
di[i] = (-s).mul_add(x, c.mul_add(y, b));
dr[i + half] = (-s).mul_add(y, (-c).mul_add(x, a));
di[i + half] = s.mul_add(x, (-c).mul_add(y, b));
}
}
#[cfg(test)]
mod tests {
use super::{Fft, Float};
fn check<T: Float + Into<f64>>(n: usize, tol: f64) {
let x: Vec<f64> = (0..n).map(|i| ((i * 7919 % 1000) as f64 / 500.0 - 1.0) * 0.3).collect();
let input: Vec<T> = x.iter().map(|&v| T::from_f64(v)).collect();
let mut buf = vec![T::default(); 4 * n];
let (re, im) = Fft::new(n).forward(&input, &mut buf);
for k in 0..n {
let (mut want_re, mut want_im) = (0f64, 0f64);
for (i, &v) in input.iter().enumerate() {
let phase = std::f64::consts::TAU * (k * i % n) as f64 / n as f64;
want_re += v.into() * phase.cos();
want_im -= v.into() * phase.sin();
}
let (re, im) = (re[k].into(), im[k].into());
assert!((re - want_re).abs() < tol && (im - want_im).abs() < tol, "n {n}, bin {k}");
}
}
#[test]
fn matches_a_dft() {
for n in [320, 360, 400, 512, 15] {
check::<f32>(n, 1e-4);
check::<f64>(n, 1e-10);
}
}
}