use crate::config::WorkloadType;
use crate::error::CbtopError;
use crate::headless::{Benchmark, BenchmarkResult};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use super::stats::{cv, mean, t_test};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
pub passed: bool,
pub improvement_percent: f64,
pub before_gflops: f64,
pub after_gflops: f64,
pub before_cv: f64,
pub after_cv: f64,
pub p_value: f64,
pub statistically_significant: bool,
}
pub struct OptimizationValidator {
pub min_improvement_percent: f64,
pub min_samples: usize,
pub max_cv_percent: f64,
}
impl Default for OptimizationValidator {
fn default() -> Self {
Self {
min_improvement_percent: 10.0,
min_samples: 5,
max_cv_percent: 10.0,
}
}
}
impl OptimizationValidator {
pub fn new(min_improvement: f64, min_samples: usize, max_cv: f64) -> Self {
Self {
min_improvement_percent: min_improvement,
min_samples: min_samples.max(2), max_cv_percent: max_cv,
}
}
pub fn validate(
&self,
before_results: &[BenchmarkResult],
after_results: &[BenchmarkResult],
) -> ValidationResult {
let before_samples: Vec<f64> = before_results.iter().map(|r| r.results.gflops).collect();
let after_samples: Vec<f64> = after_results.iter().map(|r| r.results.gflops).collect();
self.validate_samples(&before_samples, &after_samples)
}
pub fn validate_samples(&self, before: &[f64], after: &[f64]) -> ValidationResult {
let before_mean = mean(before);
let after_mean = mean(after);
let before_cv = cv(before);
let after_cv = cv(after);
let improvement = if before_mean > 0.0 {
(after_mean - before_mean) / before_mean * 100.0
} else {
0.0
};
let p_value = t_test(before, after);
let statistically_significant = p_value < 0.05;
let passed = improvement >= self.min_improvement_percent
&& before_cv <= self.max_cv_percent
&& after_cv <= self.max_cv_percent
&& statistically_significant;
ValidationResult {
passed,
improvement_percent: improvement,
before_gflops: before_mean,
after_gflops: after_mean,
before_cv,
after_cv,
p_value,
statistically_significant,
}
}
pub fn validate_ab(
&self,
workload: WorkloadType,
size: usize,
duration: Duration,
) -> Result<(Vec<BenchmarkResult>, ValidationResult), CbtopError> {
let mut before_results = Vec::new();
let mut after_results = Vec::new();
for _ in 0..self.min_samples {
let result = Benchmark::builder()
.workload_type(workload)
.size(size)
.duration(duration)
.build()?
.run()?;
before_results.push(result.clone());
after_results.push(result);
}
let validation = self.validate(&before_results, &after_results);
Ok((before_results, validation))
}
}
impl ValidationResult {
pub fn format_report(&self) -> String {
let status = if self.passed { "PASSED" } else { "FAILED" };
let significance = if self.statistically_significant {
"Yes"
} else {
"No"
};
format!(
"# Optimization Validation Report\n\n\
**Status**: {}\n\n\
## Results\n\n\
| Metric | Before | After | Change |\n\
|--------|--------|-------|--------|\n\
| GFLOP/s | {:.2} | {:.2} | {:+.1}% |\n\
| CV (%) | {:.1} | {:.1} | - |\n\n\
## Statistical Analysis\n\n\
- **Improvement**: {:+.1}%\n\
- **p-value**: {:.4}\n\
- **Statistically Significant**: {}\n",
status,
self.before_gflops,
self.after_gflops,
self.improvement_percent,
self.before_cv,
self.after_cv,
self.improvement_percent,
self.p_value,
significance
)
}
}