use alloc::vec;
use burn_backend::ops::ModuleOps;
use burn_dispatch::Dispatch;
use crate::check;
use crate::check::TensorCheck;
use crate::check::unwrap_dim_index;
use crate::ops::BridgeTensor;
use crate::{AsIndex, Tensor};
#[cfg_attr(
doc,
doc = r#"
The mathematical formulation for each element $k$ in the frequency domain is:
$$X\[k\] = \sum_{n=0}^{N-1} x\[n\] \left\[ \cos\left(\frac{2\pi kn}{N}\right) - i \sin\left(\frac{2\pi kn}{N}\right) \right\]$$
where $N$ is the size of the signal along the specified dimension.
"#
)]
#[cfg_attr(not(doc), doc = r"X\[k\] = Σ x\[n\] * exp(-i*2πkn/N)")]
pub fn rfft<const D: usize>(
signal: Tensor<D>,
dim: impl AsIndex,
n: Option<usize>,
) -> (Tensor<D>, Tensor<D>) {
let dim = unwrap_dim_index(dim.try_dim_index(D), "RFFT");
match n {
None => check!(TensorCheck::check_is_power_of_two::<D>(
&signal.shape(),
dim
)),
Some(n) => {
assert!(n >= 1, "rfft: n must be >= 1, got {n}");
assert!(
n.is_power_of_two(),
"rfft: n must be a power of two, got {n}. True non-power-of-two \
DFT support is tracked as a follow-up (Bluestein's algorithm)."
);
}
}
let (re, im) = rfft_impl(signal.primitive, dim, n);
(Tensor::new(re), Tensor::new(im))
}
fn rfft_impl(signal: BridgeTensor, dim: usize, n: Option<usize>) -> (BridgeTensor, BridgeTensor) {
let (re, im) = Dispatch::rfft(signal.into_float(), dim, n);
(BridgeTensor::float(re), BridgeTensor::float(im))
}
#[cfg_attr(
doc,
doc = r#"
The mathematical formulation for each element $n$ in the time domain is:
$$x\[n\] = \frac{1}{N} \sum_{k=0}^{N-1} X\[k\] \left\[ \cos\left(\frac{2\pi kn}{N}\right) + i \sin\left(\frac{2\pi kn}{N}\right) \right\]$$
where $N$ is the size of the reconstructed signal.
"#
)]
#[cfg_attr(not(doc), doc = r"x\[n\] = (1/N) * Σ X\[k\] * exp(i*2πkn/N)")]
pub fn irfft<const D: usize>(
spectrum_re: Tensor<D>,
spectrum_im: Tensor<D>,
dim: impl AsIndex,
n: Option<usize>,
) -> Tensor<D> {
let dim = unwrap_dim_index(dim.try_dim_index(D), "IRFFT");
if let Some(n) = n {
assert!(n >= 1, "irfft: n must be >= 1, got {n}");
assert!(
n.is_power_of_two(),
"irfft: n must be a power of two, got {n}. True non-power-of-two \
DFT support is tracked as a follow-up (Bluestein's algorithm)."
);
}
Tensor::new(irfft_impl(
spectrum_re.primitive,
spectrum_im.primitive,
dim,
n,
))
}
fn irfft_impl(
spectrum_re: BridgeTensor,
spectrum_im: BridgeTensor,
dim: usize,
n: Option<usize>,
) -> BridgeTensor {
BridgeTensor::float(Dispatch::irfft(
spectrum_re.into_float(),
spectrum_im.into_float(),
dim,
n,
))
}
#[cfg_attr(
doc,
doc = r#"
Due to the linearity of the Fourier Transform, a complex-valued signal $x\[n\] = x_{re}\[n\] + i x_{im}\[n\]$ can be transformed by applying the FFT to its real and imaginary parts separately:
$$ \text{FFT}(x\[n\]) = \text{FFT}(x_{re}\[n\]) + i \text{FFT}(x_{im}\[n\]) $$
Since $x_{re}\[n\]$ and $x_{im}\[n\]$ are purely real, their transforms can be computed efficiently using the real FFT ([`rfft`]). The full spectrum is then reconstructed by exploiting Hermitian symmetry.
"#
)]
#[cfg_attr(not(doc), doc = r"X\[k\] = Σ x\[n\] * exp(-i*2πkn/N)")]
pub fn cfft<const D: usize>(
signal_re: Tensor<D>,
signal_im: Tensor<D>,
dim: impl AsIndex,
n: Option<usize>,
) -> (Tensor<D>, Tensor<D>) {
assert!(
signal_re.shape() == signal_im.shape(),
"cfft: signal_re and signal_im must have the same shape, \
got {:?} and {:?}",
signal_re.shape(),
signal_im.shape(),
);
let dim = unwrap_dim_index(dim.try_dim_index(D), "CFFT");
let fft_size = n.unwrap_or(signal_re.dims()[dim]);
let (xr, xi) = rfft(signal_re, dim, n);
let (yr, yi) = rfft(signal_im, dim, n);
let (xr, xi) = hermitian_extend(xr, xi, dim, fft_size);
let (yr, yi) = hermitian_extend(yr, yi, dim, fft_size);
(xr - yi, xi + yr)
}
pub(super) fn hermitian_extend<const D: usize>(
half_re: Tensor<D>,
half_im: Tensor<D>,
dim: usize,
full_len: usize,
) -> (Tensor<D>, Tensor<D>) {
let half_len = half_re.dims()[dim];
if full_len <= half_len {
return (half_re, half_im);
}
let mirror_len = full_len - half_len; let mirror_re = half_re
.clone()
.narrow(dim, 1, mirror_len)
.flip([dim as isize]);
let mirror_im = half_im
.clone()
.narrow(dim, 1, mirror_len)
.flip([dim as isize])
.neg();
let full_re = Tensor::cat(vec![half_re, mirror_re], dim);
let full_im = Tensor::cat(vec![half_im, mirror_im], dim);
(full_re, full_im)
}