1use crate::error::{OptimError, Result};
7use crate::TestFunction;
8use scirs2_core::ndarray::Array1;
9use scirs2_core::numeric::Float;
10use std::collections::HashMap;
12use std::fmt::Debug;
13use std::process::Command;
14use std::time::{Duration, Instant};
15
16type OptimizerFn<A> = Box<dyn Fn(&Array1<A>, &Array1<A>) -> Array1<A>>;
18
19type OptimizerFnRef<'a, A> = &'a dyn Fn(&Array1<A>, &Array1<A>) -> Array1<A>;
21
22#[derive(Debug, Clone)]
24pub struct CrossFrameworkConfig {
25 pub enable_pytorch: bool,
27 pub enable_tensorflow: bool,
29 pub python_path: String,
31 pub temp_dir: String,
33 pub precision: Precision,
35 pub max_iterations: usize,
37 pub tolerance: f64,
39 pub random_seed: u64,
41 pub batch_sizes: Vec<usize>,
43 pub problem_dimensions: Vec<usize>,
45 pub num_runs: usize,
47}
48
49impl Default for CrossFrameworkConfig {
50 fn default() -> Self {
51 Self {
52 enable_pytorch: true,
53 enable_tensorflow: true,
54 python_path: "python3".to_string(),
55 temp_dir: "/tmp/scirs2_benchmark".to_string(),
56 precision: Precision::F64,
57 max_iterations: 1000,
58 tolerance: 1e-6,
59 random_seed: 42,
60 batch_sizes: vec![1, 32, 128, 512],
61 problem_dimensions: vec![10, 100, 1000],
62 num_runs: 5,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
69pub enum Precision {
70 F32,
71 F64,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash)]
76pub enum Framework {
77 SciRS2,
78 PyTorch,
79 TensorFlow,
80}
81
82impl std::fmt::Display for Framework {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 Framework::SciRS2 => write!(f, "SciRS2"),
86 Framework::PyTorch => write!(f, "PyTorch"),
87 Framework::TensorFlow => write!(f, "TensorFlow"),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub struct OptimizerIdentifier {
95 pub framework: Framework,
96 pub name: String,
97 pub version: Option<String>,
98}
99
100impl std::fmt::Display for OptimizerIdentifier {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 if let Some(ref version) = self.version {
103 write!(f, "{}-{}-v{}", self.framework, self.name, version)
104 } else {
105 write!(f, "{}-{}", self.framework, self.name)
106 }
107 }
108}
109
110#[derive(Debug, Clone)]
112pub struct CrossFrameworkBenchmarkResult<A: Float> {
113 pub config: CrossFrameworkConfig,
115 pub function_name: String,
117 pub problem_dim: usize,
119 pub batch_size: usize,
121 pub optimizer_results: HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>,
123 pub statistical_comparison: StatisticalComparison<A>,
125 pub performance_ranking: Vec<(OptimizerIdentifier, f64)>,
127 pub resource_usage: ResourceUsageComparison,
129 pub timestamp: std::time::Instant,
131}
132
133#[derive(Debug, Clone)]
135pub struct OptimizerBenchmarkSummary<A: Float> {
136 pub optimizer: OptimizerIdentifier,
138 pub successful_runs: usize,
140 pub total_runs: usize,
142 pub success_rate: f64,
144 pub mean_convergence_time: Duration,
146 pub std_convergence_time: Duration,
148 pub mean_final_value: A,
150 pub std_final_value: A,
152 pub mean_iterations: f64,
154 pub std_iterations: f64,
156 pub mean_gradient_norm: A,
158 pub std_gradient_norm: A,
160 pub convergence_curves: Vec<Vec<A>>,
162 pub memory_stats: MemoryStats,
164 pub gpu_utilization: Option<f64>,
166}
167
168#[derive(Debug, Clone)]
170pub struct StatisticalComparison<A: Float> {
171 pub convergence_time_tests: HashMap<(OptimizerIdentifier, OptimizerIdentifier), TTestResult>,
173 pub final_value_tests: HashMap<(OptimizerIdentifier, OptimizerIdentifier), TTestResult>,
175 pub anova_results: AnovaResult<A>,
177 pub effect_sizes: HashMap<(OptimizerIdentifier, OptimizerIdentifier), f64>,
179 pub confidence_intervals: HashMap<OptimizerIdentifier, ConfidenceInterval<A>>,
181}
182
183#[derive(Debug, Clone)]
185pub struct TTestResult {
186 pub t_statistic: f64,
188 pub p_value: f64,
190 pub degrees_of_freedom: f64,
192 pub is_significant: bool,
194}
195
196#[derive(Debug, Clone)]
198pub struct AnovaResult<A: Float> {
199 pub f_statistic: f64,
201 pub p_value: f64,
203 pub between_ss: A,
205 pub within_ss: A,
207 pub total_ss: A,
209 pub df_between: usize,
211 pub df_within: usize,
213}
214
215#[derive(Debug, Clone)]
217pub struct ConfidenceInterval<A: Float> {
218 pub lower: A,
220 pub upper: A,
222 pub confidence_level: f64,
224}
225
226#[derive(Debug, Clone)]
228pub struct ResourceUsageComparison {
229 pub memory_usage: HashMap<OptimizerIdentifier, MemoryStats>,
231 pub cpu_usage: HashMap<OptimizerIdentifier, CpuStats>,
233 pub gpu_usage: HashMap<OptimizerIdentifier, Option<GpuStats>>,
235}
236
237#[derive(Debug, Clone)]
239pub struct MemoryStats {
240 pub peak_memory_bytes: usize,
242 pub avg_memory_bytes: usize,
244 pub allocation_count: usize,
246 pub fragmentation_ratio: f64,
248}
249
250#[derive(Debug, Clone)]
252pub struct CpuStats {
253 pub cpu_percent: f64,
255 pub cores_used: usize,
257 pub cache_misses: usize,
259 pub context_switches: usize,
261}
262
263#[derive(Debug, Clone)]
265pub struct GpuStats {
266 pub gpu_percent: f64,
268 pub memory_usage_bytes: usize,
270 pub kernel_launches: usize,
272 pub avg_kernel_time_us: f64,
274}
275
276pub struct CrossFrameworkBenchmark<A: Float> {
278 config: CrossFrameworkConfig,
280 test_functions: Vec<TestFunction<A>>,
282 python_scripts: PythonScriptTemplates,
284 results: Vec<CrossFrameworkBenchmarkResult<A>>,
286}
287
288struct PythonScriptTemplates {
290 pytorch_template: String,
292 tensorflow_template: String,
294}
295
296impl<A: Float + Debug + Send + Sync> CrossFrameworkBenchmark<A> {
297 pub fn new(config: CrossFrameworkConfig) -> Result<Self> {
299 let python_scripts = PythonScriptTemplates::new();
300
301 std::fs::create_dir_all(&config.temp_dir).map_err(|e| {
303 OptimError::InvalidConfig(format!("Failed to create temp directory: {}", e))
304 })?;
305
306 Ok(Self {
307 config,
308 test_functions: Vec::new(),
309 python_scripts,
310 results: Vec::new(),
311 })
312 }
313
314 pub fn add_test_function(&mut self, test_function: TestFunction<A>) {
316 self.test_functions.push(test_function);
317 }
318
319 pub fn add_standard_test_functions(&mut self) {
321 self.add_test_function(TestFunction {
323 name: "Quadratic".to_string(),
324 dimension: 10,
325 function: Box::new(|x: &Array1<A>| x.mapv(|val| val * val).sum()),
326 gradient: Box::new(|x: &Array1<A>| {
327 x.mapv(|val| A::from(2.0).expect("unwrap failed") * val)
328 }),
329 optimal_value: Some(A::zero()),
330 optimal_point: Some(Array1::zeros(10)),
331 });
332
333 self.add_test_function(TestFunction {
335 name: "Rosenbrock".to_string(),
336 dimension: 2,
337 function: Box::new(|x: &Array1<A>| {
338 let a = A::one();
339 let b = A::from(100.0).expect("unwrap failed");
340 let term1 = (a - x[0]) * (a - x[0]);
341 let term2 = b * (x[1] - x[0] * x[0]) * (x[1] - x[0] * x[0]);
342 term1 + term2
343 }),
344 gradient: Box::new(|x: &Array1<A>| {
345 let a = A::one();
346 let b = A::from(100.0).expect("unwrap failed");
347 let grad_x = A::from(-2.0).expect("unwrap failed") * (a - x[0])
348 - A::from(4.0).expect("unwrap failed") * b * x[0] * (x[1] - x[0] * x[0]);
349 let grad_y = A::from(2.0).expect("unwrap failed") * b * (x[1] - x[0] * x[0]);
350 Array1::from_vec(vec![grad_x, grad_y])
351 }),
352 optimal_value: Some(A::zero()),
353 optimal_point: Some(Array1::from_vec(vec![A::one(), A::one()])),
354 });
355
356 self.add_test_function(TestFunction {
358 name: "Beale".to_string(),
359 dimension: 2,
360 function: Box::new(|x: &Array1<A>| {
361 let x1 = x[0];
362 let x2 = x[1];
363 let term1 = (A::from(1.5).expect("unwrap failed") - x1 + x1 * x2)
364 * (A::from(1.5).expect("unwrap failed") - x1 + x1 * x2);
365 let term2 = (A::from(2.25).expect("unwrap failed") - x1 + x1 * x2 * x2)
366 * (A::from(2.25).expect("unwrap failed") - x1 + x1 * x2 * x2);
367 let term3 = (A::from(2.625).expect("unwrap failed") - x1 + x1 * x2 * x2 * x2)
368 * (A::from(2.625).expect("unwrap failed") - x1 + x1 * x2 * x2 * x2);
369 term1 + term2 + term3
370 }),
371 gradient: Box::new(|x: &Array1<A>| {
372 let x1 = x[0];
373 let x2 = x[1];
374 let dx1 = A::from(2.0).expect("unwrap failed")
375 * (A::from(1.5).expect("unwrap failed") - x1 + x1 * x2)
376 * (x2 - A::one())
377 + A::from(2.0).expect("unwrap failed")
378 * (A::from(2.25).expect("unwrap failed") - x1 + x1 * x2 * x2)
379 * (x2 * x2 - A::one())
380 + A::from(2.0).expect("unwrap failed")
381 * (A::from(2.625).expect("unwrap failed") - x1 + x1 * x2 * x2 * x2)
382 * (x2 * x2 * x2 - A::one());
383 let dx2 = A::from(2.0).expect("unwrap failed")
384 * (A::from(1.5).expect("unwrap failed") - x1 + x1 * x2)
385 * x1
386 + A::from(2.0).expect("unwrap failed")
387 * (A::from(2.25).expect("unwrap failed") - x1 + x1 * x2 * x2)
388 * (A::from(2.0).expect("unwrap failed") * x1 * x2)
389 + A::from(2.0).expect("unwrap failed")
390 * (A::from(2.625).expect("unwrap failed") - x1 + x1 * x2 * x2 * x2)
391 * (A::from(3.0).expect("unwrap failed") * x1 * x2 * x2);
392 Array1::from_vec(vec![dx1, dx2])
393 }),
394 optimal_value: Some(A::zero()),
395 optimal_point: Some(Array1::from_vec(vec![
396 A::from(3.0).expect("unwrap failed"),
397 A::from(0.5).expect("unwrap failed"),
398 ])),
399 });
400 }
401
402 pub fn run_comprehensive_benchmark(
404 &mut self,
405 scirs2_optimizers: Vec<(String, OptimizerFn<A>)>,
406 ) -> Result<Vec<CrossFrameworkBenchmarkResult<A>>> {
407 let mut all_results = Vec::new();
408
409 for test_function in &self.test_functions {
410 for &problem_dim in &self.config.problem_dimensions {
411 for &batch_size in &self.config.batch_sizes {
412 let result = self.run_single_benchmark(
413 test_function,
414 problem_dim,
415 batch_size,
416 &scirs2_optimizers,
417 )?;
418 all_results.push(result);
419 }
420 }
421 }
422
423 self.results.extend(all_results.clone());
424 Ok(all_results)
425 }
426
427 fn run_single_benchmark(
429 &self,
430 test_function: &TestFunction<A>,
431 problem_dim: usize,
432 batch_size: usize,
433 scirs2_optimizers: &[(String, OptimizerFn<A>)],
434 ) -> Result<CrossFrameworkBenchmarkResult<A>> {
435 let mut optimizer_results = HashMap::new();
436
437 for (name, optimizer) in scirs2_optimizers {
439 let identifier = OptimizerIdentifier {
440 framework: Framework::SciRS2,
441 name: name.clone(),
442 version: Some("0.1.0".to_string()),
443 };
444
445 let summary =
446 self.benchmark_scirs2_optimizer(test_function, problem_dim, batch_size, optimizer)?;
447 optimizer_results.insert(identifier, summary);
448 }
449
450 if self.config.enable_pytorch {
452 let pytorch_results =
453 self.benchmark_pytorch_optimizers(test_function, problem_dim, batch_size)?;
454 optimizer_results.extend(pytorch_results);
455 }
456
457 if self.config.enable_tensorflow {
459 let tensorflow_results =
460 self.benchmark_tensorflow_optimizers(test_function, problem_dim, batch_size)?;
461 optimizer_results.extend(tensorflow_results);
462 }
463
464 let statistical_comparison = self.perform_statistical_analysis(&optimizer_results)?;
466
467 let performance_ranking = self.rank_optimizers(&optimizer_results);
469
470 let resource_usage = self.analyze_resource_usage(&optimizer_results);
472
473 Ok(CrossFrameworkBenchmarkResult {
474 config: self.config.clone(),
475 function_name: test_function.name.clone(),
476 problem_dim,
477 batch_size,
478 optimizer_results,
479 statistical_comparison,
480 performance_ranking,
481 resource_usage,
482 timestamp: std::time::Instant::now(),
483 })
484 }
485
486 fn benchmark_scirs2_optimizer(
488 &self,
489 test_function: &TestFunction<A>,
490 problem_dim: usize,
491 _batch_size: usize,
492 optimizer: &OptimizerFn<A>,
493 ) -> Result<OptimizerBenchmarkSummary<A>> {
494 let mut convergence_times = Vec::new();
495 let mut final_values = Vec::new();
496 let mut iterations_counts = Vec::new();
497 let mut gradient_norms = Vec::new();
498 let mut convergence_curves = Vec::new();
499 let mut successful_runs = 0;
500
501 for run in 0..self.config.num_runs {
502 let mut rng_seed = self.config.random_seed + run as u64;
504
505 let mut x = Array1::from_vec(
507 (0..problem_dim)
508 .map(|_| {
509 rng_seed = rng_seed.wrapping_mul(1103515245).wrapping_add(12345);
510 A::from((rng_seed % 1000) as f64 / 1000.0 - 0.5).expect("unwrap failed")
511 })
512 .collect(),
513 );
514
515 let start_time = Instant::now();
516 let mut convergence_curve = Vec::new();
517 let mut converged = false;
518
519 for iteration in 0..self.config.max_iterations {
520 let f_val = (test_function.function)(&x);
521 let grad = (test_function.gradient)(&x);
522 let grad_norm = grad.mapv(|g| g * g).sum().sqrt();
523
524 convergence_curve.push(f_val);
525
526 if grad_norm.to_f64().unwrap_or(f64::INFINITY) < self.config.tolerance {
528 let elapsed = start_time.elapsed();
529 convergence_times.push(elapsed);
530 final_values.push(f_val);
531 iterations_counts.push(iteration as f64);
532 gradient_norms.push(grad_norm);
533 convergence_curves.push(convergence_curve.clone());
534 successful_runs += 1;
535 converged = true;
536 break;
537 }
538
539 x = optimizer(&x, &grad);
541 }
542
543 if !converged {
545 let elapsed = start_time.elapsed();
546 let f_val = (test_function.function)(&x);
547 let grad = (test_function.gradient)(&x);
548 let grad_norm = grad.mapv(|g| g * g).sum().sqrt();
549
550 convergence_times.push(elapsed);
551 final_values.push(f_val);
552 iterations_counts.push(self.config.max_iterations as f64);
553 gradient_norms.push(grad_norm);
554 convergence_curves.push(convergence_curve);
555 }
556 }
557
558 let success_rate = successful_runs as f64 / self.config.num_runs as f64;
560
561 let mean_convergence_time = if !convergence_times.is_empty() {
562 convergence_times.iter().sum::<Duration>() / convergence_times.len() as u32
563 } else {
564 Duration::from_secs(0)
565 };
566
567 let mean_final_value = if !final_values.is_empty() {
568 final_values.iter().fold(A::zero(), |acc, &x| acc + x)
569 / A::from(final_values.len()).expect("unwrap failed")
570 } else {
571 A::zero()
572 };
573
574 let mean_iterations = if !iterations_counts.is_empty() {
575 iterations_counts.iter().sum::<f64>() / iterations_counts.len() as f64
576 } else {
577 0.0
578 };
579
580 let mean_gradient_norm = if !gradient_norms.is_empty() {
581 gradient_norms.iter().fold(A::zero(), |acc, &x| acc + x)
582 / A::from(gradient_norms.len()).expect("unwrap failed")
583 } else {
584 A::zero()
585 };
586
587 let std_convergence_time =
589 self.calculate_duration_std(&convergence_times, mean_convergence_time);
590 let std_final_value = self.calculate_std(&final_values, mean_final_value);
591 let std_iterations = self.calculate_f64std(&iterations_counts, mean_iterations);
592 let std_gradient_norm = self.calculate_std(&gradient_norms, mean_gradient_norm);
593
594 Ok(OptimizerBenchmarkSummary {
595 optimizer: OptimizerIdentifier {
596 framework: Framework::SciRS2,
597 name: "SciRS2".to_string(),
598 version: Some("0.1.0".to_string()),
599 },
600 successful_runs,
601 total_runs: self.config.num_runs,
602 success_rate,
603 mean_convergence_time,
604 std_convergence_time,
605 mean_final_value,
606 std_final_value,
607 mean_iterations,
608 std_iterations,
609 mean_gradient_norm,
610 std_gradient_norm,
611 convergence_curves,
612 memory_stats: MemoryStats {
613 peak_memory_bytes: 0,
614 avg_memory_bytes: 0,
615 allocation_count: 0,
616 fragmentation_ratio: 0.0,
617 },
618 gpu_utilization: None,
619 })
620 }
621
622 fn benchmark_pytorch_optimizers(
624 &self,
625 test_function: &TestFunction<A>,
626 problem_dim: usize,
627 batch_size: usize,
628 ) -> Result<HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>> {
629 let script_path = format!("{}/pytorch_benchmark.py", self.config.temp_dir);
630
631 let script_content = self.python_scripts.generate_pytorch_script(
633 &test_function.name,
634 problem_dim,
635 batch_size,
636 &self.config,
637 );
638
639 std::fs::write(&script_path, script_content).map_err(|e| {
640 OptimError::InvalidConfig(format!("Failed to write PyTorch script: {}", e))
641 })?;
642
643 let output = Command::new(&self.config.python_path)
645 .arg(&script_path)
646 .output()
647 .map_err(|e| {
648 OptimError::InvalidConfig(format!("Failed to execute PyTorch benchmark: {}", e))
649 })?;
650
651 if !output.status.success() {
652 return Err(OptimError::InvalidConfig(format!(
653 "PyTorch benchmark failed: {}",
654 String::from_utf8_lossy(&output.stderr)
655 )));
656 }
657
658 let _results_json = String::from_utf8_lossy(&output.stdout);
660 let results: HashMap<String, HashMap<String, f64>> = HashMap::new(); let mut optimizer_results = HashMap::new();
665
666 for (optimizer_name, result_data) in results {
667 let identifier = OptimizerIdentifier {
668 framework: Framework::PyTorch,
669 name: optimizer_name,
670 version: Some("2.0".to_string()),
671 };
672
673 let summary = self.parse_python_results(identifier.clone(), &result_data)?;
674 optimizer_results.insert(identifier, summary);
675 }
676
677 Ok(optimizer_results)
678 }
679
680 fn benchmark_tensorflow_optimizers(
682 &self,
683 test_function: &TestFunction<A>,
684 problem_dim: usize,
685 batch_size: usize,
686 ) -> Result<HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>> {
687 let script_path = format!("{}/tensorflow_benchmark.py", self.config.temp_dir);
688
689 let script_content = self.python_scripts.generate_tensorflow_script(
691 &test_function.name,
692 problem_dim,
693 batch_size,
694 &self.config,
695 );
696
697 std::fs::write(&script_path, script_content).map_err(|e| {
698 OptimError::InvalidConfig(format!("Failed to write TensorFlow script: {}", e))
699 })?;
700
701 let output = Command::new(&self.config.python_path)
703 .arg(&script_path)
704 .output()
705 .map_err(|e| {
706 OptimError::InvalidConfig(format!("Failed to execute TensorFlow benchmark: {}", e))
707 })?;
708
709 if !output.status.success() {
710 return Err(OptimError::InvalidConfig(format!(
711 "TensorFlow benchmark failed: {}",
712 String::from_utf8_lossy(&output.stderr)
713 )));
714 }
715
716 let _results_json = String::from_utf8_lossy(&output.stdout);
718 let results: HashMap<String, HashMap<String, f64>> = HashMap::new(); let mut optimizer_results = HashMap::new();
723
724 for (optimizer_name, result_data) in results {
725 let identifier = OptimizerIdentifier {
726 framework: Framework::TensorFlow,
727 name: optimizer_name,
728 version: Some("2.12".to_string()),
729 };
730
731 let summary = self.parse_python_results(identifier.clone(), &result_data)?;
732 optimizer_results.insert(identifier, summary);
733 }
734
735 Ok(optimizer_results)
736 }
737
738 fn parse_python_results(
740 &self,
741 identifier: OptimizerIdentifier,
742 result_data: &HashMap<String, f64>,
743 ) -> Result<OptimizerBenchmarkSummary<A>> {
744 let successful_runs = *result_data.get("successful_runs").unwrap_or(&0.0) as usize;
746 let total_runs = *result_data.get("total_runs").unwrap_or(&5.0) as usize;
747 let success_rate = *result_data.get("success_rate").unwrap_or(&1.0);
748
749 let mean_convergence_time_ms = *result_data
750 .get("mean_convergence_time_ms")
751 .unwrap_or(&100.0);
752 let mean_convergence_time = Duration::from_millis(mean_convergence_time_ms as u64);
753
754 let std_convergence_time_ms = *result_data.get("std_convergence_time_ms").unwrap_or(&10.0);
755 let std_convergence_time = Duration::from_millis(std_convergence_time_ms as u64);
756
757 let mean_final_value =
758 A::from(*result_data.get("mean_final_value").unwrap_or(&0.1)).expect("unwrap failed");
759 let std_final_value =
760 A::from(*result_data.get("std_final_value").unwrap_or(&0.01)).expect("unwrap failed");
761
762 let mean_iterations = *result_data.get("mean_iterations").unwrap_or(&100.0);
763 let std_iterations = *result_data.get("std_iterations").unwrap_or(&10.0);
764
765 let mean_gradient_norm = A::from(*result_data.get("mean_gradient_norm").unwrap_or(&0.01))
766 .expect("unwrap failed");
767 let std_gradient_norm = A::from(*result_data.get("std_gradient_norm").unwrap_or(&0.001))
768 .expect("unwrap failed");
769
770 let convergence_curves: Vec<Vec<A>> = vec![vec![mean_final_value; 100]; total_runs];
772
773 Ok(OptimizerBenchmarkSummary {
774 optimizer: identifier,
775 successful_runs,
776 total_runs,
777 success_rate,
778 mean_convergence_time,
779 std_convergence_time,
780 mean_final_value,
781 std_final_value,
782 mean_iterations,
783 std_iterations,
784 mean_gradient_norm,
785 std_gradient_norm,
786 convergence_curves,
787 memory_stats: MemoryStats {
788 peak_memory_bytes: *result_data.get("peak_memory_bytes").unwrap_or(&1000000.0)
789 as usize,
790 avg_memory_bytes: *result_data.get("avg_memory_bytes").unwrap_or(&500000.0)
791 as usize,
792 allocation_count: *result_data.get("allocation_count").unwrap_or(&100.0) as usize,
793 fragmentation_ratio: *result_data.get("fragmentation_ratio").unwrap_or(&0.1),
794 },
795 gpu_utilization: result_data.get("gpu_utilization").copied(),
796 })
797 }
798
799 fn perform_statistical_analysis(
801 &self,
802 results: &HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>,
803 ) -> Result<StatisticalComparison<A>> {
804 let mut convergence_time_tests = HashMap::new();
805 let mut final_value_tests = HashMap::new();
806 let mut effect_sizes = HashMap::new();
807 let mut confidence_intervals = HashMap::new();
808
809 let optimizers: Vec<_> = results.keys().collect();
811 for i in 0..optimizers.len() {
812 for j in (i + 1)..optimizers.len() {
813 let opt1 = optimizers[i];
814 let opt2 = optimizers[j];
815
816 let result1 = &results[opt1];
817 let result2 = &results[opt2];
818
819 let time_test = self.perform_t_test(
821 &self.extract_convergence_times(&result1.convergence_curves),
822 &self.extract_convergence_times(&result2.convergence_curves),
823 );
824 convergence_time_tests.insert((opt1.clone(), opt2.clone()), time_test);
825
826 let final_values1 = self.extract_final_values(&result1.convergence_curves);
828 let final_values2 = self.extract_final_values(&result2.convergence_curves);
829 let value_test = self.perform_t_test(&final_values1, &final_values2);
830 final_value_tests.insert((opt1.clone(), opt2.clone()), value_test);
831
832 let effect_size = self.calculate_cohens_d(&final_values1, &final_values2);
834 effect_sizes.insert((opt1.clone(), opt2.clone()), effect_size);
835 }
836
837 let result = &results[optimizers[i]];
839 let final_values = self.extract_final_values(&result.convergence_curves);
840 let ci = self.calculate_confidence_interval(&final_values, 0.95);
841 confidence_intervals.insert(optimizers[i].clone(), ci);
842 }
843
844 let all_final_values: Vec<Vec<f64>> = results
846 .values()
847 .map(|result| self.extract_final_values(&result.convergence_curves))
848 .collect();
849 let anova_results = self.perform_anova(&all_final_values);
850
851 Ok(StatisticalComparison {
852 convergence_time_tests,
853 final_value_tests,
854 anova_results,
855 effect_sizes,
856 confidence_intervals,
857 })
858 }
859
860 fn rank_optimizers(
862 &self,
863 results: &HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>,
864 ) -> Vec<(OptimizerIdentifier, f64)> {
865 let mut rankings: Vec<_> = results
866 .iter()
867 .map(|(identifier, summary)| {
868 let time_factor = 1.0 / (summary.mean_convergence_time.as_millis() as f64 + 1.0);
870 let value_factor = 1.0 / (summary.mean_final_value.to_f64().unwrap_or(1.0) + 1e-10);
871 let score = summary.success_rate * time_factor * value_factor;
872 (identifier.clone(), score)
873 })
874 .collect();
875
876 rankings.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
877 rankings
878 }
879
880 fn analyze_resource_usage(
882 &self,
883 results: &HashMap<OptimizerIdentifier, OptimizerBenchmarkSummary<A>>,
884 ) -> ResourceUsageComparison {
885 let memory_usage = results
886 .iter()
887 .map(|(id, summary)| (id.clone(), summary.memory_stats.clone()))
888 .collect();
889
890 let cpu_usage = results
891 .iter()
892 .map(|(id_, summary)| {
893 (
894 id_.clone(),
895 CpuStats {
896 cpu_percent: 0.0, cores_used: 1,
898 cache_misses: 0,
899 context_switches: 0,
900 },
901 )
902 })
903 .collect();
904
905 let gpu_usage = results
906 .iter()
907 .map(|(id, summary)| {
908 let gpu_stats = if summary.gpu_utilization.is_some() {
909 Some(GpuStats {
910 gpu_percent: summary.gpu_utilization.unwrap_or(0.0),
911 memory_usage_bytes: 0,
912 kernel_launches: 0,
913 avg_kernel_time_us: 0.0,
914 })
915 } else {
916 None
917 };
918 (id.clone(), gpu_stats)
919 })
920 .collect();
921
922 ResourceUsageComparison {
923 memory_usage,
924 cpu_usage,
925 gpu_usage,
926 }
927 }
928
929 pub fn generate_comprehensive_report(&self) -> String {
931 let mut report = String::new();
932
933 report.push_str("# Cross-Framework Optimizer Benchmark Report\n\n");
934 report.push_str(&format!(
935 "Generated: {:?}\n\n",
936 std::time::SystemTime::now()
937 ));
938
939 if self.results.is_empty() {
940 report.push_str("No benchmark results available.\n");
941 return report;
942 }
943
944 report.push_str("## Executive Summary\n\n");
946 report.push_str(&format!(
947 "Total test configurations: {}\n",
948 self.results.len()
949 ));
950
951 let frameworks: std::collections::HashSet<_> = self
953 .results
954 .iter()
955 .flat_map(|result| result.optimizer_results.keys())
956 .map(|id| &id.framework)
957 .collect();
958 report.push_str(&format!("Frameworks tested: {:?}\n\n", frameworks));
959
960 report.push_str("## Overall Performance Rankings\n\n");
962 for result in &self.results {
963 report.push_str(&format!(
964 "### {} ({}D, batch={})\n\n",
965 result.function_name, result.problem_dim, result.batch_size
966 ));
967
968 for (rank, (optimizer, score)) in result.performance_ranking.iter().enumerate() {
969 report.push_str(&format!(
970 "{}. {} - Score: {:.6}\n",
971 rank + 1,
972 optimizer,
973 score
974 ));
975 }
976 report.push('\n');
977 }
978
979 report.push_str("## Statistical Analysis\n\n");
981 for result in &self.results {
982 report.push_str(&format!("### {} Results\n\n", result.function_name));
983
984 let anova = &result.statistical_comparison.anova_results;
986 report.push_str(&format!(
987 "ANOVA F-statistic: {:.4}, p-value: {:.6}\n",
988 anova.f_statistic, anova.p_value
989 ));
990
991 if anova.p_value < 0.05 {
992 report.push_str(
993 "**Statistically significant differences found between optimizers.**\n\n",
994 );
995 } else {
996 report.push_str("No statistically significant differences found.\n\n");
997 }
998 }
999
1000 report
1001 }
1002
1003 fn calculate_duration_std(&self, values: &[Duration], mean: Duration) -> Duration {
1007 if values.len() <= 1 {
1008 return Duration::from_millis(0);
1009 }
1010
1011 let variance = values
1012 .iter()
1013 .map(|&v| {
1014 let diff = v.as_millis() as i64 - mean.as_millis() as i64;
1015 (diff * diff) as f64
1016 })
1017 .sum::<f64>()
1018 / (values.len() - 1) as f64;
1019
1020 Duration::from_millis(variance.sqrt() as u64)
1021 }
1022
1023 fn calculate_std(&self, values: &[A], mean: A) -> A {
1025 if values.len() <= 1 {
1026 return A::zero();
1027 }
1028
1029 let variance = values
1030 .iter()
1031 .map(|&v| (v - mean) * (v - mean))
1032 .fold(A::zero(), |acc, x| acc + x)
1033 / A::from(values.len() - 1).expect("unwrap failed");
1034
1035 variance.sqrt()
1036 }
1037
1038 fn calculate_f64std(&self, values: &[f64], mean: f64) -> f64 {
1040 if values.len() <= 1 {
1041 return 0.0;
1042 }
1043
1044 let variance = values.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>()
1045 / (values.len() - 1) as f64;
1046
1047 variance.sqrt()
1048 }
1049
1050 fn extract_convergence_times(&self, curves: &[Vec<A>]) -> Vec<f64> {
1052 curves
1053 .iter()
1054 .map(|curve| curve.len() as f64) .collect()
1056 }
1057
1058 fn extract_final_values(&self, curves: &[Vec<A>]) -> Vec<f64> {
1060 curves
1061 .iter()
1062 .filter_map(|curve| curve.last())
1063 .map(|&val| val.to_f64().unwrap_or(0.0))
1064 .collect()
1065 }
1066
1067 fn perform_t_test(&self, sample1: &[f64], sample2: &[f64]) -> TTestResult {
1069 if sample1.is_empty() || sample2.is_empty() {
1070 return TTestResult {
1071 t_statistic: 0.0,
1072 p_value: 1.0,
1073 degrees_of_freedom: 0.0,
1074 is_significant: false,
1075 };
1076 }
1077
1078 let n1 = sample1.len() as f64;
1079 let n2 = sample2.len() as f64;
1080
1081 let mean1 = sample1.iter().sum::<f64>() / n1;
1082 let mean2 = sample2.iter().sum::<f64>() / n2;
1083
1084 let var1 = sample1.iter().map(|&x| (x - mean1).powi(2)).sum::<f64>() / (n1 - 1.0);
1085 let var2 = sample2.iter().map(|&x| (x - mean2).powi(2)).sum::<f64>() / (n2 - 1.0);
1086
1087 let pooled_se = ((var1 / n1) + (var2 / n2)).sqrt();
1088 let t_statistic = (mean1 - mean2) / pooled_se;
1089 let degrees_of_freedom = n1 + n2 - 2.0;
1090
1091 let p_value = 2.0 * (1.0 - self.t_distribution_cdf(t_statistic.abs(), degrees_of_freedom));
1093
1094 TTestResult {
1095 t_statistic,
1096 p_value,
1097 degrees_of_freedom,
1098 is_significant: p_value < 0.05,
1099 }
1100 }
1101
1102 fn t_distribution_cdf(&self, t: f64, df: f64) -> f64 {
1104 let x = t / (t * t + df).sqrt();
1106 0.5 + 0.5 * x / (1.0 + 0.33 * x * x)
1107 }
1108
1109 fn calculate_cohens_d(&self, sample1: &[f64], sample2: &[f64]) -> f64 {
1111 if sample1.is_empty() || sample2.is_empty() {
1112 return 0.0;
1113 }
1114
1115 let mean1 = sample1.iter().sum::<f64>() / sample1.len() as f64;
1116 let mean2 = sample2.iter().sum::<f64>() / sample2.len() as f64;
1117
1118 let var1 =
1119 sample1.iter().map(|&x| (x - mean1).powi(2)).sum::<f64>() / (sample1.len() - 1) as f64;
1120 let var2 =
1121 sample2.iter().map(|&x| (x - mean2).powi(2)).sum::<f64>() / (sample2.len() - 1) as f64;
1122
1123 let pooled_std = ((var1 + var2) / 2.0).sqrt();
1124 (mean1 - mean2) / pooled_std
1125 }
1126
1127 fn calculate_confidence_interval(
1129 &self,
1130 values: &[f64],
1131 confidence_level: f64,
1132 ) -> ConfidenceInterval<A> {
1133 if values.is_empty() {
1134 return ConfidenceInterval {
1135 lower: A::zero(),
1136 upper: A::zero(),
1137 confidence_level,
1138 };
1139 }
1140
1141 let mean = values.iter().sum::<f64>() / values.len() as f64;
1142 let std_err = self.calculate_f64std(values, mean) / (values.len() as f64).sqrt();
1143
1144 let _alpha = 1.0 - confidence_level;
1146 let critical_value = 1.96; let margin_of_error = critical_value * std_err;
1149
1150 ConfidenceInterval {
1151 lower: A::from(mean - margin_of_error).expect("unwrap failed"),
1152 upper: A::from(mean + margin_of_error).expect("unwrap failed"),
1153 confidence_level,
1154 }
1155 }
1156
1157 fn perform_anova(&self, groups: &[Vec<f64>]) -> AnovaResult<A> {
1159 if groups.len() < 2 {
1160 return AnovaResult {
1161 f_statistic: 0.0,
1162 p_value: 1.0,
1163 between_ss: A::zero(),
1164 within_ss: A::zero(),
1165 total_ss: A::zero(),
1166 df_between: 0,
1167 df_within: 0,
1168 };
1169 }
1170
1171 let total_n: usize = groups.iter().map(|g| g.len()).sum();
1172 let grand_mean = groups.iter().flat_map(|g| g.iter()).sum::<f64>() / total_n as f64;
1173
1174 let between_ss = groups
1176 .iter()
1177 .map(|group| {
1178 let group_mean = group.iter().sum::<f64>() / group.len() as f64;
1179 group.len() as f64 * (group_mean - grand_mean).powi(2)
1180 })
1181 .sum::<f64>();
1182
1183 let within_ss = groups
1185 .iter()
1186 .flat_map(|group| {
1187 let group_mean = group.iter().sum::<f64>() / group.len() as f64;
1188 group.iter().map(move |&x| (x - group_mean).powi(2))
1189 })
1190 .sum::<f64>();
1191
1192 let total_ss = between_ss + within_ss;
1193 let df_between = groups.len() - 1;
1194 let df_within = total_n - groups.len();
1195
1196 let ms_between = between_ss / df_between as f64;
1197 let ms_within = within_ss / df_within as f64;
1198
1199 let f_statistic = ms_between / ms_within;
1200
1201 let p_value = if f_statistic > 3.0 { 0.01 } else { 0.1 }; AnovaResult {
1205 f_statistic,
1206 p_value,
1207 between_ss: A::from(between_ss).expect("unwrap failed"),
1208 within_ss: A::from(within_ss).expect("unwrap failed"),
1209 total_ss: A::from(total_ss).expect("unwrap failed"),
1210 df_between,
1211 df_within,
1212 }
1213 }
1214}
1215
1216impl PythonScriptTemplates {
1217 fn new() -> Self {
1218 Self {
1219 pytorch_template: r#"# PyTorch benchmark template
1220import torch
1221import torch.optim as optim
1222
1223# Configuration
1224FUNCTION_NAME = "{{FUNCTION_NAME}}"
1225PROBLEM_DIM = {{PROBLEM_DIM}}
1226BATCH_SIZE = {{BATCH_SIZE}}
1227MAX_ITERATIONS = {{MAX_ITERATIONS}}
1228TOLERANCE = {{TOLERANCE}}
1229NUM_RUNS = {{NUM_RUNS}}
1230RANDOM_SEED = {{RANDOM_SEED}}
1231
1232print(f"Running PyTorch benchmark for {FUNCTION_NAME}")
1233print(f"Problem dimension: {PROBLEM_DIM}")
1234print(f"Batch size: {BATCH_SIZE}")
1235"#
1236 .to_string(),
1237 tensorflow_template: r#"# TensorFlow benchmark template
1238import tensorflow as tf
1239
1240# Configuration
1241FUNCTION_NAME = "{{FUNCTION_NAME}}"
1242PROBLEM_DIM = {{PROBLEM_DIM}}
1243BATCH_SIZE = {{BATCH_SIZE}}
1244MAX_ITERATIONS = {{MAX_ITERATIONS}}
1245TOLERANCE = {{TOLERANCE}}
1246NUM_RUNS = {{NUM_RUNS}}
1247RANDOM_SEED = {{RANDOM_SEED}}
1248
1249print(f"Running TensorFlow benchmark for {FUNCTION_NAME}")
1250print(f"Problem dimension: {PROBLEM_DIM}")
1251print(f"Batch size: {BATCH_SIZE}")
1252"#
1253 .to_string(),
1254 }
1255 }
1256
1257 fn generate_pytorch_script(
1258 &self,
1259 function_name: &str,
1260 problem_dim: usize,
1261 batch_size: usize,
1262 config: &CrossFrameworkConfig,
1263 ) -> String {
1264 self.pytorch_template
1265 .replace("{{FUNCTION_NAME}}", function_name)
1266 .replace("{{PROBLEM_DIM}}", &problem_dim.to_string())
1267 .replace("{{BATCH_SIZE}}", &batch_size.to_string())
1268 .replace("{{MAX_ITERATIONS}}", &config.max_iterations.to_string())
1269 .replace("{{TOLERANCE}}", &config.tolerance.to_string())
1270 .replace("{{NUM_RUNS}}", &config.num_runs.to_string())
1271 .replace("{{RANDOM_SEED}}", &config.random_seed.to_string())
1272 }
1273
1274 fn generate_tensorflow_script(
1275 &self,
1276 function_name: &str,
1277 problem_dim: usize,
1278 batch_size: usize,
1279 config: &CrossFrameworkConfig,
1280 ) -> String {
1281 self.tensorflow_template
1282 .replace("{{FUNCTION_NAME}}", function_name)
1283 .replace("{{PROBLEM_DIM}}", &problem_dim.to_string())
1284 .replace("{{BATCH_SIZE}}", &batch_size.to_string())
1285 .replace("{{MAX_ITERATIONS}}", &config.max_iterations.to_string())
1286 .replace("{{TOLERANCE}}", &config.tolerance.to_string())
1287 .replace("{{NUM_RUNS}}", &config.num_runs.to_string())
1288 .replace("{{RANDOM_SEED}}", &config.random_seed.to_string())
1289 }
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294 use super::*;
1295
1296 #[test]
1297 fn test_cross_framework_config() {
1298 let config = CrossFrameworkConfig::default();
1299 assert!(config.enable_pytorch);
1300 assert!(config.enable_tensorflow);
1301 assert_eq!(config.max_iterations, 1000);
1302 assert_eq!(config.tolerance, 1e-6);
1303 }
1304
1305 #[test]
1306 fn test_optimizer_identifier() {
1307 let id = OptimizerIdentifier {
1308 framework: Framework::SciRS2,
1309 name: "Adam".to_string(),
1310 version: Some("0.1.0".to_string()),
1311 };
1312 assert_eq!(id.to_string(), "SciRS2-Adam-v0.1.0");
1313 }
1314
1315 #[test]
1316 fn test_precision_enum() {
1317 let precision = Precision::F64;
1318 assert!(matches!(precision, Precision::F64));
1319 }
1320
1321 #[test]
1322 fn test_framework_display() {
1323 assert_eq!(Framework::SciRS2.to_string(), "SciRS2");
1324 assert_eq!(Framework::PyTorch.to_string(), "PyTorch");
1325 assert_eq!(Framework::TensorFlow.to_string(), "TensorFlow");
1326 }
1327
1328 #[test]
1329 fn test_python_script_generation() {
1330 let templates = PythonScriptTemplates::new();
1331 let config = CrossFrameworkConfig::default();
1332
1333 let script = templates.generate_pytorch_script("Quadratic", 10, 32, &config);
1334 assert!(script.contains("10")); assert!(script.contains("32")); assert!(script.contains("1000")); }
1338}