use super::kernels;
use crate::{Array, Float};
fn to_f64<F: Float>(a: &Array<F>) -> Vec<f64> {
a.iter().map(|v| v.to_f64()).collect()
}
fn from_f64<F: Float>(data: Vec<f64>, shape: &[usize]) -> Array<F> {
Array::from_vec(data.into_iter().map(F::from_f64).collect(), shape)
}
fn check_pair<F: Float>(re: &Array<F>, im: &Array<F>) {
assert!(
re.shape() == im.shape(),
"real and imaginary parts must have the same shape, got {:?} and {:?}",
re.shape(),
im.shape()
);
}
fn along_last_axis<F: Float>(
re: &Array<F>,
im: &Array<F>,
n_out: usize,
f: impl Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>),
) -> (Array<F>, Array<F>) {
check_pair(re, im);
assert!(
re.ndim() >= 1,
"axis -1 is out of bounds for array of dimension 0"
);
let n = re.shape()[re.ndim() - 1];
assert!(n >= 1, "Invalid number of FFT data points ({n}) specified.");
let (xr, xi) = (to_f64(re), to_f64(im));
let lines = xr.len() / n;
let (mut or, mut oi) = (
Vec::with_capacity(lines * n_out),
Vec::with_capacity(lines * n_out),
);
for (lr, li) in xr.chunks_exact(n).zip(xi.chunks_exact(n)) {
let (r, i) = f(lr, li);
or.extend(r);
oi.extend(i);
}
let mut shape = re.shape().to_vec();
*shape.last_mut().unwrap() = n_out;
(from_f64(or, &shape), from_f64(oi, &shape))
}
pub fn fft<F: Float>(re: &Array<F>, im: &Array<F>) -> (Array<F>, Array<F>) {
let n = re.shape().last().copied().unwrap_or(0);
along_last_axis(re, im, n, kernels::fft::<f64>)
}
pub fn ifft<F: Float>(re: &Array<F>, im: &Array<F>) -> (Array<F>, Array<F>) {
let n = re.shape().last().copied().unwrap_or(0);
along_last_axis(re, im, n, kernels::ifft::<f64>)
}
pub fn rfft<F: Float>(a: &Array<F>) -> (Array<F>, Array<F>) {
let zeros = Array::from_vec(vec![F::from_f64(0.0); a.size()], a.shape());
let n = a.shape().last().copied().unwrap_or(0);
along_last_axis(a, &zeros, n / 2 + 1, |lr, _| kernels::rfft::<f64>(lr))
}
pub fn irfft<F: Float>(re: &Array<F>, im: &Array<F>, n: usize) -> Array<F> {
assert!(n >= 1, "Invalid number of FFT data points ({n}) specified.");
let (out, _) = along_last_axis(re, im, n, |lr, li| {
(kernels::irfft::<f64>(lr, li, n), vec![0.0; n])
});
out
}
pub fn fft2<F: Float>(re: &Array<F>, im: &Array<F>) -> (Array<F>, Array<F>) {
transform2(re, im, kernels::fft2::<f64>)
}
pub fn ifft2<F: Float>(re: &Array<F>, im: &Array<F>) -> (Array<F>, Array<F>) {
transform2(re, im, kernels::ifft2::<f64>)
}
fn transform2<F: Float>(
re: &Array<F>,
im: &Array<F>,
f: impl Fn(&[f64], &[f64], usize, usize) -> (Vec<f64>, Vec<f64>),
) -> (Array<F>, Array<F>) {
check_pair(re, im);
assert!(re.ndim() >= 2, "axes exceeds dimensionality of input");
let nd = re.ndim();
let (rows, cols) = (re.shape()[nd - 2], re.shape()[nd - 1]);
assert!(
rows >= 1 && cols >= 1,
"Invalid number of FFT data points (0) specified."
);
let (xr, xi) = (to_f64(re), to_f64(im));
let block = rows * cols;
let (mut or, mut oi) = (Vec::with_capacity(xr.len()), Vec::with_capacity(xr.len()));
for (br, bi) in xr.chunks_exact(block).zip(xi.chunks_exact(block)) {
let (r, i) = f(br, bi, rows, cols);
or.extend(r);
oi.extend(i);
}
(from_f64(or, re.shape()), from_f64(oi, re.shape()))
}
pub fn fftfreq<F: Float>(n: usize, d: F) -> Array<F> {
let v = kernels::fftfreq::<f64>(n, d.to_f64());
from_f64(v, &[n])
}
pub fn rfftfreq<F: Float>(n: usize, d: F) -> Array<F> {
let v = kernels::rfftfreq::<f64>(n, d.to_f64());
let len = v.len();
from_f64(v, &[len])
}
pub fn fftshift<T: crate::Element>(a: &Array<T>) -> Array<T> {
Array::from_vec(kernels::shift_nd(&a.to_vec(), a.shape(), false), a.shape())
}
pub fn ifftshift<T: crate::Element>(a: &Array<T>) -> Array<T> {
Array::from_vec(kernels::shift_nd(&a.to_vec(), a.shape(), true), a.shape())
}