use std::{
ffi::{c_int, c_void},
marker::PhantomData,
};
use crate::{
BaseItem, RlstScalar, Shape, StableRawAccessMut, Stride,
fftw::{FftwPlanFlags, FftwPlanPtrType, FftwPlanPtrTypeTrait, PlanInplace},
};
use itertools::{Itertools, izip};
use super::{FFTW_PLAN_INTERFACE, Fft, FftwPlanPtr, PlanInterfaceTrait};
pub use fftw_sys::fftw_r2r_kind as R2RPlanKind;
#[allow(private_bounds)]
pub trait R2RPlan<Item, ArrayImpl, const NDIM: usize>
where
ArrayImpl: BaseItem<Item = Item>,
Item: RlstScalar + 'static,
FftwPlanPtrType<Item::Real>: FftwPlanPtrTypeTrait,
{
fn r2r_fft(
&self,
flags: FftwPlanFlags,
kind: &[R2RPlanKind],
) -> Option<PlanInplace<Item, ArrayImpl, NDIM>>;
}
macro_rules! impl_r2r_inplace {
($ty: ty, $precision: ident) => {
impl<ArrayImpl, const NDIM: usize> R2RPlan<$ty, ArrayImpl, NDIM> for Fft<ArrayImpl, NDIM>
where
ArrayImpl: Shape<NDIM> + Stride<NDIM> + StableRawAccessMut<Item = $ty>,
{
fn r2r_fft(
&self,
flags: FftwPlanFlags,
kind: &[R2RPlanKind],
) -> Option<PlanInplace<$ty, ArrayImpl, NDIM>> {
let mut guard = self.borrow_mut().unwrap();
let arr = guard.array_mut();
assert_eq!(
kind.len(),
NDIM,
"`kind` has {} dimensions but {NDIM} required",
kind.len()
);
if NDIM > 3 {
return None;
}
if !arr.is_contiguous() || arr.is_empty() {
return None;
}
let mut shape = arr
.shape()
.iter()
.map(|&elem| elem as i32)
.collect_array()
.unwrap();
if matches!(arr.memory_layout(), crate::MemoryLayout::ColumnMajor) {
shape = {
let mut rev_shape = [0_i32; NDIM];
for (&orig_index, rev_index) in
izip!(shape.iter().rev(), rev_shape.iter_mut())
{
*rev_index = orig_index;
}
rev_shape
};
}
let data = arr.data_mut()?.as_mut_ptr();
let plan_ptr = unsafe {
FFTW_PLAN_INTERFACE.lock().unwrap().$precision.create_r2r(
NDIM as c_int,
shape.as_ptr(),
data,
data,
kind,
flags as u32,
)
};
if plan_ptr.is_null() {
None
} else {
Some(PlanInplace::new(
self.shared_ref.clone(),
FftwPlanPtr {
ptr: plan_ptr as *mut c_void,
_marker: PhantomData,
},
))
}
}
}
};
}
impl_r2r_inplace!(f64, fftw_double);
impl_r2r_inplace!(f32, fftw_single);
#[cfg(test)]
mod test {
use std::f64::consts::PI;
use crate::{
DynArray, EvaluateArray,
fftw::{
FftPlanInplace, FftwPlanFlags,
r2r_inplace::{R2RPlan, R2RPlanKind},
},
};
#[test]
fn test_dct_ii_against_direct_reference() {
let input = [1.0, -2.0, 0.5, 3.0, -0.25];
let fft = DynArray::<f64, _>::from_shape([input.len()]).into_fft();
let mut plan = fft
.r2r_fft(FftwPlanFlags::Estimate, &[R2RPlanKind::FFTW_REDFT10])
.unwrap();
fft.borrow_mut()
.unwrap()
.data_mut()
.unwrap()
.copy_from_slice(&input);
plan.execute();
let actual = fft.borrow().unwrap().eval();
for k in 0..input.len() {
let expected = 2.0
* input
.iter()
.enumerate()
.map(|(j, value)| {
value * (PI * (j as f64 + 0.5) * k as f64 / input.len() as f64).cos()
})
.sum::<f64>();
approx::assert_relative_eq!(actual[[k]], expected, epsilon = 1E-10);
}
}
#[test]
fn test_r2r_1d_f64() {
let n = 10;
let mut original = DynArray::<f64, _>::from_shape([10]);
original.fill_from_seed_equally_distributed(0);
let fft = original.eval().into_fft();
fft.r2r_fft(FftwPlanFlags::Estimate, &[R2RPlanKind::FFTW_REDFT10])
.unwrap()
.execute();
fft.r2r_fft(FftwPlanFlags::Estimate, &[R2RPlanKind::FFTW_REDFT01])
.unwrap()
.execute();
let actual = fft.borrow().unwrap().eval();
crate::assert_array_relative_eq!((2 * n) as f64 * original.r(), actual.r(), 1E-10);
}
#[test]
fn test_r2r_1d_f32() {
let n = 10;
let mut original = DynArray::<f32, _>::from_shape([10]);
original.fill_from_seed_equally_distributed(0);
let fft = original.eval().into_fft();
fft.r2r_fft(FftwPlanFlags::Estimate, &[R2RPlanKind::FFTW_REDFT10])
.unwrap()
.execute();
fft.r2r_fft(FftwPlanFlags::Estimate, &[R2RPlanKind::FFTW_REDFT01])
.unwrap()
.execute();
let actual = fft.borrow().unwrap().eval();
crate::assert_array_relative_eq!((2 * n) as f32 * original.r(), actual.r(), 1E-5);
}
#[test]
fn test_r2r_3d_f64() {
let mut original = DynArray::<f64, _>::from_shape([4, 6, 2]);
original.fill_from_seed_equally_distributed(0);
let fft = original.eval().into_fft();
fft.r2r_fft(
FftwPlanFlags::Estimate,
&[
R2RPlanKind::FFTW_REDFT10,
R2RPlanKind::FFTW_REDFT10,
R2RPlanKind::FFTW_REDFT10,
],
)
.unwrap()
.execute();
fft.r2r_fft(
FftwPlanFlags::Estimate,
&[
R2RPlanKind::FFTW_REDFT01,
R2RPlanKind::FFTW_REDFT01,
R2RPlanKind::FFTW_REDFT01,
],
)
.unwrap()
.execute();
let actual = fft.borrow().unwrap().eval();
crate::assert_array_relative_eq!(384.0_f64 * original.r(), actual.r(), 1E-10);
}
}