pub mod estimation;
pub mod recovery;
pub mod types;
pub use estimation::{estimate_sparsity, SparsityEstimator};
pub use recovery::{adaptive_sparse_fft_auto, AdaptiveSparseFft};
pub use types::{AdaptiveSfftConfig, AdaptiveSfftResult};
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
fn make_multi_tone(n: usize, freq_bins: &[usize]) -> Vec<f64> {
(0..n)
.map(|i| {
freq_bins
.iter()
.map(|&f| (2.0 * PI * f as f64 * i as f64 / n as f64).sin())
.sum::<f64>()
})
.collect()
}
#[test]
fn test_estimate_sparsity_single_tone() {
let n = 256;
let signal = make_multi_tone(n, &[10]);
let k = estimate_sparsity(&signal).expect("should succeed");
assert!(k <= 4, "Expected k <= 4 for single tone, got {k}");
}
#[test]
fn test_estimate_sparsity_multi_tone() {
let n = 256;
let signal = make_multi_tone(n, &[5, 13, 27, 55]);
let k = estimate_sparsity(&signal).expect("should succeed");
assert!(k >= 1, "k must be at least 1, got {k}");
}
#[test]
fn test_adaptive_sfft_config_default() {
let config = AdaptiveSfftConfig::default();
assert_eq!(config.max_sparsity, 64);
assert!((config.confidence - 0.95).abs() < 1e-10);
assert_eq!(config.filter_width, 8);
assert_eq!(config.max_iterations, 5);
}
#[test]
fn test_adaptive_sfft_single_tone_finds_frequency() {
let n = 256;
let freq_bin = 10_usize;
let signal = make_multi_tone(n, &[freq_bin]);
let config = AdaptiveSfftConfig::default();
let solver = AdaptiveSparseFft::new();
let result = solver.compute(&signal, &config).expect("should succeed");
assert!(
!result.frequencies.is_empty(),
"Should find at least one frequency"
);
assert!(
result.estimated_sparsity >= 1,
"Estimated sparsity must be >= 1"
);
}
#[test]
fn test_adaptive_sfft_result_structure() {
let n = 128;
let signal = make_multi_tone(n, &[3, 7, 11, 23]);
let config = AdaptiveSfftConfig::default();
let solver = AdaptiveSparseFft::new();
let result = solver.compute(&signal, &config).expect("should succeed");
assert_eq!(result.frequencies.len(), result.coefficients.len());
assert!(result.iterations >= 1);
assert!(result.captured_energy_fraction >= 0.0);
assert!(result.captured_energy_fraction <= 1.0 + 1e-9);
}
#[test]
fn test_sparsity_estimator_noisy_signal() {
let n = 512;
let mut state: u64 = 0xdeadbeef_cafebabe;
let noise: Vec<f64> = (0..n)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 33) as f64 / (u32::MAX as f64) * 2.0 - 1.0
})
.collect();
let estimator = SparsityEstimator::new(128);
let k = estimator
.estimate(&noise)
.expect("should succeed for noise");
assert!(k >= 1);
}
#[test]
fn test_adaptive_sfft_auto_convenience() {
let n = 128;
let signal = make_multi_tone(n, &[7]);
let result = adaptive_sparse_fft_auto(&signal).expect("should succeed");
assert!(!result.frequencies.is_empty());
}
}