use crate::core::{TimeSeries, VisibilityGraph, GraphError};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct BatchResults<T> {
pub graphs: Vec<VisibilityGraph<T>>,
pub labels: Vec<String>,
pub statistics: BatchStatistics,
}
#[derive(Debug, Clone)]
pub struct BatchStatistics {
pub avg_edges: f64,
pub avg_clustering: f64,
pub avg_path_length: f64,
pub avg_density: f64,
}
pub struct BatchProcessor<'a, T> {
series_list: Vec<(&'a TimeSeries<T>, String)>,
}
impl<'a, T> BatchProcessor<'a, T>
where
T: Copy + PartialOrd + Into<f64>,
{
pub fn new() -> Self {
Self {
series_list: Vec::new(),
}
}
pub fn add_series(mut self, series: &'a TimeSeries<T>, label: &str) -> Self {
self.series_list.push((series, label.to_string()));
self
}
pub fn process_natural(self) -> Result<BatchResults<T>, GraphError>
where
T: Copy + PartialOrd + Into<f64> + std::ops::Add<Output = T> + std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T> + std::ops::Div<Output = T> + From<f64> + Send + Sync,
{
let mut graphs = Vec::new();
let mut labels = Vec::new();
for (series, label) in &self.series_list {
let builder = VisibilityGraph::from_series(series);
let graph = builder.natural_visibility()?;
graphs.push(graph);
labels.push(label.clone());
}
let statistics = Self::compute_statistics(&graphs);
Ok(BatchResults {
graphs,
labels,
statistics,
})
}
pub fn process_horizontal(self) -> Result<BatchResults<T>, GraphError>
where
T: Copy + PartialOrd + Into<f64> + std::ops::Add<Output = T> + std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T> + std::ops::Div<Output = T> + From<f64> + Send + Sync,
{
let mut graphs = Vec::new();
let mut labels = Vec::new();
for (series, label) in &self.series_list {
let builder = VisibilityGraph::from_series(series);
let graph = builder.horizontal_visibility()?;
graphs.push(graph);
labels.push(label.clone());
}
let statistics = Self::compute_statistics(&graphs);
Ok(BatchResults {
graphs,
labels,
statistics,
})
}
fn compute_statistics(graphs: &[VisibilityGraph<T>]) -> BatchStatistics {
if graphs.is_empty() {
return BatchStatistics {
avg_edges: 0.0,
avg_clustering: 0.0,
avg_path_length: 0.0,
avg_density: 0.0,
};
}
let n = graphs.len() as f64;
let avg_edges = graphs.iter().map(|g| g.edges.len()).sum::<usize>() as f64 / n;
let avg_clustering = graphs.iter().map(|g| g.average_clustering_coefficient()).sum::<f64>() / n;
let avg_path_length = graphs.iter().map(|g| g.average_path_length()).sum::<f64>() / n;
let avg_density = graphs.iter().map(|g| g.density()).sum::<f64>() / n;
BatchStatistics {
avg_edges,
avg_clustering,
avg_path_length,
avg_density,
}
}
}
impl<'a, T> Default for BatchProcessor<'a, T>
where
T: Copy + PartialOrd + Into<f64>,
{
fn default() -> Self {
Self::new()
}
}
pub fn compare_graphs<T>(graph1: &VisibilityGraph<T>, graph2: &VisibilityGraph<T>) -> HashMap<String, f64> {
let mut metrics = HashMap::new();
let edges1: std::collections::HashSet<_> = graph1.edges.keys().collect();
let edges2: std::collections::HashSet<_> = graph2.edges.keys().collect();
let intersection = edges1.intersection(&edges2).count();
let union = edges1.union(&edges2).count();
let edge_overlap = if union > 0 {
intersection as f64 / union as f64
} else {
0.0
};
metrics.insert("edge_overlap".to_string(), edge_overlap);
let degrees1 = graph1.degree_sequence();
let degrees2 = graph2.degree_sequence();
if degrees1.len() == degrees2.len() && !degrees1.is_empty() {
let mean1 = degrees1.iter().sum::<usize>() as f64 / degrees1.len() as f64;
let mean2 = degrees2.iter().sum::<usize>() as f64 / degrees2.len() as f64;
let mut covariance = 0.0;
let mut var1 = 0.0;
let mut var2 = 0.0;
for i in 0..degrees1.len() {
let d1 = degrees1[i] as f64 - mean1;
let d2 = degrees2[i] as f64 - mean2;
covariance += d1 * d2;
var1 += d1 * d1;
var2 += d2 * d2;
}
let correlation = if var1 > 0.0 && var2 > 0.0 {
covariance / (var1.sqrt() * var2.sqrt())
} else {
1.0
};
metrics.insert("degree_correlation".to_string(), correlation);
}
let clustering_diff = (graph1.average_clustering_coefficient() -
graph2.average_clustering_coefficient()).abs();
metrics.insert("clustering_diff".to_string(), clustering_diff);
let density_diff = (graph1.density() - graph2.density()).abs();
metrics.insert("density_diff".to_string(), density_diff);
metrics
}
impl<T> BatchResults<T> {
pub fn print_summary(&self) {
println!("=== Batch Processing Results ===");
println!("Total graphs: {}", self.graphs.len());
println!("\nAggregate Statistics:");
println!(" Average edges: {:.2}", self.statistics.avg_edges);
println!(" Average clustering: {:.4}", self.statistics.avg_clustering);
println!(" Average path length: {:.2}", self.statistics.avg_path_length);
println!(" Average density: {:.4}", self.statistics.avg_density);
println!("\nIndividual Graphs:");
for (i, label) in self.labels.iter().enumerate() {
let graph = &self.graphs[i];
println!(" {} - {} nodes, {} edges", label, graph.node_count, graph.edges.len());
}
}
}