scirs2_fft/
lib.rs

1#![allow(deprecated)]
2//! Fast Fourier Transform module
3//!
4//! This module provides implementations of various Fast Fourier Transform algorithms,
5#![allow(clippy::empty_line_after_doc_comments)]
6#![allow(clippy::doc_lazy_continuation)]
7#![allow(clippy::redundant_closure)]
8#![allow(clippy::field_reassign_with_default)]
9#![allow(clippy::needless_range_loop)]
10#![allow(clippy::manual_range_contains)]
11//! following `SciPy`'s `fft` module.
12//!
13//! ## Overview
14//!
15//! * Fast Fourier Transform (FFT) and inverse FFT for 1D, 2D, and N-dimensional arrays
16//! * Real-to-complex and complex-to-real FFT optimized for real-valued data
17//! * Hermitian-to-real and real-to-Hermitian FFT for complex signals with real spectra
18//! * Discrete cosine transform (DCT) types I-IV and their inverses
19//! * Discrete sine transform (DST) types I-IV and their inverses
20//! * Fractional Fourier Transform (FrFT) for rotations in the time-frequency plane
21//! * Non-Uniform Fast Fourier Transform (NUFFT) for non-uniformly sampled data
22//! * Spectrogram and Short-Time Fourier Transform (STFT) for time-frequency analysis
23//! * Waterfall plots for 3D visualization of time-frequency data
24//! * Window functions for signal processing (Hann, Hamming, Blackman, etc.)
25//! * Helper functions for frequency domain calculations and visualization
26//!
27//! ## Implementation Notes
28//!
29//! * Built on `rustfft` for efficient computation
30//! * Provides proper zero padding and array reshaping
31//! * Normalization options for compatibility with `SciPy`
32//! * Support for ndarrays through ndarray integration
33//!
34//! ## Examples
35//!
36//! ```
37//! use scirs2_fft::{fft, ifft};
38//! use num_complex::Complex64;
39//!
40//! // Generate a simple signal
41//! let signal = vec![1.0, 2.0, 3.0, 4.0];
42//!
43//! // Compute FFT of the signal
44//! let spectrum = fft(&signal, None).unwrap();
45//!
46//! // Inverse FFT should recover the original signal
47//! let recovered = ifft(&spectrum, None).unwrap();
48//!
49//! // Check that the recovered signal matches the original
50//! for (x, y) in signal.iter().zip(recovered.iter()) {
51//!     assert!((x - y.re).abs() < 1e-10);
52//!     assert!(y.im.abs() < 1e-10);
53//! }
54//! ```
55
56// Export error types
57pub mod error;
58pub use error::{FFTError, FFTResult};
59
60// FFT plan caching
61pub mod plan_cache;
62pub use plan_cache::{get_global_cache, init_global_cache, CacheStats, PlanCache};
63
64// Worker pool management
65pub mod worker_pool;
66pub use worker_pool::{
67    get_global_pool, get_workers, init_global_pool, set_workers, with_workers, WorkerConfig,
68    WorkerPool, WorkerPoolInfo,
69};
70
71// FFT backend system
72pub mod backend;
73pub use backend::{
74    get_backend_info, get_backend_manager, get_backend_name, init_backend_manager, list_backends,
75    set_backend, BackendContext, BackendInfo, BackendManager, FftBackend,
76};
77
78// FFT context managers
79pub mod context;
80pub use context::{
81    fft_context, with_backend, with_fft_settings, without_cache, FftContext, FftContextBuilder,
82    FftSettingsGuard,
83};
84
85// Advanced striding support
86pub mod strided_fft;
87pub use strided_fft::{fft_strided, fft_strided_complex, ifft_strided};
88
89// Plan serialization
90pub mod plan_serialization;
91pub use plan_serialization::{PlanDatabaseStats, PlanInfo, PlanMetrics, PlanSerializationManager};
92
93// Advanced FFT planning system
94pub mod planning;
95pub use planning::{
96    get_global_planner, init_global_planner, plan_ahead_of_time, AdvancedFftPlanner as FftPlanner,
97    FftPlan, FftPlanExecutor, PlanBuilder, PlannerBackend, PlanningConfig, PlanningStrategy,
98};
99
100// Adaptive planning extensions
101pub mod planning_adaptive;
102
103// Parallel planning extensions
104pub mod planning_parallel;
105pub use planning_parallel::{
106    ParallelExecutor, ParallelPlanResult, ParallelPlanner, ParallelPlanningConfig,
107};
108
109// Auto-tuning for hardware optimization
110pub mod auto_tuning;
111pub use auto_tuning::{AutoTuneConfig, AutoTuner, FftVariant, SizeRange, SizeStep};
112
113// Advanced mode coordinator for advanced AI-driven optimization (temporarily disabled)
114// pub mod advanced_coordinator;
115// pub use advanced_coordinator::{
116//     create_advanced_fft_coordinator, create_advanced_fft_coordinator_with_config,
117//     FftPerformanceMetrics, FftRecommendation, advancedFftConfig, advancedFftCoordinator,
118// };
119
120// Core modules are used conditionally in feature-specific implementations
121
122// FFT module structure
123pub mod dct;
124pub mod dst;
125pub mod fft;
126pub mod fht;
127pub mod hfft;
128pub mod rfft;
129
130// Re-export basic functions
131pub use dct::{dct, dct2, dctn, idct, idct2, idctn, DCTType};
132pub use dst::{dst, dst2, dstn, idst, idst2, idstn, DSTType};
133pub use fft::{fft, fft2, fftn, ifft, ifft2, ifftn};
134pub use fht::{fht, fht_sample_points, fhtoffset, ifht};
135pub use hfft::{hfft, hfft2, hfftn, ihfft, ihfft2, ihfftn};
136
137// Re-export parallel implementations when available
138#[cfg(feature = "parallel")]
139pub use fft::{fft2_parallel, ifft2_parallel};
140pub use rfft::{irfft, irfft2, irfftn, rfft, rfft2, rfftn};
141
142// Re-export SIMD-optimized implementations
143pub use simd_fft::{
144    fft2_adaptive, fft2_simd, fft_adaptive, fft_simd, fftn_adaptive, fftn_simd, ifft2_adaptive,
145    ifft2_simd, ifft_adaptive, ifft_simd, ifftn_adaptive, ifftn_simd, simd_support_available,
146};
147
148// Real FFT SIMD module
149pub mod simd_rfft;
150pub use simd_rfft::{irfft_adaptive, irfft_simd, rfft_adaptive, rfft_simd};
151
152// Helper modules
153pub mod helper;
154pub use helper::{fftfreq, fftshift, ifftshift, next_fast_len, prev_fast_len, rfftfreq};
155
156// Advanced FFT modules
157pub mod frft;
158pub mod frft_dft;
159pub mod frft_ozaktas;
160pub mod nufft;
161pub mod spectrogram;
162pub mod waterfall;
163pub use frft::{frft, frft_complex, frft_dft, frft_stable};
164pub use spectrogram::{spectrogram, spectrogram_normalized, stft as spectrogram_stft};
165pub use waterfall::{
166    apply_colormap, waterfall_3d, waterfall_lines, waterfall_mesh, waterfall_mesh_colored,
167};
168
169// Long-term goal implementations
170#[cfg(feature = "never")]
171pub mod distributed;
172pub mod gpu_kernel_stub;
173#[cfg(feature = "never")]
174pub mod optimized_fft;
175#[cfg(feature = "never")]
176pub mod signal_processing;
177pub mod simd_fft;
178pub mod sparse_fft;
179pub mod sparse_fft_cuda_kernels;
180pub mod sparse_fft_cuda_kernels_frequency_pruning;
181pub mod sparse_fft_cuda_kernels_iterative;
182pub mod sparse_fft_cuda_kernels_spectral_flatness;
183pub mod sparse_fft_gpu;
184pub mod sparse_fft_gpu_cuda;
185pub mod sparse_fft_gpu_kernels;
186pub mod sparse_fft_gpu_memory;
187#[cfg(feature = "never")]
188pub mod time_frequency;
189#[cfg(feature = "never")]
190pub use distributed::{
191    CommunicationPattern, DecompositionStrategy, DistributedConfig, DistributedFFT,
192};
193#[cfg(feature = "never")]
194pub use optimized_fft::{OptimizationLevel, OptimizedConfig, OptimizedFFT};
195#[cfg(feature = "never")]
196pub use signal_processing::{
197    convolve, cross_correlate, design_fir_filter, fir_filter, frequency_filter, FilterSpec,
198    FilterType, FilterWindow,
199};
200pub use sparse_fft::WindowFunction;
201pub use sparse_fft::{
202    adaptive_sparse_fft, frequency_pruning_sparse_fft, reconstruct_filtered,
203    reconstruct_high_resolution, reconstruct_spectrum, reconstruct_time_domain, sparse_fft,
204    sparse_fft2, sparse_fftn, spectral_flatness_sparse_fft,
205};
206pub use sparse_fft_cuda_kernels::{
207    execute_cuda_compressed_sensing_sparse_fft, execute_cuda_sublinear_sparse_fft,
208    CUDACompressedSensingSparseFFTKernel, CUDASublinearSparseFFTKernel, CUDAWindowKernel,
209};
210pub use sparse_fft_cuda_kernels_frequency_pruning::{
211    execute_cuda_frequency_pruning_sparse_fft, CUDAFrequencyPruningSparseFFTKernel,
212};
213pub use sparse_fft_cuda_kernels_iterative::{
214    execute_cuda_iterative_sparse_fft, CUDAIterativeSparseFFTKernel,
215};
216pub use sparse_fft_cuda_kernels_spectral_flatness::{
217    execute_cuda_spectral_flatness_sparse_fft, CUDASpectralFlatnessSparseFFTKernel,
218};
219pub use sparse_fft_gpu::{gpu_batch_sparse_fft, gpu_sparse_fft, GPUBackend};
220pub use sparse_fft_gpu_cuda::{
221    cuda_batch_sparse_fft,
222    cuda_sparse_fft,
223    get_cuda_devices,
224    FftGpuContext,
225    GpuDeviceInfo,
226    // CUDAStream - migrated to core GPU abstractions
227};
228pub use sparse_fft_gpu_kernels::{
229    execute_sparse_fft_kernel, GPUKernel, KernelConfig, KernelFactory, KernelImplementation,
230    KernelLauncher, KernelStats,
231};
232pub use sparse_fft_gpu_memory::{
233    get_global_memory_manager, init_global_memory_manager, memory_efficient_gpu_sparse_fft,
234    AllocationStrategy, BufferLocation, BufferType,
235};
236pub use sparse_fft_gpu_memory::{is_cuda_available, is_hip_available, is_sycl_available};
237
238// Multi-GPU processing module
239pub mod sparse_fft_multi_gpu;
240pub use sparse_fft_multi_gpu::{
241    multi_gpu_sparse_fft, GPUDeviceInfo, MultiGPUConfig, MultiGPUSparseFFT, WorkloadDistribution,
242};
243
244// Specialized hardware support module
245pub mod sparse_fft_specialized_hardware;
246pub use sparse_fft_specialized_hardware::{
247    specialized_hardware_sparse_fft, AcceleratorCapabilities, AcceleratorInfo, AcceleratorType,
248    HardwareAbstractionLayer, SpecializedHardwareManager,
249};
250// Batch processing module
251pub mod sparse_fft_batch;
252pub use sparse_fft_batch::{batch_sparse_fft, spectral_flatness_batch_sparse_fft, BatchConfig};
253
254#[cfg(feature = "never")]
255pub use time_frequency::{time_frequency_transform, TFConfig, TFTransform, WaveletType};
256
257// Memory-efficient FFT operations
258pub mod memory_efficient;
259pub use memory_efficient::{
260    fft2_efficient, fft_inplace, fft_streaming, process_in_chunks, FftMode,
261};
262
263// Optimized N-dimensional FFT
264pub mod ndim_optimized;
265pub use ndim_optimized::{fftn_memory_efficient, fftn_optimized, rfftn_optimized};
266
267// Hartley transform
268pub mod hartley;
269pub use hartley::{dht, dht2, fht as hartley_fht, idht};
270
271// Higher-order DCT and DST types (V-VIII)
272pub mod higher_order_dct_dst;
273pub use higher_order_dct_dst::{
274    dct_v, dct_vi, dct_vii, dct_viii, dst_v, dst_vi, dst_vii, dst_viii, idct_v, idct_vi, idct_vii,
275    idct_viii, idst_v, idst_vi, idst_vii, idst_viii,
276};
277
278// Modified DCT and DST (MDCT/MDST)
279pub mod mdct;
280pub use mdct::{imdct, imdst, mdct, mdct_overlap_add, mdst};
281
282// Window functions
283pub mod window;
284pub use window::{apply_window, get_window, Window};
285
286// Extended window functions and analysis
287pub mod window_extended;
288pub use window_extended::{
289    analyze_window, compare_windows, get_extended_window, visualize_window, ExtendedWindow,
290    WindowProperties,
291};
292
293// Chirp Z-Transform
294pub mod czt;
295pub use czt::{czt, czt_points, zoom_fft, CZT};
296
297// Automatic padding strategies
298pub mod padding;
299pub use padding::{
300    auto_pad_1d, auto_pad_complex, auto_pad_nd, remove_padding_1d, AutoPadConfig, PaddingMode,
301};
302
303/// Performs a Short-Time Fourier Transform (STFT).
304///
305/// Short-Time Fourier Transform (STFT) is used to determine the sinusoidal
306/// frequency and phase content of local sections of a signal as it changes over time.
307///
308/// # Arguments
309///
310/// * `x` - Input signal
311/// * `window` - Window function to apply
312/// * `nperseg` - Length of each segment
313/// * `noverlap` - Number of points to overlap between segments
314/// * `nfft` - Length of the FFT (optional, default is nperseg)
315/// * `fs` - Sampling frequency of the signal
316/// * `detrend` - Whether to remove the mean from each segment
317/// * `boundary` - Boundary to pad with ('zeros', 'constant', 'reflect', etc.)
318///
319/// # Returns
320///
321/// * Tuple of (frequencies, times, Zxx) where Zxx is the STFT result
322///
323/// # Errors
324///
325/// Returns an error if the computation fails.
326///
327/// # Examples
328///
329/// ```
330/// use scirs2_fft::{stft, window::Window};
331/// use std::f64::consts::PI;
332///
333/// // Generate a simple sine wave
334/// let fs = 1000.0; // 1 kHz sampling rate
335/// let t = (0..1000).map(|i| i as f64 / fs).collect::<Vec<_>>();
336/// let signal = t.iter().map(|&ti| (2.0 * PI * 100.0 * ti).sin()).collect::<Vec<_>>();
337///
338/// // Compute STFT
339/// let (frequencies, times, result) = stft(
340///     &signal,
341///     Window::Hann,
342///     256,
343///     Some(128),
344///     None,
345///     Some(fs),
346///     None,
347///     None,
348/// ).unwrap();
349///
350/// // Check dimensions
351/// assert_eq!(frequencies.len(), result.shape()[0]);
352/// assert_eq!(times.len(), result.shape()[1]);
353/// ```
354#[allow(clippy::too_many_arguments)]
355#[allow(dead_code)]
356pub fn stft<T>(
357    x: &[T],
358    window: Window,
359    nperseg: usize,
360    noverlap: Option<usize>,
361    nfft: Option<usize>,
362    fs: Option<f64>,
363    detrend: Option<bool>,
364    boundary: Option<&str>,
365) -> FFTResult<(Vec<f64>, Vec<f64>, ndarray::Array2<num_complex::Complex64>)>
366where
367    T: num_traits::NumCast + Copy + std::fmt::Debug,
368{
369    spectrogram::stft(
370        x,
371        window,
372        nperseg,
373        noverlap,
374        nfft,
375        fs,
376        detrend,
377        Some(true),
378        boundary,
379    )
380}
381
382/// Performs the Hilbert transform.
383///
384/// The Hilbert transform finds the analytical signal, which can be used to
385/// determine instantaneous amplitude and frequency. It is defined by convolving
386/// the signal with 1/(πt).
387///
388/// # Arguments
389///
390/// * `x` - Input signal (real-valued array)
391///
392/// # Returns
393///
394/// * A complex-valued array containing the analytic signal, where the real part
395///   is the original signal and the imaginary part is the Hilbert transform.
396///
397/// # Errors
398///
399/// Returns an error if the computation fails.
400///
401/// # Examples
402///
403/// ```
404/// use scirs2_fft::hilbert;
405/// use std::f64::consts::PI;
406///
407/// // Generate a cosine signal
408/// let n = 100;
409/// let freq = 5.0; // Hz
410/// let dt = 0.01;  // 100 Hz sampling
411/// let signal: Vec<f64> = (0..n).map(|i| (2.0 * PI * freq * i as f64 * dt).cos()).collect();
412///
413/// // Compute Hilbert transform
414/// let analytic_signal = hilbert(&signal).unwrap();
415///
416/// // For a cosine wave, the analytical signal should have a magnitude of approximately 1
417/// let mid_point = n / 2;
418/// let magnitude = (analytic_signal[mid_point].re.powi(2) +
419///                 analytic_signal[mid_point].im.powi(2)).sqrt();
420/// assert!((magnitude - 1.0).abs() < 0.1);
421/// ```
422///
423/// # References
424///
425/// * Marple, S. L. "Computing the Discrete-Time Analytic Signal via FFT."
426///   IEEE Transactions on Signal Processing, Vol. 47, No. 9, 1999.
427
428/// Helper function to try and extract a Complex value
429#[allow(dead_code)]
430fn try_as_complex<U: 'static + Copy>(val: U) -> Option<num_complex::Complex64> {
431    use num_complex::Complex64;
432    use std::any::Any;
433
434    // Try to use runtime type checking with Any for _complex types
435    if let Some(_complex) = (&val as &dyn Any).downcast_ref::<Complex64>() {
436        return Some(*_complex);
437    }
438
439    // Try to handle f32 _complex numbers
440    if let Some(complex32) = (&val as &dyn Any).downcast_ref::<num_complex::Complex<f32>>() {
441        return Some(Complex64::new(complex32.re as f64, complex32.im as f64));
442    }
443
444    None
445}
446
447#[allow(dead_code)]
448pub fn hilbert<T>(x: &[T]) -> FFTResult<Vec<num_complex::Complex64>>
449where
450    T: num_traits::NumCast + Copy + std::fmt::Debug + 'static,
451{
452    use num_complex::Complex64;
453
454    // Input length
455    let n = x.len();
456
457    // Convert input to a vector of f64
458    let signal: Vec<f64> = x
459        .iter()
460        .map(|&val| {
461            // First, try to cast directly to f64
462            if let Some(val_f64) = num_traits::cast::<T, f64>(val) {
463                return Ok(val_f64);
464            }
465
466            // If direct casting fails, check if it's a Complex value
467            // and use just the real part (for doctests which use Complex inputs)
468            match try_as_complex(val) {
469                Some(c) => Ok(c.re),
470                None => Err(FFTError::ValueError(format!(
471                    "Could not convert {val:?} to numeric type"
472                ))),
473            }
474        })
475        .collect::<FFTResult<Vec<_>>>()?;
476
477    // Compute FFT of the input signal
478    let spectrum = fft(&signal, None)?;
479
480    // Create the frequency domain filter for the Hilbert transform
481    // For a proper Hilbert transform, we need to:
482    // 1. Set the DC component (0 frequency) to 1
483    // 2. Double the positive frequencies and multiply by -i
484    // 3. Zero out the negative frequencies
485    let mut h = vec![Complex64::new(1.0, 0.0); n];
486
487    if n % 2 == 0 {
488        // Even length case
489        h[0] = Complex64::new(1.0, 0.0); // DC component
490        h[n / 2] = Complex64::new(1.0, 0.0); // Nyquist component
491
492        // Positive frequencies (multiply by 2 and by -i)
493        h.iter_mut().take(n / 2).skip(1).for_each(|val| {
494            *val = Complex64::new(0.0, -2.0); // Equivalent to 2 * (-i)
495        });
496
497        // Negative frequencies (set to 0)
498        h.iter_mut().skip(n / 2 + 1).for_each(|val| {
499            *val = Complex64::new(0.0, 0.0);
500        });
501    } else {
502        // Odd length case
503        h[0] = Complex64::new(1.0, 0.0); // DC component
504
505        // Positive frequencies (multiply by 2 and by -i)
506        h.iter_mut().take(n.div_ceil(2)).skip(1).for_each(|val| {
507            *val = Complex64::new(0.0, -2.0); // Equivalent to 2 * (-i)
508        });
509
510        // Negative frequencies (set to 0)
511        h.iter_mut().skip(n.div_ceil(2)).for_each(|val| {
512            *val = Complex64::new(0.0, 0.0);
513        });
514    }
515
516    // Apply the filter in frequency domain
517    let filtered_spectrum: Vec<Complex64> = spectrum
518        .iter()
519        .zip(h.iter())
520        .map(|(&s, &h)| s * h)
521        .collect();
522
523    // Compute inverse FFT to get the analytic signal
524    let analytic_signal = ifft(&filtered_spectrum, None)?;
525
526    Ok(analytic_signal)
527}
528
529/// Returns the minimum and maximum values for each FFT dimension.
530///
531/// # Arguments
532///
533/// * `shape` - The shape of the FFT
534///
535/// # Returns
536///
537/// A vector of tuples (min, max) for each dimension of the FFT.
538///
539/// # Examples
540///
541/// ```
542/// use scirs2_fft::fft_bounds;
543///
544/// let bounds = fft_bounds(&[4, 4]);
545/// assert_eq!(bounds, vec![(-2, 1), (-2, 1)]);
546///
547/// let bounds = fft_bounds(&[5, 3]);
548/// assert_eq!(bounds, vec![(-2, 2), (-1, 1)]);
549/// ```
550#[must_use]
551#[allow(dead_code)]
552pub fn fft_bounds(shape: &[usize]) -> Vec<(i32, i32)> {
553    shape
554        .iter()
555        .map(|&n| {
556            // Cast with explicit handling for possible truncation/wrapping
557            let n_i32 = i32::try_from(n).unwrap_or(i32::MAX);
558            let min = -(n_i32 / 2);
559            let max = n_i32 - 1 + min;
560            (min, max)
561        })
562        .collect()
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use approx::assert_relative_eq;
569    use std::f64::consts::PI;
570
571    #[test]
572    fn test_fft_bounds() {
573        // Test even sizes
574        let bounds = fft_bounds(&[4, 4]);
575        assert_eq!(bounds, vec![(-2, 1), (-2, 1)]);
576
577        // Test odd sizes
578        let bounds = fft_bounds(&[5, 3]);
579        assert_eq!(bounds, vec![(-2, 2), (-1, 1)]);
580
581        // Test mixed sizes
582        let bounds = fft_bounds(&[6, 7, 8]);
583        assert_eq!(bounds, vec![(-3, 2), (-3, 3), (-4, 3)]);
584    }
585
586    #[test]
587    fn test_hilbert_transform() {
588        // Test on a cosine wave instead of sine wave to make the math easier
589        let n = 1000;
590        let freq = 5.0; // 5 Hz
591        let sample_rate = 100.0; // 100 Hz
592        let dt = 1.0 / sample_rate;
593
594        // Create a cosine wave
595        let t: Vec<f64> = (0..n).map(|i| i as f64 * dt).collect();
596        let signal: Vec<f64> = t.iter().map(|&ti| (2.0 * PI * freq * ti).cos()).collect();
597
598        // Compute Hilbert transform
599        let analytic = hilbert(&signal).unwrap();
600
601        // The Hilbert transform of cos(x) is sin(x)
602        // So the analytic signal should be cos(x) + i*sin(x) = e^(ix)
603        // Check the envelope (magnitude) which should be approximately 1
604        let start_idx = n / 4;
605        let end_idx = 3 * n / 4;
606
607        for i in start_idx..end_idx {
608            let magnitude = (analytic[i].re.powi(2) + analytic[i].im.powi(2)).sqrt();
609            assert_relative_eq!(magnitude, 1.0, epsilon = 0.1);
610
611            // Also check if the phase is advancing correctly
612            if i > start_idx {
613                let phase_i = analytic[i].im.atan2(analytic[i].re);
614                let phase_i_prev = analytic[i - 1].im.atan2(analytic[i - 1].re);
615
616                // Check if phase is advancing in the right direction
617                // We need to handle phase wrapping around ±π
618                let mut phase_diff = phase_i - phase_i_prev;
619                if phase_diff > PI {
620                    phase_diff -= 2.0 * PI;
621                } else if phase_diff < -PI {
622                    phase_diff += 2.0 * PI;
623                }
624
625                // For positive frequency, phase should generally advance positively
626                assert!(phase_diff > 0.0);
627            }
628        }
629    }
630}
631
632// Include ARM-specific FFT tests
633#[cfg(test)]
634mod arm_fft_test;