use crate::error::{FFTError, FFTResult};
use scirs2_core::numeric::Complex64;
use scirs2_core::numeric::NumCast;
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum HfftNorm {
Backward,
Forward,
Ortho,
}
impl HfftNorm {
pub(crate) fn parse(norm: Option<&str>) -> FFTResult<Self> {
match norm {
None | Some("backward") => Ok(HfftNorm::Backward),
Some("forward") => Ok(HfftNorm::Forward),
Some("ortho") => Ok(HfftNorm::Ortho),
Some(other) => Err(FFTError::ValueError(format!(
"Invalid norm value '{other}': expected \"backward\", \"forward\", or \"ortho\""
))),
}
}
pub(crate) fn forward_scale(self, n: usize) -> FFTResult<f64> {
match self {
HfftNorm::Backward => Ok(1.0),
HfftNorm::Forward => Ok(1.0 / Self::checked_n(n)?),
HfftNorm::Ortho => Ok(1.0 / Self::checked_n(n)?.sqrt()),
}
}
pub(crate) fn inverse_scale(self, n: usize) -> FFTResult<f64> {
match self {
HfftNorm::Forward => Ok(1.0),
HfftNorm::Backward => Ok(1.0 / Self::checked_n(n)?),
HfftNorm::Ortho => Ok(1.0 / Self::checked_n(n)?.sqrt()),
}
}
fn checked_n(n: usize) -> FFTResult<f64> {
if n == 0 {
return Err(FFTError::ValueError(
"Cannot normalize a zero-length HFFT/IHFFT transform".to_string(),
));
}
Ok(n as f64)
}
}
pub(crate) fn other_axis_index_combinations(shape: &[usize], axis: usize) -> Vec<Vec<usize>> {
let ndim = shape.len();
let mut combos: Vec<Vec<usize>> = vec![vec![0; ndim]];
for (dim, &size) in shape.iter().enumerate() {
if dim == axis {
continue;
}
let mut expanded = Vec::with_capacity(combos.len() * size.max(1));
for combo in &combos {
for v in 0..size {
let mut next = combo.clone();
next[dim] = v;
expanded.push(next);
}
}
combos = expanded;
}
combos
}
pub(crate) fn try_as_complex<T: Copy + Debug + 'static + NumCast>(val: T) -> Option<Complex64> {
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<Complex64>() {
unsafe {
let ptr = &val as *const T as *const Complex64;
return Some(*ptr);
}
}
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<scirs2_core::numeric::Complex32>() {
unsafe {
let ptr = &val as *const T as *const scirs2_core::numeric::Complex32;
let complex32 = *ptr;
return Some(Complex64::new(complex32.re as f64, complex32.im as f64));
}
}
let type_name = std::any::type_name::<T>();
if type_name.contains("Complex") {
let debug_str = format!("{val:?}");
let re_im: Vec<f64> = debug_str
.split(&[',', '(', ')', '{', '}', ':', ' '][..])
.filter_map(|s| s.trim().parse::<f64>().ok())
.collect();
if re_im.len() == 2 {
return Some(Complex64::new(re_im[0], re_im[1]));
}
}
macro_rules! handle_primitive {
($type:ty) => {
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<$type>() {
unsafe {
let ptr = &val as *const T as *const $type;
return Some(Complex64::new(*ptr as f64, 0.0));
}
}
};
}
handle_primitive!(f64);
handle_primitive!(f32);
handle_primitive!(i32);
handle_primitive!(i64);
handle_primitive!(u32);
handle_primitive!(u64);
handle_primitive!(i16);
handle_primitive!(u16);
handle_primitive!(i8);
handle_primitive!(u8);
let debug_str = format!("{val:?}");
if debug_str.contains("Complex") || (debug_str.contains("re") && debug_str.contains("im")) {
let re_im: Vec<f64> = debug_str
.split(&[',', '(', ')', '{', '}', ':', ' '][..])
.filter_map(|s| {
let trimmed = s.trim();
if !trimmed.is_empty() {
trimmed.parse::<f64>().ok()
} else {
None
}
})
.collect();
if re_im.len() == 2 {
return Some(Complex64::new(re_im[0], re_im[1]));
} else if debug_str.contains("re:") && debug_str.contains("im:") {
let re_str = debug_str
.split("re:")
.nth(1)
.and_then(|s| s.split(',').next());
let im_str = debug_str
.split("im:")
.nth(1)
.and_then(|s| s.split('}').next());
if let (Some(re_s), Some(im_s)) = (re_str, im_str) {
if let (Ok(re), Ok(im)) = (re_s.trim().parse::<f64>(), im_s.trim().parse::<f64>()) {
return Some(Complex64::new(re, im));
}
}
}
}
NumCast::from(val).map(|v| Complex64::new(v, 0.0))
}