terminals-core 0.1.0

Core runtime primitives for Terminals OS: phase dynamics, AXON wire protocol, substrate engine, and sematonic types
Documentation
/// Shannon entropy of a spectral distribution (e.g., FFT frequency bins).
/// Input: raw magnitude bins (u8). Output: normalized entropy in [0, 1].
///
/// H = -Σ p_i * log2(p_i), normalized by log2(N)
///
/// - All zeros → 0.0 (silence)
/// - Uniform distribution → 1.0 (maximum chaos)
/// - Single dominant bin → ~0.0 (pure tone)
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
    }
}

/// Same as spectral_entropy_u8 but for f32 magnitude bins.
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() {
        // Single dominant bin — near-zero entropy
        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() {
        // All bins equal → maximum entropy → 1.0
        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);
    }
}