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