use crate::error::{FFTError, FFTResult};
use crate::fft::fft;
pub fn estimate_sparsity(signal: &[f64]) -> FFTResult<usize> {
if signal.is_empty() {
return Err(FFTError::ValueError("Signal must not be empty".to_string()));
}
let spectrum = fft(signal, None)?;
let n = spectrum.len();
let mut power: Vec<f64> = spectrum.iter().map(|c| c.re * c.re + c.im * c.im).collect();
let total_energy: f64 = power.iter().sum();
if total_energy < f64::EPSILON {
return Ok(1);
}
power.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let normalised: Vec<f64> = power.iter().map(|&p| p / total_energy).collect();
let limit = n.min(256); if limit < 3 {
return Ok(1);
}
let mut max_second_diff = 0.0_f64;
let mut knee_idx = 1_usize;
for i in 1..(limit - 1) {
let second_diff = (normalised[i - 1] - 2.0 * normalised[i] + normalised[i + 1]).abs();
if second_diff > max_second_diff {
max_second_diff = second_diff;
knee_idx = i;
}
}
Ok((knee_idx + 1).max(1))
}
pub struct SparsityEstimator {
pub window_size: usize,
pub hop_size: usize,
}
impl SparsityEstimator {
pub fn new(window_size: usize) -> Self {
let hop_size = (window_size / 2).max(1);
Self {
window_size,
hop_size,
}
}
pub fn with_hop(window_size: usize, hop_size: usize) -> Self {
Self {
window_size,
hop_size: hop_size.max(1),
}
}
pub fn estimate(&self, signal: &[f64]) -> FFTResult<usize> {
if signal.is_empty() {
return Err(FFTError::ValueError("Signal must not be empty".to_string()));
}
let n = signal.len();
if n <= self.window_size {
return estimate_sparsity(signal);
}
let mut estimates = Vec::new();
let mut start = 0;
while start + self.window_size <= n {
let window = &signal[start..start + self.window_size];
if let Ok(k) = estimate_sparsity(window) {
estimates.push(k)
}
start += self.hop_size;
}
if estimates.is_empty() {
return Ok(1);
}
estimates.sort_unstable();
let mid = estimates.len() / 2;
Ok(estimates[mid])
}
pub fn estimate_all_windows(&self, signal: &[f64]) -> FFTResult<Vec<usize>> {
if signal.is_empty() {
return Err(FFTError::ValueError("Signal must not be empty".to_string()));
}
let n = signal.len();
if n <= self.window_size {
return Ok(vec![estimate_sparsity(signal)?]);
}
let mut estimates = Vec::new();
let mut start = 0;
while start + self.window_size <= n {
let window = &signal[start..start + self.window_size];
if let Ok(k) = estimate_sparsity(window) {
estimates.push(k);
}
start += self.hop_size;
}
if estimates.is_empty() {
estimates.push(1);
}
Ok(estimates)
}
}
pub fn estimate_noise_floor(signal: &[f64]) -> FFTResult<f64> {
if signal.is_empty() {
return Err(FFTError::ValueError("Signal must not be empty".to_string()));
}
let spectrum = fft(signal, None)?;
let mut power: Vec<f64> = spectrum.iter().map(|c| c.re * c.re + c.im * c.im).collect();
if power.is_empty() {
return Ok(0.0);
}
power.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let half = power.len() / 2;
let noise = if half == 0 { power[0] } else { power[half / 2] };
Ok(noise)
}