use crate::storage::CsrGraph;
use anyhow::Result;
const DAMPING_FACTOR: f32 = 0.85;
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_possible_truncation)] pub fn pagerank(graph: &CsrGraph, max_iterations: usize, tolerance: f32) -> Result<Vec<f32>> {
let n = graph.num_nodes();
if n == 0 {
return Ok(Vec::new());
}
let teleport = (1.0 - DAMPING_FACTOR) / n as f32;
let mut ranks = vec![1.0 / n as f32; n];
let mut new_ranks = vec![0.0; n];
let (row_offsets, col_indices, _edge_weights) = graph.csr_components();
let mut out_degrees = vec![0_u32; n];
for node in 0..n {
let start = row_offsets[node] as usize;
let end = row_offsets[node + 1] as usize;
out_degrees[node] = (end - start) as u32;
}
#[allow(unused_variables)] for iteration in 0..max_iterations {
new_ranks.fill(teleport);
for node in 0..n {
let start = row_offsets[node] as usize;
let end = row_offsets[node + 1] as usize;
if out_degrees[node] > 0 {
let rank_contribution = DAMPING_FACTOR * ranks[node] / out_degrees[node] as f32;
for &target in &col_indices[start..end] {
new_ranks[target as usize] += rank_contribution;
}
} else {
let dangling_contribution = DAMPING_FACTOR * ranks[node] / n as f32;
for r in &mut new_ranks {
*r += dangling_contribution;
}
}
}
let mut diff = 0.0;
for i in 0..n {
diff += (new_ranks[i] - ranks[i]).abs();
}
std::mem::swap(&mut ranks, &mut new_ranks);
if diff < tolerance {
#[cfg(test)]
eprintln!("PageRank converged after {} iterations (diff={:.2e})", iteration + 1, diff);
break;
}
}
Ok(ranks)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NodeId;
#[test]
fn test_pagerank_simple_chain() {
let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(2), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let scores = pagerank(&graph, 20, 1e-6).unwrap();
assert_eq!(scores.len(), 3);
let sum: f32 = scores.iter().sum();
assert!((sum - 1.0).abs() < 1e-5, "Sum = {sum}");
assert!(scores[2] > scores[1], "Node 2 should have higher score than 1");
assert!(scores[1] > scores[0], "Node 1 should have higher score than 0");
}
#[test]
fn test_pagerank_cycle() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(2), 1.0),
(NodeId(2), NodeId(0), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let scores = pagerank(&graph, 50, 1e-6).unwrap();
assert_eq!(scores.len(), 3);
let sum: f32 = scores.iter().sum();
assert!((sum - 1.0).abs() < 1e-5);
for score in &scores {
assert!((*score - 1.0 / 3.0).abs() < 0.01, "Score = {score}");
}
}
#[test]
fn test_pagerank_star() {
let edges = vec![
(NodeId(1), NodeId(0), 1.0),
(NodeId(2), NodeId(0), 1.0),
(NodeId(3), NodeId(0), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let scores = pagerank(&graph, 20, 1e-6).unwrap();
assert!(scores[0] > scores[1]);
assert!(scores[0] > scores[2]);
assert!(scores[0] > scores[3]);
assert!((scores[1] - scores[2]).abs() < 0.01);
assert!((scores[2] - scores[3]).abs() < 0.01);
}
#[test]
fn test_pagerank_empty_graph() {
let graph = CsrGraph::new();
let scores = pagerank(&graph, 20, 1e-6).unwrap();
assert_eq!(scores.len(), 0);
}
#[test]
fn test_pagerank_single_node() {
let mut graph = CsrGraph::new();
graph.add_edge(NodeId(0), NodeId(0), 1.0).unwrap();
let scores = pagerank(&graph, 20, 1e-6).unwrap();
assert_eq!(scores.len(), 1);
assert!((scores[0] - 1.0).abs() < 1e-5); }
#[test]
fn test_pagerank_convergence() {
let mut edges = Vec::new();
for i in 0..10 {
edges.push((NodeId(i), NodeId((i + 1) % 10), 1.0));
}
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let scores = pagerank(&graph, 100, 1e-6).unwrap();
for score in &scores {
assert!((*score - 0.1).abs() < 0.01, "Score = {score}");
}
}
}