KeiSeiKit-1.0/_primitives/_rust/kei-sage/src/pagerank.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

60 lines
2.1 KiB
Rust

//! PageRank — power-iteration, 50 iterations, d=0.85. Operates on the edges table.
use crate::store::Store;
use anyhow::Result;
use std::collections::HashMap;
const DAMPING: f64 = 0.85;
const ITERATIONS: usize = 50;
/// Compute PageRank over the edges table. Returns [(path, score)] sorted desc.
pub fn pagerank(store: &Store) -> Result<Vec<(String, f64)>> {
let (nodes, out_edges) = collect_graph(store)?;
if nodes.is_empty() {
return Ok(Vec::new());
}
let mut rank: HashMap<String, f64> = nodes.iter()
.map(|n| (n.clone(), 1.0 / nodes.len() as f64)).collect();
for _ in 0..ITERATIONS {
rank = one_iteration(&nodes, &out_edges, &rank);
}
let mut out: Vec<(String, f64)> = rank.into_iter().collect();
out.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(out)
}
fn collect_graph(store: &Store) -> Result<(Vec<String>, HashMap<String, Vec<String>>)> {
let mut stmt = store.conn().prepare("SELECT src_path, dst_path FROM edges")?;
let rows = stmt.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)))?;
let mut nodes: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut out_edges: HashMap<String, Vec<String>> = HashMap::new();
for row in rows {
let (src, dst) = row?;
nodes.insert(src.clone());
nodes.insert(dst.clone());
out_edges.entry(src).or_default().push(dst);
}
Ok((nodes.into_iter().collect(), out_edges))
}
fn one_iteration(
nodes: &[String],
out_edges: &HashMap<String, Vec<String>>,
prev: &HashMap<String, f64>,
) -> HashMap<String, f64> {
let n = nodes.len() as f64;
let base = (1.0 - DAMPING) / n;
let mut next: HashMap<String, f64> = nodes.iter().map(|k| (k.clone(), base)).collect();
for (src, dsts) in out_edges {
if dsts.is_empty() {
continue;
}
let share = DAMPING * prev.get(src).copied().unwrap_or(0.0) / dsts.len() as f64;
for dst in dsts {
if let Some(slot) = next.get_mut(dst) {
*slot += share;
}
}
}
next
}