use std::collections::HashMap;
use crate::graph::capability::{Directed, StableNode};
use crate::graph::Graph;
pub fn page_rank<'r, G>(
graph: &'r G,
damping_factor: f64,
max_iterations: usize,
tolerance: f64,
) -> HashMap<G::NodeIx, f64>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let nodes: Vec<G::NodeIx> = <_ as crate::graph::GraphOperation<'_>>::node_indices(graph).collect();
let n = nodes.len();
if n == 0 {
return HashMap::new();
}
let initial_rank = 1.0 / n as f64;
let mut rank: HashMap<G::NodeIx, f64> =
nodes.iter().map(|&node| (node, initial_rank)).collect();
let mut out_degree: HashMap<G::NodeIx, usize> = HashMap::new();
for &node in &nodes {
let deg = unsafe { graph.neighbor_indices_from_unchecked(node) }.count();
out_degree.insert(node, deg);
}
let mut predecessors: HashMap<G::NodeIx, Vec<G::NodeIx>> = HashMap::new();
for &node in &nodes {
predecessors.insert(node, Vec::new());
}
for &node in &nodes {
for succ in unsafe { graph.neighbor_indices_from_unchecked(node) } {
predecessors.get_mut(&succ).unwrap().push(node);
}
}
for _ in 0..max_iterations {
let mut new_rank: HashMap<G::NodeIx, f64> = HashMap::new();
let dangling_sum: f64 = nodes
.iter()
.filter(|&&node| out_degree[&node] == 0)
.map(|&node| rank[&node])
.sum();
for &node in &nodes {
let mut incoming_rank = 0.0;
for &pred in &predecessors[&node] {
let pred_out = out_degree[&pred];
if pred_out > 0 {
incoming_rank += rank[&pred] / pred_out as f64;
}
}
let pr = (1.0 - damping_factor) / n as f64
+ damping_factor * (incoming_rank + dangling_sum / n as f64);
new_rank.insert(node, pr);
}
let max_diff = nodes
.iter()
.map(|&node| (new_rank[&node] - rank[&node]).abs())
.fold(0.0_f64, f64::max);
rank = new_rank;
if max_diff < tolerance {
break;
}
}
rank
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::BTreeGraph;
#[test]
fn page_rank_uniform() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->0", [2, 0]).unwrap();
let pr = page_rank(&g, 0.85, 100, 1e-6);
assert_eq!(pr.len(), 3);
let r0 = pr[&0];
let r1 = pr[&1];
let r2 = pr[&2];
assert!((r0 - r1).abs() < 0.01);
assert!((r1 - r2).abs() < 0.01);
assert!(((r0 + r1 + r2) - 1.0).abs() < 0.01);
}
#[test]
fn page_rank_star() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("0->3", [0, 3]).unwrap();
let pr = page_rank(&g, 0.85, 100, 1e-6);
assert_eq!(pr.len(), 4);
assert!(pr[&0] < pr[&1]);
}
#[test]
fn page_rank_empty() {
let g = BTreeGraph::<u32, &str>::default();
let pr = page_rank(&g, 0.85, 100, 1e-6);
assert!(pr.is_empty());
}
#[test]
fn page_rank_single_node() {
let mut g = BTreeGraph::<u32, &str>::default();
g.insert_node(0).unwrap();
let pr = page_rank(&g, 0.85, 100, 1e-6);
assert!((pr[&0] - 1.0).abs() < 0.01);
}
}