use crate::GraphData;
use torsh_core::device::DeviceType;
use torsh_tensor::Tensor;
pub mod algorithms {
use super::*;
pub fn pagerank(graph: &GraphData, damping: f64, max_iter: usize) -> Tensor {
let num_nodes = graph.num_nodes;
let mut ranks = torsh_tensor::creation::full(&[num_nodes], 1.0 / num_nodes as f32)
.expect("initial ranks tensor creation should succeed");
for _ in 0..max_iter {
let damped_ranks = ranks
.mul_scalar(damping as f32)
.expect("damping multiplication should succeed");
let teleport_prob = (1.0 - damping) / num_nodes as f64;
let teleport_tensor = torsh_tensor::creation::full(&[num_nodes], teleport_prob as f32)
.expect("teleport tensor creation should succeed");
ranks = damped_ranks
.add(&teleport_tensor)
.expect("operation should succeed");
}
ranks
}
pub fn community_detection(graph: &GraphData, _resolution: f64) -> Vec<usize> {
(0..graph.num_nodes).map(|i| i % 3).collect()
}
pub fn spectral_clustering(graph: &GraphData, num_clusters: usize) -> Vec<usize> {
(0..graph.num_nodes).map(|i| i % num_clusters).collect()
}
pub fn betweenness_centrality(graph: &GraphData) -> Tensor {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
torsh_tensor::creation::from_vec(uniform_centrality, &[graph.num_nodes], DeviceType::Cpu)
.expect("betweenness centrality tensor creation should succeed")
}
pub fn eigenvector_centrality(graph: &GraphData, _max_iter: usize) -> Tensor {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
torsh_tensor::creation::from_vec(uniform_centrality, &[graph.num_nodes], DeviceType::Cpu)
.expect("eigenvector centrality tensor creation should succeed")
}
pub fn closeness_centrality(graph: &GraphData) -> Tensor {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
torsh_tensor::creation::from_vec(uniform_centrality, &[graph.num_nodes], DeviceType::Cpu)
.expect("closeness centrality tensor creation should succeed")
}
pub fn katz_centrality(graph: &GraphData, _alpha: f64) -> Tensor {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
torsh_tensor::creation::from_vec(uniform_centrality, &[graph.num_nodes], DeviceType::Cpu)
.expect("katz centrality tensor creation should succeed")
}
pub fn graph_connectivity(graph: &GraphData) -> (Vec<Vec<usize>>, bool) {
let single_component = vec![(0..graph.num_nodes).collect()];
let is_connected = if graph.num_nodes <= 1 {
true } else if graph.num_edges == 0 {
false } else {
true
};
(single_component, is_connected)
}
pub fn compute_graph_density(graph: &GraphData) -> f64 {
let max_edges = graph.num_nodes * (graph.num_nodes - 1) / 2;
if max_edges > 0 {
graph.num_edges as f64 / max_edges as f64
} else {
0.0
}
}
pub fn compute_diameter(graph: &GraphData) -> Option<usize> {
if graph.num_nodes <= 1 {
Some(0)
} else {
Some(graph.num_nodes - 1) }
}
}
pub mod spectral {
use super::*;
pub fn laplacian_eigendecomposition(graph: &GraphData) -> (Tensor, Tensor) {
let eigenvalues = torsh_tensor::creation::ones(&[graph.num_nodes])
.expect("eigenvalues tensor creation should succeed");
let eigenvectors = torsh_tensor::creation::eye(graph.num_nodes)
.expect("eigenvectors tensor creation should succeed");
(eigenvalues, eigenvectors)
}
pub fn graph_fourier_transform(graph: &GraphData, signal: &Tensor) -> Tensor {
let (_eigenvals, eigenvecs) = laplacian_eigendecomposition(graph);
eigenvecs
.t()
.expect("operation should succeed")
.matmul(signal)
.expect("operation should succeed")
}
pub fn inverse_graph_fourier_transform(graph: &GraphData, spectral_signal: &Tensor) -> Tensor {
let (_eigenvals, eigenvecs) = laplacian_eigendecomposition(graph);
eigenvecs
.matmul(spectral_signal)
.expect("operation should succeed")
}
pub fn spectral_convolution(
graph: &GraphData,
signal: &Tensor,
_filter_coeffs: &[f64],
) -> Tensor {
let (eigenvals, _eigenvecs) = laplacian_eigendecomposition(graph);
let transformed = graph_fourier_transform(graph, signal);
let filtered = transformed
.mul(&eigenvals.unsqueeze(-1).expect("operation should succeed"))
.expect("operation should succeed");
inverse_graph_fourier_transform(graph, &filtered)
}
}
pub mod generation {
use super::*;
use scirs2_core::random::Random;
use scirs2_core::RngExt;
pub fn erdos_renyi(num_nodes: usize, edge_prob: f64) -> GraphData {
let mut rng = Random::seed(42);
let mut edges = Vec::new();
for i in 0..num_nodes {
for j in (i + 1)..num_nodes {
if rng.random::<f64>() < edge_prob {
edges.extend_from_slice(&[i as i64, j as i64]);
edges.extend_from_slice(&[j as i64, i as i64]); }
}
}
let num_edges = edges.len() / 2;
let edge_index = if num_edges > 0 {
torsh_tensor::creation::from_vec(
edges.iter().map(|&x| x as f32).collect(),
&[2, num_edges],
DeviceType::Cpu,
)
.expect("erdos_renyi edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])
.expect("erdos_renyi features tensor creation should succeed");
GraphData::new(x, edge_index)
}
pub fn barabasi_albert(num_nodes: usize, edges_per_node: usize) -> GraphData {
let mut rng = Random::seed(42);
let mut edges = Vec::new();
let mut degrees = vec![0; num_nodes];
let start_nodes = edges_per_node.min(num_nodes);
for i in 0..start_nodes {
for j in (i + 1)..start_nodes {
edges.extend_from_slice(&[i as i64, j as i64]);
edges.extend_from_slice(&[j as i64, i as i64]);
degrees[i] += 1;
degrees[j] += 1;
}
}
for new_node in start_nodes..num_nodes {
let total_degree: usize = degrees.iter().sum();
let mut targets = Vec::new();
while targets.len() < edges_per_node && targets.len() < new_node {
let mut cumsum = 0;
let threshold = (rng.random::<f64>() * total_degree.max(1) as f64) as usize;
for (node_id, °ree) in degrees.iter().enumerate() {
cumsum += degree;
if cumsum > threshold && !targets.contains(&node_id) && node_id < new_node {
targets.push(node_id);
break;
}
}
}
for target in targets {
edges.extend_from_slice(&[new_node as i64, target as i64]);
edges.extend_from_slice(&[target as i64, new_node as i64]);
degrees[new_node] += 1;
degrees[target] += 1;
}
}
let num_edges = edges.len() / 2;
let edge_index = if num_edges > 0 {
torsh_tensor::creation::from_vec(
edges.iter().map(|&x| x as f32).collect(),
&[2, num_edges],
DeviceType::Cpu,
)
.expect("barabasi_albert edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])
.expect("barabasi_albert features tensor creation should succeed");
GraphData::new(x, edge_index)
}
pub fn watts_strogatz(num_nodes: usize, k: usize, rewire_prob: f64) -> GraphData {
let mut rng = Random::seed(42);
let mut edges = Vec::new();
for i in 0..num_nodes {
for j in 1..=k / 2 {
let target = (i + j) % num_nodes;
edges.extend_from_slice(&[i as i64, target as i64]);
edges.extend_from_slice(&[target as i64, i as i64]);
}
}
let mut rewired_edges = Vec::new();
let mut i = 0;
while i < edges.len() {
if rng.random::<f64>() < rewire_prob {
let src = edges[i];
let new_target = (rng.random::<f64>() * num_nodes as f64) as i64;
if new_target != src {
rewired_edges.extend_from_slice(&[src, new_target]);
}
} else {
rewired_edges.push(edges[i]);
}
i += 2; }
let num_edges = rewired_edges.len() / 2;
let edge_index = if num_edges > 0 {
torsh_tensor::creation::from_vec(
rewired_edges.iter().map(|&x| x as f32).collect(),
&[2, num_edges],
DeviceType::Cpu,
)
.expect("watts_strogatz edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])
.expect("watts_strogatz features tensor creation should succeed");
GraphData::new(x, edge_index)
}
pub fn complete(num_nodes: usize) -> GraphData {
let mut edges = Vec::new();
for i in 0..num_nodes {
for j in (i + 1)..num_nodes {
edges.extend_from_slice(&[i as i64, j as i64]);
edges.extend_from_slice(&[j as i64, i as i64]); }
}
let num_directed_edges = edges.len() / 2;
let edge_index = if num_directed_edges > 0 {
torsh_tensor::creation::from_vec(
edges.iter().map(|&x| x as f32).collect(),
&[2, num_directed_edges],
DeviceType::Cpu,
)
.expect("complete graph edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])
.expect("complete graph features tensor creation should succeed");
GraphData::new(x, edge_index)
}
}
pub mod spatial {
use super::*;
pub fn knn_graph(points: &Tensor, k: usize) -> GraphData {
let num_points = points.shape().dims()[0];
let point_dim = points.shape().dims()[1];
let points_flat = points.to_vec().expect("conversion should succeed");
let points_data: Vec<Vec<f64>> = points_flat
.chunks(point_dim)
.map(|chunk| chunk.iter().map(|&x| x as f64).collect())
.collect();
let mut edges = Vec::new();
for i in 0..num_points {
let mut distances = Vec::new();
for j in 0..num_points {
if i != j {
let dist: f64 = (0..point_dim)
.map(|d| (points_data[i][d] - points_data[j][d]).powi(2))
.sum::<f64>()
.sqrt();
distances.push((dist, j));
}
}
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
for (_, neighbor) in distances.iter().take(k) {
edges.extend_from_slice(&[i as f32, *neighbor as f32]);
}
}
let num_edges = edges.len() / 2;
let edge_index = if num_edges > 0 {
torsh_tensor::creation::from_vec(edges, &[2, num_edges], DeviceType::Cpu)
.expect("knn edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
GraphData::new(points.clone(), edge_index)
}
pub fn radius_graph(points: &Tensor, radius: f64) -> GraphData {
let num_points = points.shape().dims()[0];
let point_dim = points.shape().dims()[1];
let points_flat = points.to_vec().expect("conversion should succeed");
let points_data: Vec<Vec<f64>> = points_flat
.chunks(point_dim)
.map(|chunk| chunk.iter().map(|&x| x as f64).collect())
.collect();
let mut edges = Vec::new();
for i in 0..num_points {
for j in (i + 1)..num_points {
let dist: f64 = (0..point_dim)
.map(|d| (points_data[i][d] - points_data[j][d]).powi(2))
.sum::<f64>()
.sqrt();
if dist <= radius {
edges.extend_from_slice(&[i as f32, j as f32]);
edges.extend_from_slice(&[j as f32, i as f32]); }
}
}
let num_edges = edges.len() / 2;
let edge_index = if num_edges > 0 {
torsh_tensor::creation::from_vec(edges, &[2, num_edges], DeviceType::Cpu)
.expect("radius graph edge index tensor creation should succeed")
} else {
torsh_tensor::creation::zeros(&[2, 0])
.expect("empty edge index tensor creation should succeed")
};
GraphData::new(points.clone(), edge_index)
}
pub fn delaunay_graph(points: &Tensor) -> GraphData {
let num_points = points.shape().dims()[0];
if num_points < 3 {
let x = points.clone();
let edge_index = torsh_tensor::creation::zeros(&[2, 0])
.expect("delaunay empty edge index tensor creation should succeed");
return GraphData::new(x, edge_index);
}
knn_graph(points, 6.min(num_points - 1)) }
}
pub mod quantum {
use super::*;
pub fn quantum_walk(graph: &GraphData, steps: usize) -> Tensor {
let adjacency = crate::utils::degree_matrix(&graph.edge_index, graph.num_nodes);
let mut state = torsh_tensor::creation::zeros(&[graph.num_nodes])
.expect("initial quantum walk state tensor creation should succeed");
let mut state_data = state.to_vec().expect("conversion should succeed");
if !state_data.is_empty() {
state_data[0] = 1.0;
state =
torsh_tensor::creation::from_vec(state_data, state.shape().dims(), DeviceType::Cpu)
.expect("quantum walk state initialization should succeed");
}
for _ in 0..steps {
state = adjacency
.matmul(&state.unsqueeze(-1).expect("operation should succeed"))
.expect("operation should succeed")
.squeeze(-1)
.expect("quantum walk squeeze should succeed");
let norm_val = state
.norm()
.expect("quantum walk norm should succeed")
.to_vec()
.expect("conversion should succeed")[0];
if norm_val > 0.0 {
state = state
.div_scalar(norm_val)
.expect("quantum walk normalization should succeed");
}
}
state
}
pub fn quantum_graph_coloring(graph: &GraphData, num_colors: usize) -> Vec<usize> {
let num_nodes = graph.num_nodes;
let edge_tensor_data = graph
.edge_index
.to_vec()
.expect("conversion should succeed");
let edge_data = vec![
edge_tensor_data[0..edge_tensor_data.len() / 2]
.iter()
.map(|&x| x as i64)
.collect::<Vec<i64>>(),
edge_tensor_data[edge_tensor_data.len() / 2..]
.iter()
.map(|&x| x as i64)
.collect::<Vec<i64>>(),
];
let mut colors = vec![0; num_nodes];
let mut adjacency_list: Vec<Vec<usize>> = vec![Vec::new(); num_nodes];
for j in 0..edge_data[0].len() {
let src = edge_data[0][j] as usize;
let dst = edge_data[1][j] as usize;
if src < num_nodes && dst < num_nodes {
adjacency_list[src].push(dst);
}
}
for node in 0..num_nodes {
let mut used_colors = vec![false; num_colors];
for &neighbor in &adjacency_list[node] {
if neighbor < node && colors[neighbor] < num_colors {
used_colors[colors[neighbor]] = true;
}
}
colors[node] = used_colors
.iter()
.position(|&used| !used)
.unwrap_or(num_colors.saturating_sub(1));
}
colors
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
use torsh_tensor::creation::from_vec;
fn create_test_graph() -> GraphData {
let x = from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2], DeviceType::Cpu).unwrap();
let edges = vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0];
let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
GraphData::new(x, edge_index)
}
#[test]
fn test_pagerank() {
let graph = create_test_graph();
let ranks = algorithms::pagerank(&graph, 0.85, 10);
assert_eq!(ranks.shape().dims(), &[3]);
let rank_values = ranks.to_vec().expect("conversion should succeed");
let sum: f32 = rank_values.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_spectral_clustering() {
let graph = create_test_graph();
let clusters = algorithms::spectral_clustering(&graph, 2);
assert_eq!(clusters.len(), 3);
assert!(clusters.iter().all(|&c| c < 2));
}
#[test]
fn test_graph_generation() {
let er_graph = generation::erdos_renyi(5, 0.3);
assert_eq!(er_graph.num_nodes, 5);
assert_eq!(er_graph.x.shape().dims(), &[5, 16]);
let ba_graph = generation::barabasi_albert(6, 2);
assert_eq!(ba_graph.num_nodes, 6);
let ws_graph = generation::watts_strogatz(8, 4, 0.2);
assert_eq!(ws_graph.num_nodes, 8);
}
#[test]
fn test_spatial_graphs() {
let points = from_vec(
vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0],
&[4, 2],
DeviceType::Cpu,
)
.unwrap();
let knn = spatial::knn_graph(&points, 2);
assert_eq!(knn.num_nodes, 4);
assert!(knn.num_edges > 0);
let radius = spatial::radius_graph(&points, 1.5);
assert_eq!(radius.num_nodes, 4);
}
#[test]
fn test_quantum_algorithms() {
let graph = create_test_graph();
let walk_state = quantum::quantum_walk(&graph, 5);
assert_eq!(walk_state.shape().dims(), &[3]);
let coloring = quantum::quantum_graph_coloring(&graph, 3);
assert_eq!(coloring.len(), 3);
assert!(coloring.iter().all(|&c| c < 3));
}
}