scirs2_fft/sparse_fft/
reconstruction.rs

1//! Spectrum reconstruction utilities for Sparse FFT
2//!
3//! This module provides functions for reconstructing signals and spectra
4//! from sparse FFT results.
5
6use crate::error::{FFTError, FFTResult};
7use crate::fft::ifft;
8use scirs2_core::numeric::Complex64;
9use std::f64::consts::PI;
10
11use super::algorithms::SparseFFTResult;
12
13/// Reconstruct the full spectrum from sparse FFT result
14///
15/// # Arguments
16///
17/// * `sparse_result` - The sparse FFT result
18/// * `n` - Length of the full spectrum
19///
20/// # Returns
21///
22/// * Full spectrum
23///
24/// # Examples
25///
26/// ```rust
27/// use scirs2_fft::sparse_fft::{sparse_fft, reconstruct_spectrum};
28///
29/// // Generate a sparse signal
30/// let n = 64;
31/// let mut signal = vec![0.0; n];
32/// for i in 0..n {
33///     let t = 2.0 * std::f64::consts::PI * (i as f64) / (n as f64);
34///     signal[i] = 1.0 * (3.0 * t).sin() + 0.5 * (7.0 * t).sin();
35/// }
36///
37/// // Compute sparse FFT with k=4
38/// let sparse_result = sparse_fft(&signal, 4, None, None).unwrap();
39///
40/// // Reconstruct full spectrum
41/// let full_spectrum = reconstruct_spectrum(&sparse_result, n).unwrap();
42///
43/// // The reconstructed spectrum should have length n
44/// assert_eq!(full_spectrum.len(), n);
45/// ```
46#[allow(dead_code)]
47pub fn reconstruct_spectrum(
48    sparse_result: &SparseFFTResult,
49    n: usize,
50) -> FFTResult<Vec<Complex64>> {
51    // Create full spectrum from sparse representation
52    let mut spectrum = vec![Complex64::new(0.0, 0.0); n];
53    for (value, &index) in sparse_result
54        .values
55        .iter()
56        .zip(sparse_result.indices.iter())
57    {
58        spectrum[index] = *value;
59    }
60    Ok(spectrum)
61}
62
63/// Reconstructs time-domain signal from sparse frequency components
64///
65/// # Arguments
66///
67/// * `sparse_result` - The sparse FFT result
68/// * `n` - Length of the signal
69///
70/// # Returns
71///
72/// * Reconstructed time-domain signal
73///
74/// # Examples
75///
76/// ```rust
77/// use scirs2_fft::sparse_fft::{sparse_fft, reconstruct_time_domain};
78///
79/// // Generate a sparse signal
80/// let n = 64;
81/// let mut original_signal = vec![0.0; n];
82/// for i in 0..n {
83///     let t = 2.0 * std::f64::consts::PI * (i as f64) / (n as f64);
84///     original_signal[i] = 1.0 * (3.0 * t).sin() + 0.5 * (7.0 * t).sin();
85/// }
86///
87/// // Compute sparse FFT
88/// let sparse_result = sparse_fft(&original_signal, 4, None, None).unwrap();
89///
90/// // Reconstruct time-domain signal
91/// let reconstructed = reconstruct_time_domain(&sparse_result, n).unwrap();
92///
93/// // The reconstructed signal should have the same length
94/// assert_eq!(reconstructed.len(), n);
95/// ```
96#[allow(dead_code)]
97pub fn reconstruct_time_domain(
98    sparse_result: &SparseFFTResult,
99    n: usize,
100) -> FFTResult<Vec<Complex64>> {
101    // Create full spectrum from sparse representation
102    let mut spectrum = vec![Complex64::new(0.0, 0.0); n];
103    for (value, &index) in sparse_result
104        .values
105        .iter()
106        .zip(sparse_result.indices.iter())
107    {
108        spectrum[index] = *value;
109    }
110
111    // Perform inverse FFT to get time-domain signal
112    ifft(&spectrum, None)
113}
114
115/// Reconstructs a signal with enhanced frequency resolution by zero padding
116///
117/// This method allows reconstructing a signal with higher frequency resolution
118/// by zero-padding the sparse spectrum before performing the inverse FFT.
119///
120/// # Arguments
121///
122/// * `sparse_result` - The sparse FFT result containing frequency components
123/// * `original_length` - The original length of the signal
124/// * `target_length` - The desired length after zero padding (must be >= original_length)
125///
126/// # Returns
127///
128/// * The reconstructed signal with enhanced frequency resolution
129///
130/// # Examples
131///
132/// ```rust
133/// use scirs2_fft::sparse_fft::{sparse_fft, reconstruct_high_resolution};
134///
135/// // Generate a sparse signal
136/// let n = 32;
137/// let mut signal = vec![0.0; n];
138/// for i in 0..n {
139///     let t = 2.0 * std::f64::consts::PI * (i as f64) / (n as f64);
140///     signal[i] = 1.0 * (3.0 * t).sin();
141/// }
142///
143/// // Compute sparse FFT
144/// let sparse_result = sparse_fft(&signal, 2, None, None).unwrap();
145///
146/// // Reconstruct with higher resolution (2x the original length)
147/// let high_res = reconstruct_high_resolution(&sparse_result, n, 2 * n).unwrap();
148///
149/// // The high-resolution signal should have the target length
150/// assert_eq!(high_res.len(), 2 * n);
151/// ```
152#[allow(dead_code)]
153pub fn reconstruct_high_resolution(
154    sparse_result: &SparseFFTResult,
155    original_length: usize,
156    target_length: usize,
157) -> FFTResult<Vec<Complex64>> {
158    if target_length < original_length {
159        return Err(FFTError::DimensionError(format!(
160            "Target _length {target_length} must be greater than or equal to original _length {original_length}"
161        )));
162    }
163
164    // First reconstruct the spectrum at original resolution
165    let mut spectrum = vec![Complex64::new(0.0, 0.0); original_length];
166    for (value, &index) in sparse_result
167        .values
168        .iter()
169        .zip(sparse_result.indices.iter())
170    {
171        spectrum[index] = *value;
172    }
173
174    // Scale the frequencies to the new _length
175    let mut high_res_spectrum = vec![Complex64::new(0.0, 0.0); target_length];
176
177    // For components below the Nyquist frequency
178    let original_nyquist = original_length / 2;
179    let target_nyquist = target_length / 2;
180
181    // Copy DC component
182    high_res_spectrum[0] = spectrum[0];
183
184    // Scale positive frequencies (0 to Nyquist)
185    #[allow(clippy::needless_range_loop)]
186    for i in 1..=original_nyquist {
187        // Calculate the scaled frequency index in the new spectrum
188        let new_idx =
189            ((i as f64) * (target_nyquist as f64) / (original_nyquist as f64)).round() as usize;
190        if new_idx < target_length {
191            high_res_spectrum[new_idx] = spectrum[i];
192        }
193    }
194
195    // Handle the negative frequencies (those above Nyquist in the original spectrum)
196    if original_length.is_multiple_of(2) {
197        // Even _length case - map original negative frequencies to the new negative frequencies
198        #[allow(clippy::needless_range_loop)]
199        for i in (original_nyquist + 1)..original_length {
200            // Calculate the relative position in the negative frequency range
201            let rel_pos = original_length - i;
202            let new_idx = target_length - rel_pos;
203            if new_idx < target_length {
204                high_res_spectrum[new_idx] = spectrum[i];
205            }
206        }
207
208        // If even length, also copy the Nyquist component
209        if original_length.is_multiple_of(2) && target_length.is_multiple_of(2) {
210            high_res_spectrum[target_nyquist] = spectrum[original_nyquist];
211        }
212    } else {
213        // Odd _length case
214        #[allow(clippy::needless_range_loop)]
215        for i in (original_nyquist + 1)..original_length {
216            // Calculate the relative position in the negative frequency range
217            let rel_pos = original_length - i;
218            let new_idx = target_length - rel_pos;
219            if new_idx < target_length {
220                high_res_spectrum[new_idx] = spectrum[i];
221            }
222        }
223    }
224
225    // Compute the inverse FFT to get the high-resolution time-domain signal
226    ifft(&high_res_spectrum, None)
227}
228
229/// Reconstructs a filtered version of the signal by frequency-domain filtering
230///
231/// # Arguments
232///
233/// * `sparse_result` - The sparse FFT result
234/// * `n` - The length of the original signal
235/// * `filter_fn` - A function that takes a frequency index and returns a scaling factor (0.0 to 1.0)
236///
237/// # Returns
238///
239/// * The filtered signal
240///
241/// # Examples
242///
243/// ```rust
244/// use scirs2_fft::sparse_fft::{sparse_fft, reconstruct_filtered};
245///
246/// // Generate a sparse signal
247/// let n = 64;
248/// let mut signal = vec![0.0; n];
249/// for i in 0..n {
250///     let t = 2.0 * std::f64::consts::PI * (i as f64) / (n as f64);
251///     signal[i] = 1.0 * (3.0 * t).sin() + 0.5 * (7.0 * t).sin() + 0.25 * (15.0 * t).sin();
252/// }
253///
254/// // Compute sparse FFT
255/// let sparse_result = sparse_fft(&signal, 6, None, None).unwrap();
256///
257/// // Create a low-pass filter (keep only low frequencies)
258/// let low_pass_filter = |freq_index: usize, n: usize| -> f64 {
259///     if freq_index <= n / 8 || freq_index >= 7 * n / 8 {
260///         1.0  // Keep low frequencies
261///     } else {
262///         0.0  // Remove high frequencies
263///     }
264/// };
265///
266/// // Reconstruct with filtering
267/// let filtered = reconstruct_filtered(&sparse_result, n, low_pass_filter).unwrap();
268///
269/// assert_eq!(filtered.len(), n);
270/// ```
271#[allow(dead_code)]
272pub fn reconstruct_filtered<F>(
273    sparse_result: &SparseFFTResult,
274    n: usize,
275    filter_fn: F,
276) -> FFTResult<Vec<Complex64>>
277where
278    F: Fn(usize, usize) -> f64,
279{
280    // Create full spectrum from sparse representation
281    let mut spectrum = vec![Complex64::new(0.0, 0.0); n];
282
283    // Apply the filter function to each component
284    for (value, &index) in sparse_result
285        .values
286        .iter()
287        .zip(sparse_result.indices.iter())
288    {
289        let scale = filter_fn(index, n);
290        spectrum[index] = *value * scale;
291    }
292
293    // Perform inverse FFT to get filtered time-domain signal
294    ifft(&spectrum, None)
295}
296
297#[cfg(test)]
298mod tests {
299    use super::super::algorithms::sparse_fft;
300    use super::*;
301
302    fn create_test_signal(n: usize) -> Vec<f64> {
303        let mut signal = vec![0.0; n];
304        for i in 0..n {
305            let t = 2.0 * PI * (i as f64) / (n as f64);
306            signal[i] = 1.0 * (3.0 * t).sin() + 0.5 * (7.0 * t).sin();
307        }
308        signal
309    }
310
311    #[test]
312    fn test_reconstruct_spectrum() {
313        let signal = create_test_signal(64);
314        let sparse_result = sparse_fft(&signal, 4, None, None).unwrap();
315        let spectrum = reconstruct_spectrum(&sparse_result, 64).unwrap();
316
317        assert_eq!(spectrum.len(), 64);
318        // Verify that the reconstructed spectrum has the expected sparse structure
319        // Count non-zero components
320        let non_zero_count = spectrum.iter().filter(|&c| c.norm() > 1e-10).count();
321        assert_eq!(non_zero_count, sparse_result.values.len());
322
323        // Verify that all sparse components are present in the spectrum
324        for (&index, &value) in sparse_result
325            .indices
326            .iter()
327            .zip(sparse_result.values.iter())
328        {
329            assert!(index < spectrum.len());
330            // The value should be non-zero at this index
331            assert!(
332                spectrum[index].norm() > 1e-10,
333                "Expected non-zero value at index {index}"
334            );
335        }
336    }
337
338    #[test]
339    fn test_reconstruct_time_domain() {
340        let signal = create_test_signal(64);
341        let sparse_result = sparse_fft(&signal, 4, None, None).unwrap();
342        let reconstructed = reconstruct_time_domain(&sparse_result, 64).unwrap();
343
344        assert_eq!(reconstructed.len(), 64);
345    }
346
347    #[test]
348    fn test_reconstruct_high_resolution() {
349        let signal = create_test_signal(32);
350        let sparse_result = sparse_fft(&signal, 4, None, None).unwrap();
351        let high_res = reconstruct_high_resolution(&sparse_result, 32, 64).unwrap();
352
353        assert_eq!(high_res.len(), 64);
354    }
355
356    #[test]
357    fn test_reconstruct_high_resolution_invalid_length() {
358        let signal = create_test_signal(32);
359        let sparse_result = sparse_fft(&signal, 4, None, None).unwrap();
360        let result = reconstruct_high_resolution(&sparse_result, 32, 16);
361
362        assert!(result.is_err());
363    }
364
365    #[test]
366    fn test_reconstruct_filtered() {
367        let signal = create_test_signal(64);
368        let sparse_result = sparse_fft(&signal, 6, None, None).unwrap();
369
370        // Low-pass filter
371        let filter = |freq_index: usize, n: usize| -> f64 {
372            if freq_index <= n / 8 || freq_index >= 7 * n / 8 {
373                1.0
374            } else {
375                0.0
376            }
377        };
378
379        let filtered = reconstruct_filtered(&sparse_result, 64, filter).unwrap();
380        assert_eq!(filtered.len(), 64);
381    }
382}