type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
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) -> Result<Tensor> {
let num_nodes = graph.num_nodes;
let mut ranks = torsh_tensor::creation::full(&[num_nodes], 1.0 / num_nodes as f32)?;
for _ in 0..max_iter {
let damped_ranks = ranks.mul_scalar(damping as f32)?;
let teleport_prob = (1.0 - damping) / num_nodes as f64;
let teleport_tensor = torsh_tensor::creation::full(&[num_nodes], teleport_prob as f32)?;
ranks = damped_ranks.add(&teleport_tensor)?;
}
Ok(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) -> Result<Tensor> {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
Ok(torsh_tensor::creation::from_vec(
uniform_centrality,
&[graph.num_nodes],
DeviceType::Cpu,
)?)
}
pub fn eigenvector_centrality(graph: &GraphData, _max_iter: usize) -> Result<Tensor> {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
Ok(torsh_tensor::creation::from_vec(
uniform_centrality,
&[graph.num_nodes],
DeviceType::Cpu,
)?)
}
pub fn closeness_centrality(graph: &GraphData) -> Result<Tensor> {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
Ok(torsh_tensor::creation::from_vec(
uniform_centrality,
&[graph.num_nodes],
DeviceType::Cpu,
)?)
}
pub fn katz_centrality(graph: &GraphData, _alpha: f64) -> Result<Tensor> {
let uniform_centrality = vec![1.0 / graph.num_nodes as f32; graph.num_nodes];
Ok(torsh_tensor::creation::from_vec(
uniform_centrality,
&[graph.num_nodes],
DeviceType::Cpu,
)?)
}
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) -> Result<(Tensor, Tensor)> {
let eigenvalues = torsh_tensor::creation::ones(&[graph.num_nodes])?;
let eigenvectors = torsh_tensor::creation::eye(graph.num_nodes)?;
Ok((eigenvalues, eigenvectors))
}
pub fn graph_fourier_transform(graph: &GraphData, signal: &Tensor) -> Result<Tensor> {
let (_eigenvals, eigenvecs) = laplacian_eigendecomposition(graph)?;
Ok(eigenvecs.t()?.matmul(signal)?)
}
pub fn inverse_graph_fourier_transform(
graph: &GraphData,
spectral_signal: &Tensor,
) -> Result<Tensor> {
let (_eigenvals, eigenvecs) = laplacian_eigendecomposition(graph)?;
Ok(eigenvecs.matmul(spectral_signal)?)
}
pub fn spectral_convolution(
graph: &GraphData,
signal: &Tensor,
_filter_coeffs: &[f64],
) -> Result<Tensor> {
let (eigenvals, _eigenvecs) = laplacian_eigendecomposition(graph)?;
let transformed = graph_fourier_transform(graph, signal)?;
let filtered = transformed.mul(&eigenvals.unsqueeze(-1)?)?;
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) -> Result<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,
)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])?;
Ok(GraphData::new(x, edge_index))
}
pub fn barabasi_albert(num_nodes: usize, edges_per_node: usize) -> Result<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,
)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])?;
Ok(GraphData::new(x, edge_index))
}
pub fn watts_strogatz(num_nodes: usize, k: usize, rewire_prob: f64) -> Result<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,
)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])?;
Ok(GraphData::new(x, edge_index))
}
pub fn complete(num_nodes: usize) -> Result<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,
)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
let x = torsh_tensor::creation::randn(&[num_nodes, 16])?;
Ok(GraphData::new(x, edge_index))
}
}
pub mod spatial {
use super::*;
pub fn knn_graph(points: &Tensor, k: usize) -> Result<GraphData> {
let num_points = points.shape().dims()[0];
let point_dim = points.shape().dims()[1];
let points_flat = points.to_vec()?;
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)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
Ok(GraphData::new(points.clone(), edge_index))
}
pub fn radius_graph(points: &Tensor, radius: f64) -> Result<GraphData> {
let num_points = points.shape().dims()[0];
let point_dim = points.shape().dims()[1];
let points_flat = points.to_vec()?;
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)?
} else {
torsh_tensor::creation::zeros(&[2, 0])?
};
Ok(GraphData::new(points.clone(), edge_index))
}
pub fn delaunay_graph(points: &Tensor) -> Result<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])?;
return Ok(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) -> Result<Tensor> {
let adjacency = crate::utils::degree_matrix(&graph.edge_index, graph.num_nodes);
let mut state = torsh_tensor::creation::zeros(&[graph.num_nodes])?;
let mut state_data = state.to_vec()?;
if !state_data.is_empty() {
state_data[0] = 1.0;
state = torsh_tensor::creation::from_vec(
state_data,
state.shape().dims(),
DeviceType::Cpu,
)?;
}
let adjacency = adjacency?;
for _ in 0..steps {
state = adjacency.matmul(&state.unsqueeze(-1)?)?.squeeze(-1)?;
let norm_val = state.norm()?.to_vec()?[0];
if norm_val > 0.0 {
state = state.div_scalar(norm_val)?;
}
}
Ok(state)
}
pub fn quantum_graph_coloring(graph: &GraphData, num_colors: usize) -> Result<Vec<usize>> {
let num_nodes = graph.num_nodes;
let edge_tensor_data = graph.edge_index.to_vec()?;
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));
}
Ok(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).expect("operation should succeed");
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).expect("operation should succeed");
assert_eq!(er_graph.num_nodes, 5);
assert_eq!(er_graph.x.shape().dims(), &[5, 16]);
let ba_graph = generation::barabasi_albert(6, 2).expect("operation should succeed");
assert_eq!(ba_graph.num_nodes, 6);
let ws_graph = generation::watts_strogatz(8, 4, 0.2).expect("operation should succeed");
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).expect("operation should succeed");
assert_eq!(knn.num_nodes, 4);
assert!(knn.num_edges > 0);
let radius = spatial::radius_graph(&points, 1.5).expect("operation should succeed");
assert_eq!(radius.num_nodes, 4);
}
#[test]
fn test_quantum_algorithms() {
let graph = create_test_graph();
let walk_state = quantum::quantum_walk(&graph, 5).expect("operation should succeed");
assert_eq!(walk_state.shape().dims(), &[3]);
let coloring =
quantum::quantum_graph_coloring(&graph, 3).expect("operation should succeed");
assert_eq!(coloring.len(), 3);
assert!(coloring.iter().all(|&c| c < 3));
}
}