use crate::KnowledgeGraph;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct LabelPropagationConfig {
pub max_iterations: usize,
pub seed: u64,
}
impl Default for LabelPropagationConfig {
fn default() -> Self {
Self {
max_iterations: 50,
seed: 42,
}
}
}
struct AdjList(Vec<Vec<usize>>);
impl graphops::GraphRef for AdjList {
fn node_count(&self) -> usize {
self.0.len()
}
fn neighbors_ref(&self, node: usize) -> &[usize] {
&self.0[node]
}
}
#[must_use]
pub fn label_propagation(
kg: &KnowledgeGraph,
config: LabelPropagationConfig,
) -> HashMap<String, usize> {
let graph = kg.as_petgraph();
let n = graph.node_count();
if n == 0 {
return HashMap::new();
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for edge in graph.edge_indices() {
if let Some((src, dst)) = graph.edge_endpoints(edge) {
let s = src.index();
let d = dst.index();
adj[s].push(d);
adj[d].push(s);
}
}
for neighbors in &mut adj {
neighbors.sort_unstable();
neighbors.dedup();
}
let adapter = AdjList(adj);
let labels =
graphops::partition::label_propagation(&adapter, config.max_iterations, config.seed);
let mut result = HashMap::with_capacity(n);
for (idx, label) in labels.into_iter().enumerate() {
let entity = &graph[petgraph::graph::NodeIndex::new(idx)];
result.insert(entity.id.as_str().to_owned(), label);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Triple;
#[test]
fn label_propagation_finds_communities() {
let mut kg = KnowledgeGraph::new();
kg.add_triple(Triple::new("A", "rel", "B"));
kg.add_triple(Triple::new("B", "rel", "A"));
kg.add_triple(Triple::new("A", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "A"));
kg.add_triple(Triple::new("B", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "B"));
kg.add_triple(Triple::new("D", "rel", "E"));
kg.add_triple(Triple::new("E", "rel", "D"));
kg.add_triple(Triple::new("D", "rel", "F"));
kg.add_triple(Triple::new("F", "rel", "D"));
kg.add_triple(Triple::new("E", "rel", "F"));
kg.add_triple(Triple::new("F", "rel", "E"));
kg.add_triple(Triple::new("C", "bridge", "D"));
let communities = label_propagation(&kg, LabelPropagationConfig::default());
assert_eq!(communities.len(), 6);
let a = communities["A"];
let b = communities["B"];
let c = communities["C"];
let d = communities["D"];
let e = communities["E"];
let f = communities["F"];
assert_eq!(a, b, "A and B should be in the same community");
assert_eq!(a, c, "A and C should be in the same community");
assert_eq!(d, e, "D and E should be in the same community");
assert_eq!(d, f, "D and F should be in the same community");
}
#[test]
fn label_propagation_empty_graph() {
let kg = KnowledgeGraph::new();
let communities = label_propagation(&kg, LabelPropagationConfig::default());
assert!(communities.is_empty());
}
#[test]
fn label_propagation_deterministic() {
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 config = LabelPropagationConfig::default();
let a = label_propagation(&kg, config);
let b = label_propagation(&kg, config);
assert_eq!(a, b);
}
}