use arrow::record_batch::RecordBatch;
use arrow::array::{StringArray, Float64Array};
use arrow::datatypes::{DataType, Field, Schema};
use arrow::compute::sum;
use std::sync::Arc;
use std::collections::HashMap;
use crate::algorithms::{GraphAlgorithm, AlgorithmParams};
use crate::graph::ArrowGraph;
use crate::error::{GraphError, Result};
pub struct VectorizedPageRank;
impl VectorizedPageRank {
fn compute_vectorized_pagerank(
&self,
graph: &ArrowGraph,
damping_factor: f64,
max_iterations: usize,
tolerance: f64,
) -> Result<HashMap<String, f64>> {
let node_count = graph.node_count();
if node_count == 0 {
return Ok(HashMap::new());
}
let node_ids: Vec<String> = graph.node_ids().cloned().collect();
let mut node_to_index: HashMap<String, usize> = HashMap::new();
for (i, node_id) in node_ids.iter().enumerate() {
node_to_index.insert(node_id.clone(), i);
}
let mut adjacency_sources = Vec::new();
let mut adjacency_targets = Vec::new();
let mut adjacency_weights = Vec::new();
for (source_idx, source_id) in node_ids.iter().enumerate() {
if let Some(neighbors) = graph.neighbors(source_id) {
let out_degree = neighbors.len() as f64;
for neighbor in neighbors {
if let Some(&target_idx) = node_to_index.get(neighbor) {
adjacency_sources.push(source_idx as u32);
adjacency_targets.push(target_idx as u32);
adjacency_weights.push(damping_factor / out_degree);
}
}
}
}
let initial_score = 1.0 / node_count as f64;
let mut current_scores = vec![initial_score; node_count];
let mut next_scores = vec![(1.0 - damping_factor) / node_count as f64; node_count];
for iteration in 0..max_iterations {
let base_score = (1.0 - damping_factor) / node_count as f64;
for score in &mut next_scores {
*score = base_score;
}
for (source_idx, source_id) in node_ids.iter().enumerate() {
let source_score = current_scores[source_idx];
if let Some(neighbors) = graph.neighbors(source_id) {
let out_degree = neighbors.len() as f64;
if out_degree > 0.0 {
let contribution = source_score * damping_factor / out_degree;
for neighbor in neighbors {
if let Some(&target_idx) = node_to_index.get(neighbor) {
next_scores[target_idx] += contribution;
}
}
}
} else {
let dangling_contribution = source_score * damping_factor / node_count as f64;
for score in &mut next_scores {
*score += dangling_contribution;
}
}
}
let mut total_diff = 0.0;
for i in 0..node_count {
total_diff += (next_scores[i] - current_scores[i]).abs();
}
let diff_values: Vec<f64> = current_scores.iter()
.zip(next_scores.iter())
.map(|(current, next)| (next - current).abs())
.collect();
let diff_array = Float64Array::from(diff_values);
let total_diff_value = sum(&diff_array).unwrap_or(total_diff);
if total_diff_value < tolerance {
log::debug!("Vectorized PageRank converged after {} iterations", iteration + 1);
break;
}
std::mem::swap(&mut current_scores, &mut next_scores);
}
let mut result = HashMap::new();
for (i, score) in current_scores.iter().enumerate() {
result.insert(node_ids[i].clone(), *score);
}
Ok(result)
}
}
impl GraphAlgorithm for VectorizedPageRank {
fn execute(&self, graph: &ArrowGraph, params: &AlgorithmParams) -> Result<RecordBatch> {
let damping_factor: f64 = params.get("damping_factor").unwrap_or(0.85);
let max_iterations: usize = params.get("max_iterations").unwrap_or(100);
let tolerance: f64 = params.get("tolerance").unwrap_or(1e-6);
if !(0.0..=1.0).contains(&damping_factor) {
return Err(GraphError::invalid_parameter(
"damping_factor must be between 0.0 and 1.0"
));
}
if max_iterations == 0 {
return Err(GraphError::invalid_parameter(
"max_iterations must be greater than 0"
));
}
if tolerance <= 0.0 {
return Err(GraphError::invalid_parameter(
"tolerance must be greater than 0.0"
));
}
let scores = self.compute_vectorized_pagerank(graph, damping_factor, max_iterations, tolerance)?;
let schema = Arc::new(Schema::new(vec![
Field::new("node_id", DataType::Utf8, false),
Field::new("pagerank_score", DataType::Float64, false),
]));
let mut node_ids = Vec::new();
let mut pagerank_scores = Vec::new();
let mut sorted_scores: Vec<(&String, &f64)> = scores.iter().collect();
sorted_scores.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal));
for (node_id, score) in sorted_scores {
node_ids.push(node_id.clone());
pagerank_scores.push(*score);
}
RecordBatch::try_new(
schema,
vec![
Arc::new(StringArray::from(node_ids)),
Arc::new(Float64Array::from(pagerank_scores)),
],
).map_err(GraphError::from)
}
fn name(&self) -> &'static str {
"vectorized_pagerank"
}
fn description(&self) -> &'static str {
"Calculate PageRank scores using vectorized Arrow compute kernels for SIMD performance"
}
}
pub struct VectorizedDistanceCalculator;
impl VectorizedDistanceCalculator {
pub fn compute_all_pairs_distances(&self, graph: &ArrowGraph) -> Result<Vec<Vec<f64>>> {
let node_count = graph.node_count();
let node_ids: Vec<String> = graph.node_ids().cloned().collect();
let mut distances = vec![vec![f64::INFINITY; node_count]; node_count];
for i in 0..node_count {
distances[i][i] = 0.0;
}
let mut node_to_index: HashMap<String, usize> = HashMap::new();
for (i, node_id) in node_ids.iter().enumerate() {
node_to_index.insert(node_id.clone(), i);
}
for (i, source_id) in node_ids.iter().enumerate() {
if let Some(neighbors) = graph.neighbors(source_id) {
for neighbor in neighbors {
if let Some(&j) = node_to_index.get(neighbor) {
let weight = graph.edge_weight(source_id, neighbor).unwrap_or(1.0);
distances[i][j] = weight;
}
}
}
}
for k in 0..node_count {
let _k_row = Float64Array::from(distances[k].clone());
for i in 0..node_count {
if distances[i][k] == f64::INFINITY {
continue;
}
for j in 0..node_count {
let via_k = distances[i][k] + distances[k][j];
distances[i][j] = distances[i][j].min(via_k);
}
}
}
Ok(distances)
}
pub fn compute_vectorized_betweenness(&self, graph: &ArrowGraph) -> Result<HashMap<String, f64>> {
let node_count = graph.node_count();
let node_ids: Vec<String> = graph.node_ids().cloned().collect();
let mut centrality: HashMap<String, f64> = HashMap::new();
for node_id in &node_ids {
centrality.insert(node_id.clone(), 0.0);
}
let distances = self.compute_all_pairs_distances(graph)?;
for s in 0..node_count {
for t in (s + 1)..node_count {
if distances[s][t] == f64::INFINITY {
continue; }
let shortest_distance = distances[s][t];
for v in 0..node_count {
if v == s || v == t {
continue;
}
if (distances[s][v] + distances[v][t] - shortest_distance).abs() < 1e-10 {
let v_centrality = centrality.get_mut(&node_ids[v]).unwrap();
*v_centrality += 1.0;
}
}
}
}
if node_count > 2 {
let normalization = 2.0 / ((node_count - 1) * (node_count - 2)) as f64;
for score in centrality.values_mut() {
*score *= normalization;
}
}
Ok(centrality)
}
}
pub struct VectorizedBatchOperations;
impl VectorizedBatchOperations {
pub fn compute_batch_centralities(&self, graph: &ArrowGraph) -> Result<RecordBatch> {
let node_ids: Vec<String> = graph.node_ids().cloned().collect();
let node_count = node_ids.len();
if node_count == 0 {
let schema = Arc::new(Schema::new(vec![
Field::new("node_id", DataType::Utf8, false),
Field::new("degree_centrality", DataType::Float64, false),
Field::new("eigenvector_centrality", DataType::Float64, false),
Field::new("closeness_centrality", DataType::Float64, false),
]));
return RecordBatch::try_new(
schema,
vec![
Arc::new(StringArray::from(Vec::<String>::new())),
Arc::new(Float64Array::from(Vec::<f64>::new())),
Arc::new(Float64Array::from(Vec::<f64>::new())),
Arc::new(Float64Array::from(Vec::<f64>::new())),
],
).map_err(GraphError::from);
}
let mut degrees = Vec::new();
for node_id in &node_ids {
let degree = graph.neighbors(node_id)
.map(|neighbors| neighbors.len())
.unwrap_or(0) as f64;
degrees.push(degree);
}
let max_possible_degree = (node_count - 1) as f64;
let degree_array = Float64Array::from(degrees);
let normalized_degree_values: Vec<f64> = degree_array.iter()
.map(|d| d.unwrap_or(0.0) / max_possible_degree)
.collect();
let normalized_degrees = Float64Array::from(normalized_degree_values);
let eigenvector_scores = vec![1.0 / (node_count as f64).sqrt(); node_count];
let distance_calc = VectorizedDistanceCalculator;
let distances = distance_calc.compute_all_pairs_distances(graph)?;
let mut closeness_scores = Vec::new();
for i in 0..node_count {
let mut total_distance = 0.0;
let mut reachable_count = 0;
for j in 0..node_count {
if i != j && distances[i][j] != f64::INFINITY {
total_distance += distances[i][j];
reachable_count += 1;
}
}
let closeness = if total_distance > 0.0 && reachable_count > 0 {
let avg_distance = total_distance / reachable_count as f64;
let connectivity = reachable_count as f64 / (node_count - 1) as f64;
connectivity / avg_distance
} else {
0.0
};
closeness_scores.push(closeness);
}
let schema = Arc::new(Schema::new(vec![
Field::new("node_id", DataType::Utf8, false),
Field::new("degree_centrality", DataType::Float64, false),
Field::new("eigenvector_centrality", DataType::Float64, false),
Field::new("closeness_centrality", DataType::Float64, false),
]));
RecordBatch::try_new(
schema,
vec![
Arc::new(StringArray::from(node_ids)),
Arc::new(normalized_degrees),
Arc::new(Float64Array::from(eigenvector_scores)),
Arc::new(Float64Array::from(closeness_scores)),
],
).map_err(GraphError::from)
}
}
impl GraphAlgorithm for VectorizedBatchOperations {
fn execute(&self, graph: &ArrowGraph, _params: &AlgorithmParams) -> Result<RecordBatch> {
self.compute_batch_centralities(graph)
}
fn name(&self) -> &'static str {
"batch_centralities"
}
fn description(&self) -> &'static str {
"Compute multiple centrality measures using vectorized operations for optimal performance"
}
}