use super::super::results::*;
use crate::DeviceResult;
use scirs2_core::ndarray::Array2;
pub struct CorrelationAnalyzer {
}
impl CorrelationAnalyzer {
pub const fn new() -> Self {
Self {}
}
pub fn analyze(
&self,
latencies: &[f64],
confidences: &[f64],
timestamps: &[f64],
) -> DeviceResult<CorrelationAnalysisResults> {
if latencies.is_empty() || confidences.is_empty() || timestamps.is_empty() {
return Ok(CorrelationAnalysisResults::default());
}
let n_vars = 3;
let n_samples = latencies.len();
let pearson_correlations =
self.calculate_pearson_correlations(latencies, confidences, timestamps)?;
let spearman_correlations =
self.calculate_spearman_correlations(latencies, confidences, timestamps)?;
let kendall_correlations =
self.calculate_kendall_correlations(latencies, confidences, timestamps)?;
let significant_correlations = self.find_significant_correlations(&pearson_correlations)?;
let partial_correlations =
self.calculate_partial_correlations(latencies, confidences, timestamps)?;
let network_analysis = self.perform_network_analysis(&pearson_correlations)?;
Ok(CorrelationAnalysisResults {
pearson_correlations,
spearman_correlations,
kendall_correlations,
significant_correlations,
partial_correlations,
network_analysis,
})
}
fn calculate_pearson_correlations(
&self,
latencies: &[f64],
confidences: &[f64],
timestamps: &[f64],
) -> DeviceResult<Array2<f64>> {
let variables = [latencies, confidences, timestamps];
let n_vars = variables.len();
let mut correlationmatrix = Array2::zeros((n_vars, n_vars));
for i in 0..n_vars {
for j in 0..n_vars {
if i == j {
correlationmatrix[[i, j]] = 1.0;
} else {
let corr = self.pearson_correlation(variables[i], variables[j]);
correlationmatrix[[i, j]] = corr;
}
}
}
Ok(correlationmatrix)
}
fn calculate_spearman_correlations(
&self,
latencies: &[f64],
confidences: &[f64],
timestamps: &[f64],
) -> DeviceResult<Array2<f64>> {
let variables = [latencies, confidences, timestamps];
let n_vars = variables.len();
let mut correlationmatrix = Array2::zeros((n_vars, n_vars));
for i in 0..n_vars {
for j in 0..n_vars {
if i == j {
correlationmatrix[[i, j]] = 1.0;
} else {
let ranks_i = self.convert_to_ranks(variables[i]);
let ranks_j = self.convert_to_ranks(variables[j]);
let corr = self.pearson_correlation(&ranks_i, &ranks_j);
correlationmatrix[[i, j]] = corr;
}
}
}
Ok(correlationmatrix)
}
fn calculate_kendall_correlations(
&self,
latencies: &[f64],
confidences: &[f64],
timestamps: &[f64],
) -> DeviceResult<Array2<f64>> {
let variables = [latencies, confidences, timestamps];
let n_vars = variables.len();
let mut correlationmatrix = Array2::zeros((n_vars, n_vars));
for i in 0..n_vars {
for j in 0..n_vars {
if i == j {
correlationmatrix[[i, j]] = 1.0;
} else {
let corr = self.kendall_tau(variables[i], variables[j]);
correlationmatrix[[i, j]] = corr;
}
}
}
Ok(correlationmatrix)
}
fn pearson_correlation(&self, x: &[f64], y: &[f64]) -> f64 {
if x.len() != y.len() || x.len() < 2 {
return 0.0;
}
let n = x.len() as f64;
let mean_x = x.iter().sum::<f64>() / n;
let mean_y = y.iter().sum::<f64>() / n;
let numerator: f64 = x
.iter()
.zip(y.iter())
.map(|(&xi, &yi)| (xi - mean_x) * (yi - mean_y))
.sum();
let sum_sq_x: f64 = x.iter().map(|&xi| (xi - mean_x).powi(2)).sum();
let sum_sq_y: f64 = y.iter().map(|&yi| (yi - mean_y).powi(2)).sum();
let denominator = (sum_sq_x * sum_sq_y).sqrt();
if denominator > 1e-10 {
numerator / denominator
} else {
0.0
}
}
fn convert_to_ranks(&self, values: &[f64]) -> Vec<f64> {
let mut indexed_values: Vec<(usize, f64)> =
values.iter().enumerate().map(|(i, &v)| (i, v)).collect();
indexed_values.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let mut ranks = vec![0.0; values.len()];
for (rank, &(original_index, _)) in indexed_values.iter().enumerate() {
ranks[original_index] = (rank + 1) as f64;
}
ranks
}
fn kendall_tau(&self, x: &[f64], y: &[f64]) -> f64 {
if x.len() != y.len() || x.len() < 2 {
return 0.0;
}
let n = x.len();
let mut concordant = 0;
let mut discordant = 0;
for i in 0..n {
for j in (i + 1)..n {
let x_diff = x[i] - x[j];
let y_diff = y[i] - y[j];
if x_diff * y_diff > 0.0 {
concordant += 1;
} else if x_diff * y_diff < 0.0 {
discordant += 1;
}
}
}
let total_pairs = n * (n - 1) / 2;
if total_pairs > 0 {
(concordant as f64 - discordant as f64) / total_pairs as f64
} else {
0.0
}
}
fn find_significant_correlations(
&self,
correlationmatrix: &Array2<f64>,
) -> DeviceResult<Vec<CorrelationPair>> {
let mut significant_correlations = Vec::new();
let variable_names = ["latency", "confidence", "timestamp"];
let threshold = 0.3;
for i in 0..correlationmatrix.nrows() {
for j in (i + 1)..correlationmatrix.ncols() {
let correlation = correlationmatrix[[i, j]];
if correlation.abs() > threshold {
significant_correlations.push(CorrelationPair {
variable1: variable_names[i].to_string(),
variable2: variable_names[j].to_string(),
correlation,
p_value: self.estimate_p_value(correlation, correlationmatrix.nrows()),
correlation_type: CorrelationType::Pearson,
});
}
}
}
Ok(significant_correlations)
}
fn estimate_p_value(&self, correlation: f64, n: usize) -> f64 {
if n < 3 {
return 0.5;
}
let t_stat = correlation * ((n - 2) as f64 / correlation.mul_add(-correlation, 1.0)).sqrt();
if t_stat.abs() > 2.0 {
0.01
} else if t_stat.abs() > 1.5 {
0.05
} else {
0.10
}
}
fn calculate_partial_correlations(
&self,
latencies: &[f64],
confidences: &[f64],
timestamps: &[f64],
) -> DeviceResult<Array2<f64>> {
self.calculate_pearson_correlations(latencies, confidences, timestamps)
}
fn perform_network_analysis(
&self,
correlationmatrix: &Array2<f64>,
) -> DeviceResult<CorrelationNetworkAnalysis> {
let threshold = 0.3;
let n = correlationmatrix.nrows();
let mut adjacency_matrix = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
if i != j && correlationmatrix[[i, j]].abs() > threshold {
adjacency_matrix[[i, j]] = correlationmatrix[[i, j]].abs();
}
}
}
let centrality_measures = self.calculate_centrality_measures(&adjacency_matrix)?;
let communities = vec![(0..n).collect()];
let total_possible_edges = n * (n - 1);
let actual_edges = adjacency_matrix.iter().filter(|&&x| x > 0.0).count();
let network_density = if total_possible_edges > 0 {
actual_edges as f64 / total_possible_edges as f64
} else {
0.0
};
let clustering_coefficient = 0.5;
Ok(CorrelationNetworkAnalysis {
adjacency_matrix,
centrality_measures,
communities,
network_density,
clustering_coefficient,
})
}
fn calculate_centrality_measures(
&self,
adjacency_matrix: &Array2<f64>,
) -> DeviceResult<NodeCentralityMeasures> {
let n = adjacency_matrix.nrows();
let mut degree = vec![0.0; n];
for i in 0..n {
for j in 0..n {
if adjacency_matrix[[i, j]] > 0.0 {
degree[i] += 1.0;
}
}
}
let max_degree = (n - 1) as f64;
for d in &mut degree {
if max_degree > 0.0 {
*d /= max_degree;
}
}
let betweenness = vec![0.5; n];
let closeness = vec![0.5; n];
let eigenvector = vec![0.5; n];
Ok(NodeCentralityMeasures {
betweenness,
closeness,
eigenvector,
degree,
})
}
}
impl Default for CorrelationAnalyzer {
fn default() -> Self {
Self::new()
}
}