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}