scirs2_fft/fft/
utility.rs

1//! Utility functions for FFT operations
2//!
3//! This module contains helper functions used by the FFT implementation.
4
5use crate::error::{FFTError, FFTResult};
6use scirs2_core::ndarray::{Array, ArrayD, IxDyn};
7use scirs2_core::numeric::Complex64;
8use std::fmt::Debug;
9
10/// Try to convert a value to Complex64
11///
12/// Attempts to interpret the given value as a Complex64. Currently only handles
13/// direct Complex64 values, but can be extended to support more types.
14///
15/// # Arguments
16///
17/// * `val` - The value to convert
18///
19/// # Returns
20///
21/// * `Some(Complex64)` if the conversion was successful
22/// * `None` if the conversion failed
23pub(crate) fn try_as_complex<T: Copy + Debug + 'static>(val: T) -> Option<Complex64> {
24    // Check if the type is Complex64
25    if std::any::TypeId::of::<T>() == std::any::TypeId::of::<Complex64>() {
26        unsafe {
27            // This is a bit of a hack, but necessary for type detection at runtime
28            let ptr = &val as *const T as *const Complex64;
29            return Some(*ptr);
30        }
31    }
32
33    None
34}
35
36/// Check if a number is a power of two
37///
38/// # Arguments
39///
40/// * `n` - The number to check
41///
42/// # Returns
43///
44/// `true` if the number is a power of 2, `false` otherwise
45#[inline]
46#[allow(dead_code)]
47pub fn is_power_of_two(n: usize) -> bool {
48    n != 0 && (n & (n - 1)) == 0
49}
50
51/// Calculate the next power of two greater than or equal to `n`
52///
53/// # Arguments
54///
55/// * `n` - The input number
56///
57/// # Returns
58///
59/// The next power of 2 that is greater than or equal to `n`
60#[inline]
61#[allow(dead_code)]
62pub fn next_power_of_two(n: usize) -> usize {
63    if n == 0 {
64        return 1;
65    }
66
67    // If n is already a power of 2, return it
68    if is_power_of_two(n) {
69        return n;
70    }
71
72    // Find the position of the most significant bit
73    let msb_pos = usize::BITS - n.leading_zeros();
74
75    // Return 2^msb_pos
76    1 << msb_pos
77}
78
79/// Validate FFT arguments and determine output size
80///
81/// # Arguments
82///
83/// * `input_size` - The size of the input data
84/// * `n` - The requested output size (optional)
85///
86/// # Returns
87///
88/// The validated FFT size
89///
90/// # Errors
91///
92/// Returns an error if the input size is zero or n is zero
93#[allow(dead_code)]
94pub fn validate_fft_size(inputsize: usize, n: Option<usize>) -> FFTResult<usize> {
95    if inputsize == 0 {
96        return Err(FFTError::ValueError("Input cannot be empty".to_string()));
97    }
98
99    match n {
100        Some(0) => Err(FFTError::ValueError("FFT _size cannot be zero".to_string())),
101        Some(_size) => Ok(_size),
102        None => Ok(next_power_of_two(inputsize)),
103    }
104}
105
106/// Determine the N-dimensional shapes for FFT operations
107///
108/// # Arguments
109///
110/// * `inputshape` - The shape of the input array
111/// * `shape` - The requested output shape (optional)
112///
113/// # Returns
114///
115/// The validated output shape
116///
117/// # Errors
118///
119/// Returns an error if the dimensions don't match
120#[allow(dead_code)]
121pub fn validate_fftshapes(inputshape: &[usize], shape: Option<&[usize]>) -> FFTResult<Vec<usize>> {
122    match shape {
123        Some(outputshape) => {
124            if outputshape.len() != inputshape.len() {
125                return Err(FFTError::ValueError(
126                    "Output shape must have the same number of dimensions as input".to_string(),
127                ));
128            }
129            Ok(outputshape.to_vec())
130        }
131        None => Ok(inputshape.to_vec()),
132    }
133}
134
135/// Validate axes for FFT operations
136///
137/// # Arguments
138///
139/// * `ndim` - The number of dimensions in the array
140/// * `axes` - The requested axes for FFT (optional)
141///
142/// # Returns
143///
144/// The validated axes
145///
146/// # Errors
147///
148/// Returns an error if any axis is out of bounds
149#[allow(dead_code)]
150pub fn validate_fft_axes(ndim: usize, axes: Option<&[usize]>) -> FFTResult<Vec<usize>> {
151    match axes {
152        Some(axes) => {
153            // Check for axes out of bounds
154            for &axis in axes {
155                if axis >= ndim {
156                    return Err(FFTError::ValueError(format!(
157                        "Axis {axis} out of bounds for array of dimension {ndim}"
158                    )));
159                }
160            }
161            Ok(axes.to_vec())
162        }
163        None => Ok((0..ndim).collect()),
164    }
165}
166
167/// Create a complex array filled with zeros with the given shape
168///
169/// # Arguments
170///
171/// * `shape` - The shape of the array as a slice
172///
173/// # Returns
174///
175/// A new complex array with the specified shape
176/// Marked as dead code but kept for API consistency
177#[allow(dead_code)]
178pub fn zeros_like_complex(shape: &[usize]) -> ArrayD<Complex64> {
179    ArrayD::<Complex64>::zeros(IxDyn(shape))
180}
181
182/// Expand a real array into a complex array with zero imaginary part
183///
184/// # Arguments
185///
186/// * `real_array` - The real-valued array
187///
188/// # Returns
189///
190/// A complex array with the same shape
191#[allow(dead_code)]
192pub fn real_to_complex<D>(_realarray: &Array<f64, D>) -> Array<Complex64, D>
193where
194    D: scirs2_core::ndarray::Dimension,
195{
196    _realarray.mapv(|x| Complex64::new(x, 0.0))
197}
198
199/// Extract the real part of a complex array
200///
201/// # Arguments
202///
203/// * `complex_array` - The complex-valued array
204///
205/// # Returns
206///
207/// A real array with the same shape
208#[allow(dead_code)]
209pub fn complex_to_real<D>(_complexarray: &Array<Complex64, D>) -> Array<f64, D>
210where
211    D: scirs2_core::ndarray::Dimension,
212{
213    _complexarray.mapv(|x| x.re)
214}
215
216/// Calculate the magnitude of a complex array (absolute values)
217///
218/// # Arguments
219///
220/// * `complex_array` - The complex-valued array
221///
222/// # Returns
223///
224/// A real array with the magnitude (absolute value) of each element
225#[allow(dead_code)]
226pub fn complex_magnitude<D>(_complexarray: &Array<Complex64, D>) -> Array<f64, D>
227where
228    D: scirs2_core::ndarray::Dimension,
229{
230    _complexarray.mapv(|x| x.norm())
231}
232
233/// Calculate the phase angle of a complex array (in radians)
234///
235/// # Arguments
236///
237/// * `complex_array` - The complex-valued array
238///
239/// # Returns
240///
241/// A real array with the phase angle of each element
242#[allow(dead_code)]
243pub fn complex_angle<D>(_complexarray: &Array<Complex64, D>) -> Array<f64, D>
244where
245    D: scirs2_core::ndarray::Dimension,
246{
247    _complexarray.mapv(|x| x.arg())
248}
249
250/// Calculate the power spectrum of a complex array (squared magnitude)
251///
252/// # Arguments
253///
254/// * `complex_array` - The complex-valued array
255///
256/// # Returns
257///
258/// A real array with the power spectrum (squared magnitude) of each element
259#[allow(dead_code)]
260pub fn power_spectrum<D>(_complexarray: &Array<Complex64, D>) -> Array<f64, D>
261where
262    D: scirs2_core::ndarray::Dimension,
263{
264    _complexarray.mapv(|x| x.norm_sqr())
265}
266
267/// Convert an array slice to an index vector
268///
269/// # Arguments
270///
271/// * `slice` - The array slice
272///
273/// # Returns
274///
275/// A vector of indices for the slice
276/// Marked as dead code but kept for API consistency
277#[allow(dead_code)]
278pub fn slice_to_indices(slice: &[usize]) -> Vec<usize> {
279    slice.to_vec()
280}