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)?;
477
478 let consistency_measure = self.calculate_consistency_measure(hidden_states)?;
480
481 let robustness_to_noise = self.assess_noise_robustness(hidden_states)?;
483
484 Ok(RepresentationStability {
485 stability_score,
486 variance_across_batches,
487 consistency_measure,
488 robustness_to_noise,
489 })
490 }
491
492 pub fn generate_activation_heatmap(
494 &self,
495 layer_stats: &[LayerActivationStats],
496 ) -> Result<ActivationHeatmap> {
497 if layer_stats.is_empty() {
498 return Err(anyhow::anyhow!("No layer statistics provided"));
499 }
500
501 let mut data = Vec::new();
503 let mut min_val = f64::INFINITY;
504 let mut max_val = f64::NEG_INFINITY;
505
506 for stats in layer_stats {
507 let row = vec![
508 stats.mean_activation,
509 stats.std_activation,
510 stats.min_activation,
511 stats.max_activation,
512 stats.dead_neurons_ratio,
513 stats.saturated_neurons_ratio,
514 stats.sparsity,
515 ];
516
517 for &val in &row {
518 min_val = min_val.min(val);
519 max_val = max_val.max(val);
520 }
521
522 data.push(row);
523 }
524
525 let dimensions = (data.len(), data.first().map_or(0, |row| row.len()));
526
527 Ok(ActivationHeatmap {
528 data,
529 dimensions,
530 value_range: (min_val, max_val),
531 interpretation: "Activation statistics heatmap showing layer behavior patterns"
532 .to_string(),
533 })
534 }
535
536 pub fn detect_performance_anomalies(&self) -> Result<AnomalyDetectionResults> {
538 let mut all_values = Vec::new();
540 for correlation_data in self.performance_correlations.values() {
541 all_values.extend(correlation_data.values.iter().cloned());
542 }
543
544 if all_values.is_empty() {
545 return Err(anyhow::anyhow!("No performance data available"));
546 }
547
548 let method = AnomalyDetectionMethod::StatisticalThreshold { n_std: 2.0 };
550
551 let mean = all_values.iter().sum::<f64>() / all_values.len() as f64;
552 let variance =
553 all_values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / all_values.len() as f64;
554 let std_dev = variance.sqrt();
555
556 let threshold = mean + 2.0 * std_dev;
557
558 let mut anomalies = Vec::new();
559 let mut anomaly_scores = Vec::new();
560
561 for (i, &value) in all_values.iter().enumerate() {
562 let score = (value - mean).abs() / std_dev;
563 anomaly_scores.push(score);
564
565 if value > threshold {
566 anomalies.push(Anomaly {
567 index: i,
568 score,
569 timestamp: chrono::Utc::now(),
570 context: HashMap::new(),
571 });
572 }
573 }
574
575 Ok(AnomalyDetectionResults {
576 anomalies,
577 anomaly_scores,
578 threshold,
579 method,
580 })
581 }
582
583 pub fn calculate_correlation_matrix(&self) -> Result<Vec<Vec<f64>>> {
585 let metric_names: Vec<_> = self.performance_correlations.keys().cloned().collect();
586 let n_metrics = metric_names.len();
587
588 if n_metrics == 0 {
589 return Err(anyhow::anyhow!(
590 "No metrics available for correlation analysis"
591 ));
592 }
593
594 let mut correlation_matrix = vec![vec![0.0; n_metrics]; n_metrics];
595
596 for (i, metric1) in metric_names.iter().enumerate() {
597 for (j, metric2) in metric_names.iter().enumerate() {
598 if i == j {
599 correlation_matrix[i][j] = 1.0;
600 } else {
601 let correlation = self.calculate_correlation(metric1, metric2)?;
602 correlation_matrix[i][j] = correlation;
603 }
604 }
605 }
606
607 Ok(correlation_matrix)
608 }
609
610 pub fn perform_statistical_analysis(&self) -> Result<StatisticalAnalysis> {
612 if self.performance_correlations.is_empty() {
613 return Err(anyhow::anyhow!(
614 "No data available for statistical analysis"
615 ));
616 }
617
618 let mut means = Vec::new();
620 let mut std_devs = Vec::new();
621
622 for correlation_data in self.performance_correlations.values() {
623 let values: Vec<f64> = correlation_data.values.iter().cloned().collect();
624 if !values.is_empty() {
625 let mean = values.iter().sum::<f64>() / values.len() as f64;
626 let variance =
627 values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
628 let std_dev = variance.sqrt();
629
630 means.push(mean);
631 std_devs.push(std_dev);
632 }
633 }
634
635 let correlation_matrix = self.calculate_correlation_matrix()?;
637
638 let principal_components = vec![vec![1.0; means.len()]; means.len()];
640 let explained_variance_ratios = vec![1.0 / means.len() as f64; means.len()];
641
642 let significance_tests = vec![SignificanceTest {
644 test_name: "Sample t-test".to_string(),
645 statistic: 1.0,
646 p_value: 0.05,
647 degrees_of_freedom: Some(means.len() - 1),
648 confidence_interval: Some((0.0, 1.0)),
649 }];
650
651 Ok(StatisticalAnalysis {
652 means,
653 std_devs,
654 correlation_matrix,
655 principal_components,
656 explained_variance_ratios,
657 significance_tests,
658 })
659 }
660
661 pub fn generate_analytics_report(&self) -> Result<AnalyticsReport> {
663 let correlation_matrix = self.calculate_correlation_matrix().unwrap_or_default();
664 let statistical_analysis = self.perform_statistical_analysis().unwrap_or_default();
665 let anomaly_detection = self.detect_performance_anomalies().unwrap_or_default();
666
667 let mut layer_analyses = HashMap::new();
669 let unique_layers: std::collections::HashSet<String> =
670 self.hidden_states_history.iter().map(|data| data.layer_name.clone()).collect();
671
672 for layer_name in unique_layers {
673 if let Ok(analysis) = self.analyze_hidden_states(&layer_name) {
674 layer_analyses.insert(layer_name, analysis);
675 }
676 }
677
678 Ok(AnalyticsReport {
679 correlation_matrix,
680 statistical_analysis,
681 layer_analyses,
682 anomaly_detection,
683 temporal_summary: self.generate_temporal_summary(),
684 recommendations: self.generate_analytics_recommendations(),
685 })
686 }
687
688 fn update_correlation_data(&mut self, metric_name: &str, value: f64) {
692 let correlation_data = self
693 .performance_correlations
694 .entry(metric_name.to_string())
695 .or_insert_with(|| CorrelationData {
696 metric_name: metric_name.to_string(),
697 values: VecDeque::new(),
698 correlations: HashMap::new(),
699 last_updated: chrono::Utc::now(),
700 });
701
702 correlation_data.values.push_back(value);
703 correlation_data.last_updated = chrono::Utc::now();
704
705 while correlation_data.values.len() > self.config.max_history_samples {
707 correlation_data.values.pop_front();
708 }
709 }
710
711 fn initialize_cluster_centers(
713 &self,
714 data: &[Vec<f64>],
715 num_clusters: usize,
716 ) -> Result<Vec<Vec<f64>>> {
717 if data.is_empty() || num_clusters == 0 {
718 return Err(anyhow::anyhow!("Invalid input for cluster initialization"));
719 }
720
721 let mut centers = Vec::new();
722 let _dimensions = data[0].len();
723
724 centers.push(data[0].clone());
726
727 for _ in 1..num_clusters {
729 if centers.len() >= data.len() {
730 break;
731 }
732
733 let mut best_distance = 0.0;
734 let mut best_point = data[0].clone();
735
736 for point in data {
737 let mut min_distance = f64::INFINITY;
738 for center in ¢ers {
739 let distance =
740 self.calculate_distance(point, center, &DistanceMetric::Euclidean)?;
741 min_distance = min_distance.min(distance);
742 }
743
744 if min_distance > best_distance {
745 best_distance = min_distance;
746 best_point = point.clone();
747 }
748 }
749
750 centers.push(best_point);
751 }
752
753 Ok(centers)
754 }
755
756 fn calculate_distance(
758 &self,
759 point1: &[f64],
760 point2: &[f64],
761 metric: &DistanceMetric,
762 ) -> Result<f64> {
763 if point1.len() != point2.len() {
764 return Err(anyhow::anyhow!("Points must have same dimensionality"));
765 }
766
767 match metric {
768 DistanceMetric::Euclidean => {
769 let sum_squared =
770 point1.iter().zip(point2.iter()).map(|(a, b)| (a - b).powi(2)).sum::<f64>();
771 Ok(sum_squared.sqrt())
772 },
773 DistanceMetric::Manhattan => {
774 let sum_abs =
775 point1.iter().zip(point2.iter()).map(|(a, b)| (a - b).abs()).sum::<f64>();
776 Ok(sum_abs)
777 },
778 DistanceMetric::Cosine => {
779 let dot_product = point1.iter().zip(point2.iter()).map(|(a, b)| a * b).sum::<f64>();
780 let norm1 = point1.iter().map(|x| x.powi(2)).sum::<f64>().sqrt();
781 let norm2 = point2.iter().map(|x| x.powi(2)).sum::<f64>().sqrt();
782
783 if norm1 == 0.0 || norm2 == 0.0 {
784 Ok(1.0)
785 } else {
786 Ok(1.0 - (dot_product / (norm1 * norm2)))
787 }
788 },
789 DistanceMetric::Minkowski { p } => {
790 let sum_powered = point1
791 .iter()
792 .zip(point2.iter())
793 .map(|(a, b)| (a - b).abs().powf(*p))
794 .sum::<f64>();
795 Ok(sum_powered.powf(1.0 / p))
796 },
797 }
798 }
799
800 fn update_cluster_centers(
802 &self,
803 data: &[Vec<f64>],
804 assignments: &[usize],
805 num_clusters: usize,
806 ) -> Result<Vec<Vec<f64>>> {
807 let dimensions = data[0].len();
808 let mut new_centers = vec![vec![0.0; dimensions]; num_clusters];
809 let mut cluster_counts = vec![0; num_clusters];
810
811 for (point, &cluster_id) in data.iter().zip(assignments.iter()) {
813 if cluster_id < num_clusters {
814 for (i, &value) in point.iter().enumerate() {
815 new_centers[cluster_id][i] += value;
816 }
817 cluster_counts[cluster_id] += 1;
818 }
819 }
820
821 for (cluster_id, count) in cluster_counts.iter().enumerate() {
823 if *count > 0 {
824 for value in &mut new_centers[cluster_id] {
825 *value /= *count as f64;
826 }
827 }
828 }
829
830 Ok(new_centers)
831 }
832
833 fn calculate_silhouette_score(
835 &self,
836 data: &[Vec<f64>],
837 assignments: &[usize],
838 centers: &[Vec<f64>],
839 ) -> Result<f64> {
840 if data.is_empty() {
841 return Ok(0.0);
842 }
843
844 let mut total_score = 0.0;
845 let mut valid_points = 0;
846
847 for (i, point) in data.iter().enumerate() {
848 let cluster_id = assignments[i];
849
850 let mut same_cluster_distances = Vec::new();
852 for (j, other_point) in data.iter().enumerate() {
853 if i != j && assignments[j] == cluster_id {
854 let distance =
855 self.calculate_distance(point, other_point, &DistanceMetric::Euclidean)?;
856 same_cluster_distances.push(distance);
857 }
858 }
859
860 let a = if same_cluster_distances.is_empty() {
861 0.0
862 } else {
863 same_cluster_distances.iter().sum::<f64>() / same_cluster_distances.len() as f64
864 };
865
866 let mut min_other_cluster_distance = f64::INFINITY;
868 for (other_cluster_id, _) in centers.iter().enumerate() {
869 if other_cluster_id != cluster_id {
870 let mut other_cluster_distances = Vec::new();
871 for (j, other_point) in data.iter().enumerate() {
872 if assignments[j] == other_cluster_id {
873 let distance = self.calculate_distance(
874 point,
875 other_point,
876 &DistanceMetric::Euclidean,
877 )?;
878 other_cluster_distances.push(distance);
879 }
880 }
881
882 if !other_cluster_distances.is_empty() {
883 let avg_distance = other_cluster_distances.iter().sum::<f64>()
884 / other_cluster_distances.len() as f64;
885 min_other_cluster_distance = min_other_cluster_distance.min(avg_distance);
886 }
887 }
888 }
889
890 let b = min_other_cluster_distance;
891
892 if a < b {
893 total_score += (b - a) / b;
894 } else if a > b {
895 total_score += (b - a) / a;
896 }
897 valid_points += 1;
900 }
901
902 Ok(if valid_points > 0 { total_score / valid_points as f64 } else { 0.0 })
903 }
904
905 fn calculate_inertia(
907 &self,
908 data: &[Vec<f64>],
909 assignments: &[usize],
910 centers: &[Vec<f64>],
911 ) -> Result<f64> {
912 let mut inertia = 0.0;
913
914 for (point, &cluster_id) in data.iter().zip(assignments.iter()) {
915 if cluster_id < centers.len() {
916 let distance = self.calculate_distance(
917 point,
918 ¢ers[cluster_id],
919 &DistanceMetric::Euclidean,
920 )?;
921 inertia += distance.powi(2);
922 }
923 }
924
925 Ok(inertia)
926 }
927
928 fn calculate_information_content(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
930 if hidden_states.is_empty() {
931 return Ok(0.0);
932 }
933
934 let dimensions = hidden_states[0].len();
935 let mut total_variance = 0.0;
936
937 for dim in 0..dimensions {
938 let values: Vec<f64> = hidden_states.iter().map(|state| state[dim]).collect();
939 if values.len() > 1 {
940 let mean = values.iter().sum::<f64>() / values.len() as f64;
941 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
942 / (values.len() - 1) as f64;
943 total_variance += variance;
944 }
945 }
946
947 Ok(total_variance / dimensions as f64)
949 }
950
951 fn calculate_temporal_consistency(&self, layer_data: &[&HiddenStateData]) -> Result<f64> {
953 if layer_data.len() < 2 {
954 return Ok(1.0);
955 }
956
957 let mut consistency_scores = Vec::new();
958
959 for i in 1..layer_data.len() {
960 let prev_states = &layer_data[i - 1].hidden_states;
961 let curr_states = &layer_data[i].hidden_states;
962
963 if !prev_states.is_empty() && !curr_states.is_empty() {
964 let prev_mean = self.calculate_mean_state(prev_states);
966 let curr_mean = self.calculate_mean_state(curr_states);
967
968 if prev_mean.len() == curr_mean.len() {
969 let distance = self.calculate_distance(
970 &prev_mean,
971 &curr_mean,
972 &DistanceMetric::Euclidean,
973 )?;
974 consistency_scores.push(1.0 / (1.0 + distance));
975 }
976 }
977 }
978
979 Ok(if consistency_scores.is_empty() {
980 1.0
981 } else {
982 consistency_scores.iter().sum::<f64>() / consistency_scores.len() as f64
983 })
984 }
985
986 fn calculate_mean_state(&self, states: &[Vec<f64>]) -> Vec<f64> {
988 if states.is_empty() {
989 return Vec::new();
990 }
991
992 let dimensions = states[0].len();
993 let mut mean_state = vec![0.0; dimensions];
994
995 for state in states {
996 for (i, &value) in state.iter().enumerate() {
997 if i < dimensions {
998 mean_state[i] += value;
999 }
1000 }
1001 }
1002
1003 for value in &mut mean_state {
1004 *value /= states.len() as f64;
1005 }
1006
1007 mean_state
1008 }
1009
1010 fn calculate_change_rate(&self, layer_data: &[&HiddenStateData]) -> Result<f64> {
1012 if layer_data.len() < 2 {
1013 return Ok(0.0);
1014 }
1015
1016 let mut total_change = 0.0;
1017 let mut valid_comparisons = 0;
1018
1019 for i in 1..layer_data.len() {
1020 let prev_mean = self.calculate_mean_state(&layer_data[i - 1].hidden_states);
1021 let curr_mean = self.calculate_mean_state(&layer_data[i].hidden_states);
1022
1023 if !prev_mean.is_empty() && !curr_mean.is_empty() && prev_mean.len() == curr_mean.len()
1024 {
1025 let change =
1026 self.calculate_distance(&prev_mean, &curr_mean, &DistanceMetric::Euclidean)?;
1027 total_change += change;
1028 valid_comparisons += 1;
1029 }
1030 }
1031
1032 Ok(if valid_comparisons > 0 {
1033 total_change / valid_comparisons as f64
1034 } else {
1035 0.0
1036 })
1037 }
1038
1039 fn identify_stability_windows(
1041 &self,
1042 layer_data: &[&HiddenStateData],
1043 ) -> Result<Vec<(usize, usize)>> {
1044 if layer_data.len() < 3 {
1045 return Ok(Vec::new());
1046 }
1047
1048 let mut stability_windows = Vec::new();
1049 let mut window_start = 0;
1050 let stability_threshold = 0.1; for i in 1..layer_data.len() {
1053 let prev_mean = self.calculate_mean_state(&layer_data[i - 1].hidden_states);
1054 let curr_mean = self.calculate_mean_state(&layer_data[i].hidden_states);
1055
1056 if !prev_mean.is_empty() && !curr_mean.is_empty() && prev_mean.len() == curr_mean.len()
1057 {
1058 let change = self
1059 .calculate_distance(&prev_mean, &curr_mean, &DistanceMetric::Euclidean)
1060 .unwrap_or(f64::INFINITY);
1061
1062 if change > stability_threshold {
1063 if i - window_start > 2 {
1065 stability_windows.push((window_start, i - 1));
1066 }
1067 window_start = i;
1068 }
1069 }
1070 }
1071
1072 if layer_data.len() - window_start > 2 {
1074 stability_windows.push((window_start, layer_data.len() - 1));
1075 }
1076
1077 Ok(stability_windows)
1078 }
1079
1080 fn detect_distribution_drift(&self, layer_data: &[&HiddenStateData]) -> Result<DriftInfo> {
1082 if layer_data.len() < self.config.temporal_analysis_window {
1083 return Ok(DriftInfo {
1084 drift_detected: false,
1085 drift_magnitude: 0.0,
1086 drift_direction: "unknown".to_string(),
1087 onset_step: None,
1088 });
1089 }
1090
1091 let window_size = self.config.temporal_analysis_window;
1092 let mid_point = layer_data.len() / 2;
1093
1094 let early_data = &layer_data[0..window_size.min(mid_point)];
1096 let late_data = &layer_data[mid_point.max(layer_data.len() - window_size)..];
1097
1098 let early_mean = self.calculate_aggregated_mean(early_data);
1099 let late_mean = self.calculate_aggregated_mean(late_data);
1100
1101 if early_mean.len() == late_mean.len() && !early_mean.is_empty() {
1102 let drift_magnitude =
1103 self.calculate_distance(&early_mean, &late_mean, &DistanceMetric::Euclidean)?;
1104 let drift_detected = drift_magnitude > self.config.drift_detection_sensitivity;
1105
1106 Ok(DriftInfo {
1107 drift_detected,
1108 drift_magnitude,
1109 drift_direction: if drift_detected {
1110 "forward".to_string()
1111 } else {
1112 "stable".to_string()
1113 },
1114 onset_step: if drift_detected { Some(mid_point) } else { None },
1115 })
1116 } else {
1117 Ok(DriftInfo {
1118 drift_detected: false,
1119 drift_magnitude: 0.0,
1120 drift_direction: "unknown".to_string(),
1121 onset_step: None,
1122 })
1123 }
1124 }
1125
1126 fn calculate_aggregated_mean(&self, layer_data: &[&HiddenStateData]) -> Vec<f64> {
1128 let all_states: Vec<Vec<f64>> =
1129 layer_data.iter().flat_map(|data| data.hidden_states.iter()).cloned().collect();
1130
1131 self.calculate_mean_state(&all_states)
1132 }
1133
1134 fn calculate_stability_score(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1136 if hidden_states.len() < 2 {
1137 return Ok(1.0);
1138 }
1139
1140 let mut stability_scores = Vec::new();
1141 let window_size = (hidden_states.len() / 10).max(2);
1142
1143 for i in window_size..hidden_states.len() {
1144 let current_window = &hidden_states[i - window_size..i];
1145 let mean_current = self.calculate_mean_state(current_window);
1146
1147 if i >= 2 * window_size {
1148 let prev_window = &hidden_states[i - 2 * window_size..i - window_size];
1149 let mean_prev = self.calculate_mean_state(prev_window);
1150
1151 if mean_current.len() == mean_prev.len() && !mean_current.is_empty() {
1152 let distance = self.calculate_distance(
1153 &mean_current,
1154 &mean_prev,
1155 &DistanceMetric::Euclidean,
1156 )?;
1157 stability_scores.push(1.0 / (1.0 + distance));
1158 }
1159 }
1160 }
1161
1162 Ok(if stability_scores.is_empty() {
1163 1.0
1164 } else {
1165 stability_scores.iter().sum::<f64>() / stability_scores.len() as f64
1166 })
1167 }
1168
1169 fn calculate_batch_variance(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1171 if hidden_states.is_empty() {
1172 return Ok(0.0);
1173 }
1174
1175 let dimensions = hidden_states[0].len();
1176 let mut total_variance = 0.0;
1177
1178 for dim in 0..dimensions {
1179 let values: Vec<f64> = hidden_states.iter().map(|state| state[dim]).collect();
1180 if values.len() > 1 {
1181 let mean = values.iter().sum::<f64>() / values.len() as f64;
1182 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
1183 / (values.len() - 1) as f64;
1184 total_variance += variance;
1185 }
1186 }
1187
1188 Ok(total_variance / dimensions as f64)
1189 }
1190
1191 fn calculate_consistency_measure(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1193 if hidden_states.len() < 2 {
1194 return Ok(1.0);
1195 }
1196
1197 let mut similarities = Vec::new();
1199 let sample_size = hidden_states.len().min(100); for i in 0..sample_size {
1202 for j in (i + 1)..sample_size {
1203 let distance = self.calculate_distance(
1204 &hidden_states[i],
1205 &hidden_states[j],
1206 &DistanceMetric::Cosine,
1207 )?;
1208 similarities.push(1.0 - distance); }
1210 }
1211
1212 Ok(if similarities.is_empty() {
1213 1.0
1214 } else {
1215 similarities.iter().sum::<f64>() / similarities.len() as f64
1216 })
1217 }
1218
1219 fn assess_noise_robustness(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1221 self.calculate_batch_variance(hidden_states).map(|variance| {
1223 1.0 / (1.0 + variance)
1225 })
1226 }
1227
1228 fn calculate_correlation(&self, metric1: &str, metric2: &str) -> Result<f64> {
1230 let data1 = self
1231 .performance_correlations
1232 .get(metric1)
1233 .ok_or_else(|| anyhow::anyhow!("Metric {} not found", metric1))?;
1234
1235 let data2 = self
1236 .performance_correlations
1237 .get(metric2)
1238 .ok_or_else(|| anyhow::anyhow!("Metric {} not found", metric2))?;
1239
1240 let values1: Vec<f64> = data1.values.iter().cloned().collect();
1241 let values2: Vec<f64> = data2.values.iter().cloned().collect();
1242
1243 if values1.len() != values2.len() || values1.is_empty() {
1244 return Ok(0.0);
1245 }
1246
1247 let mean1 = values1.iter().sum::<f64>() / values1.len() as f64;
1248 let mean2 = values2.iter().sum::<f64>() / values2.len() as f64;
1249
1250 let numerator: f64 = values1
1251 .iter()
1252 .zip(values2.iter())
1253 .map(|(x1, x2)| (x1 - mean1) * (x2 - mean2))
1254 .sum();
1255
1256 let var1: f64 = values1.iter().map(|x| (x - mean1).powi(2)).sum();
1257 let var2: f64 = values2.iter().map(|x| (x - mean2).powi(2)).sum();
1258
1259 let denominator = (var1 * var2).sqrt();
1260
1261 Ok(if denominator == 0.0 { 0.0 } else { numerator / denominator })
1262 }
1263
1264 fn generate_temporal_summary(&self) -> String {
1266 format!(
1267 "Temporal analysis: {} hidden state samples collected across {} layers. \
1268 Average stability observed with {} correlation metrics tracked.",
1269 self.hidden_states_history.len(),
1270 self.hidden_states_history
1271 .iter()
1272 .map(|data| &data.layer_name)
1273 .collect::<std::collections::HashSet<_>>()
1274 .len(),
1275 self.performance_correlations.len()
1276 )
1277 }
1278
1279 fn generate_analytics_recommendations(&self) -> Vec<String> {
1281 let mut recommendations = Vec::new();
1282
1283 if self.performance_correlations.len() < 3 {
1284 recommendations.push(
1285 "Collect more performance metrics for comprehensive correlation analysis"
1286 .to_string(),
1287 );
1288 }
1289
1290 if self.hidden_states_history.len() < 50 {
1291 recommendations
1292 .push("Increase hidden state sampling for better temporal analysis".to_string());
1293 }
1294
1295 recommendations
1296 .push("Consider implementing automated anomaly detection alerts".to_string());
1297 recommendations.push("Enable advanced visualization for better insights".to_string());
1298
1299 recommendations
1300 }
1301}
1302
1303#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1305pub struct AnalyticsReport {
1306 pub correlation_matrix: Vec<Vec<f64>>,
1308 pub statistical_analysis: StatisticalAnalysis,
1310 pub layer_analyses: HashMap<String, HiddenStateAnalysis>,
1312 pub anomaly_detection: AnomalyDetectionResults,
1314 pub temporal_summary: String,
1316 pub recommendations: Vec<String>,
1318}
1319
1320impl TemporalAnalysisCache {
1321 fn new() -> Self {
1323 Self {
1324 drift_results: HashMap::new(),
1325 consistency_scores: HashMap::new(),
1326 stability_windows: HashMap::new(),
1327 last_analysis: chrono::Utc::now(),
1328 }
1329 }
1330}
1331
1332impl Default for AnomalyDetectionResults {
1333 fn default() -> Self {
1334 Self {
1335 anomalies: Vec::new(),
1336 anomaly_scores: Vec::new(),
1337 threshold: 0.0,
1338 method: AnomalyDetectionMethod::StatisticalThreshold { n_std: 2.0 },
1339 }
1340 }
1341}
1342
1343impl Default for AdvancedAnalytics {
1344 fn default() -> Self {
1345 Self::new()
1346 }
1347}
1348
1349#[cfg(test)]
1350mod tests {
1351 use super::*;
1352
1353 #[test]
1354 fn test_advanced_analytics_creation() {
1355 let analytics = AdvancedAnalytics::new();
1356 assert_eq!(analytics.hidden_states_history.len(), 0);
1357 assert_eq!(analytics.performance_correlations.len(), 0);
1358 }
1359
1360 #[test]
1361 fn test_distance_calculation() {
1362 let analytics = AdvancedAnalytics::new();
1363 let point1 = vec![1.0, 2.0, 3.0];
1364 let point2 = vec![4.0, 5.0, 6.0];
1365
1366 let distance = analytics
1367 .calculate_distance(&point1, &point2, &DistanceMetric::Euclidean)
1368 .expect("operation failed in test");
1369 assert!(distance > 0.0);
1370 }
1371
1372 #[test]
1373 fn test_clustering_parameters() {
1374 let params = ClusteringParameters::default();
1375 assert_eq!(params.num_clusters, 8);
1376 assert_eq!(params.max_iterations, 100);
1377 }
1378
1379 #[test]
1380 fn test_correlation_calculation() {
1381 let mut analytics = AdvancedAnalytics::new();
1382
1383 analytics.update_correlation_data("metric1", 1.0);
1385 analytics.update_correlation_data("metric1", 2.0);
1386 analytics.update_correlation_data("metric2", 3.0);
1387 analytics.update_correlation_data("metric2", 4.0);
1388
1389 assert_eq!(analytics.performance_correlations.len(), 2);
1390 }
1391}