Skip to main content

trustformers_debug/model_diagnostics/
analytics.rs

1//! Advanced analytics for model behavior analysis.
2//!
3//! This module provides sophisticated analytical capabilities including
4//! clustering analysis, temporal dynamics monitoring, representation
5//! stability assessment, and multi-dimensional data visualization.
6// reason: debug/profiling scaffolding — structs are constructed and their fields/methods
7// are retained for the data model, serialization completeness, and future consumers that
8// do not yet read every member. Consolidated from many item-level #[allow(dead_code)].
9#![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/// Advanced analytics engine for model behavior analysis.
20#[derive(Debug)]
21pub struct AdvancedAnalytics {
22    /// Analytics configuration
23    config: AnalyticsConfig,
24    /// Historical hidden state data
25    hidden_states_history: VecDeque<HiddenStateData>,
26    /// Performance correlation data
27    performance_correlations: HashMap<String, CorrelationData>,
28    /// Temporal analysis cache
29    temporal_analysis_cache: TemporalAnalysisCache,
30    /// Clustering analysis results
31    clustering_results_cache: HashMap<String, ClusteringResults>,
32}
33
34/// Configuration for advanced analytics.
35#[derive(Debug, Clone)]
36pub struct AnalyticsConfig {
37    /// Maximum number of historical samples to retain
38    pub max_history_samples: usize,
39    /// Minimum samples required for clustering analysis
40    pub min_clustering_samples: usize,
41    /// Number of clusters for k-means analysis
42    pub default_num_clusters: usize,
43    /// Window size for temporal analysis
44    pub temporal_analysis_window: usize,
45    /// Drift detection sensitivity
46    pub drift_detection_sensitivity: f64,
47    /// Correlation analysis threshold
48    pub correlation_threshold: f64,
49    /// Enable advanced visualizations
50    pub enable_visualizations: bool,
51}
52
53/// Hidden state data for analysis.
54#[derive(Debug, Clone)]
55pub struct HiddenStateData {
56    /// Layer name
57    pub layer_name: String,
58    /// Hidden state vectors
59    pub hidden_states: Vec<Vec<f64>>,
60    /// Corresponding labels or metadata
61    pub labels: Option<Vec<String>>,
62    /// Timestamp of collection
63    pub timestamp: chrono::DateTime<chrono::Utc>,
64    /// Training step when collected
65    pub training_step: usize,
66}
67
68/// Correlation analysis data.
69#[derive(Debug, Clone)]
70pub struct CorrelationData {
71    /// Metric name
72    pub metric_name: String,
73    /// Historical values
74    pub values: VecDeque<f64>,
75    /// Correlations with other metrics
76    pub correlations: HashMap<String, f64>,
77    /// Last update timestamp
78    pub last_updated: chrono::DateTime<chrono::Utc>,
79}
80
81/// Temporal analysis cache for performance optimization.
82#[derive(Debug, Clone)]
83pub struct TemporalAnalysisCache {
84    /// Cached drift detection results
85    pub drift_results: HashMap<String, DriftInfo>,
86    /// Cached temporal consistency scores
87    pub consistency_scores: HashMap<String, f64>,
88    /// Cached stability windows
89    pub stability_windows: HashMap<String, Vec<(usize, usize)>>,
90    /// Last analysis timestamp
91    pub last_analysis: chrono::DateTime<chrono::Utc>,
92}
93
94/// Advanced clustering analysis parameters.
95#[derive(Debug, Clone)]
96pub struct ClusteringParameters {
97    /// Number of clusters
98    pub num_clusters: usize,
99    /// Maximum iterations for k-means
100    pub max_iterations: usize,
101    /// Convergence tolerance
102    pub tolerance: f64,
103    /// Random seed for reproducibility
104    pub random_seed: Option<u64>,
105    /// Distance metric to use
106    pub distance_metric: DistanceMetric,
107}
108
109/// Distance metrics for clustering.
110#[derive(Debug, Clone)]
111pub enum DistanceMetric {
112    /// Euclidean distance
113    Euclidean,
114    /// Manhattan distance
115    Manhattan,
116    /// Cosine similarity
117    Cosine,
118    /// Minkowski distance with parameter p
119    Minkowski { p: f64 },
120}
121
122/// Dimensionality reduction parameters.
123#[derive(Debug, Clone)]
124pub struct DimensionalityReductionParams {
125    /// Target dimensions
126    pub target_dimensions: usize,
127    /// Reduction method
128    pub method: ReductionMethod,
129    /// Preserve variance ratio
130    pub preserve_variance_ratio: f64,
131}
132
133/// Dimensionality reduction methods.
134#[derive(Debug, Clone)]
135pub enum ReductionMethod {
136    /// Principal Component Analysis
137    PCA,
138    /// t-SNE
139    TSNE { perplexity: f64 },
140    /// UMAP
141    UMAP { n_neighbors: usize, min_dist: f64 },
142}
143
144/// Visualization generation parameters.
145#[derive(Debug, Clone)]
146pub struct VisualizationParams {
147    /// Output dimensions (width, height)
148    pub dimensions: (usize, usize),
149    /// Color scheme
150    pub color_scheme: ColorScheme,
151    /// Include annotations
152    pub include_annotations: bool,
153    /// Export format
154    pub export_format: ExportFormat,
155}
156
157/// Color schemes for visualizations.
158#[derive(Debug, Clone)]
159pub enum ColorScheme {
160    /// Viridis color scale
161    Viridis,
162    /// Plasma color scale
163    Plasma,
164    /// Inferno color scale
165    Inferno,
166    /// Custom color map
167    Custom(Vec<(f64, f64, f64)>),
168}
169
170/// Export formats for visualizations.
171#[derive(Debug, Clone)]
172pub enum ExportFormat {
173    /// PNG image
174    PNG,
175    /// SVG vector
176    SVG,
177    /// JSON data
178    JSON,
179    /// CSV data
180    CSV,
181}
182
183/// Statistical analysis results.
184#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
185pub struct StatisticalAnalysis {
186    /// Mean values
187    pub means: Vec<f64>,
188    /// Standard deviations
189    pub std_devs: Vec<f64>,
190    /// Correlation matrix
191    pub correlation_matrix: Vec<Vec<f64>>,
192    /// Principal components
193    pub principal_components: Vec<Vec<f64>>,
194    /// Explained variance ratios
195    pub explained_variance_ratios: Vec<f64>,
196    /// Statistical significance tests
197    pub significance_tests: Vec<SignificanceTest>,
198}
199
200/// Statistical significance test result.
201#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
202pub struct SignificanceTest {
203    /// Test name
204    pub test_name: String,
205    /// Test statistic
206    pub statistic: f64,
207    /// P-value
208    pub p_value: f64,
209    /// Degrees of freedom
210    pub degrees_of_freedom: Option<usize>,
211    /// Confidence interval
212    pub confidence_interval: Option<(f64, f64)>,
213}
214
215/// Anomaly detection results.
216#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
217pub struct AnomalyDetectionResults {
218    /// Detected anomalies
219    pub anomalies: Vec<Anomaly>,
220    /// Anomaly scores for all data points
221    pub anomaly_scores: Vec<f64>,
222    /// Detection threshold used
223    pub threshold: f64,
224    /// Detection method
225    pub method: AnomalyDetectionMethod,
226}
227
228/// Individual anomaly information.
229#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
230pub struct Anomaly {
231    /// Index in dataset
232    pub index: usize,
233    /// Anomaly score
234    pub score: f64,
235    /// Timestamp
236    pub timestamp: chrono::DateTime<chrono::Utc>,
237    /// Additional context
238    pub context: HashMap<String, String>,
239}
240
241/// Anomaly detection methods.
242#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
243pub enum AnomalyDetectionMethod {
244    /// Isolation Forest
245    IsolationForest { n_trees: usize },
246    /// Local Outlier Factor
247    LocalOutlierFactor { n_neighbors: usize },
248    /// One-Class SVM
249    OneClassSVM { nu: f64 },
250    /// Statistical threshold
251    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    /// Create a new advanced analytics engine.
282    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    /// Create analytics engine with custom configuration.
293    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    /// Record hidden state data for analysis.
304    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    /// Record performance metrics for correlation analysis.
313    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    /// Perform comprehensive hidden state analysis.
328    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        // Extract all hidden states for this layer
343        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        // Perform clustering analysis
356        let clustering_results = self.perform_clustering_analysis(&all_states)?;
357
358        // Analyze temporal dynamics
359        let temporal_dynamics = self.analyze_temporal_dynamics(&layer_data)?;
360
361        // Assess representation stability
362        let representation_stability = self.assess_representation_stability(&all_states)?;
363
364        // Calculate information content
365        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    /// Perform clustering analysis on hidden states.
377    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        // Simple k-means clustering implementation
386        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            // Assign points to nearest cluster
391            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, &params.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            // Check for convergence
408            if new_assignments == cluster_assignments {
409                break;
410            }
411            cluster_assignments = new_assignments;
412
413            // Update cluster centers
414            cluster_centers =
415                self.update_cluster_centers(data, &cluster_assignments, num_clusters)?;
416        }
417
418        // Calculate silhouette score
419        let silhouette_score =
420            self.calculate_silhouette_score(data, &cluster_assignments, &cluster_centers)?;
421
422        // Calculate inertia
423        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    /// Analyze temporal dynamics of hidden states.
435    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        // Calculate temporal consistency
444        let temporal_consistency = self.calculate_temporal_consistency(layer_data)?;
445
446        // Calculate change rate
447        let change_rate = self.calculate_change_rate(layer_data)?;
448
449        // Identify stability windows
450        let stability_windows = self.identify_stability_windows(layer_data)?;
451
452        // Detect distribution drift
453        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    /// Assess representation stability.
464    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        // Calculate overall stability score
473        let stability_score = self.calculate_stability_score(hidden_states)?;
474
475        // Calculate variance across batches (simulated)
476        let variance_across_batches = self.calculate_batch_variance(hidden_states)?;
477
478        // Calculate consistency measure
479        let consistency_measure = self.calculate_consistency_measure(hidden_states)?;
480
481        // Assess robustness to noise (simulated)
482        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    /// Generate activation heatmap for visualization.
493    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        // Create heatmap data based on layer statistics
502        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    /// Perform anomaly detection on performance metrics.
537    pub fn detect_performance_anomalies(&self) -> Result<AnomalyDetectionResults> {
538        // Extract performance data
539        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        // Use statistical threshold method for anomaly detection
549        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    /// Calculate correlation matrix for all metrics.
584    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    /// Perform statistical analysis on collected data.
611    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        // Calculate means and standard deviations
619        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        // Calculate correlation matrix
636        let correlation_matrix = self.calculate_correlation_matrix()?;
637
638        // Placeholder for principal components and explained variance
639        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        // Placeholder for significance tests
643        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    /// Generate comprehensive analytics report.
662    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        // Analyze each layer if data is available
668        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    // Helper methods
689
690    /// Update correlation data for a metric.
691    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        // Limit history size
706        while correlation_data.values.len() > self.config.max_history_samples {
707            correlation_data.values.pop_front();
708        }
709    }
710
711    /// Initialize cluster centers using k-means++ algorithm.
712    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        // Choose first center randomly (simplified - just use first point)
725        centers.push(data[0].clone());
726
727        // Choose remaining centers using k-means++ logic (simplified)
728        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 &centers {
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    /// Calculate distance between two points.
757    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    /// Update cluster centers based on current assignments.
801    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        // Sum points in each cluster
812        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        // Average to get new centers
822        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    /// Calculate silhouette score for clustering quality.
834    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            // Calculate average distance to points in same cluster (a)
851            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            // Calculate minimum average distance to points in other clusters (b)
867            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            // If a == b, silhouette score is 0 (no contribution)
898
899            valid_points += 1;
900        }
901
902        Ok(if valid_points > 0 { total_score / valid_points as f64 } else { 0.0 })
903    }
904
905    /// Calculate inertia (sum of squared distances to centroids).
906    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                    &centers[cluster_id],
919                    &DistanceMetric::Euclidean,
920                )?;
921                inertia += distance.powi(2);
922            }
923        }
924
925        Ok(inertia)
926    }
927
928    /// Calculate information content of hidden states.
929    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        // Information content as normalized variance
948        Ok(total_variance / dimensions as f64)
949    }
950
951    /// Calculate temporal consistency of hidden states.
952    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                // Simple consistency measure based on mean state similarity
965                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    /// Calculate mean state from a collection of states.
987    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    /// Calculate change rate between temporal samples.
1011    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    /// Identify stability windows in temporal data.
1040    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; // Configurable threshold
1051
1052        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                    // End of stability window
1064                    if i - window_start > 2 {
1065                        stability_windows.push((window_start, i - 1));
1066                    }
1067                    window_start = i;
1068                }
1069            }
1070        }
1071
1072        // Handle final window
1073        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    /// Detect distribution drift in temporal data.
1081    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        // Compare early and late windows
1095        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    /// Calculate aggregated mean across multiple data samples.
1127    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    /// Calculate stability score for representation.
1135    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    /// Calculate variance across batches (simulated).
1170    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    /// Calculate consistency measure for representation.
1192    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        // Calculate pairwise similarities and return average
1198        let mut similarities = Vec::new();
1199        let sample_size = hidden_states.len().min(100); // Limit for performance
1200
1201        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); // Convert distance to similarity
1209            }
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    /// Assess robustness to noise (simulated).
1220    fn assess_noise_robustness(&self, hidden_states: &[Vec<f64>]) -> Result<f64> {
1221        // Simplified robustness assessment based on state variance
1222        self.calculate_batch_variance(hidden_states).map(|variance| {
1223            // High variance might indicate low robustness to noise
1224            1.0 / (1.0 + variance)
1225        })
1226    }
1227
1228    /// Calculate correlation between two metrics.
1229    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    /// Generate temporal summary.
1265    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    /// Generate analytics recommendations.
1280    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/// Comprehensive analytics report.
1304#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1305pub struct AnalyticsReport {
1306    /// Correlation matrix between metrics
1307    pub correlation_matrix: Vec<Vec<f64>>,
1308    /// Statistical analysis results
1309    pub statistical_analysis: StatisticalAnalysis,
1310    /// Layer-specific analyses
1311    pub layer_analyses: HashMap<String, HiddenStateAnalysis>,
1312    /// Anomaly detection results
1313    pub anomaly_detection: AnomalyDetectionResults,
1314    /// Temporal analysis summary
1315    pub temporal_summary: String,
1316    /// Analytics recommendations
1317    pub recommendations: Vec<String>,
1318}
1319
1320impl TemporalAnalysisCache {
1321    /// Create a new temporal analysis cache.
1322    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        // Add some test data
1384        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}