use mlx_internal_macros::generate_macro;
use smallvec::SmallVec;
use crate::{
array::Array, constants::DEFAULT_STACK_VEC_LEN, error::Result, utils::guard::Guarded,
utils::IntoOption, with_stream, Stream,
};
fn resolve_axes(a: &Array, axes: Option<&[i32]>) -> SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> {
match axes {
Some(axes) => SmallVec::from_slice(axes),
None => (0..a.ndim() as i32).collect(),
}
}
pub fn fftshift<'a>(a: impl AsRef<Array>, axes: impl IntoOption<&'a [i32]>) -> Result<Array> {
let a = a.as_ref();
let axes = resolve_axes(a, axes.into_option());
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_fft_fftshift(
res,
a.as_ptr(),
axes.as_ptr(),
axes.len(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::fft"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `fftshift`"
)]
pub fn fftshift_device<'a>(
a: impl AsRef<Array>,
#[optional] axes: impl IntoOption<&'a [i32]>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
with_stream(stream.as_ref(), || fftshift(a, axes))
}
pub fn ifftshift<'a>(a: impl AsRef<Array>, axes: impl IntoOption<&'a [i32]>) -> Result<Array> {
let a = a.as_ref();
let axes = resolve_axes(a, axes.into_option());
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_fft_ifftshift(
res,
a.as_ptr(),
axes.as_ptr(),
axes.len(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::fft"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `ifftshift`"
)]
pub fn ifftshift_device<'a>(
a: impl AsRef<Array>,
#[optional] axes: impl IntoOption<&'a [i32]>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
with_stream(stream.as_ref(), || ifftshift(a, axes))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random;
fn check_fftshift(a: &Array, axes: Option<&[i32]>) {
let shifted = fftshift(a, axes).unwrap();
let unshifted = ifftshift(&shifted, axes).unwrap();
assert!(
unshifted.all_close(a, 1e-5, 1e-6, None).unwrap(),
"ifftshift(fftshift(x)) should equal x"
);
}
#[test]
fn test_fftshift_1d() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[100], None).unwrap();
check_fftshift(&r, None);
}
#[test]
fn test_fftshift_with_axes() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[4, 6], None).unwrap();
check_fftshift(&r, Some(&[0]));
check_fftshift(&r, Some(&[1]));
check_fftshift(&r, Some(&[0, 1]));
}
#[test]
fn test_fftshift_negative_axes() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[4, 6], None).unwrap();
check_fftshift(&r, Some(&[-1]));
}
#[test]
fn test_fftshift_odd_lengths() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[5, 7], None).unwrap();
check_fftshift(&r, None);
check_fftshift(&r, Some(&[0]));
}
#[test]
fn test_ifftshift_1d() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[100], None).unwrap();
let shifted = ifftshift(&r, None).unwrap();
let unshifted = fftshift(&shifted, None).unwrap();
assert!(
unshifted.all_close(&r, 1e-5, 1e-6, None).unwrap(),
"fftshift(ifftshift(x)) should equal x"
);
}
#[test]
fn test_ifftshift_with_axes() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[4, 6], None).unwrap();
for axes in [&[0][..], &[1][..], &[0, 1][..]] {
let shifted = ifftshift(&r, axes).unwrap();
let unshifted = fftshift(&shifted, axes).unwrap();
assert!(
unshifted.all_close(&r, 1e-5, 1e-6, None).unwrap(),
"fftshift(ifftshift(x)) should equal x for axes {:?}",
axes
);
}
}
#[test]
fn test_ifftshift_negative_axes() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[4, 6], None).unwrap();
let shifted = ifftshift(&r, &[-1]).unwrap();
let unshifted = fftshift(&shifted, &[-1]).unwrap();
assert!(unshifted.all_close(&r, 1e-5, 1e-6, None).unwrap(),);
}
#[test]
fn test_ifftshift_odd_lengths() {
random::seed(42).unwrap();
let r = random::uniform::<_, f32>(0.0, 1.0, &[5, 7], None).unwrap();
let shifted = ifftshift(&r, None).unwrap();
let unshifted = fftshift(&shifted, None).unwrap();
assert!(unshifted.all_close(&r, 1e-5, 1e-6, None).unwrap(),);
let shifted = ifftshift(&r, &[0]).unwrap();
let unshifted = fftshift(&shifted, &[0]).unwrap();
assert!(unshifted.all_close(&r, 1e-5, 1e-6, None).unwrap(),);
}
#[test]
fn test_fftshift_empty_array() {
let x = Array::from_slice::<f32>(&[], &[0]);
let shifted = fftshift(&x, None).unwrap();
assert!(shifted.eq_exact(&x).unwrap());
}
}