swarm-engine-eval 0.1.6

Evaluation framework for SwarmEngine
Documentation
//! Robustness metrics

use serde::{Deserialize, Serialize};

/// Robustness metrics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RobustnessMetrics {
    /// Number of faults injected
    pub fault_count: u64,

    /// Number of recoveries
    pub recovery_count: u64,

    /// Recovery rate (recoveries / faults)
    pub recovery_rate: f64,

    /// Consistency score (1 - coefficient of variation)
    /// Only meaningful when calculated across multiple runs
    pub consistency_score: Option<f64>,
}

impl RobustnessMetrics {
    /// Calculate from fault data
    pub fn from_fault_data(fault_count: u64, recovery_count: u64) -> Self {
        let recovery_rate = if fault_count > 0 {
            recovery_count as f64 / fault_count as f64
        } else {
            1.0 // No faults = perfect recovery
        };

        Self {
            fault_count,
            recovery_count,
            recovery_rate,
            consistency_score: None,
        }
    }

    /// Calculate consistency score from multiple runs
    pub fn calculate_consistency(values: &[f64]) -> Option<f64> {
        if values.len() < 2 {
            return None;
        }

        let n = values.len() as f64;
        let mean: f64 = values.iter().sum::<f64>() / n;

        if mean.abs() < 1e-9 {
            return None; // Can't calculate CV with zero mean
        }

        let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
        let std_dev = variance.sqrt();
        let cv = std_dev / mean.abs();

        // Consistency score = 1 - CV, clamped to [0, 1]
        Some((1.0 - cv).clamp(0.0, 1.0))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_recovery_rate() {
        let metrics = RobustnessMetrics::from_fault_data(10, 8);
        assert!((metrics.recovery_rate - 0.8).abs() < 0.01);
    }

    #[test]
    fn test_no_faults() {
        let metrics = RobustnessMetrics::from_fault_data(0, 0);
        assert!((metrics.recovery_rate - 1.0).abs() < 0.01);
    }

    #[test]
    fn test_consistency_perfect() {
        // All same values = perfect consistency
        let values = vec![0.8, 0.8, 0.8, 0.8];
        let score = RobustnessMetrics::calculate_consistency(&values);
        assert!(score.unwrap() > 0.99);
    }

    #[test]
    fn test_consistency_variable() {
        // Variable values = lower consistency
        let values = vec![0.5, 0.7, 0.9, 1.1];
        let score = RobustnessMetrics::calculate_consistency(&values);
        assert!(score.unwrap() < 0.9);
    }

    #[test]
    fn test_consistency_insufficient_data() {
        let values = vec![0.8];
        let score = RobustnessMetrics::calculate_consistency(&values);
        assert!(score.is_none());
    }
}