reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
//! Performance optimizations for the reputation engine
//! 
//! This module contains performance-critical optimizations including
//! inlined functions, zero-allocation strategies, and cache-friendly
//! data structures.

use crate::Calculator;
use reputation_types::{AgentData, ReputationScore, PriorBreakdown};

/// Optimized confidence calculation with inline annotation
#[inline(always)]
pub fn calculate_confidence_fast(n: u32, k: f64) -> f64 {
    let n_f64 = n as f64;
    n_f64 / (n_f64 + k)
}

/// Optimized empirical score calculation
#[inline(always)]
pub fn calculate_empirical_fast(average_rating: Option<f64>) -> f64 {
    match average_rating {
        Some(rating) => {
            // Convert 1-5 rating to 0-100 score
            // (rating - 1) / 4 * 100 = (rating - 1) * 25
            (rating - 1.0) * 25.0
        }
        None => 50.0, // Default for no reviews
    }
}

/// Fast prior calculation using stack allocation
#[inline]
pub fn calculate_prior_fast(
    agent: &AgentData,
    prior_base: f64,
    prior_max: f64,
) -> PriorBreakdown {
    // Stack-allocated bonus array
    let mut bonuses = [0.0; 6];
    let mut idx = 0;

    // Base score
    bonuses[idx] = prior_base;
    idx += 1;

    // MCP bonus (0-15 points)
    if let Some(level) = agent.mcp_level {
        bonuses[idx] = match level {
            1 => 5.0,
            2 => 10.0,
            3 => 15.0,
            _ => 0.0,
        };
        idx += 1;
    }

    // Identity verified bonus
    if agent.identity_verified {
        bonuses[idx] = 5.0;
        idx += 1;
    }

    // Security audit bonus
    if agent.security_audit_passed {
        bonuses[idx] = 7.0;
        idx += 1;
    }

    // Open source bonus
    if agent.open_source {
        bonuses[idx] = 3.0;
        idx += 1;
    }

    // Age bonus
    let age_days = (chrono::Utc::now() - agent.created_at).num_days();
    if age_days > 365 {
        bonuses[idx] = 5.0;
    }

    // Sum bonuses without allocation
    let sum: f64 = bonuses.iter().sum();
    let total = sum.min(prior_max);

    PriorBreakdown {
        base_score: prior_base,
        mcp_bonus: if agent.mcp_level.is_some() { bonuses[1] } else { 0.0 },
        identity_bonus: if agent.identity_verified { 5.0 } else { 0.0 },
        security_audit_bonus: if agent.security_audit_passed { 7.0 } else { 0.0 },
        open_source_bonus: if agent.open_source { 3.0 } else { 0.0 },
        age_bonus: if age_days > 365 { 5.0 } else { 0.0 },
        total,
    }
}

/// Branch prediction hints for error paths
#[cold]
#[inline(never)]
pub fn handle_validation_error() -> crate::error::CalculationError {
    crate::error::CalculationError::NaNResult
}

/// Optimal chunk size for parallel processing based on CPU cache
pub fn optimal_chunk_size() -> usize {
    // Each AgentData is ~200 bytes, L3 cache line is typically 64 bytes
    // Aim for chunks that fit in L3 cache (8MB typical)
    // 8MB / 200 bytes = ~40,000 agents per chunk
    // But we want smaller chunks for better work distribution
    // Default to 8 CPUs if we can't detect
    let cpus = std::thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(8);
    cpus * 64 // 64 agents per CPU for good cache locality
}

/// Cache-friendly batch calculation using optimal chunking
pub fn calculate_batch_optimized(
    calculator: &Calculator,
    agents: &[AgentData],
) -> Vec<Result<ReputationScore, crate::error::ReputationError>> {
    use rayon::prelude::*;
    
    let chunk_size = optimal_chunk_size();
    
    agents
        .par_chunks(chunk_size)
        .flat_map(|chunk| {
            chunk
                .iter()
                .map(|agent| calculator.calculate(agent))
                .collect::<Vec<_>>()
        })
        .collect()
}

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

    #[test]
    fn test_confidence_fast() {
        assert_eq!(calculate_confidence_fast(0, 15.0), 0.0);
        assert_eq!(calculate_confidence_fast(15, 15.0), 0.5);
        assert!((calculate_confidence_fast(100, 15.0) - 0.869565).abs() < 0.00001);
    }

    #[test]
    fn test_empirical_fast() {
        assert_eq!(calculate_empirical_fast(Some(1.0)), 0.0);
        assert_eq!(calculate_empirical_fast(Some(3.0)), 50.0);
        assert_eq!(calculate_empirical_fast(Some(5.0)), 100.0);
        assert_eq!(calculate_empirical_fast(None), 50.0);
    }

    #[test]
    fn test_prior_fast() {
        let agent = AgentDataBuilder::new("did:test:perf")
            .mcp_level(2)
            .identity_verified(true)
            .build()
            .unwrap();

        let breakdown = calculate_prior_fast(&agent, 50.0, 80.0);
        assert_eq!(breakdown.base_score, 50.0);
        assert_eq!(breakdown.mcp_bonus, 10.0);
        assert_eq!(breakdown.identity_bonus, 5.0);
        assert_eq!(breakdown.total, 65.0);
    }

    #[test]
    fn test_optimal_chunk_size() {
        let size = optimal_chunk_size();
        assert!(size > 0);
        assert!(size <= 10000); // Reasonable upper bound
    }
}