1#![allow(dead_code)]
10
11use anyhow::Result;
12use std::collections::{HashMap, VecDeque};
13
14use super::types::{
15 ActivationHeatmap, ClusteringResults, DriftInfo, HiddenStateAnalysis, LayerActivationStats,
16 ModelPerformanceMetrics, RepresentationStability, TemporalDynamics,
17};
18
19#[derive(Debug)]
21pub struct AdvancedAnalytics {
22 config: AnalyticsConfig,
24 hidden_states_history: VecDeque<HiddenStateData>,
26 performance_correlations: HashMap<String, CorrelationData>,
28 temporal_analysis_cache: TemporalAnalysisCache,
30 clustering_results_cache: HashMap<String, ClusteringResults>,
32}
33
34#[derive(Debug, Clone)]
36pub struct AnalyticsConfig {
37 pub max_history_samples: usize,
39 pub min_clustering_samples: usize,
41 pub default_num_clusters: usize,
43 pub temporal_analysis_window: usize,
45 pub drift_detection_sensitivity: f64,
47 pub correlation_threshold: f64,
49 pub enable_visualizations: bool,
51}
52
53#[derive(Debug, Clone)]
55pub struct HiddenStateData {
56 pub layer_name: String,
58 pub hidden_states: Vec<Vec<f64>>,
60 pub labels: Option<Vec<String>>,
62 pub timestamp: chrono::DateTime<chrono::Utc>,
64 pub training_step: usize,
66}
67
68#[derive(Debug, Clone)]
70pub struct CorrelationData {
71 pub metric_name: String,
73 pub values: VecDeque<f64>,
75 pub correlations: HashMap<String, f64>,
77 pub last_updated: chrono::DateTime<chrono::Utc>,
79}
80
81#[derive(Debug, Clone)]
83pub struct TemporalAnalysisCache {
84 pub drift_results: HashMap<String, DriftInfo>,
86 pub consistency_scores: HashMap<String, f64>,
88 pub stability_windows: HashMap<String, Vec<(usize, usize)>>,
90 pub last_analysis: chrono::DateTime<chrono::Utc>,
92}
93
94#[derive(Debug, Clone)]
96pub struct ClusteringParameters {
97 pub num_clusters: usize,
99 pub max_iterations: usize,
101 pub tolerance: f64,
103 pub random_seed: Option<u64>,
105 pub distance_metric: DistanceMetric,
107}
108
109#[derive(Debug, Clone)]
111pub enum DistanceMetric {
112 Euclidean,
114 Manhattan,
116 Cosine,
118 Minkowski { p: f64 },
120}
121
122#[derive(Debug, Clone)]
124pub struct DimensionalityReductionParams {
125 pub target_dimensions: usize,
127 pub method: ReductionMethod,
129 pub preserve_variance_ratio: f64,
131}
132
133#[derive(Debug, Clone)]
135pub enum ReductionMethod {
136 PCA,
138 TSNE { perplexity: f64 },
140 UMAP { n_neighbors: usize, min_dist: f64 },
142}
143
144#[derive(Debug, Clone)]
146pub struct VisualizationParams {
147 pub dimensions: (usize, usize),
149 pub color_scheme: ColorScheme,
151 pub include_annotations: bool,
153 pub export_format: ExportFormat,
155}
156
157#[derive(Debug, Clone)]
159pub enum ColorScheme {
160 Viridis,
162 Plasma,
164 Inferno,
166 Custom(Vec<(f64, f64, f64)>),
168}
169
170#[derive(Debug, Clone)]
172pub enum ExportFormat {
173 PNG,
175 SVG,
177 JSON,
179 CSV,
181}
182
183#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
185pub struct StatisticalAnalysis {
186 pub means: Vec<f64>,
188 pub std_devs: Vec<f64>,
190 pub correlation_matrix: Vec<Vec<f64>>,
192 pub principal_components: Vec<Vec<f64>>,
194 pub explained_variance_ratios: Vec<f64>,
196 pub significance_tests: Vec<SignificanceTest>,
198}
199
200#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
202pub struct SignificanceTest {
203 pub test_name: String,
205 pub statistic: f64,
207 pub p_value: f64,
209 pub degrees_of_freedom: Option<usize>,
211 pub confidence_interval: Option<(f64, f64)>,
213}
214
215#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
217pub struct AnomalyDetectionResults {
218 pub anomalies: Vec<Anomaly>,
220 pub anomaly_scores: Vec<f64>,
222 pub threshold: f64,
224 pub method: AnomalyDetectionMethod,
226}
227
228#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
230pub struct Anomaly {
231 pub index: usize,
233 pub score: f64,
235 pub timestamp: chrono::DateTime<chrono::Utc>,
237 pub context: HashMap<String, String>,
239}
240
241#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
243pub enum AnomalyDetectionMethod {
244 IsolationForest { n_trees: usize },
246 LocalOutlierFactor { n_neighbors: usize },
248 OneClassSVM { nu: f64 },
250 StatisticalThreshold { n_std: f64 },
252}
253
254impl Default for AnalyticsConfig {
255 fn default() -> Self {
256 Self {
257 max_history_samples: 10000,
258 min_clustering_samples: 50,
259 default_num_clusters: 8,
260 temporal_analysis_window: 100,
261 drift_detection_sensitivity: 0.05,
262 correlation_threshold: 0.7,
263 enable_visualizations: true,
264 }
265 }
266}
267
268impl Default for ClusteringParameters {
269 fn default() -> Self {
270 Self {
271 num_clusters: 8,
272 max_iterations: 100,
273 tolerance: 1e-4,
274 random_seed: Some(42),
275 distance_metric: DistanceMetric::Euclidean,
276 }
277 }
278}
279
280impl AdvancedAnalytics {
281 pub fn new() -> Self {
283 Self {
284 config: AnalyticsConfig::default(),
285 hidden_states_history: VecDeque::new(),
286 performance_correlations: HashMap::new(),
287 temporal_analysis_cache: TemporalAnalysisCache::new(),
288 clustering_results_cache: HashMap::new(),
289 }
290 }
291
292 pub fn with_config(config: AnalyticsConfig) -> Self {
294 Self {
295 config,
296 hidden_states_history: VecDeque::new(),
297 performance_correlations: HashMap::new(),
298 temporal_analysis_cache: TemporalAnalysisCache::new(),
299 clustering_results_cache: HashMap::new(),
300 }
301 }
302
303 pub fn record_hidden_states(&mut self, hidden_states: HiddenStateData) {
305 self.hidden_states_history.push_back(hidden_states);
306
307 while self.hidden_states_history.len() > self.config.max_history_samples {
308 self.hidden_states_history.pop_front();
309 }
310 }
311
312 pub fn record_performance_metrics(&mut self, metrics: &ModelPerformanceMetrics) {
314 self.update_correlation_data("loss", metrics.loss);
315 self.update_correlation_data("throughput", metrics.throughput_samples_per_sec);
316 self.update_correlation_data("memory_usage", metrics.memory_usage_mb);
317
318 if let Some(accuracy) = metrics.accuracy {
319 self.update_correlation_data("accuracy", accuracy);
320 }
321
322 if let Some(gpu_util) = metrics.gpu_utilization {
323 self.update_correlation_data("gpu_utilization", gpu_util);
324 }
325 }
326
327 pub fn analyze_hidden_states(&self, layer_name: &str) -> Result<HiddenStateAnalysis> {
329 let layer_data: Vec<_> = self
330 .hidden_states_history
331 .iter()
332 .filter(|data| data.layer_name == layer_name)
333 .collect();
334
335 if layer_data.is_empty() {
336 return Err(anyhow::anyhow!(
337 "No hidden state data available for layer: {}",
338 layer_name
339 ));
340 }
341
342 let all_states: Vec<Vec<f64>> =
344 layer_data.iter().flat_map(|data| data.hidden_states.iter()).cloned().collect();
345
346 if all_states.is_empty() {
347 return Err(anyhow::anyhow!(
348 "No hidden states found for layer: {}",
349 layer_name
350 ));
351 }
352
353 let dimensionality = all_states[0].len();
354
355 let clustering_results = self.perform_clustering_analysis(&all_states)?;
357
358 let temporal_dynamics = self.analyze_temporal_dynamics(&layer_data)?;
360
361 let representation_stability = self.assess_representation_stability(&all_states)?;
363
364 let information_content = self.calculate_information_content(&all_states)?;
366
367 Ok(HiddenStateAnalysis {
368 dimensionality,
369 information_content,
370 clustering_results,
371 temporal_dynamics,
372 representation_stability,
373 })
374 }
375
376 pub fn perform_clustering_analysis(&self, data: &[Vec<f64>]) -> Result<ClusteringResults> {
378 if data.len() < self.config.min_clustering_samples {
379 return Err(anyhow::anyhow!("Insufficient data for clustering analysis"));
380 }
381
382 let params = ClusteringParameters::default();
383 let num_clusters = params.num_clusters.min(data.len() / 2);
384
385 let mut cluster_centers = self.initialize_cluster_centers(data, num_clusters)?;
387 let mut cluster_assignments = vec![0; data.len()];
388
389 for _iteration in 0..params.max_iterations {
390 let mut new_assignments = vec![0; data.len()];
392 for (i, point) in data.iter().enumerate() {
393 let mut best_distance = f64::INFINITY;
394 let mut best_cluster = 0;
395
396 for (j, center) in cluster_centers.iter().enumerate() {
397 let distance =
398 self.calculate_distance(point, center, ¶ms.distance_metric)?;
399 if distance < best_distance {
400 best_distance = distance;
401 best_cluster = j;
402 }
403 }
404 new_assignments[i] = best_cluster;
405 }
406
407 if new_assignments == cluster_assignments {
409 break;
410 }
411 cluster_assignments = new_assignments;
412
413 cluster_centers =
415 self.update_cluster_centers(data, &cluster_assignments, num_clusters)?;
416 }
417
418 let silhouette_score =
420 self.calculate_silhouette_score(data, &cluster_assignments, &cluster_centers)?;
421
422 let inertia = self.calculate_inertia(data, &cluster_assignments, &cluster_centers)?;
424
425 Ok(ClusteringResults {
426 num_clusters,
427 cluster_centers,
428 cluster_assignments,
429 silhouette_score,
430 inertia,
431 })
432 }
433
434 pub fn analyze_temporal_dynamics(
436 &self,
437 layer_data: &[&HiddenStateData],
438 ) -> Result<TemporalDynamics> {
439 if layer_data.len() < 2 {
440 return Err(anyhow::anyhow!("Insufficient temporal data"));
441 }
442
443 let temporal_consistency = self.calculate_temporal_consistency(layer_data)?;
445
446 let change_rate = self.calculate_change_rate(layer_data)?;
448
449 let stability_windows = self.identify_stability_windows(layer_data)?;
451
452 let drift_detection = self.detect_distribution_drift(layer_data)?;
454
455 Ok(TemporalDynamics {
456 temporal_consistency,
457 change_rate,
458 stability_windows,
459 drift_detection,
460 })
461 }
462
463 pub fn assess_representation_stability(
465 &self,
466 hidden_states: &[Vec<f64>],
467 ) -> Result<RepresentationStability> {
468 if hidden_states.is_empty() {
469 return Err(anyhow::anyhow!("No hidden states provided"));
470 }
471
472 let stability_score = self.calculate_stability_score(hidden_states)?;
474
475 let variance_across_batches = self.calculate_batch_variance(hidden_states)?;
476
477 let consistency_measure = self.calculate_consistency_measure(hidden_states)?;
479
480 let robustness_to_noise = self.assess_noise_robustness(hidden_states)?;
481
482 Ok(RepresentationStability {
483 stability_score,
484 variance_across_batches,
485 consistency_measure,
486 robustness_to_noise,
487 })
488 }
489
490 pub fn generate_activation_heatmap(
492 &self,
493 layer_stats: &[LayerActivationStats],
494 ) -> Result<ActivationHeatmap> {
495 if layer_stats.is_empty() {
496 return Err(anyhow::anyhow!("No layer statistics provided"));
497 }
498
499 let mut data = Vec::new();
501 let mut min_val = f64::INFINITY;
502 let mut max_val = f64::NEG_INFINITY;
503
504 for stats in layer_stats {
505 let row = vec![
506 stats.mean_activation,
507 stats.std_activation,
508 stats.min_activation,
509 stats.max_activation,
510 stats.dead_neurons_ratio,
511 stats.saturated_neurons_ratio,
512 stats.sparsity,
513 ];
514
515 for &val in &row {
516 min_val = min_val.min(val);
517 max_val = max_val.max(val);
518 }
519
520 data.push(row);
521 }
522
523 let dimensions = (data.len(), data.first().map_or(0, |row| row.len()));
524
525 Ok(ActivationHeatmap {
526 data,
527 dimensions,
528 value_range: (min_val, max_val),
529 interpretation: "Activation statistics heatmap showing layer behavior patterns"
530 .to_string(),
531 })
532 }
533
534 pub fn detect_performance_anomalies(&self) -> Result<AnomalyDetectionResults> {
536 let mut all_values = Vec::new();
538 for correlation_data in self.performance_correlations.values() {
539 all_values.extend(correlation_data.values.iter().cloned());
540 }
541
542 if all_values.is_empty() {
543 return Err(anyhow::anyhow!("No performance data available"));
544 }
545
546 let method = AnomalyDetectionMethod::StatisticalThreshold { n_std: 2.0 };
548
549 let mean = all_values.iter().sum::<f64>() / all_values.len() as f64;
550 let variance =
551 all_values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / all_values.len() as f64;
552 let std_dev = variance.sqrt();
553
554 let threshold = mean + 2.0 * std_dev;
555
556 let mut anomalies = Vec::new();
557 let mut anomaly_scores = Vec::new();
558
559 for (i, &value) in all_values.iter().enumerate() {
560 let score = (value - mean).abs() / std_dev;
561 anomaly_scores.push(score);
562
563 if value > threshold {
564 anomalies.push(Anomaly {
565 index: i,
566 score,
567 timestamp: chrono::Utc::now(),
568 context: HashMap::new(),
569 });
570 }
571 }
572
573 Ok(AnomalyDetectionResults {
574 anomalies,
575 anomaly_scores,
576 threshold,
577 method,
578 })
579 }
580
581 pub fn calculate_correlation_matrix(&self) -> Result<Vec<Vec<f64>>> {
583 let metric_names: Vec<_> = self.performance_correlations.keys().cloned().collect();
584 let n_metrics = metric_names.len();
585
586 if n_metrics == 0 {
587 return Err(anyhow::anyhow!(
588 "No metrics available for correlation analysis"
589 ));
590 }
591
592 let mut correlation_matrix = vec![vec![0.0; n_metrics]; n_metrics];
593
594 for (i, metric1) in metric_names.iter().enumerate() {
595 for (j, metric2) in metric_names.iter().enumerate() {
596 if i == j {
597 correlation_matrix[i][j] = 1.0;
598 } else {
599 let correlation = self.calculate_correlation(metric1, metric2)?;
600 correlation_matrix[i][j] = correlation;
601 }
602 }
603 }
604
605 Ok(correlation_matrix)
606 }
607
608 pub fn perform_statistical_analysis(&self) -> Result<StatisticalAnalysis> {
610 if self.performance_correlations.is_empty() {
611 return Err(anyhow::anyhow!(
612 "No data available for statistical analysis"
613 ));
614 }
615
616 let mut means = Vec::new();
618 let mut std_devs = Vec::new();
619
620 for correlation_data in self.performance_correlations.values() {
621 let values: Vec<f64> = correlation_data.values.iter().cloned().collect();
622 if !values.is_empty() {
623 let mean = values.iter().sum::<f64>() / values.len() as f64;
624 let variance =
625 values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
626 let std_dev = variance.sqrt();
627
628 means.push(mean);
629 std_devs.push(std_dev);
630 }
631 }
632
633 let correlation_matrix = self.calculate_correlation_matrix()?;
635
636 let (principal_components, explained_variance_ratios) =
646 Self::principal_components_of(&correlation_matrix);
647
648 let significance_tests = Vec::new();
653
654 Ok(StatisticalAnalysis {
655 means,
656 std_devs,
657 correlation_matrix,
658 principal_components,
659 explained_variance_ratios,
660 significance_tests,
661 })
662 }
663
664 fn principal_components_of(correlation_matrix: &[Vec<f64>]) -> (Vec<Vec<f64>>, Vec<f64>) {
669 let n = correlation_matrix.len();
670 if n == 0 || correlation_matrix.iter().any(|row| row.len() != n) {
671 return (Vec::new(), Vec::new());
672 }
673 if correlation_matrix.iter().flatten().any(|v| !v.is_finite()) {
674 return (Vec::new(), Vec::new());
675 }
676
677 let matrix = nalgebra::DMatrix::<f64>::from_fn(n, n, |i, j| correlation_matrix[i][j]);
678 let eigen = nalgebra::linalg::SymmetricEigen::new(matrix);
681
682 let mut order: Vec<usize> = (0..n).collect();
683 order.sort_by(|&a, &b| {
684 eigen.eigenvalues[b]
685 .partial_cmp(&eigen.eigenvalues[a])
686 .unwrap_or(std::cmp::Ordering::Equal)
687 });
688
689 let total: f64 = eigen.eigenvalues.iter().map(|v| v.max(0.0)).sum();
692 let components: Vec<Vec<f64>> = order
693 .iter()
694 .map(|&k| eigen.eigenvectors.column(k).iter().copied().collect())
695 .collect();
696 let ratios: Vec<f64> = order
697 .iter()
698 .map(|&k| if total > 0.0 { eigen.eigenvalues[k].max(0.0) / total } else { 0.0 })
699 .collect();
700
701 (components, ratios)
702 }
703
704 pub fn generate_analytics_report(&self) -> Result<AnalyticsReport> {
706 let correlation_matrix = self.calculate_correlation_matrix().unwrap_or_default();
707 let statistical_analysis = self.perform_statistical_analysis().unwrap_or_default();
708 let anomaly_detection = self.detect_performance_anomalies().unwrap_or_default();
709
710 let mut layer_analyses = HashMap::new();
712 let unique_layers: std::collections::HashSet<String> =
713 self.hidden_states_history.iter().map(|data| data.layer_name.clone()).collect();
714
715 for layer_name in unique_layers {
716 if let Ok(analysis) = self.analyze_hidden_states(&layer_name) {
717 layer_analyses.insert(layer_name, analysis);
718 }
719 }
720
721 Ok(AnalyticsReport {
722 correlation_matrix,
723 statistical_analysis,
724 layer_analyses,
725 anomaly_detection,
726 temporal_summary: self.generate_temporal_summary(),
727 recommendations: self.generate_analytics_recommendations(),
728 })
729 }
730
731 fn update_correlation_data(&mut self, metric_name: &str, value: f64) {
735 let correlation_data = self
736 .performance_correlations
737 .entry(metric_name.to_string())
738 .or_insert_with(|| CorrelationData {
739 metric_name: metric_name.to_string(),
740 values: VecDeque::new(),
741 correlations: HashMap::new(),
742 last_updated: chrono::Utc::now(),
743 });
744
745 correlation_data.values.push_back(value);
746 correlation_data.last_updated = chrono::Utc::now();
747
748 while correlation_data.values.len() > self.config.max_history_samples {
750 correlation_data.values.pop_front();
751 }
752 }
753
754 fn initialize_cluster_centers(
756 &self,
757 data: &[Vec<f64>],
758 num_clusters: usize,
759 ) -> Result<Vec<Vec<f64>>> {
760 if data.is_empty() || num_clusters == 0 {
761 return Err(anyhow::anyhow!("Invalid input for cluster initialization"));
762 }
763
764 let mut centers = Vec::new();
765 let _dimensions = data[0].len();
766
767 centers.push(data[0].clone());
772
773 for _ in 1..num_clusters {
775 if centers.len() >= data.len() {
776 break;
777 }
778
779 let mut best_distance = 0.0;
780 let mut best_point = data[0].clone();
781
782 for point in data {
783 let mut min_distance = f64::INFINITY;
784 for center in ¢ers {
785 let distance =
786 self.calculate_distance(point, center, &DistanceMetric::Euclidean)?;
787 min_distance = min_distance.min(distance);
788 }
789
790 if min_distance > best_distance {
791 best_distance = min_distance;
792 best_point = point.clone();
793 }
794 }
795
796 centers.push(best_point);
797 }
798
799 Ok(centers)
800 }
801
802 fn calculate_distance(
804 &self,
805 point1: &[f64],
806 point2: &[f64],
807 metric: &DistanceMetric,
808 ) -> Result<f64> {
809 if point1.len() != point2.len() {
810 return Err(anyhow::anyhow!("Points must have same dimensionality"));
811 }
812
813 match metric {
814 DistanceMetric::Euclidean => {
815 let sum_squared =
816 point1.iter().zip(point2.iter()).map(|(a, b)| (a - b).powi(2)).sum::<f64>();
817 Ok(sum_squared.sqrt())
818 },
819 DistanceMetric::Manhattan => {
820 let sum_abs =
821 point1.iter().zip(point2.iter()).map(|(a, b)| (a - b).abs()).sum::<f64>();
822 Ok(sum_abs)
823 },
824 DistanceMetric::Cosine => {
825 let dot_product = point1.iter().zip(point2.iter()).map(|(a, b)| a * b).sum::<f64>();
826 let norm1 = point1.iter().map(|x| x.powi(2)).sum::<f64>().sqrt();
827 let norm2 = point2.iter().map(|x| x.powi(2)).sum::<f64>().sqrt();
828
829 if norm1 == 0.0 || norm2 == 0.0 {
830 Ok(1.0)
831 } else {
832 Ok(1.0 - (dot_product / (norm1 * norm2)))
833 }
834 },
835 DistanceMetric::Minkowski { p } => {
836 let sum_powered = point1
837 .iter()
838 .zip(point2.iter())
839 .map(|(a, b)| (a - b).abs().powf(*p))
840 .sum::<f64>();
841 Ok(sum_powered.powf(1.0 / p))
842 },
843 }
844 }
845
846 fn update_cluster_centers(
848 &self,
849 data: &[Vec<f64>],
850 assignments: &[usize],
851 num_clusters: usize,
852 ) -> Result<Vec<Vec<f64>>> {
853 let dimensions = data[0].len();
854 let mut new_centers = vec![vec![0.0; dimensions]; num_clusters];
855 let mut cluster_counts = vec![0; num_clusters];
856
857 for (point, &cluster_id) in data.iter().zip(assignments.iter()) {
859 if cluster_id < num_clusters {
860 for (i, &value) in point.iter().enumerate() {
861 new_centers[cluster_id][i] += value;
862 }
863 cluster_counts[cluster_id] += 1;
864 }
865 }
866
867 for (cluster_id, count) in cluster_counts.iter().enumerate() {
869 if *count > 0 {
870 for value in &mut new_centers[cluster_id] {
871 *value /= *count as f64;
872 }
873 }
874 }
875
876 Ok(new_centers)
877 }
878
879 fn calculate_silhouette_score(
881 &self,
882 data: &[Vec<f64>],
883 assignments: &[usize],
884 centers: &[Vec<f64>],
885 ) -> Result<f64> {
886 if data.is_empty() {
887 return Ok(0.0);
888 }
889
890 let mut total_score = 0.0;
891 let mut valid_points = 0;
892
893 for (i, point) in data.iter().enumerate() {
894 let cluster_id = assignments[i];
895
896 let mut same_cluster_distances = Vec::new();
898 for (j, other_point) in data.iter().enumerate() {
899 if i != j && assignments[j] == cluster_id {
900 let distance =
901 self.calculate_distance(point, other_point, &DistanceMetric::Euclidean)?;
902 same_cluster_distances.push(distance);
903 }
904 }
905
906 let a = if same_cluster_distances.is_empty() {
907 0.0
908 } else {
909 same_cluster_distances.iter().sum::<f64>() / same_cluster_distances.len() as f64
910 };
911
912 let mut min_other_cluster_distance = f64::INFINITY;
914 for (other_cluster_id, _) in centers.iter().enumerate() {
915 if other_cluster_id != cluster_id {
916 let mut other_cluster_distances = Vec::new();
917 for (j, other_point) in data.iter().enumerate() {
918 if assignments[j] == other_cluster_id {
919 let distance = self.calculate_distance(
920 point,
921 other_point,
922 &DistanceMetric::Euclidean,
923 )?;
924 other_cluster_distances.push(distance);
925 }
926 }
927
928 if !other_cluster_distances.is_empty() {
929 let avg_distance = other_cluster_distances.iter().sum::<f64>()
930 / other_cluster_distances.len() as f64;
931 min_other_cluster_distance = min_other_cluster_distance.min(avg_distance);
932 }
933 }
934 }
935
936 let b = min_other_cluster_distance;
937
938 if a < b {
939 total_score += (b - a) / b;
940 } else if a > b {
941 total_score += (b - a) / a;
942 }
943 valid_points += 1;
946 }
947
948 Ok(if valid_points > 0 { total_score / valid_points as f64 } else { 0.0 })
949 }
950
951 fn calculate_inertia(
953 &self,
954 data: &[Vec<f64>],
955 assignments: &[usize],
956 centers: &[Vec<f64>],
957 ) -> Result<f64> {
958 let mut inertia = 0.0;
959
960 for (point, &cluster_id) in data.iter().zip(assignments.iter()) {
961 if cluster_id < centers.len() {
962 let distance = self.calculate_distance(
963 point,
964 ¢ers[cluster_id],
965 &DistanceMetric::Euclidean,
966 )?;
967 inertia += distance.powi(2);
968 }
969 }
970
971 Ok(inertia)
972 }
973
974 fn calculate_information_content(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
976 if hidden_states.is_empty() {
977 return Ok(0.0);
978 }
979
980 let dimensions = hidden_states[0].len();
981 let mut total_variance = 0.0;
982
983 for dim in 0..dimensions {
984 let values: Vec<f64> = hidden_states.iter().map(|state| state[dim]).collect();
985 if values.len() > 1 {
986 let mean = values.iter().sum::<f64>() / values.len() as f64;
987 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
988 / (values.len() - 1) as f64;
989 total_variance += variance;
990 }
991 }
992
993 Ok(total_variance / dimensions as f64)
995 }
996
997 fn calculate_temporal_consistency(&self, layer_data: &[&HiddenStateData]) -> Result<f64> {
999 if layer_data.len() < 2 {
1000 return Ok(1.0);
1001 }
1002
1003 let mut consistency_scores = Vec::new();
1004
1005 for i in 1..layer_data.len() {
1006 let prev_states = &layer_data[i - 1].hidden_states;
1007 let curr_states = &layer_data[i].hidden_states;
1008
1009 if !prev_states.is_empty() && !curr_states.is_empty() {
1010 let prev_mean = self.calculate_mean_state(prev_states);
1012 let curr_mean = self.calculate_mean_state(curr_states);
1013
1014 if prev_mean.len() == curr_mean.len() {
1015 let distance = self.calculate_distance(
1016 &prev_mean,
1017 &curr_mean,
1018 &DistanceMetric::Euclidean,
1019 )?;
1020 consistency_scores.push(1.0 / (1.0 + distance));
1021 }
1022 }
1023 }
1024
1025 Ok(if consistency_scores.is_empty() {
1026 1.0
1027 } else {
1028 consistency_scores.iter().sum::<f64>() / consistency_scores.len() as f64
1029 })
1030 }
1031
1032 fn calculate_mean_state(&self, states: &[Vec<f64>]) -> Vec<f64> {
1034 if states.is_empty() {
1035 return Vec::new();
1036 }
1037
1038 let dimensions = states[0].len();
1039 let mut mean_state = vec![0.0; dimensions];
1040
1041 for state in states {
1042 for (i, &value) in state.iter().enumerate() {
1043 if i < dimensions {
1044 mean_state[i] += value;
1045 }
1046 }
1047 }
1048
1049 for value in &mut mean_state {
1050 *value /= states.len() as f64;
1051 }
1052
1053 mean_state
1054 }
1055
1056 fn calculate_change_rate(&self, layer_data: &[&HiddenStateData]) -> Result<f64> {
1058 if layer_data.len() < 2 {
1059 return Ok(0.0);
1060 }
1061
1062 let mut total_change = 0.0;
1063 let mut valid_comparisons = 0;
1064
1065 for i in 1..layer_data.len() {
1066 let prev_mean = self.calculate_mean_state(&layer_data[i - 1].hidden_states);
1067 let curr_mean = self.calculate_mean_state(&layer_data[i].hidden_states);
1068
1069 if !prev_mean.is_empty() && !curr_mean.is_empty() && prev_mean.len() == curr_mean.len()
1070 {
1071 let change =
1072 self.calculate_distance(&prev_mean, &curr_mean, &DistanceMetric::Euclidean)?;
1073 total_change += change;
1074 valid_comparisons += 1;
1075 }
1076 }
1077
1078 Ok(if valid_comparisons > 0 {
1079 total_change / valid_comparisons as f64
1080 } else {
1081 0.0
1082 })
1083 }
1084
1085 fn identify_stability_windows(
1087 &self,
1088 layer_data: &[&HiddenStateData],
1089 ) -> Result<Vec<(usize, usize)>> {
1090 if layer_data.len() < 3 {
1091 return Ok(Vec::new());
1092 }
1093
1094 let mut stability_windows = Vec::new();
1095 let mut window_start = 0;
1096 let stability_threshold = 0.1; for i in 1..layer_data.len() {
1099 let prev_mean = self.calculate_mean_state(&layer_data[i - 1].hidden_states);
1100 let curr_mean = self.calculate_mean_state(&layer_data[i].hidden_states);
1101
1102 if !prev_mean.is_empty() && !curr_mean.is_empty() && prev_mean.len() == curr_mean.len()
1103 {
1104 let change = self
1105 .calculate_distance(&prev_mean, &curr_mean, &DistanceMetric::Euclidean)
1106 .unwrap_or(f64::INFINITY);
1107
1108 if change > stability_threshold {
1109 if i - window_start > 2 {
1111 stability_windows.push((window_start, i - 1));
1112 }
1113 window_start = i;
1114 }
1115 }
1116 }
1117
1118 if layer_data.len() - window_start > 2 {
1120 stability_windows.push((window_start, layer_data.len() - 1));
1121 }
1122
1123 Ok(stability_windows)
1124 }
1125
1126 fn detect_distribution_drift(&self, layer_data: &[&HiddenStateData]) -> Result<DriftInfo> {
1128 if layer_data.len() < self.config.temporal_analysis_window {
1129 return Ok(DriftInfo {
1130 drift_detected: false,
1131 drift_magnitude: 0.0,
1132 drift_direction: "unknown".to_string(),
1133 onset_step: None,
1134 });
1135 }
1136
1137 let window_size = self.config.temporal_analysis_window;
1138 let mid_point = layer_data.len() / 2;
1139
1140 let early_data = &layer_data[0..window_size.min(mid_point)];
1142 let late_data = &layer_data[mid_point.max(layer_data.len() - window_size)..];
1143
1144 let early_mean = self.calculate_aggregated_mean(early_data);
1145 let late_mean = self.calculate_aggregated_mean(late_data);
1146
1147 if early_mean.len() == late_mean.len() && !early_mean.is_empty() {
1148 let drift_magnitude =
1149 self.calculate_distance(&early_mean, &late_mean, &DistanceMetric::Euclidean)?;
1150 let drift_detected = drift_magnitude > self.config.drift_detection_sensitivity;
1151
1152 Ok(DriftInfo {
1153 drift_detected,
1154 drift_magnitude,
1155 drift_direction: if drift_detected {
1156 "forward".to_string()
1157 } else {
1158 "stable".to_string()
1159 },
1160 onset_step: if drift_detected { Some(mid_point) } else { None },
1161 })
1162 } else {
1163 Ok(DriftInfo {
1164 drift_detected: false,
1165 drift_magnitude: 0.0,
1166 drift_direction: "unknown".to_string(),
1167 onset_step: None,
1168 })
1169 }
1170 }
1171
1172 fn calculate_aggregated_mean(&self, layer_data: &[&HiddenStateData]) -> Vec<f64> {
1174 let all_states: Vec<Vec<f64>> =
1175 layer_data.iter().flat_map(|data| data.hidden_states.iter()).cloned().collect();
1176
1177 self.calculate_mean_state(&all_states)
1178 }
1179
1180 fn calculate_stability_score(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1182 if hidden_states.len() < 2 {
1183 return Ok(1.0);
1184 }
1185
1186 let mut stability_scores = Vec::new();
1187 let window_size = (hidden_states.len() / 10).max(2);
1188
1189 for i in window_size..hidden_states.len() {
1190 let current_window = &hidden_states[i - window_size..i];
1191 let mean_current = self.calculate_mean_state(current_window);
1192
1193 if i >= 2 * window_size {
1194 let prev_window = &hidden_states[i - 2 * window_size..i - window_size];
1195 let mean_prev = self.calculate_mean_state(prev_window);
1196
1197 if mean_current.len() == mean_prev.len() && !mean_current.is_empty() {
1198 let distance = self.calculate_distance(
1199 &mean_current,
1200 &mean_prev,
1201 &DistanceMetric::Euclidean,
1202 )?;
1203 stability_scores.push(1.0 / (1.0 + distance));
1204 }
1205 }
1206 }
1207
1208 Ok(if stability_scores.is_empty() {
1209 1.0
1210 } else {
1211 stability_scores.iter().sum::<f64>() / stability_scores.len() as f64
1212 })
1213 }
1214
1215 fn calculate_batch_variance(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1217 if hidden_states.is_empty() {
1218 return Ok(0.0);
1219 }
1220
1221 let dimensions = hidden_states[0].len();
1222 let mut total_variance = 0.0;
1223
1224 for dim in 0..dimensions {
1225 let values: Vec<f64> = hidden_states.iter().map(|state| state[dim]).collect();
1226 if values.len() > 1 {
1227 let mean = values.iter().sum::<f64>() / values.len() as f64;
1228 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
1229 / (values.len() - 1) as f64;
1230 total_variance += variance;
1231 }
1232 }
1233
1234 Ok(total_variance / dimensions as f64)
1235 }
1236
1237 fn calculate_consistency_measure(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1239 if hidden_states.len() < 2 {
1240 return Ok(1.0);
1241 }
1242
1243 let mut similarities = Vec::new();
1245 let sample_size = hidden_states.len().min(100); for i in 0..sample_size {
1248 for j in (i + 1)..sample_size {
1249 let distance = self.calculate_distance(
1250 &hidden_states[i],
1251 &hidden_states[j],
1252 &DistanceMetric::Cosine,
1253 )?;
1254 similarities.push(1.0 - distance); }
1256 }
1257
1258 Ok(if similarities.is_empty() {
1259 1.0
1260 } else {
1261 similarities.iter().sum::<f64>() / similarities.len() as f64
1262 })
1263 }
1264
1265 fn assess_noise_robustness(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1274 self.calculate_batch_variance(hidden_states).map(|variance| {
1275 1.0 / (1.0 + variance)
1277 })
1278 }
1279
1280 fn calculate_correlation(&self, metric1: &str, metric2: &str) -> Result<f64> {
1282 let data1 = self
1283 .performance_correlations
1284 .get(metric1)
1285 .ok_or_else(|| anyhow::anyhow!("Metric {} not found", metric1))?;
1286
1287 let data2 = self
1288 .performance_correlations
1289 .get(metric2)
1290 .ok_or_else(|| anyhow::anyhow!("Metric {} not found", metric2))?;
1291
1292 let values1: Vec<f64> = data1.values.iter().cloned().collect();
1293 let values2: Vec<f64> = data2.values.iter().cloned().collect();
1294
1295 if values1.len() != values2.len() || values1.is_empty() {
1296 return Ok(0.0);
1297 }
1298
1299 let mean1 = values1.iter().sum::<f64>() / values1.len() as f64;
1300 let mean2 = values2.iter().sum::<f64>() / values2.len() as f64;
1301
1302 let numerator: f64 = values1
1303 .iter()
1304 .zip(values2.iter())
1305 .map(|(x1, x2)| (x1 - mean1) * (x2 - mean2))
1306 .sum();
1307
1308 let var1: f64 = values1.iter().map(|x| (x - mean1).powi(2)).sum();
1309 let var2: f64 = values2.iter().map(|x| (x - mean2).powi(2)).sum();
1310
1311 let denominator = (var1 * var2).sqrt();
1312
1313 Ok(if denominator == 0.0 { 0.0 } else { numerator / denominator })
1314 }
1315
1316 fn generate_temporal_summary(&self) -> String {
1318 format!(
1319 "Temporal analysis: {} hidden state samples collected across {} layers. \
1320 Average stability observed with {} correlation metrics tracked.",
1321 self.hidden_states_history.len(),
1322 self.hidden_states_history
1323 .iter()
1324 .map(|data| &data.layer_name)
1325 .collect::<std::collections::HashSet<_>>()
1326 .len(),
1327 self.performance_correlations.len()
1328 )
1329 }
1330
1331 fn generate_analytics_recommendations(&self) -> Vec<String> {
1333 let mut recommendations = Vec::new();
1334
1335 if self.performance_correlations.len() < 3 {
1336 recommendations.push(
1337 "Collect more performance metrics for comprehensive correlation analysis"
1338 .to_string(),
1339 );
1340 }
1341
1342 if self.hidden_states_history.len() < 50 {
1343 recommendations
1344 .push("Increase hidden state sampling for better temporal analysis".to_string());
1345 }
1346
1347 recommendations
1348 .push("Consider implementing automated anomaly detection alerts".to_string());
1349 recommendations.push("Enable advanced visualization for better insights".to_string());
1350
1351 recommendations
1352 }
1353}
1354
1355#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1357pub struct AnalyticsReport {
1358 pub correlation_matrix: Vec<Vec<f64>>,
1360 pub statistical_analysis: StatisticalAnalysis,
1362 pub layer_analyses: HashMap<String, HiddenStateAnalysis>,
1364 pub anomaly_detection: AnomalyDetectionResults,
1366 pub temporal_summary: String,
1368 pub recommendations: Vec<String>,
1370}
1371
1372impl TemporalAnalysisCache {
1373 fn new() -> Self {
1375 Self {
1376 drift_results: HashMap::new(),
1377 consistency_scores: HashMap::new(),
1378 stability_windows: HashMap::new(),
1379 last_analysis: chrono::Utc::now(),
1380 }
1381 }
1382}
1383
1384impl Default for AnomalyDetectionResults {
1385 fn default() -> Self {
1386 Self {
1387 anomalies: Vec::new(),
1388 anomaly_scores: Vec::new(),
1389 threshold: 0.0,
1390 method: AnomalyDetectionMethod::StatisticalThreshold { n_std: 2.0 },
1391 }
1392 }
1393}
1394
1395impl Default for AdvancedAnalytics {
1396 fn default() -> Self {
1397 Self::new()
1398 }
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403 use super::*;
1404
1405 #[test]
1408 fn principal_components_of_the_identity_are_unit_and_equally_weighted() {
1409 let identity = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
1410 let (components, ratios) = AdvancedAnalytics::principal_components_of(&identity);
1411 assert_eq!(components.len(), 2);
1412 for ratio in &ratios {
1414 assert!((ratio - 0.5).abs() < 1e-9, "expected 0.5, got {ratio}");
1415 }
1416 }
1417
1418 #[test]
1419 fn principal_components_of_perfectly_correlated_variables_collapse_to_one() {
1420 let correlated = vec![vec![1.0, 1.0], vec![1.0, 1.0]];
1422 let (components, ratios) = AdvancedAnalytics::principal_components_of(&correlated);
1423 assert_eq!(components.len(), 2);
1424 assert!(
1425 (ratios[0] - 1.0).abs() < 1e-9,
1426 "PC1 must explain all the variance, got {}",
1427 ratios[0]
1428 );
1429 assert!(
1430 ratios[1].abs() < 1e-9,
1431 "PC2 must explain none, got {}",
1432 ratios[1]
1433 );
1434 assert!(
1437 (ratios[0] - ratios[1]).abs() > 0.5,
1438 "the ratios must actually differ"
1439 );
1440 }
1441
1442 #[test]
1443 fn principal_components_are_ordered_by_explained_variance() {
1444 let matrix = vec![vec![1.0, 0.8], vec![0.8, 1.0]];
1445 let (_, ratios) = AdvancedAnalytics::principal_components_of(&matrix);
1446 assert!(
1447 ratios[0] >= ratios[1],
1448 "components must be sorted descending: {ratios:?}"
1449 );
1450 assert!(
1451 (ratios.iter().sum::<f64>() - 1.0).abs() < 1e-9,
1452 "ratios must sum to 1"
1453 );
1454 assert!((ratios[0] - 0.9).abs() < 1e-9, "{ratios:?}");
1456 }
1457
1458 #[test]
1459 fn principal_components_reject_a_malformed_matrix() {
1460 assert_eq!(
1461 AdvancedAnalytics::principal_components_of(&[]),
1462 (Vec::new(), Vec::new())
1463 );
1464 let ragged = vec![vec![1.0, 0.0], vec![0.0]];
1465 assert_eq!(
1466 AdvancedAnalytics::principal_components_of(&ragged),
1467 (Vec::new(), Vec::new())
1468 );
1469 }
1470
1471 #[test]
1472 fn test_advanced_analytics_creation() {
1473 let analytics = AdvancedAnalytics::new();
1474 assert_eq!(analytics.hidden_states_history.len(), 0);
1475 assert_eq!(analytics.performance_correlations.len(), 0);
1476 }
1477
1478 #[test]
1479 fn test_distance_calculation() {
1480 let analytics = AdvancedAnalytics::new();
1481 let point1 = vec![1.0, 2.0, 3.0];
1482 let point2 = vec![4.0, 5.0, 6.0];
1483
1484 let distance = analytics
1485 .calculate_distance(&point1, &point2, &DistanceMetric::Euclidean)
1486 .expect("operation failed in test");
1487 assert!(distance > 0.0);
1488 }
1489
1490 #[test]
1491 fn test_clustering_parameters() {
1492 let params = ClusteringParameters::default();
1493 assert_eq!(params.num_clusters, 8);
1494 assert_eq!(params.max_iterations, 100);
1495 }
1496
1497 #[test]
1498 fn test_correlation_calculation() {
1499 let mut analytics = AdvancedAnalytics::new();
1500
1501 analytics.update_correlation_data("metric1", 1.0);
1503 analytics.update_correlation_data("metric1", 2.0);
1504 analytics.update_correlation_data("metric2", 3.0);
1505 analytics.update_correlation_data("metric2", 4.0);
1506
1507 assert_eq!(analytics.performance_correlations.len(), 2);
1508 }
1509}