use num_traits::NumCast;
use scirs2_core::ndarray::{ArrayD, Slice};
use scirs2_core::Complex64;
use scirs2_fft::{FFTError, FFTResult};
use std::fmt::Debug;
fn normalize_axes(axes: Option<&[isize]>, ndim: usize) -> FFTResult<Option<Vec<usize>>> {
let Some(axes) = axes else {
return Ok(None);
};
let mut out = Vec::with_capacity(axes.len());
for &axis in axes {
let normalized = if axis < 0 { axis + ndim as isize } else { axis };
if normalized < 0 || normalized as usize >= ndim {
return Err(FFTError::ValueError(format!(
"axis {axis} is out of bounds for array of dimension {ndim}"
)));
}
out.push(normalized as usize);
}
Ok(Some(out))
}
fn expand_shape(
input_shape: &[usize],
s: Option<&[usize]>,
axes: &Option<Vec<usize>>,
) -> FFTResult<Option<Vec<usize>>> {
let Some(sizes) = s else {
return Ok(None);
};
let mut full = input_shape.to_vec();
match axes {
Some(ax) => {
if sizes.len() != ax.len() {
return Err(FFTError::ValueError(format!(
"s must have the same length as axes ({} vs {})",
sizes.len(),
ax.len()
)));
}
for (&axis, &size) in ax.iter().zip(sizes.iter()) {
full[axis] = size;
}
}
None => {
if sizes.len() != input_shape.len() {
return Err(FFTError::ValueError(format!(
"s must have the same length as the input's number of dimensions ({}) when axes is not given, got {}",
input_shape.len(),
sizes.len()
)));
}
full.copy_from_slice(sizes);
}
}
Ok(Some(full))
}
fn validate_norm(norm: Option<&str>) -> FFTResult<()> {
match norm {
None | Some("backward") | Some("ortho") | Some("forward") => Ok(()),
Some(other) => Err(FFTError::ValueError(format!(
"Invalid norm value '{other}': expected \"backward\", \"forward\", or \"ortho\""
))),
}
}
fn transformed_axes_size(outshape: &[usize], axes: &Option<Vec<usize>>) -> usize {
match axes {
Some(ax) => ax.iter().map(|&a| outshape[a]).product(),
None => outshape.iter().product(),
}
}
pub fn fftn<T>(
input: &ArrayD<T>,
s: Option<&[usize]>,
axes: Option<&[isize]>,
norm: Option<&str>,
) -> FFTResult<ArrayD<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_norm(norm)?;
let normalized_axes = normalize_axes(axes, input.ndim())?;
let outshape = expand_shape(input.shape(), s, &normalized_axes)?;
let mut result = scirs2_fft::fftn(
input,
outshape.clone(),
normalized_axes.clone(),
None,
None,
None,
)?;
let scale = forward_norm_scale(
norm,
transformed_axes_size(result.shape(), &normalized_axes),
)?;
if scale != 1.0 {
result.mapv_inplace(|c| c * scale);
}
Ok(result)
}
pub fn ifftn<T>(
input: &ArrayD<T>,
s: Option<&[usize]>,
axes: Option<&[isize]>,
norm: Option<&str>,
) -> FFTResult<ArrayD<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_norm(norm)?;
let normalized_axes = normalize_axes(axes, input.ndim())?;
let outshape = expand_shape(input.shape(), s, &normalized_axes)?;
scirs2_fft::ifftn(input, outshape, normalized_axes, norm, None, None)
}
pub fn rfftn<T>(
input: &ArrayD<T>,
s: Option<&[usize]>,
axes: Option<&[isize]>,
norm: Option<&str>,
) -> FFTResult<ArrayD<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
let ndim = input.ndim();
let normalized_axes = normalize_axes(axes, ndim)?;
let last_axis = match &normalized_axes {
Some(ax) => *ax.last().ok_or_else(|| {
FFTError::ValueError("axes must contain at least one axis".to_string())
})?,
None => ndim.checked_sub(1).ok_or_else(|| {
FFTError::ValueError("rfftn requires an array with at least one dimension".to_string())
})?,
};
let full = fftn(input, s, axes, norm)?;
let half_len = full.shape()[last_axis] / 2 + 1;
let sliced = full
.slice_each_axis(|ax| {
if ax.axis.index() == last_axis {
Slice::new(0, Some(half_len as isize), 1)
} else {
Slice::new(0, None, 1)
}
})
.to_owned();
Ok(sliced)
}
pub fn irfftn<T>(
input: &ArrayD<T>,
s: Option<&[usize]>,
axes: Option<&[isize]>,
norm: Option<&str>,
) -> FFTResult<ArrayD<f64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_norm(norm)?;
let normalized_axes = normalize_axes(axes, input.ndim())?;
let outshape = expand_shape(input.shape(), s, &normalized_axes)?;
scirs2_fft::irfftn(&input.view(), outshape, normalized_axes, norm, None, None)
}
fn validate_flat_axis(axis: Option<isize>) -> FFTResult<()> {
match axis {
None | Some(0) | Some(-1) => Ok(()),
Some(other) => Err(FFTError::ValueError(format!(
"axis {other} is out of bounds for a 1-D transform (expected 0 or -1)"
))),
}
}
fn forward_norm_scale(norm: Option<&str>, n: usize) -> FFTResult<f64> {
match norm {
None | Some("backward") => Ok(1.0),
Some("forward") => Ok(1.0 / n as f64),
Some("ortho") => Ok(1.0 / (n as f64).sqrt()),
Some(other) => Err(FFTError::ValueError(format!(
"Invalid norm value '{other}': expected \"backward\", \"forward\", or \"ortho\""
))),
}
}
fn inverse_norm_correction(norm: Option<&str>, n: usize) -> FFTResult<f64> {
match norm {
None | Some("backward") => Ok(1.0),
Some("forward") => Ok(n as f64),
Some("ortho") => Ok((n as f64).sqrt()),
Some(other) => Err(FFTError::ValueError(format!(
"Invalid norm value '{other}': expected \"backward\", \"forward\", or \"ortho\""
))),
}
}
pub fn fft_with<T>(
x: &[T],
n: Option<usize>,
axis: Option<isize>,
norm: Option<&str>,
) -> FFTResult<Vec<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_flat_axis(axis)?;
let size = n.unwrap_or(x.len());
let scale = forward_norm_scale(norm, size)?;
let mut result = scirs2_fft::fft(x, Some(size))?;
result.iter_mut().for_each(|c| *c *= scale);
Ok(result)
}
pub fn ifft_with<T>(
x: &[T],
n: Option<usize>,
axis: Option<isize>,
norm: Option<&str>,
) -> FFTResult<Vec<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_flat_axis(axis)?;
let size = n.unwrap_or(x.len());
let correction = inverse_norm_correction(norm, size)?;
let mut result = scirs2_fft::ifft(x, Some(size))?;
result.iter_mut().for_each(|c| *c *= correction);
Ok(result)
}
pub fn rfft_with<T>(
x: &[T],
n: Option<usize>,
axis: Option<isize>,
norm: Option<&str>,
) -> FFTResult<Vec<Complex64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_flat_axis(axis)?;
let size = n.unwrap_or(x.len());
let scale = forward_norm_scale(norm, size)?;
let mut result = scirs2_fft::rfft(x, Some(size))?;
result.iter_mut().for_each(|c| *c *= scale);
Ok(result)
}
pub fn irfft_with<T>(
x: &[T],
n: Option<usize>,
axis: Option<isize>,
norm: Option<&str>,
) -> FFTResult<Vec<f64>>
where
T: NumCast + Copy + Debug + 'static,
{
validate_flat_axis(axis)?;
let size = n.unwrap_or_else(|| 2 * x.len().saturating_sub(1));
let correction = inverse_norm_correction(norm, size)?;
let mut result = scirs2_fft::irfft(x, Some(size))?;
result.iter_mut().for_each(|v| *v *= correction);
Ok(result)
}