use super::entry::BenchmarkEntry;
use super::statistics::BenchmarkStatistics;
use crate::efficiency::{ComputeDevice, ModelParadigm};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CostPerformanceBenchmark {
pub entries: Vec<BenchmarkEntry>,
}
impl CostPerformanceBenchmark {
pub fn new() -> Self {
Self { entries: Vec::new() }
}
pub fn add(&mut self, entry: BenchmarkEntry) {
self.entries.push(entry);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn pareto_frontier(&self) -> Vec<&BenchmarkEntry> {
if self.entries.is_empty() {
return Vec::new();
}
let mut frontier = Vec::new();
for entry in &self.entries {
let is_dominated = self
.entries
.iter()
.any(|other| !std::ptr::eq(entry, other) && other.dominates(entry));
if !is_dominated {
frontier.push(entry);
}
}
frontier.sort_by(|a, b| {
b.quality_score.partial_cmp(&a.quality_score).unwrap_or(std::cmp::Ordering::Equal)
});
frontier
}
pub fn best_for_budget(&self, max_usd: f64) -> Option<&BenchmarkEntry> {
self.entries.iter().filter(|e| e.cost.total_cost_usd <= max_usd).max_by(|a, b| {
a.quality_score.partial_cmp(&b.quality_score).unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn cheapest_for_quality(&self, min_quality: f64) -> Option<&BenchmarkEntry> {
self.entries.iter().filter(|e| e.quality_score >= min_quality).min_by(|a, b| {
a.cost
.total_cost_usd
.partial_cmp(&b.cost.total_cost_usd)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn efficiency_score(&self, entry: &BenchmarkEntry) -> f64 {
entry.efficiency_score()
}
pub fn most_efficient(&self) -> Option<&BenchmarkEntry> {
self.entries.iter().max_by(|a, b| {
a.efficiency_score()
.partial_cmp(&b.efficiency_score())
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn greenest(&self) -> Option<&BenchmarkEntry> {
self.entries.iter().max_by(|a, b| {
a.energy_efficiency()
.partial_cmp(&b.energy_efficiency())
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn best_quality(&self) -> Option<&BenchmarkEntry> {
self.entries.iter().max_by(|a, b| {
a.quality_score.partial_cmp(&b.quality_score).unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn cheapest(&self) -> Option<&BenchmarkEntry> {
self.entries.iter().min_by(|a, b| {
a.cost
.total_cost_usd
.partial_cmp(&b.cost.total_cost_usd)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn filter_by_paradigm(&self, paradigm: &ModelParadigm) -> Vec<&BenchmarkEntry> {
self.entries
.iter()
.filter(|e| std::mem::discriminant(&e.paradigm) == std::mem::discriminant(paradigm))
.collect()
}
pub fn filter_by_device_type<F>(&self, predicate: F) -> Vec<&BenchmarkEntry>
where
F: Fn(&ComputeDevice) -> bool,
{
self.entries.iter().filter(|e| predicate(&e.device)).collect()
}
pub fn statistics(&self) -> BenchmarkStatistics {
contract_pre_statistics!();
if self.entries.is_empty() {
return BenchmarkStatistics::default();
}
let qualities: Vec<f64> = self.entries.iter().map(|e| e.quality_score).collect();
let costs: Vec<f64> = self.entries.iter().map(|e| e.cost.total_cost_usd).collect();
let energies: Vec<f64> = self.entries.iter().map(|e| e.energy.joules_total).collect();
BenchmarkStatistics {
count: self.entries.len(),
quality_min: qualities.iter().copied().fold(f64::INFINITY, f64::min),
quality_max: qualities.iter().copied().fold(f64::NEG_INFINITY, f64::max),
quality_avg: qualities.iter().sum::<f64>() / qualities.len().max(1) as f64,
cost_min: costs.iter().copied().fold(f64::INFINITY, f64::min),
cost_max: costs.iter().copied().fold(f64::NEG_INFINITY, f64::max),
cost_avg: costs.iter().sum::<f64>() / costs.len().max(1) as f64,
energy_min: energies.iter().copied().fold(f64::INFINITY, f64::min),
energy_max: energies.iter().copied().fold(f64::NEG_INFINITY, f64::max),
energy_avg: energies.iter().sum::<f64>() / energies.len().max(1) as f64,
pareto_count: self.pareto_frontier().len(),
}
}
pub fn comparison_report(&self) -> String {
let stats = self.statistics();
let frontier = self.pareto_frontier();
let mut report = String::new();
report.push_str(&format!("=== Benchmark Report ({} entries) ===\n\n", stats.count));
report.push_str("Quality Scores:\n");
report.push_str(&format!(
" Min: {:.4} Max: {:.4} Avg: {:.4}\n\n",
stats.quality_min, stats.quality_max, stats.quality_avg
));
report.push_str("Costs (USD):\n");
report.push_str(&format!(
" Min: ${:.2} Max: ${:.2} Avg: ${:.2}\n\n",
stats.cost_min, stats.cost_max, stats.cost_avg
));
report.push_str(&format!("Pareto Frontier ({} entries):\n", frontier.len()));
for entry in frontier.iter().take(5) {
report.push_str(&format!(
" - {} ({}): quality={:.4}, cost=${:.2}\n",
entry.run_id, entry.paradigm, entry.quality_score, entry.cost.total_cost_usd
));
}
if let Some(best) = self.most_efficient() {
report.push_str(&format!(
"\nMost Efficient: {} (score={:.2})\n",
best.run_id,
best.efficiency_score()
));
}
report
}
}