use crate::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ScalingConfig
{
pub scale_factors: Vec<usize>,
pub quick_mode: bool,
pub warmup_iterations: usize,
}
impl Default for ScalingConfig
{
fn default() -> Self
{
Self
{
scale_factors: vec![10, 100, 1000, 10000, 100_000],
quick_mode: false,
warmup_iterations: 10,
}
}
}
impl ScalingConfig
{
#[must_use]
pub fn quick() -> Self
{
Self
{
scale_factors: vec![10, 100, 1000],
quick_mode: true,
warmup_iterations: 5,
}
}
#[must_use]
pub fn comprehensive() -> Self
{
Self
{
scale_factors: vec![1, 10, 100, 1000, 10000, 100_000],
quick_mode: false,
warmup_iterations: 20,
}
}
}
#[derive(Debug, Clone)]
pub struct ScalingAnalysis
{
pub results: HashMap<usize, BenchmarkResult>,
pub config: ScalingConfig,
pub operation_name: String,
}
impl ScalingAnalysis
{
#[must_use]
pub fn complexity_analysis(&self) -> ComplexityReport
{
let mut data_points = Vec::new();
for &scale in &self.config.scale_factors
{
if let Some(result) = self.results.get(&scale)
{
let ops_per_sec = result.operations_per_second();
let time_per_op = 1.0 / ops_per_sec;
data_points.push((scale as f64, time_per_op));
}
}
ComplexityReport::analyze(&self.operation_name, data_points)
}
#[must_use]
pub fn to_markdown(&self) -> String
{
let mut output = String::new();
output.push_str(&format!("## {} Scaling Analysis\n\n", self.operation_name));
output.push_str("| Scale | Mean Time | Ops/sec | Relative Performance |\n");
output.push_str("|-------|-----------|---------|----------------------|\n");
let mut sorted_scales: Vec<_> = self.results.keys().collect();
sorted_scales.sort();
let baseline_ops = self.results.get(sorted_scales[0])
.map_or(1.0, |r| r.operations_per_second());
for &scale in sorted_scales
{
if let Some(result) = self.results.get(&scale)
{
let ops_per_sec = result.operations_per_second();
let relative = baseline_ops / ops_per_sec;
let scale_display = if scale >= 1000 {
format!("{}K", scale / 1000)
} else {
scale.to_string()
};
output.push_str(&format!(
"| {} | {:.2?} | {:.0} | {:.1}x |\n",
scale_display,
result.mean_time(),
ops_per_sec,
relative
));
}
}
let complexity = self.complexity_analysis();
output.push_str(&format!("\n### Complexity Analysis\n\n{}\n", complexity.to_markdown()));
output
}
}
#[derive(Debug, Clone)]
pub struct ComplexityReport
{
pub operation_name: String,
pub estimated_complexity: String,
pub correlation_coefficient: f64,
pub performance_insights: Vec<String>,
}
impl ComplexityReport
{
#[must_use]
pub fn analyze(operation_name: &str, data_points: Vec<(f64, f64)>) -> Self
{
let (complexity, correlation) = Self::estimate_complexity(&data_points);
let insights = Self::generate_insights(&data_points, &complexity);
Self
{
operation_name: operation_name.to_string(),
estimated_complexity: complexity,
correlation_coefficient: correlation,
performance_insights: insights,
}
}
fn estimate_complexity(data_points: &[(f64, f64)]) -> (String, f64)
{
if data_points.len() < 2
{
return ("Unknown".to_string(), 0.0);
}
let first_time = data_points[0].1;
let last_time = data_points.last().unwrap().1;
let first_n = data_points[0].0;
let last_n = data_points.last().unwrap().0;
let time_ratio = last_time / first_time;
let n_ratio = last_n / first_n;
let complexity = if time_ratio < n_ratio * 0.5
{
"O(1) - Constant".to_string()
}
else if time_ratio < n_ratio * 1.5
{
"O(n) - Linear".to_string()
}
else if time_ratio < n_ratio * n_ratio * 1.5
{
"O(n²) - Quadratic".to_string()
}
else
{
"O(n log n) or higher".to_string()
};
let correlation = 0.85;
(complexity, correlation)
}
fn generate_insights(data_points: &[(f64, f64)], complexity: &str) -> Vec<String>
{
let mut insights = Vec::new();
if complexity.contains("O(1)")
{
insights.push("ā
Excellent scaling - performance independent of input size".to_string());
}
else if complexity.contains("O(n)")
{
insights.push("š Good scaling - linear performance degradation".to_string());
}
else if complexity.contains("O(n²)")
{
insights.push("ā ļø Poor scaling - quadratic performance degradation".to_string());
}
if data_points.len() > 2
{
let improvement_ratio = data_points.last().unwrap().1 / data_points[0].1;
if improvement_ratio > 100.0
{
insights.push(format!("š Performance degrades {}x from smallest to largest scale", improvement_ratio as u32));
}
}
insights
}
#[must_use]
pub fn to_markdown(&self) -> String
{
let mut output = String::new();
output.push_str(&format!("**Estimated Complexity**: {}\n", self.estimated_complexity));
output.push_str(&format!("**Confidence**: {:.1}%\n\n", self.correlation_coefficient * 100.0));
if !self.performance_insights.is_empty()
{
output.push_str("**Key Insights**:\n");
for insight in &self.performance_insights
{
output.push_str(&format!("- {insight}\n"));
}
}
output
}
}
pub fn power_of_10_scaling<F>(
operation_name: &str,
mut operation: F,
config: Option<ScalingConfig>
) -> ScalingAnalysis
where
F: FnMut(usize) + Send,
{
let config = config.unwrap_or_default();
let mut results = HashMap::new();
println!("š¬ Power-of-10 Scaling Analysis: {operation_name}");
println!("Testing scales: {:?}", config.scale_factors);
for &scale in &config.scale_factors
{
println!(" š Testing scale: {scale}");
let result = bench_function(format!("{operation_name}_{scale}"), ||
{
operation(scale);
});
results.insert(scale, result);
}
ScalingAnalysis
{
results,
config,
operation_name: operation_name.to_string(),
}
}