use crate::KnowledgeGraph;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct PageRankConfig {
pub damping_factor: f64,
pub max_iterations: usize,
pub tolerance: f64,
}
impl Default for PageRankConfig {
fn default() -> Self {
Self {
damping_factor: 0.85,
max_iterations: 100,
tolerance: 1e-6,
}
}
}
#[must_use]
pub fn pagerank(kg: &KnowledgeGraph, config: PageRankConfig) -> HashMap<String, f64> {
let graph = kg.as_petgraph();
let n = graph.node_count();
if n == 0 {
return HashMap::new();
}
let gp_config = graphops::pagerank::PageRankConfig {
damping: config.damping_factor,
max_iterations: config.max_iterations,
tolerance: config.tolerance,
};
let scores = graphops::pagerank::pagerank(graph, gp_config);
let mut result = HashMap::with_capacity(n);
for (idx, score) in scores.into_iter().enumerate() {
let entity = &graph[petgraph::graph::NodeIndex::new(idx)];
result.insert(entity.id.as_str().to_owned(), score);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Triple;
#[test]
fn test_pagerank_cycle() {
let mut kg = KnowledgeGraph::new();
kg.add_triple(Triple::new("A", "rel", "B"));
kg.add_triple(Triple::new("B", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "A"));
let scores = pagerank(&kg, PageRankConfig::default());
let a = scores.get("A").unwrap();
let b = scores.get("B").unwrap();
let c = scores.get("C").unwrap();
assert!((a - b).abs() < 1e-4, "A={a} B={b}");
assert!((b - c).abs() < 1e-4, "B={b} C={c}");
assert!((a - 1.0 / 3.0).abs() < 0.01);
}
#[test]
fn test_pagerank_star() {
let mut kg = KnowledgeGraph::new();
kg.add_triple(Triple::new("Hub", "rel", "A"));
kg.add_triple(Triple::new("Hub", "rel", "B"));
kg.add_triple(Triple::new("Hub", "rel", "C"));
let scores = pagerank(&kg, PageRankConfig::default());
let hub = scores.get("Hub").unwrap();
let a = scores.get("A").unwrap();
assert!(a > hub, "Leaf A ({a}) should rank higher than Hub ({hub})",);
}
#[test]
fn test_pagerank_sums_to_one() {
let mut kg = KnowledgeGraph::new();
kg.add_triple(Triple::new("A", "rel", "B"));
kg.add_triple(Triple::new("B", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "A"));
kg.add_triple(Triple::new("A", "rel", "D"));
let scores = pagerank(&kg, PageRankConfig::default());
let total: f64 = scores.values().sum();
assert!(
(total - 1.0).abs() < 1e-6,
"Scores should sum to 1.0, got {total}",
);
}
}