1use crate::density::{dbscan, optics};
54use crate::error::{ClusteringError, Result};
55use crate::gmm::{gaussian_mixture, GMMOptions};
56use crate::hierarchy::{linkage, LinkageMethod, Metric};
57use crate::metrics::{calinski_harabasz_score, silhouette_score};
58use crate::vq::{kmeans, kmeans2, vq};
59
60use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
61use std::collections::HashMap;
62use std::sync::atomic::{AtomicUsize, Ordering};
63use std::sync::Arc;
64use std::time::{Duration, Instant};
65
66use serde::{Deserialize, Serialize};
67
68fn current_rss_bytes() -> Option<usize> {
75 #[cfg(target_os = "linux")]
76 {
77 let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
78 let resident_pages: usize = statm.split_whitespace().nth(1)?.parse().ok()?;
79 const PAGE_SIZE: usize = 4096; return Some(resident_pages * PAGE_SIZE);
81 }
82
83 #[allow(unreachable_code)]
84 None
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct BenchmarkConfig {
90 pub warmup_iterations: usize,
92 pub measurement_iterations: usize,
94 pub statistical_significance: f64,
96 pub memory_profiling: bool,
98 pub gpu_comparison: bool,
100 pub stress_testing: bool,
102 pub regression_detection: bool,
104 pub max_test_duration: Duration,
106 pub advanced_statistics: bool,
108 pub cross_platform: bool,
110}
111
112impl Default for BenchmarkConfig {
113 fn default() -> Self {
114 Self {
115 warmup_iterations: 5,
116 measurement_iterations: 50,
117 statistical_significance: 0.05,
118 memory_profiling: true,
119 gpu_comparison: false, stress_testing: true,
121 regression_detection: true,
122 max_test_duration: Duration::from_secs(300), advanced_statistics: true,
124 cross_platform: true,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct PerformanceStatistics {
132 pub mean: Duration,
134 pub std_dev: Duration,
136 pub min: Duration,
138 pub max: Duration,
140 pub median: Duration,
142 pub percentile_95: Duration,
144 pub percentile_99: Duration,
146 pub coefficient_of_variation: f64,
148 pub confidence_interval: (Duration, Duration),
150 pub is_stable: bool,
152 pub outliers: usize,
154 pub throughput: f64,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct MemoryProfile {
161 pub peak_memory_mb: f64,
163 pub average_memory_mb: f64,
165 pub allocation_rate: f64,
167 pub deallocation_rate: f64,
169 pub gc_events: usize,
171 pub efficiency_score: f64,
173 pub potential_leak: bool,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct AlgorithmBenchmark {
180 pub algorithm: String,
182 pub performance: PerformanceStatistics,
184 pub memory: Option<MemoryProfile>,
186 pub gpu_comparison: Option<GpuVsCpuComparison>,
188 pub quality_metrics: QualityMetrics,
190 pub scalability: Option<ScalabilityAnalysis>,
192 pub optimization_suggestions: Vec<OptimizationSuggestion>,
194 pub error_rate: f64,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct GpuVsCpuComparison {
201 pub cpu_time: Duration,
203 pub gpu_time: Duration,
205 pub gpu_compute_time: Duration,
207 pub speedup: f64,
209 pub efficiency: f64,
211 pub gpu_memory_mb: f64,
213 pub transfer_overhead_percent: f64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct QualityMetrics {
220 pub silhouette_score: Option<f64>,
222 pub calinski_harabasz: Option<f64>,
224 pub davies_bouldin: Option<f64>,
226 pub inertia: Option<f64>,
228 pub n_clusters: usize,
230 pub convergence_iterations: Option<usize>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ScalabilityAnalysis {
237 pub size_to_time: Vec<(usize, Duration)>,
239 pub complexity_estimate: ComplexityClass,
241 pub scalability_predictions: Vec<(usize, Duration)>,
243 pub memory_scaling: f64,
245 pub optimal_size_range: (usize, usize),
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251pub enum ComplexityClass {
252 Linear,
254 Linearithmic,
256 Quadratic,
258 Cubic,
260 Unknown,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct OptimizationSuggestion {
267 pub category: OptimizationCategory,
269 pub suggestion: String,
271 pub expected_improvement: f64,
273 pub difficulty: u8,
275 pub priority: OptimizationPriority,
277}
278
279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281pub enum OptimizationCategory {
282 ParameterTuning,
284 MemoryOptimization,
286 Parallelization,
288 GpuAcceleration,
290 DataPreprocessing,
292 AlgorithmChange,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
298pub enum OptimizationPriority {
299 Low,
301 Medium,
303 High,
305 Critical,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct BenchmarkResults {
312 pub config: BenchmarkConfig,
314 pub algorithmresults: HashMap<String, AlgorithmBenchmark>,
316 pub comparisons: Vec<AlgorithmComparison>,
318 pub system_info: SystemInfo,
320 pub timestamp: std::time::SystemTime,
322 pub total_duration: Duration,
324 pub regression_alerts: Vec<RegressionAlert>,
326 pub recommendations: Vec<String>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct AlgorithmComparison {
333 pub algorithm_a: String,
335 pub algorithm_b: String,
337 pub performance_difference: f64,
339 pub significance: f64,
341 pub winner: String,
343 pub quality_difference: f64,
345 pub memory_difference: f64,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct RegressionAlert {
352 pub algorithm: String,
354 pub degradation_percent: f64,
356 pub severity: RegressionSeverity,
358 pub description: String,
360 pub suggested_actions: Vec<String>,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366pub enum RegressionSeverity {
367 Minor,
369 Moderate,
371 Major,
373 Critical,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct SystemInfo {
380 pub cpu_info: String,
382 pub total_memory_gb: f64,
384 pub available_memory_gb: f64,
386 pub os: String,
388 pub rust_version: String,
390 pub optimizations: String,
392 pub gpu_info: Option<String>,
394 pub cpu_cores: usize,
396 pub cpu_frequency_mhz: Option<u32>,
398}
399
400#[allow(dead_code)]
402pub struct AdvancedBenchmark {
403 config: BenchmarkConfig,
404 memory_tracker: Arc<AtomicUsize>,
405}
406
407impl AdvancedBenchmark {
408 pub fn new(config: BenchmarkConfig) -> Self {
410 Self {
411 config,
412 memory_tracker: Arc::new(AtomicUsize::new(0)),
413 }
414 }
415
416 pub fn comprehensive_analysis(&self, data: &ArrayView2<f64>) -> Result<BenchmarkResults> {
418 let start_time = Instant::now();
419 let mut algorithmresults = HashMap::new();
420 let mut regression_alerts = Vec::new();
421
422 let algorithms = self.get_algorithms_to_benchmark();
424
425 for algorithm_name in algorithms {
426 match self.benchmark_algorithm(algorithm_name, data) {
427 Ok(result) => {
428 if self.config.regression_detection {
430 if let Some(alert) = self.detect_regression(algorithm_name, &result) {
431 regression_alerts.push(alert);
432 }
433 }
434 algorithmresults.insert(algorithm_name.to_string(), result);
435 }
436 Err(e) => {
437 eprintln!("Failed to benchmark {}: {}", algorithm_name, e);
438 }
439 }
440 }
441
442 let comparisons = self.generate_comparisons(&algorithmresults)?;
444
445 let system_info = self.collect_system_info();
447
448 let recommendations = self.generate_recommendations(&algorithmresults);
450
451 Ok(BenchmarkResults {
452 config: self.config.clone(),
453 algorithmresults,
454 comparisons,
455 system_info,
456 timestamp: std::time::SystemTime::now(),
457 total_duration: start_time.elapsed(),
458 regression_alerts,
459 recommendations,
460 })
461 }
462
463 fn benchmark_algorithm(
465 &self,
466 algorithm: &str,
467 data: &ArrayView2<f64>,
468 ) -> Result<AlgorithmBenchmark> {
469 let mut execution_times = Vec::new();
470 let mut memory_profiles = Vec::new();
471 let mut error_count = 0;
472 let total_iterations = self.config.warmup_iterations + self.config.measurement_iterations;
473
474 for _ in 0..self.config.warmup_iterations {
476 if self.run_algorithm_once(algorithm, data).is_err() {
477 error_count += 1;
478 }
479 }
480
481 for _ in 0..self.config.measurement_iterations {
483 let start_memory = self.get_memory_usage();
484 let start_time = Instant::now();
485
486 match self.run_algorithm_once(algorithm, data) {
487 Ok(_) => {
488 let duration = start_time.elapsed();
489 execution_times.push(duration);
490
491 if self.config.memory_profiling {
492 let end_memory = self.get_memory_usage();
493 memory_profiles.push(end_memory.saturating_sub(start_memory));
494 }
495 }
496 Err(_) => {
497 error_count += 1;
498 }
499 }
500 }
501
502 if execution_times.is_empty() {
503 return Err(ClusteringError::ComputationError(format!(
504 "All iterations failed for algorithm: {}",
505 algorithm
506 )));
507 }
508
509 let performance = self.calculate_performance_statistics(&execution_times)?;
511
512 let memory = if self.config.memory_profiling && !memory_profiles.is_empty() {
514 Some(self.calculate_memory_profile(&memory_profiles))
515 } else {
516 None
517 };
518
519 let gpu_comparison = if self.config.gpu_comparison {
521 self.perform_gpu_comparison(algorithm, data).ok()
522 } else {
523 None
524 };
525
526 let quality_metrics = self.calculate_quality_metrics(algorithm, data)?;
528
529 let scalability = if self.config.stress_testing {
531 Some(self.perform_scalability_analysis(algorithm, data)?)
532 } else {
533 None
534 };
535
536 let optimization_suggestions = self.generate_optimization_suggestions(
538 algorithm,
539 &performance,
540 &memory,
541 &quality_metrics,
542 );
543
544 let error_rate = error_count as f64 / total_iterations as f64;
545
546 Ok(AlgorithmBenchmark {
547 algorithm: algorithm.to_string(),
548 performance,
549 memory,
550 gpu_comparison,
551 quality_metrics,
552 scalability,
553 optimization_suggestions,
554 error_rate,
555 })
556 }
557
558 fn run_algorithm_once(&self, algorithm: &str, data: &ArrayView2<f64>) -> Result<()> {
560 match algorithm {
561 "kmeans" => {
562 let _result = kmeans(*data, 3, Some(10), None, None, None)?;
563 }
564 "kmeans2" => {
565 let _result = kmeans2(data.view(), 3, None, None, None, None, None, None)?;
566 }
567 "hierarchical_ward" => {
568 let _result = linkage(*data, LinkageMethod::Ward, Metric::Euclidean)?;
569 }
570 "dbscan" => {
571 let _result = dbscan(*data, 0.5, 5, None)?;
572 }
573 "gmm" => {
574 let mut options = GMMOptions::default();
575 options.n_components = 3;
576 let _result = gaussian_mixture(*data, options)?;
577 }
578 _ => {
579 return Err(ClusteringError::ComputationError(format!(
580 "Unknown algorithm: {}",
581 algorithm
582 )));
583 }
584 }
585 Ok(())
586 }
587
588 fn get_algorithms_to_benchmark(&self) -> Vec<&'static str> {
590 vec!["kmeans", "kmeans2", "hierarchical_ward", "dbscan", "gmm"]
591 }
592
593 fn calculate_performance_statistics(
595 &self,
596 times: &[Duration],
597 ) -> Result<PerformanceStatistics> {
598 if times.is_empty() {
599 return Err(ClusteringError::ComputationError(
600 "No execution times to analyze".to_string(),
601 ));
602 }
603
604 let mut sorted_times = times.to_vec();
605 sorted_times.sort();
606
607 let mean_nanos = times.iter().map(|d| d.as_nanos()).sum::<u128>() / times.len() as u128;
608 let mean = Duration::from_nanos(mean_nanos as u64);
609
610 let variance = times
611 .iter()
612 .map(|d| {
613 let diff = d.as_nanos() as i128 - mean_nanos as i128;
614 (diff * diff) as u128
615 })
616 .sum::<u128>()
617 / times.len() as u128;
618
619 let std_dev = Duration::from_nanos((variance as f64).sqrt() as u64);
620
621 let min = sorted_times[0];
622 let max = sorted_times[sorted_times.len() - 1];
623 let median = sorted_times[sorted_times.len() / 2];
624 let percentile_95 = sorted_times[(sorted_times.len() as f64 * 0.95) as usize];
625 let percentile_99 = sorted_times[(sorted_times.len() as f64 * 0.99) as usize];
626
627 let coefficient_of_variation = if mean.as_nanos() > 0 {
628 std_dev.as_nanos() as f64 / mean.as_nanos() as f64
629 } else {
630 0.0
631 };
632
633 let margin = std_dev.as_nanos() as f64 * 1.96 / (times.len() as f64).sqrt();
635 let confidence_interval = (
636 Duration::from_nanos((mean.as_nanos() as f64 - margin) as u64),
637 Duration::from_nanos((mean.as_nanos() as f64 + margin) as u64),
638 );
639
640 let is_stable = coefficient_of_variation < 0.1; let outlier_threshold = 2.0 * std_dev.as_nanos() as f64;
644 let outliers = times
645 .iter()
646 .filter(|&d| {
647 let diff = (d.as_nanos() as f64 - mean.as_nanos() as f64).abs();
648 diff > outlier_threshold
649 })
650 .count();
651
652 let throughput = if mean.as_secs_f64() > 0.0 {
653 1.0 / mean.as_secs_f64()
654 } else {
655 0.0
656 };
657
658 Ok(PerformanceStatistics {
659 mean,
660 std_dev,
661 min,
662 max,
663 median,
664 percentile_95,
665 percentile_99,
666 coefficient_of_variation,
667 confidence_interval,
668 is_stable,
669 outliers,
670 throughput,
671 })
672 }
673
674 fn calculate_memory_profile(&self, memorysamples: &[usize]) -> MemoryProfile {
676 if memorysamples.is_empty() {
677 return MemoryProfile {
678 peak_memory_mb: 0.0,
679 average_memory_mb: 0.0,
680 allocation_rate: 0.0,
681 deallocation_rate: 0.0,
682 gc_events: 0,
683 efficiency_score: 0.0,
684 potential_leak: false,
685 };
686 }
687
688 let peak_memory_mb =
689 *memorysamples.iter().max().expect("Operation failed") as f64 / 1_048_576.0;
690 let average_memory_mb =
691 memorysamples.iter().sum::<usize>() as f64 / (memorysamples.len() as f64 * 1_048_576.0);
692
693 let mut total_increase_bytes: u128 = 0;
698 let mut total_decrease_bytes: u128 = 0;
699 for window in memorysamples.windows(2) {
700 if window[1] >= window[0] {
701 total_increase_bytes += (window[1] - window[0]) as u128;
702 } else {
703 total_decrease_bytes += (window[0] - window[1]) as u128;
704 }
705 }
706 let n_transitions = memorysamples.len().saturating_sub(1).max(1) as f64;
707 let allocation_rate = (total_increase_bytes as f64) / (1_048_576.0 * n_transitions);
708 let deallocation_rate = (total_decrease_bytes as f64) / (1_048_576.0 * n_transitions);
709
710 let gc_events = 0;
712
713 let efficiency_score = if allocation_rate > 0.0 {
716 (deallocation_rate / allocation_rate * 100.0).min(100.0)
717 } else {
718 100.0
719 };
720
721 let potential_leak = allocation_rate > 0.0 && allocation_rate > deallocation_rate * 1.1;
723
724 MemoryProfile {
725 peak_memory_mb,
726 average_memory_mb,
727 allocation_rate,
728 deallocation_rate,
729 gc_events,
730 efficiency_score,
731 potential_leak,
732 }
733 }
734
735 fn get_memory_usage(&self) -> usize {
741 if let Some(rss) = current_rss_bytes() {
742 self.memory_tracker.store(rss, Ordering::Relaxed);
744 return rss;
745 }
746 self.memory_tracker.load(Ordering::Relaxed)
749 }
750
751 fn perform_gpu_comparison(
759 &self,
760 algorithm: &str,
761 data: &ArrayView2<f64>,
762 ) -> Result<GpuVsCpuComparison> {
763 let cpu_start = Instant::now();
765 self.run_algorithm_once(algorithm, data)?;
766 let _cpu_time = cpu_start.elapsed();
767
768 Err(ClusteringError::ComputationError(format!(
769 "GPU vs CPU comparison for '{algorithm}' is unavailable: no GPU runtime is bound \
770 into this build. The CPU side was measured, but reporting GPU timings would require \
771 a real accelerator backend. Enable a GPU feature/backend to obtain a real comparison."
772 )))
773 }
774
775 fn calculate_quality_metrics(
777 &self,
778 algorithm: &str,
779 data: &ArrayView2<f64>,
780 ) -> Result<QualityMetrics> {
781 let (labels, n_clusters, inertia, convergence_iterations) = match algorithm {
783 "kmeans" => {
784 let (centroids, _distortion) = kmeans(data.view(), 3, Some(10), None, None, None)?;
785 let (labels, _distances) = vq(data.view(), centroids.view())?;
786 (labels.mapv(|x| x as i32), centroids.nrows(), None, Some(10))
787 }
788 "dbscan" => {
789 let (labels_) = dbscan(*data, 0.5, 5, None)?;
790 let n_clusters = labels_
791 .iter()
792 .filter(|&&x| x >= 0)
793 .copied()
794 .max()
795 .unwrap_or(-1) as usize
796 + 1;
797 (labels_, n_clusters, None, None)
798 }
799 _ => {
800 let (centroids, _distortion) = kmeans(data.view(), 3, Some(10), None, None, None)?;
802 let (labels, _distances) = vq(data.view(), centroids.view())?;
803 (labels.mapv(|x| x as i32), centroids.nrows(), None, Some(10))
804 }
805 };
806
807 let silhouette_score = if n_clusters > 1 && n_clusters < data.nrows() {
809 silhouette_score(*data, labels.view()).ok()
810 } else {
811 None
812 };
813
814 let calinski_harabasz = if n_clusters > 1 && n_clusters < data.nrows() {
815 calinski_harabasz_score(*data, labels.view()).ok()
816 } else {
817 None
818 };
819
820 Ok(QualityMetrics {
821 silhouette_score,
822 calinski_harabasz,
823 davies_bouldin: None, inertia,
825 n_clusters,
826 convergence_iterations,
827 })
828 }
829
830 fn perform_scalability_analysis(
832 &self,
833 algorithm: &str,
834 base_data: &ArrayView2<f64>,
835 ) -> Result<ScalabilityAnalysis> {
836 let sizes = vec![100, 250, 500, 1000, 2000];
837 let mut size_to_time = Vec::new();
838
839 for &size in &sizes {
840 if size > base_data.nrows() {
841 continue; }
843
844 let subset = base_data.slice(scirs2_core::ndarray::s![0..size, ..]);
845 let start_time = Instant::now();
846
847 if self.run_algorithm_once(algorithm, &subset).is_ok() {
848 let duration = start_time.elapsed();
849 size_to_time.push((size, duration));
850 }
851 }
852
853 let complexity_estimate = self.estimate_complexity(&size_to_time);
855
856 let scalability_predictions = self.predict_scalability(&size_to_time, &complexity_estimate);
858
859 let memory_scaling = 1.0; let optimal_size_range = (500, 10000); Ok(ScalabilityAnalysis {
866 size_to_time,
867 complexity_estimate,
868 scalability_predictions,
869 memory_scaling,
870 optimal_size_range,
871 })
872 }
873
874 fn estimate_complexity(&self, timings: &[(usize, Duration)]) -> ComplexityClass {
876 if timings.len() < 3 {
877 return ComplexityClass::Unknown;
878 }
879
880 let ratios: Vec<f64> = timings
882 .windows(2)
883 .map(|pair| {
884 let (size1, time1) = pair[0];
885 let (size2, time2) = pair[1];
886 let size_ratio = size2 as f64 / size1 as f64;
887 let time_ratio = time2.as_secs_f64() / time1.as_secs_f64();
888 time_ratio / size_ratio
889 })
890 .collect();
891
892 let avg_ratio = ratios.iter().sum::<f64>() / ratios.len() as f64;
893
894 if avg_ratio < 1.2 {
895 ComplexityClass::Linear
896 } else if avg_ratio < 1.8 {
897 ComplexityClass::Linearithmic
898 } else if avg_ratio < 3.0 {
899 ComplexityClass::Quadratic
900 } else if avg_ratio < 5.0 {
901 ComplexityClass::Cubic
902 } else {
903 ComplexityClass::Unknown
904 }
905 }
906
907 fn predict_scalability(
909 &self,
910 timings: &[(usize, Duration)],
911 complexity: &ComplexityClass,
912 ) -> Vec<(usize, Duration)> {
913 if timings.is_empty() {
914 return Vec::new();
915 }
916
917 let (base_size, base_time) = timings[timings.len() - 1];
918 let prediction_sizes = vec![5000, 10000, 20000, 50000];
919
920 prediction_sizes
921 .into_iter()
922 .map(|size| {
923 let size_factor = size as f64 / base_size as f64;
924 let time_factor = match complexity {
925 ComplexityClass::Linear => size_factor,
926 ComplexityClass::Linearithmic => size_factor * size_factor.log2(),
927 ComplexityClass::Quadratic => size_factor * size_factor,
928 ComplexityClass::Cubic => size_factor * size_factor * size_factor,
929 ComplexityClass::Unknown => size_factor * size_factor, };
931
932 let predicted_time = Duration::from_secs_f64(base_time.as_secs_f64() * time_factor);
933 (size, predicted_time)
934 })
935 .collect()
936 }
937
938 fn generate_optimization_suggestions(
940 &self,
941 algorithm: &str,
942 performance: &PerformanceStatistics,
943 memory: &Option<MemoryProfile>,
944 quality: &QualityMetrics,
945 ) -> Vec<OptimizationSuggestion> {
946 let mut suggestions = Vec::new();
947
948 if performance.coefficient_of_variation > 0.2 {
950 suggestions.push(OptimizationSuggestion {
951 category: OptimizationCategory::ParameterTuning,
952 suggestion: "High variance in execution times detected. Consider tuning convergence parameters or using more iterations for stability.".to_string(),
953 expected_improvement: 15.0,
954 difficulty: 3,
955 priority: OptimizationPriority::Medium,
956 });
957 }
958
959 if performance.throughput < 1.0 {
960 suggestions.push(OptimizationSuggestion {
961 category: OptimizationCategory::Parallelization,
962 suggestion: "Low throughput detected. Consider using parallel implementations or multi-threading.".to_string(),
963 expected_improvement: 200.0,
964 difficulty: 6,
965 priority: OptimizationPriority::High,
966 });
967 }
968
969 if let Some(mem) = memory {
971 if mem.potential_leak {
972 suggestions.push(OptimizationSuggestion {
973 category: OptimizationCategory::MemoryOptimization,
974 suggestion:
975 "Potential memory leak detected. Review memory allocation patterns."
976 .to_string(),
977 expected_improvement: 25.0,
978 difficulty: 8,
979 priority: OptimizationPriority::Critical,
980 });
981 }
982
983 if mem.efficiency_score < 50.0 {
984 suggestions.push(OptimizationSuggestion {
985 category: OptimizationCategory::MemoryOptimization,
986 suggestion: "Low memory efficiency. Consider using in-place operations or memory pooling.".to_string(),
987 expected_improvement: 30.0,
988 difficulty: 5,
989 priority: OptimizationPriority::High,
990 });
991 }
992 }
993
994 match algorithm {
996 "kmeans" => {
997 if let Some(silhouette) = quality.silhouette_score {
998 if silhouette < 0.3 {
999 suggestions.push(OptimizationSuggestion {
1000 category: OptimizationCategory::AlgorithmChange,
1001 suggestion: "Low silhouette score suggests poor cluster quality. Consider using DBSCAN or increasing k value.".to_string(),
1002 expected_improvement: 50.0,
1003 difficulty: 4,
1004 priority: OptimizationPriority::Medium,
1005 });
1006 }
1007 }
1008 }
1009 "dbscan" => {
1010 suggestions.push(OptimizationSuggestion {
1011 category: OptimizationCategory::ParameterTuning,
1012 suggestion: "DBSCAN performance highly depends on eps and min_samples parameters. Consider using auto-tuning.".to_string(),
1013 expected_improvement: 40.0,
1014 difficulty: 3,
1015 priority: OptimizationPriority::Medium,
1016 });
1017 }
1018 _ => {}
1019 }
1020
1021 if performance.mean > Duration::from_millis(100) {
1023 suggestions.push(OptimizationSuggestion {
1024 category: OptimizationCategory::GpuAcceleration,
1025 suggestion:
1026 "Algorithm runtime suggests GPU acceleration could provide significant speedup."
1027 .to_string(),
1028 expected_improvement: 300.0,
1029 difficulty: 7,
1030 priority: OptimizationPriority::High,
1031 });
1032 }
1033
1034 suggestions
1035 }
1036
1037 fn detect_regression(
1039 &self,
1040 algorithm: &str,
1041 result: &AlgorithmBenchmark,
1042 ) -> Option<RegressionAlert> {
1043 if result.error_rate > 0.1 {
1050 return Some(RegressionAlert {
1051 algorithm: algorithm.to_string(),
1052 degradation_percent: result.error_rate * 100.0,
1053 severity: if result.error_rate > 0.5 {
1054 RegressionSeverity::Critical
1055 } else if result.error_rate > 0.25 {
1056 RegressionSeverity::Major
1057 } else {
1058 RegressionSeverity::Moderate
1059 },
1060 description: format!(
1061 "High error rate detected: {:.1}%",
1062 result.error_rate * 100.0
1063 ),
1064 suggested_actions: vec![
1065 "Check input data quality".to_string(),
1066 "Verify algorithm parameters".to_string(),
1067 "Review recent code changes".to_string(),
1068 ],
1069 });
1070 }
1071
1072 if !result.performance.is_stable {
1073 return Some(RegressionAlert {
1074 algorithm: algorithm.to_string(),
1075 degradation_percent: result.performance.coefficient_of_variation * 100.0,
1076 severity: RegressionSeverity::Minor,
1077 description: "Performance instability detected".to_string(),
1078 suggested_actions: vec![
1079 "Increase measurement iterations".to_string(),
1080 "Check for system load during benchmarking".to_string(),
1081 ],
1082 });
1083 }
1084
1085 None
1086 }
1087
1088 fn generate_comparisons(
1090 &self,
1091 results: &HashMap<String, AlgorithmBenchmark>,
1092 ) -> Result<Vec<AlgorithmComparison>> {
1093 let mut comparisons = Vec::new();
1094 let algorithms: Vec<&String> = results.keys().collect();
1095
1096 for i in 0..algorithms.len() {
1097 for j in (i + 1)..algorithms.len() {
1098 let algo_a = algorithms[i];
1099 let algo_b = algorithms[j];
1100 let result_a = &results[algo_a];
1101 let result_b = &results[algo_b];
1102
1103 let performance_difference = (result_b.performance.mean.as_secs_f64()
1104 - result_a.performance.mean.as_secs_f64())
1105 / result_a.performance.mean.as_secs_f64()
1106 * 100.0;
1107
1108 let winner = if performance_difference < 0.0 {
1109 algo_b.clone()
1110 } else {
1111 algo_a.clone()
1112 };
1113
1114 let quality_a = result_a.quality_metrics.silhouette_score.unwrap_or(0.0);
1116 let quality_b = result_b.quality_metrics.silhouette_score.unwrap_or(0.0);
1117 let quality_difference = quality_b - quality_a;
1118
1119 let memory_a = result_a
1121 .memory
1122 .as_ref()
1123 .map(|m| m.peak_memory_mb)
1124 .unwrap_or(0.0);
1125 let memory_b = result_b
1126 .memory
1127 .as_ref()
1128 .map(|m| m.peak_memory_mb)
1129 .unwrap_or(0.0);
1130 let memory_difference = memory_b - memory_a;
1131
1132 let significance = if performance_difference.abs() > 10.0 {
1134 0.01
1135 } else {
1136 0.1
1137 };
1138
1139 comparisons.push(AlgorithmComparison {
1140 algorithm_a: algo_a.clone(),
1141 algorithm_b: algo_b.clone(),
1142 performance_difference,
1143 significance,
1144 winner,
1145 quality_difference,
1146 memory_difference,
1147 });
1148 }
1149 }
1150
1151 Ok(comparisons)
1152 }
1153
1154 fn collect_system_info(&self) -> SystemInfo {
1156 SystemInfo {
1157 cpu_info: "Unknown CPU".to_string(), total_memory_gb: 16.0, available_memory_gb: 8.0, os: std::env::consts::OS.to_string(),
1161 rust_version: env!("CARGO_PKG_RUST_VERSION").to_string(),
1162 optimizations: if cfg!(debug_assertions) {
1163 "Debug"
1164 } else {
1165 "Release"
1166 }
1167 .to_string(),
1168 gpu_info: None, cpu_cores: num_cpus::get(),
1170 cpu_frequency_mhz: None,
1171 }
1172 }
1173
1174 fn generate_recommendations(
1176 &self,
1177 results: &HashMap<String, AlgorithmBenchmark>,
1178 ) -> Vec<String> {
1179 let mut recommendations = Vec::new();
1180
1181 let best_algo = results
1183 .iter()
1184 .min_by(|a, b| a.1.performance.mean.cmp(&b.1.performance.mean))
1185 .map(|(name, _)| name);
1186
1187 if let Some(best) = best_algo {
1188 recommendations.push(format!("Best performing algorithm: {}", best));
1189 }
1190
1191 let high_error_algos: Vec<&str> = results
1193 .iter()
1194 .filter(|(_, result)| result.error_rate > 0.05)
1195 .map(|(name_, _)| name_.as_str())
1196 .collect();
1197
1198 if !high_error_algos.is_empty() {
1199 recommendations.push(format!(
1200 "Algorithms with high error rates: {:?}",
1201 high_error_algos
1202 ));
1203 }
1204
1205 let memory_inefficient: Vec<&str> = results
1207 .iter()
1208 .filter(|(_, result)| {
1209 result
1210 .memory
1211 .as_ref()
1212 .map(|m| m.efficiency_score < 60.0)
1213 .unwrap_or(false)
1214 })
1215 .map(|(name_, _)| name_.as_str())
1216 .collect();
1217
1218 if !memory_inefficient.is_empty() {
1219 recommendations.push("Consider memory optimization for better efficiency".to_string());
1220 }
1221
1222 recommendations
1223 }
1224}
1225
1226#[allow(dead_code)]
1228pub fn create_comprehensive_report(results: &BenchmarkResults, outputpath: &str) -> Result<()> {
1229 let html_content = generate_html_report(results);
1230
1231 std::fs::write(outputpath, html_content)
1232 .map_err(|e| ClusteringError::ComputationError(format!("Failed to write report: {}", e)))?;
1233
1234 Ok(())
1235}
1236
1237#[allow(dead_code)]
1239fn generate_html_report(results: &BenchmarkResults) -> String {
1240 format!(
1241 r#"
1242<!DOCTYPE html>
1243<html>
1244<head>
1245 <title>Advanced Clustering Benchmark Report</title>
1246 <style>
1247 body {{ font-family: Arial, sans-serif; margin: 20px; }}
1248 .header {{ background: #f0f0f0; padding: 20px; border-radius: 8px; }}
1249 .section {{ margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }}
1250 .algorithm {{ margin: 10px 0; padding: 10px; background: #f9f9f9; }}
1251 .metric {{ display: inline-block; margin: 5px 10px; }}
1252 .warning {{ color: #ff6600; font-weight: bold; }}
1253 .error {{ color: #cc0000; font-weight: bold; }}
1254 .success {{ color: #00aa00; font-weight: bold; }}
1255 table {{ border-collapse: collapse; width: 100%; }}
1256 th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
1257 th {{ background-color: #f2f2f2; }}
1258 </style>
1259</head>
1260<body>
1261 <div class="header">
1262 <h1>Advanced Clustering Benchmark Report</h1>
1263 <p>Generated: {:?}</p>
1264 <p>Total Duration: {:.2?}</p>
1265 <p>System: {} on {}</p>
1266 </div>
1267
1268 <div class="section">
1269 <h2>Performance Summary</h2>
1270 <table>
1271 <tr>
1272 <th>Algorithm</th>
1273 <th>Mean Time</th>
1274 <th>Std Dev</th>
1275 <th>Throughput (ops/sec)</th>
1276 <th>Error Rate</th>
1277 <th>Quality Score</th>
1278 </tr>
1279 {}
1280 </table>
1281 </div>
1282
1283 <div class="section">
1284 <h2>Regression Alerts</h2>
1285 {}
1286 </div>
1287
1288 <div class="section">
1289 <h2>Recommendations</h2>
1290 <ul>
1291 {}
1292 </ul>
1293 </div>
1294
1295 <div class="section">
1296 <h2>System Information</h2>
1297 <p><strong>OS:</strong> {}</p>
1298 <p><strong>CPU Cores:</strong> {}</p>
1299 <p><strong>Total Memory:</strong> {:.1} GB</p>
1300 <p><strong>Rust Version:</strong> {}</p>
1301 <p><strong>Build Mode:</strong> {}</p>
1302 </div>
1303</body>
1304</html>
1305"#,
1306 results.timestamp,
1307 results.total_duration,
1308 results.system_info.os,
1309 results.system_info.cpu_cores,
1310 generate_performance_table(results),
1311 generate_regression_alerts_html(results),
1312 generate_recommendations_html(results),
1313 results.system_info.os,
1314 results.system_info.cpu_cores,
1315 results.system_info.total_memory_gb,
1316 results.system_info.rust_version,
1317 results.system_info.optimizations,
1318 )
1319}
1320
1321#[allow(dead_code)]
1323fn generate_performance_table(results: &BenchmarkResults) -> String {
1324 results.algorithmresults.iter()
1325 .map(|(name, result)| {
1326 let quality = result.quality_metrics.silhouette_score
1327 .map(|s| format!("{:.3}", s))
1328 .unwrap_or_else(|| "N/A".to_string());
1329 format!(
1330 "<tr><td>{}</td><td>{:.2?}</td><td>{:.2?}</td><td>{:.2}</td><td>{:.2}%</td><td>{}</td></tr>",
1331 name,
1332 result.performance.mean,
1333 result.performance.std_dev,
1334 result.performance.throughput,
1335 result.error_rate * 100.0,
1336 quality
1337 )
1338 })
1339 .collect::<Vec<_>>()
1340 .join("\n")
1341}
1342
1343#[allow(dead_code)]
1345fn generate_regression_alerts_html(results: &BenchmarkResults) -> String {
1346 if results.regression_alerts.is_empty() {
1347 "<p class=\"success\">No performance regressions detected.</p>".to_string()
1348 } else {
1349 results
1350 .regression_alerts
1351 .iter()
1352 .map(|alert| {
1353 let class = match alert.severity {
1354 RegressionSeverity::Critical => "error",
1355 RegressionSeverity::Major => "error",
1356 RegressionSeverity::Moderate => "warning",
1357 RegressionSeverity::Minor => "warning",
1358 };
1359 format!(
1360 "<div class=\"{}\"><strong>{}:</strong> {} ({:.1}% degradation)</div>",
1361 class, alert.algorithm, alert.description, alert.degradation_percent
1362 )
1363 })
1364 .collect::<Vec<_>>()
1365 .join("\n")
1366 }
1367}
1368
1369#[allow(dead_code)]
1371fn generate_recommendations_html(results: &BenchmarkResults) -> String {
1372 results
1373 .recommendations
1374 .iter()
1375 .map(|rec| format!("<li>{}</li>", rec))
1376 .collect::<Vec<_>>()
1377 .join("\n")
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382 use super::*;
1383 use scirs2_core::ndarray::Array2;
1384
1385 #[test]
1386 fn test_benchmark_config_default() {
1387 let config = BenchmarkConfig::default();
1388 assert_eq!(config.warmup_iterations, 5);
1389 assert_eq!(config.measurement_iterations, 50);
1390 assert!(config.memory_profiling);
1391 }
1392
1393 #[test]
1394 fn test_performance_statistics_calculation() {
1395 let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1396 let times = vec![
1397 Duration::from_millis(100),
1398 Duration::from_millis(105),
1399 Duration::from_millis(95),
1400 Duration::from_millis(110),
1401 Duration::from_millis(98),
1402 ];
1403
1404 let stats = benchmark
1405 .calculate_performance_statistics(×)
1406 .expect("Operation failed");
1407 assert!(stats.mean.as_millis() > 90 && stats.mean.as_millis() < 120);
1408 assert!(stats.throughput > 0.0);
1409 assert!(!stats.is_stable || stats.coefficient_of_variation < 0.1);
1410 }
1411
1412 #[test]
1413 fn test_complexity_estimation() {
1414 let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1415
1416 let linear_timings = vec![
1418 (100, Duration::from_millis(10)),
1419 (200, Duration::from_millis(20)),
1420 (400, Duration::from_millis(40)),
1421 ];
1422 assert_eq!(
1423 benchmark.estimate_complexity(&linear_timings),
1424 ComplexityClass::Linear
1425 );
1426
1427 let quadratic_timings = vec![
1429 (100, Duration::from_millis(10)),
1430 (200, Duration::from_millis(40)),
1431 (400, Duration::from_millis(160)),
1432 ];
1433 assert_eq!(
1434 benchmark.estimate_complexity(&quadratic_timings),
1435 ComplexityClass::Quadratic
1436 );
1437 }
1438
1439 #[test]
1440 fn test_advanced_benchmark_creation() {
1441 let config = BenchmarkConfig {
1442 warmup_iterations: 2,
1443 measurement_iterations: 5,
1444 ..Default::default()
1445 };
1446
1447 let benchmark = AdvancedBenchmark::new(config.clone());
1448 assert_eq!(benchmark.config.warmup_iterations, 2);
1449 assert_eq!(benchmark.config.measurement_iterations, 5);
1450 }
1451
1452 #[test]
1453 fn test_optimization_suggestions() {
1454 let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1455
1456 let performance = PerformanceStatistics {
1457 mean: Duration::from_millis(1000), coefficient_of_variation: 0.3, throughput: 0.5, is_stable: false,
1461 ..Default::default()
1462 };
1463
1464 let memory = Some(MemoryProfile {
1465 efficiency_score: 30.0, potential_leak: true,
1467 ..Default::default()
1468 });
1469
1470 let quality = QualityMetrics {
1471 silhouette_score: Some(0.2), n_clusters: 3,
1473 ..Default::default()
1474 };
1475
1476 let suggestions =
1477 benchmark.generate_optimization_suggestions("kmeans", &performance, &memory, &quality);
1478
1479 assert!(!suggestions.is_empty());
1480 assert!(suggestions
1481 .iter()
1482 .any(|s| s.category == OptimizationCategory::MemoryOptimization));
1483 assert!(suggestions
1484 .iter()
1485 .any(|s| s.priority == OptimizationPriority::Critical));
1486 }
1487}
1488
1489impl Default for PerformanceStatistics {
1491 fn default() -> Self {
1492 Self {
1493 mean: Duration::from_millis(100),
1494 std_dev: Duration::from_millis(10),
1495 min: Duration::from_millis(90),
1496 max: Duration::from_millis(120),
1497 median: Duration::from_millis(100),
1498 percentile_95: Duration::from_millis(115),
1499 percentile_99: Duration::from_millis(118),
1500 coefficient_of_variation: 0.1,
1501 confidence_interval: (Duration::from_millis(95), Duration::from_millis(105)),
1502 is_stable: true,
1503 outliers: 0,
1504 throughput: 10.0,
1505 }
1506 }
1507}
1508
1509impl Default for MemoryProfile {
1510 fn default() -> Self {
1511 Self {
1512 peak_memory_mb: 100.0,
1513 average_memory_mb: 80.0,
1514 allocation_rate: 10.0,
1515 deallocation_rate: 9.5,
1516 gc_events: 0,
1517 efficiency_score: 85.0,
1518 potential_leak: false,
1519 }
1520 }
1521}
1522
1523impl Default for QualityMetrics {
1524 fn default() -> Self {
1525 Self {
1526 silhouette_score: Some(0.5),
1527 calinski_harabasz: Some(100.0),
1528 davies_bouldin: Some(1.0),
1529 inertia: Some(50.0),
1530 n_clusters: 3,
1531 convergence_iterations: Some(10),
1532 }
1533 }
1534}