Skip to main content

batuta/recipes/
benchmark.rs

1//! Cost-performance benchmarking recipe implementation.
2
3use crate::experiment::{CostPerformanceBenchmark, CostPerformancePoint, ExperimentRun};
4use crate::recipes::RecipeResult;
5use std::collections::HashMap;
6
7/// Cost-performance benchmarking recipe
8#[derive(Debug)]
9pub struct CostPerformanceBenchmarkRecipe {
10    benchmark: CostPerformanceBenchmark,
11    pub(crate) budget_constraint: Option<f64>,
12    performance_target: Option<f64>,
13}
14
15impl CostPerformanceBenchmarkRecipe {
16    /// Create a new benchmarking recipe
17    pub fn new(name: impl Into<String>) -> Self {
18        Self {
19            benchmark: CostPerformanceBenchmark::new(name),
20            budget_constraint: None,
21            performance_target: None,
22        }
23    }
24
25    /// Set a budget constraint
26    pub fn with_budget(mut self, max_cost: f64) -> Self {
27        self.budget_constraint = Some(max_cost);
28        self
29    }
30
31    /// Set a performance target
32    pub fn with_performance_target(mut self, target: f64) -> Self {
33        self.performance_target = Some(target);
34        self
35    }
36
37    /// Add an experiment run as a data point
38    pub fn add_run(&mut self, run: &ExperimentRun, performance_metric: &str) {
39        let performance = run.metrics.get(performance_metric).copied().unwrap_or(0.0);
40        let cost = run.cost.as_ref().map(|c| c.total_cost_usd).unwrap_or(0.0);
41        let energy = run.energy.as_ref().map(|e| e.total_joules).unwrap_or(0.0);
42
43        let mut metadata = HashMap::new();
44        metadata.insert("paradigm".to_string(), format!("{:?}", run.paradigm));
45        metadata.insert("device".to_string(), format!("{:?}", run.device));
46        metadata.insert("platform".to_string(), format!("{:?}", run.platform));
47
48        self.benchmark.add_point(CostPerformancePoint {
49            id: run.run_id.clone(),
50            performance,
51            cost,
52            energy_joules: energy,
53            latency_ms: None,
54            metadata,
55        });
56    }
57
58    /// Run the benchmark analysis
59    pub fn analyze(&mut self) -> RecipeResult {
60        contract_pre_analyze!(self);
61        let mut result = RecipeResult::success("cost-performance-benchmark");
62
63        // Compute Pareto frontier
64        let frontier = self.benchmark.compute_pareto_frontier().to_vec();
65        result = result.with_metric("pareto_optimal_count", frontier.len() as f64);
66        result = result.with_metric("total_configurations", self.benchmark.points.len() as f64);
67
68        // Find best within budget if constraint set
69        if let Some(budget) = self.budget_constraint {
70            if let Some(best) = self.benchmark.best_within_budget(budget) {
71                result = result.with_metric("best_in_budget_performance", best.performance);
72                result = result.with_metric("best_in_budget_cost", best.cost);
73            }
74        }
75
76        // Check if any point meets performance target
77        if let Some(target) = self.performance_target {
78            let meets_target = self.benchmark.points.iter().any(|p| p.performance >= target);
79            result = result.with_metric("meets_target", if meets_target { 1.0 } else { 0.0 });
80
81            // Find cheapest that meets target
82            let cheapest =
83                self.benchmark.points.iter().filter(|p| p.performance >= target).min_by(|a, b| {
84                    a.cost.partial_cmp(&b.cost).unwrap_or(std::cmp::Ordering::Equal)
85                });
86
87            if let Some(cheapest) = cheapest {
88                result = result.with_metric("cheapest_meeting_target_cost", cheapest.cost);
89            }
90        }
91
92        // Add efficiency scores
93        let efficiency = self.benchmark.efficiency_scores();
94        if !efficiency.is_empty() {
95            let max_efficiency = efficiency.iter().map(|e| e.1).fold(f64::NEG_INFINITY, f64::max);
96            result = result.with_metric("max_efficiency", max_efficiency);
97        }
98
99        result
100    }
101
102    /// Get the benchmark
103    pub fn benchmark(&self) -> &CostPerformanceBenchmark {
104        &self.benchmark
105    }
106
107    /// Get mutable benchmark
108    pub fn benchmark_mut(&mut self) -> &mut CostPerformanceBenchmark {
109        &mut self.benchmark
110    }
111}