use crate::KnowledgeGraph;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DegreeCentrality {
pub in_degree: usize,
pub out_degree: usize,
pub in_normalized: f64,
pub out_normalized: f64,
}
impl DegreeCentrality {
#[must_use]
pub fn total(&self) -> usize {
self.in_degree + self.out_degree
}
#[must_use]
pub fn total_normalized(&self) -> f64 {
self.in_normalized + self.out_normalized
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn degree_centrality(kg: &KnowledgeGraph) -> HashMap<String, DegreeCentrality> {
let graph = kg.as_petgraph();
let n = graph.node_count();
if n == 0 {
return HashMap::new();
}
let norm_factor = if n > 1 { (n - 1) as f64 } else { 1.0 };
let mut result = HashMap::with_capacity(n);
for idx in graph.node_indices() {
let in_deg = graph
.neighbors_directed(idx, petgraph::Direction::Incoming)
.count();
let out_deg = graph
.neighbors_directed(idx, petgraph::Direction::Outgoing)
.count();
let entity = &graph[idx];
result.insert(
entity.id.as_str().to_owned(),
DegreeCentrality {
in_degree: in_deg,
out_degree: out_deg,
in_normalized: in_deg as f64 / norm_factor,
out_normalized: out_deg as f64 / norm_factor,
},
);
}
result
}
#[must_use]
pub fn in_degree_centrality(kg: &KnowledgeGraph) -> HashMap<String, f64> {
degree_centrality(kg)
.into_iter()
.map(|(k, v)| (k, v.in_normalized))
.collect()
}
#[must_use]
pub fn out_degree_centrality(kg: &KnowledgeGraph) -> HashMap<String, f64> {
degree_centrality(kg)
.into_iter()
.map(|(k, v)| (k, v.out_normalized))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Triple;
#[test]
fn test_degree_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 degrees = degree_centrality(&kg);
let hub = degrees.get("Hub").unwrap();
assert_eq!(hub.out_degree, 3);
assert_eq!(hub.in_degree, 0);
let a = degrees.get("A").unwrap();
assert_eq!(a.out_degree, 0);
assert_eq!(a.in_degree, 1);
}
#[test]
fn test_degree_normalization() {
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("B", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "B"));
kg.add_triple(Triple::new("A", "rel", "C"));
kg.add_triple(Triple::new("C", "rel", "A"));
let degrees = degree_centrality(&kg);
for (_, deg) in degrees {
assert!((deg.in_normalized - 1.0).abs() < 1e-6);
assert!((deg.out_normalized - 1.0).abs() < 1e-6);
}
}
#[test]
fn test_empty_graph() {
let kg = KnowledgeGraph::new();
let degrees = degree_centrality(&kg);
assert!(degrees.is_empty());
}
}