use super::{Graph, NodeId, Weight, CompressedSparseRow};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct AdjacencyList {
num_nodes: usize,
adjacency: Vec<Vec<(NodeId, Weight)>>,
reverse_adjacency: Vec<Vec<(NodeId, Weight)>>,
degrees: Vec<Weight>,
reverse_degrees: Vec<Weight>,
}
impl AdjacencyList {
pub fn new(num_nodes: usize) -> Self {
Self {
num_nodes,
adjacency: vec![Vec::new(); num_nodes],
reverse_adjacency: vec![Vec::new(); num_nodes],
degrees: vec![0.0; num_nodes],
reverse_degrees: vec![0.0; num_nodes],
}
}
pub fn add_edge(&mut self, from: NodeId, to: NodeId, weight: Weight) {
if from < self.num_nodes && to < self.num_nodes {
self.adjacency[from].push((to, weight));
self.reverse_adjacency[to].push((from, weight));
self.degrees[from] += weight;
self.reverse_degrees[to] += weight;
}
}
pub fn from_csr(csr: &CompressedSparseRow) -> Self {
let mut graph = Self::new(csr.nrows);
for row in 0..csr.nrows {
for (col, value) in csr.row(row) {
if col < csr.ncols {
graph.adjacency[row].push((col, value));
graph.reverse_adjacency[col].push((row, value));
graph.degrees[row] += value;
graph.reverse_degrees[col] += value;
}
}
}
graph
}
pub fn to_csr(&self) -> CompressedSparseRow {
let mut row_ptr = vec![0; self.num_nodes + 1];
let mut col_indices = Vec::new();
let mut values = Vec::new();
for (row, neighbors) in self.adjacency.iter().enumerate() {
row_ptr[row + 1] = row_ptr[row] + neighbors.len();
for &(col, weight) in neighbors {
col_indices.push(col);
values.push(weight);
}
}
CompressedSparseRow {
nrows: self.num_nodes,
ncols: self.num_nodes,
row_ptr,
col_indices,
values,
}
}
pub fn reverse_neighbors(&self, node: NodeId) -> &[(NodeId, Weight)] {
if node < self.num_nodes {
&self.reverse_adjacency[node]
} else {
&[]
}
}
pub fn reverse_degree(&self, node: NodeId) -> Weight {
if node < self.num_nodes {
self.reverse_degrees[node]
} else {
0.0
}
}
pub fn normalize(&mut self) {
for node in 0..self.num_nodes {
let degree = self.degrees[node];
if degree > 0.0 {
for (_, weight) in &mut self.adjacency[node] {
*weight /= degree;
}
}
}
self.degrees.fill(1.0);
for node in 0..self.num_nodes {
let reverse_degree = self.reverse_degrees[node];
if reverse_degree > 0.0 {
for (_, weight) in &mut self.reverse_adjacency[node] {
*weight /= reverse_degree;
}
}
}
self.reverse_degrees.fill(1.0);
}
pub fn sparsity(&self) -> f64 {
let total_edges: usize = self.adjacency.iter().map(|adj| adj.len()).sum();
let max_edges = self.num_nodes * self.num_nodes;
1.0 - (total_edges as f64 / max_edges as f64)
}
pub fn is_strongly_connected(&self) -> bool {
for node in 0..self.num_nodes {
if self.adjacency[node].is_empty() || self.reverse_adjacency[node].is_empty() {
return false;
}
}
true
}
}
impl Graph for AdjacencyList {
fn num_nodes(&self) -> usize {
self.num_nodes
}
fn num_edges(&self) -> usize {
self.adjacency.iter().map(|adj| adj.len()).sum()
}
fn neighbors(&self, node: NodeId) -> Vec<(NodeId, Weight)> {
if node < self.num_nodes {
self.adjacency[node].clone()
} else {
Vec::new()
}
}
fn degree(&self, node: NodeId) -> Weight {
if node < self.num_nodes {
self.degrees[node]
} else {
0.0
}
}
fn has_edge(&self, from: NodeId, to: NodeId) -> bool {
if from < self.num_nodes {
self.adjacency[from].iter().any(|&(neighbor, _)| neighbor == to)
} else {
false
}
}
fn edge_weight(&self, from: NodeId, to: NodeId) -> Option<Weight> {
if from < self.num_nodes {
self.adjacency[from]
.iter()
.find(|&&(neighbor, _)| neighbor == to)
.map(|&(_, weight)| weight)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct PushGraph {
pub adjacency: CompressedSparseRow,
pub reverse_adjacency: CompressedSparseRow,
pub degrees: Vec<f64>,
pub reverse_degrees: Vec<f64>,
}
impl PushGraph {
pub fn from_matrix(matrix: &CompressedSparseRow) -> Self {
let adjacency = matrix.clone();
let reverse_adjacency = matrix.transpose();
let degrees = adjacency.row_sums();
let reverse_degrees = reverse_adjacency.row_sums();
Self {
adjacency,
reverse_adjacency,
degrees,
reverse_degrees,
}
}
pub fn from_edges(num_nodes: usize, edges: &[(usize, usize, f64)]) -> Self {
let mut adjacency_list = AdjacencyList::new(num_nodes);
for &(from, to, weight) in edges {
if from < num_nodes && to < num_nodes {
adjacency_list.add_edge(from, to, weight);
}
}
let matrix = adjacency_list.to_csr();
Self::from_matrix(&matrix)
}
pub fn num_nodes(&self) -> usize {
self.adjacency.nrows
}
pub fn num_edges(&self) -> usize {
self.adjacency.nnz()
}
pub fn forward_neighbors(&self, node: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
self.adjacency.row(node)
}
pub fn backward_neighbors(&self, node: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
self.reverse_adjacency.row(node)
}
pub fn out_degree(&self, node: usize) -> f64 {
if node < self.degrees.len() {
self.degrees[node]
} else {
0.0
}
}
pub fn in_degree(&self, node: usize) -> f64 {
if node < self.reverse_degrees.len() {
self.reverse_degrees[node]
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adjacency_list_creation() {
let mut graph = AdjacencyList::new(3);
graph.add_edge(0, 1, 0.5);
graph.add_edge(1, 2, 1.0);
graph.add_edge(2, 0, 0.3);
assert_eq!(graph.num_nodes(), 3);
assert_eq!(graph.num_edges(), 3);
assert_eq!(graph.degree(0), 0.5);
assert_eq!(graph.degree(1), 1.0);
assert_eq!(graph.degree(2), 0.3);
}
#[test]
fn test_csr_conversion() {
let mut graph = AdjacencyList::new(2);
graph.add_edge(0, 1, 1.0);
graph.add_edge(1, 0, 0.5);
let csr = graph.to_csr();
assert_eq!(csr.nrows, 2);
assert_eq!(csr.ncols, 2);
assert_eq!(csr.nnz(), 2);
let reconstructed = AdjacencyList::from_csr(&csr);
assert_eq!(reconstructed.degree(0), 1.0);
assert_eq!(reconstructed.degree(1), 0.5);
}
#[test]
fn test_push_graph() {
let mut csr = CompressedSparseRow::new(3, 3);
csr.row_ptr = vec![0, 1, 2, 3];
csr.col_indices = vec![1, 2, 0];
csr.values = vec![1.0, 0.5, 0.8];
let push_graph = PushGraph::from_matrix(&csr);
assert_eq!(push_graph.num_nodes(), 3);
assert_eq!(push_graph.out_degree(0), 1.0);
assert_eq!(push_graph.in_degree(0), 0.8);
}
}