use alloc::vec::Vec;
use core::convert::TryFrom;
use slatec_sys::FortranInteger;
use crate::runtime::lock_native;
pub use crate::transforms::fft::FftError;
#[derive(Debug)]
struct PlanCore {
length: usize,
native_length: FortranInteger,
workspace: Vec<f32>,
}
impl PlanCore {
fn new(
length: usize,
minimum: usize,
workspace_length: usize,
initializer: unsafe extern "C" fn(*mut FortranInteger, *mut f32),
) -> Result<Self, FftError> {
if length < minimum {
return Err(FftError::InvalidLength { length, minimum });
}
let native_length =
FortranInteger::try_from(length).map_err(|_| FftError::DimensionOverflow)?;
let mut workspace = allocate_zeroed(workspace_length)?;
let mut native_length_for_call = native_length;
let _native = lock_native();
unsafe { initializer(&mut native_length_for_call, workspace.as_mut_ptr()) };
Ok(Self {
length,
native_length,
workspace,
})
}
fn check_values(&self, values: &[f32]) -> Result<(), FftError> {
if values.len() == self.length {
Ok(())
} else {
Err(FftError::LengthMismatch {
expected: self.length,
actual: values.len(),
})
}
}
}
fn allocate_zeroed(length: usize) -> Result<Vec<f32>, FftError> {
let mut values = Vec::new();
values
.try_reserve_exact(length)
.map_err(|_| FftError::AllocationFailed)?;
values.resize(length, 0.0);
Ok(values)
}
fn rfft_workspace(length: usize) -> Result<usize, FftError> {
length
.checked_mul(2)
.and_then(|value| value.checked_add(15))
.ok_or(FftError::DimensionOverflow)
}
fn three_n_workspace(length: usize) -> Result<usize, FftError> {
length
.checked_mul(3)
.and_then(|value| value.checked_add(15))
.ok_or(FftError::DimensionOverflow)
}
fn sint_workspace(length: usize) -> Result<usize, FftError> {
length
.checked_mul(7)
.and_then(|value| value.checked_div(2))
.and_then(|value| value.checked_add(16))
.ok_or(FftError::DimensionOverflow)
}
#[derive(Debug)]
pub struct RealFftPlan {
core: PlanCore,
}
impl RealFftPlan {
pub fn new(length: usize) -> Result<Self, FftError> {
Ok(Self {
core: PlanCore::new(
length,
1,
rfft_workspace(length)?,
slatec_sys::fftpack::rffti,
)?,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.core.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn forward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.core.check_values(values)?;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
slatec_sys::fftpack::rfftf(
&mut n,
values.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
pub fn backward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.core.check_values(values)?;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
slatec_sys::fftpack::rfftb(
&mut n,
values.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
pub fn spectrum<'a>(&self, values: &'a [f32]) -> Result<RealSpectrumRef<'a>, FftError> {
self.core.check_values(values)?;
Ok(RealSpectrumRef { values })
}
}
#[derive(Clone, Copy, Debug)]
pub struct RealSpectrumRef<'a> {
values: &'a [f32],
}
impl<'a> RealSpectrumRef<'a> {
#[must_use]
pub fn dc(self) -> f32 {
self.values[0]
}
#[must_use]
pub fn harmonic(self, harmonic: usize) -> Option<(f32, f32)> {
if harmonic == 0 || harmonic > (self.values.len() - 1) / 2 {
return None;
}
Some((self.values[2 * harmonic - 1], self.values[2 * harmonic]))
}
#[must_use]
pub fn nyquist(self) -> Option<f32> {
(self.values.len() % 2 == 0).then(|| self.values[self.values.len() - 1])
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EasyRealSpectrum {
pub mean: f32,
pub cosine: Vec<f32>,
pub sine: Vec<f32>,
}
#[derive(Debug)]
pub struct EasyRealFftPlan {
core: PlanCore,
}
impl EasyRealFftPlan {
pub fn new(length: usize) -> Result<Self, FftError> {
Ok(Self {
core: PlanCore::new(
length,
1,
three_n_workspace(length)?,
slatec_sys::fftpack::ezffti,
)?,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.core.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn forward(&mut self, values: &[f32]) -> Result<EasyRealSpectrum, FftError> {
self.core.check_values(values)?;
let coefficient_length = self.core.length / 2;
let mut cosine = allocate_zeroed(coefficient_length)?;
let mut sine = allocate_zeroed(coefficient_length)?;
let mut mean = 0.0;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
slatec_sys::fftpack::ezfftf(
&mut n,
values.as_ptr(),
&mut mean,
cosine.as_mut_ptr(),
sine.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(EasyRealSpectrum { mean, cosine, sine })
}
pub fn backward(
&mut self,
spectrum: &EasyRealSpectrum,
values: &mut [f32],
) -> Result<(), FftError> {
self.core.check_values(values)?;
let expected = self.core.length / 2;
if spectrum.cosine.len() != expected {
return Err(FftError::LengthMismatch {
expected,
actual: spectrum.cosine.len(),
});
}
if spectrum.sine.len() != expected {
return Err(FftError::LengthMismatch {
expected,
actual: spectrum.sine.len(),
});
}
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
slatec_sys::fftpack::ezfftb(
&mut n,
values.as_mut_ptr(),
&spectrum.mean,
spectrum.cosine.as_ptr(),
spectrum.sine.as_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
}
macro_rules! define_self_inverse_plan {
($type_name:ident, $initializer:ident, $transform:ident, $minimum:expr, $workspace:ident, $description:literal, $scale:literal) => {
#[doc = $description]
#[doc = "\n\nThis plan owns its initialized native workspace. `transform` is"]
#[doc = "self-inverse up to the native scale "]
#[doc = $scale]
#[derive(Debug)]
pub struct $type_name {
core: PlanCore,
}
impl $type_name {
pub fn new(length: usize) -> Result<Self, FftError> {
Ok(Self {
core: PlanCore::new(
length,
$minimum,
$workspace(length)?,
slatec_sys::fftpack::$initializer,
)?,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.core.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn transform(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.core.check_values(values)?;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
slatec_sys::fftpack::$transform(
&mut n,
values.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
}
};
}
define_self_inverse_plan!(
SineTransformPlan,
sinti,
sint,
1,
sint_workspace,
"A plan for SLATEC `SINT`, the full sine transform.",
"`2 * (length + 1)`."
);
define_self_inverse_plan!(
CosineTransformPlan,
costi,
cost,
2,
three_n_workspace,
"A plan for SLATEC `COST`, the full cosine transform.",
"`2 * (length - 1)`."
);
#[derive(Debug)]
pub struct QuarterWaveSinePlan {
core: PlanCore,
}
impl QuarterWaveSinePlan {
pub fn new(length: usize) -> Result<Self, FftError> {
Ok(Self {
core: PlanCore::new(
length,
1,
three_n_workspace(length)?,
slatec_sys::fftpack::sinqi,
)?,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.core.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn forward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.apply(values, slatec_sys::fftpack::sinqf)
}
pub fn backward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.apply(values, slatec_sys::fftpack::sinqb)
}
fn apply(
&mut self,
values: &mut [f32],
transform: unsafe extern "C" fn(*mut FortranInteger, *mut f32, *mut f32),
) -> Result<(), FftError> {
self.core.check_values(values)?;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
transform(
&mut n,
values.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
}
#[derive(Debug)]
pub struct QuarterWaveCosinePlan {
core: PlanCore,
}
impl QuarterWaveCosinePlan {
pub fn new(length: usize) -> Result<Self, FftError> {
Ok(Self {
core: PlanCore::new(
length,
1,
three_n_workspace(length)?,
slatec_sys::fftpack::cosqi,
)?,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.core.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn forward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.apply(values, slatec_sys::fftpack::cosqf)
}
pub fn backward(&mut self, values: &mut [f32]) -> Result<(), FftError> {
self.apply(values, slatec_sys::fftpack::cosqb)
}
fn apply(
&mut self,
values: &mut [f32],
transform: unsafe extern "C" fn(*mut FortranInteger, *mut f32, *mut f32),
) -> Result<(), FftError> {
self.core.check_values(values)?;
let mut n = self.core.native_length;
let _native = lock_native();
unsafe {
transform(
&mut n,
values.as_mut_ptr(),
self.core.workspace.as_mut_ptr(),
)
};
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{CosineTransformPlan, EasyRealFftPlan, FftError, RealFftPlan, SineTransformPlan};
#[test]
fn validates_lengths_without_native_entry() {
assert!(matches!(
CosineTransformPlan::new(1),
Err(FftError::InvalidLength {
length: 1,
minimum: 2
})
));
let plan = RealFftPlan::new(1).expect("single-point real FFT plan");
assert!(matches!(
plan.spectrum(&[]),
Err(FftError::LengthMismatch {
expected: 1,
actual: 0
})
));
assert_eq!(SineTransformPlan::new(0).is_err(), true);
assert_eq!(EasyRealFftPlan::new(usize::MAX).is_err(), true);
}
}