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}