use crate::kernels::borrow::operand;
use crate::python::array::PyArray;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use scirs2_core::Complex64;
use scirs2_numpy::{IntoPyArray, PyArray1, PyArrayDyn, PyReadonlyArrayDyn};
fn flat_or_owned<'a>(
view: scirs2_core::ndarray::ArrayViewD<'a, Complex64>,
) -> std::borrow::Cow<'a, [Complex64]> {
match view.to_slice() {
Some(s) => std::borrow::Cow::Borrowed(s),
None => std::borrow::Cow::Owned(view.iter().cloned().collect()),
}
}
fn require_1d(x: &crate::array::Array<f64>, nd_name: &str) -> PyResult<()> {
if x.ndim() != 1 {
return Err(PyValueError::new_err(format!(
"expected a 1-D array (got {}-D); use {} for multi-dimensional input",
x.ndim(),
nd_name
)));
}
Ok(())
}
#[pyfunction]
#[pyo3(signature = (x, n=None, axis=None, norm=None))]
fn fft<'py>(
py: Python<'py>,
x: &PyArray,
n: Option<usize>,
axis: Option<isize>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArray1<Complex64>>> {
require_1d(&x.inner, "fftn")?;
let op = operand(&x.inner);
let result = crate::fft::fft_with(&op, n, axis, norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("fft failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, n=None, axis=None, norm=None))]
fn ifft<'py>(
py: Python<'py>,
x: PyReadonlyArrayDyn<'py, Complex64>,
n: Option<usize>,
axis: Option<isize>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArray1<Complex64>>> {
let view = x.as_array();
if view.ndim() != 1 {
return Err(PyValueError::new_err(format!(
"expected a 1-D array (got {}-D); use ifftn for multi-dimensional input",
view.ndim()
)));
}
let data = flat_or_owned(view);
let result = crate::fft::ifft_with(&data, n, axis, norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("ifft failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, n=None, axis=None, norm=None))]
fn rfft<'py>(
py: Python<'py>,
x: &PyArray,
n: Option<usize>,
axis: Option<isize>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArray1<Complex64>>> {
require_1d(&x.inner, "rfftn")?;
let op = operand(&x.inner);
let result = crate::fft::rfft_with(&op, n, axis, norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("rfft failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, n=None, axis=None, norm=None))]
fn irfft(
x: PyReadonlyArrayDyn<'_, Complex64>,
n: Option<usize>,
axis: Option<isize>,
norm: Option<String>,
) -> PyResult<PyArray> {
let view = x.as_array();
if view.ndim() != 1 {
return Err(PyValueError::new_err(format!(
"expected a 1-D array (got {}-D); use irfftn for multi-dimensional input",
view.ndim()
)));
}
let data = flat_or_owned(view);
let result = crate::fft::irfft_with(&data, n, axis, norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("irfft failed: {e}")))?;
Ok(PyArray {
inner: crate::array::Array::from_vec(result),
})
}
#[pyfunction]
#[pyo3(signature = (x, s=None, axes=None, norm=None))]
fn fftn<'py>(
py: Python<'py>,
x: &PyArray,
s: Option<Vec<usize>>,
axes: Option<Vec<isize>>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let result = crate::fft::fftn(
x.inner.array(),
s.as_deref(),
axes.as_deref(),
norm.as_deref(),
)
.map_err(|e| PyValueError::new_err(format!("fftn failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, s=None, axes=None, norm=None))]
fn ifftn<'py>(
py: Python<'py>,
x: PyReadonlyArrayDyn<'py, Complex64>,
s: Option<Vec<usize>>,
axes: Option<Vec<isize>>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let view = x.as_array();
let owned = view.to_owned();
let result = crate::fft::ifftn(&owned, s.as_deref(), axes.as_deref(), norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("ifftn failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, s=None, axes=None, norm=None))]
fn rfftn<'py>(
py: Python<'py>,
x: &PyArray,
s: Option<Vec<usize>>,
axes: Option<Vec<isize>>,
norm: Option<String>,
) -> PyResult<Bound<'py, PyArrayDyn<Complex64>>> {
let result = crate::fft::rfftn(
x.inner.array(),
s.as_deref(),
axes.as_deref(),
norm.as_deref(),
)
.map_err(|e| PyValueError::new_err(format!("rfftn failed: {e}")))?;
Ok(result.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (x, s=None, axes=None, norm=None))]
fn irfftn(
x: PyReadonlyArrayDyn<'_, Complex64>,
s: Option<Vec<usize>>,
axes: Option<Vec<isize>>,
norm: Option<String>,
) -> PyResult<PyArray> {
let view = x.as_array();
let owned = view.to_owned();
let result = crate::fft::irfftn(&owned, s.as_deref(), axes.as_deref(), norm.as_deref())
.map_err(|e| PyValueError::new_err(format!("irfftn failed: {e}")))?;
let shape = result.shape().to_vec();
let (vec, _offset) = result.into_raw_vec_and_offset();
Ok(PyArray {
inner: crate::array::Array::from_vec_shape(vec, &shape)?,
})
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
let fft_module = PyModule::new(m.py(), "fft")?;
fft_module.add_function(wrap_pyfunction!(fft, m)?)?;
fft_module.add_function(wrap_pyfunction!(ifft, m)?)?;
fft_module.add_function(wrap_pyfunction!(rfft, m)?)?;
fft_module.add_function(wrap_pyfunction!(irfft, m)?)?;
fft_module.add_function(wrap_pyfunction!(fftn, m)?)?;
fft_module.add_function(wrap_pyfunction!(ifftn, m)?)?;
fft_module.add_function(wrap_pyfunction!(rfftn, m)?)?;
fft_module.add_function(wrap_pyfunction!(irfftn, m)?)?;
m.add_submodule(&fft_module)?;
Ok(())
}