pub fn spectral_entropy_u8(bins: &[u8]) -> f32 {
if bins.is_empty() {
return 0.0;
}
let total: f32 = bins.iter().map(|&b| b as f32).sum();
if total == 0.0 {
return 0.0;
}
let mut entropy = 0.0f32;
for &b in bins {
let p = b as f32 / total;
if p > 0.0 {
entropy -= p * p.log2();
}
}
let max_entropy = (bins.len() as f32).log2();
if max_entropy > 0.0 {
entropy / max_entropy
} else {
0.0
}
}
pub fn spectral_entropy_f32(bins: &[f32]) -> f32 {
if bins.is_empty() {
return 0.0;
}
let total: f32 = bins.iter().sum();
if total <= 0.0 {
return 0.0;
}
let mut entropy = 0.0f32;
for &b in bins {
let p = b / total;
if p > 0.0 {
entropy -= p * p.log2();
}
}
let max_entropy = (bins.len() as f32).log2();
if max_entropy > 0.0 {
entropy / max_entropy
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entropy_silence() {
let bins = vec![0u8; 256];
assert_eq!(spectral_entropy_u8(&bins), 0.0);
}
#[test]
fn test_entropy_pure_tone() {
let mut bins = vec![0u8; 256];
bins[42] = 255;
let e = spectral_entropy_u8(&bins);
assert!(
e < 0.01,
"Pure tone should have near-zero entropy, got {}",
e
);
}
#[test]
fn test_entropy_uniform() {
let bins = vec![100u8; 256];
let e = spectral_entropy_u8(&bins);
assert!((e - 1.0).abs() < 1e-6, "Uniform should be 1.0, got {}", e);
}
#[test]
fn test_entropy_range() {
let bins = vec![10u8, 20, 30, 40, 50, 60, 70, 80];
let e = spectral_entropy_u8(&bins);
assert!(
e > 0.0 && e < 1.0,
"Mixed signal entropy should be in (0,1), got {}",
e
);
}
#[test]
fn test_entropy_f32_matches_u8() {
let u8_bins = vec![10u8, 20, 30, 40];
let f32_bins: Vec<f32> = u8_bins.iter().map(|&b| b as f32).collect();
let e_u8 = spectral_entropy_u8(&u8_bins);
let e_f32 = spectral_entropy_f32(&f32_bins);
assert!(
(e_u8 - e_f32).abs() < 1e-6,
"u8 and f32 entropy should match: {} vs {}",
e_u8,
e_f32
);
}
#[test]
fn test_entropy_empty() {
assert_eq!(spectral_entropy_u8(&[]), 0.0);
assert_eq!(spectral_entropy_f32(&[]), 0.0);
}
#[test]
fn test_entropy_f32_uniform() {
let bins = vec![1.0f32; 128];
let e = spectral_entropy_f32(&bins);
assert!(
(e - 1.0).abs() < 1e-6,
"Uniform f32 should be 1.0, got {}",
e
);
}
#[test]
fn test_entropy_f32_silence() {
let bins = vec![0.0f32; 64];
assert_eq!(spectral_entropy_f32(&bins), 0.0);
}
}