#[cfg(feature = "hgraph")]
use crate::core::view::NodeId;
#[cfg(feature = "hgraph")]
use crate::hgraph::h_graph::HeterogeneousGraph;
#[cfg(feature = "hgraph")]
use std::collections::HashMap;
#[cfg(feature = "hgraph")]
use std::fmt::Debug;
#[cfg(feature = "hgraph")]
use std::hash::Hash;
#[cfg(feature = "hgraph")]
impl From<crate::core::CentralityError> for CentralityError {
fn from(error: crate::core::CentralityError) -> Self {
match error {
crate::core::CentralityError::InvalidNode(id) => {
CentralityError::InvalidNodeReference(id)
}
crate::core::CentralityError::InvalidParameter {
name,
value,
expected,
} => CentralityError::InvalidParameter {
name,
value,
expected,
},
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CentralityError {
#[error("Invalid edge reference: edge ID {0} not found")]
InvalidEdgeReference(NodeId),
#[error("Invalid node reference: node ID {0} not found")]
InvalidNodeReference(NodeId),
#[error("Parameter `{name}` is {value}, expected {expected}")]
InvalidParameter {
name: &'static str,
value: f64,
expected: &'static str,
},
}
pub type Result<T> = std::result::Result<T, CentralityError>;
#[cfg(feature = "hgraph")]
#[deprecated(
since = "3.0.0",
note = "superseded by `xgraph::core::Centrality`; the `*_by_types` variants become \
`graph.of_edge_kinds(&[..])` followed by the ordinary method"
)]
pub trait HeteroCentrality<W, N, E> {
fn degree_centrality(&self) -> Result<HashMap<NodeId, usize>>;
fn degree_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, usize>>;
fn betweenness_centrality(&self) -> Result<HashMap<NodeId, f64>>;
fn betweenness_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, f64>>;
fn closeness_centrality(&self) -> Result<HashMap<NodeId, f64>>;
fn closeness_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, f64>>;
fn eigenvector_centrality(
&self,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
fn eigenvector_centrality_by_types(
&self,
edge_types: &[&str],
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
fn pagerank(
&self,
damping: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
fn pagerank_by_types(
&self,
edge_types: &[&str],
damping: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
fn katz_centrality(
&self,
alpha: f64,
beta: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
fn katz_centrality_by_types(
&self,
edge_types: &[&str],
alpha: f64,
beta: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>>;
}
#[cfg(feature = "hgraph")]
#[cfg(feature = "hgraph")]
#[allow(deprecated)]
impl<W, N, E> HeteroCentrality<W, N, E> for HeterogeneousGraph<W, N, E>
where
W: Copy + Default + PartialEq + Debug + Into<f64> + std::ops::Add<Output = W> + PartialOrd,
N: Clone + Eq + Hash + Debug + crate::hgraph::h_node::NodeType,
E: Clone + Default + Debug + crate::hgraph::h_edge::EdgeType,
{
fn degree_centrality(&self) -> Result<HashMap<NodeId, usize>> {
Ok(crate::core::Centrality::degree_centrality(self))
}
fn degree_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, usize>> {
Ok(crate::core::Centrality::degree_centrality(
&self.of_edge_kinds(edge_types),
))
}
fn betweenness_centrality(&self) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::betweenness_centrality(self).map_err(CentralityError::from)
}
fn betweenness_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, f64>> {
let by_kind = self.of_edge_kinds(edge_types);
let connected: std::collections::HashSet<NodeId> =
crate::core::GraphView::node_ids(&by_kind)
.filter(|&node| {
crate::core::GraphView::links(&by_kind, node)
.next()
.is_some()
})
.collect();
let induced =
crate::core::NodeFiltered::new(&by_kind, move |node| connected.contains(&node));
crate::core::Centrality::betweenness_centrality(&induced).map_err(CentralityError::from)
}
fn closeness_centrality(&self) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::closeness_centrality(self).map_err(CentralityError::from)
}
fn closeness_centrality_by_types(&self, edge_types: &[&str]) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::closeness_centrality(&self.of_edge_kinds(edge_types))
.map_err(CentralityError::from)
}
fn eigenvector_centrality(
&self,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::eigenvector_centrality(self, max_iter, tolerance)
.map_err(CentralityError::from)
}
fn eigenvector_centrality_by_types(
&self,
edge_types: &[&str],
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::eigenvector_centrality(
&self.of_edge_kinds(edge_types),
max_iter,
tolerance,
)
.map_err(CentralityError::from)
}
fn pagerank(
&self,
damping: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::pagerank(self, damping, max_iter, tolerance)
.map_err(CentralityError::from)
}
fn pagerank_by_types(
&self,
edge_types: &[&str],
damping: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::pagerank(
&self.of_edge_kinds(edge_types),
damping,
max_iter,
tolerance,
)
.map_err(CentralityError::from)
}
fn katz_centrality(
&self,
alpha: f64,
beta: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::katz_centrality(self, alpha, beta, max_iter, tolerance)
.map_err(CentralityError::from)
}
fn katz_centrality_by_types(
&self,
edge_types: &[&str],
alpha: f64,
beta: f64,
max_iter: usize,
tolerance: f64,
) -> Result<HashMap<NodeId, f64>> {
crate::core::Centrality::katz_centrality(
&self.of_edge_kinds(edge_types),
alpha,
beta,
max_iter,
tolerance,
)
.map_err(CentralityError::from)
}
}
#[cfg(test)]
#[allow(deprecated)]
#[cfg(feature = "hgraph")]
mod tests {
use super::*;
use crate::hgraph::h_edge::EdgeType;
use crate::hgraph::h_node::NodeType;
use float_cmp::approx_eq;
const TOLERANCE: f64 = 1e-5;
fn assert_approx_eq(a: f64, b: f64) {
assert!(approx_eq!(f64, a, b, epsilon = TOLERANCE), "{} ≈ {}", a, b);
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct TestNode(String);
impl NodeType for TestNode {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[derive(Clone, Debug, Default)]
struct TestEdge(String);
impl EdgeType for TestEdge {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[test]
fn test_degree_centrality() {
let mut graph = HeterogeneousGraph::<u32, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n0, n1, 2, TestEdge("path".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1, TestEdge("bridge".to_string()))
.unwrap();
let centrality = graph.degree_centrality().unwrap();
assert_eq!(centrality[&n0], 2);
assert_eq!(centrality[&n1], 3);
assert_eq!(centrality[&n2], 1);
}
#[test]
fn test_degree_centrality_by_types() {
let mut graph = HeterogeneousGraph::<u32, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n0, n1, 2, TestEdge("path".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1, TestEdge("bridge".to_string()))
.unwrap();
let centrality = graph
.degree_centrality_by_types(&["road", "bridge"])
.unwrap();
assert_eq!(centrality[&n0], 1); assert_eq!(centrality[&n1], 2); assert_eq!(centrality[&n2], 1); }
#[test]
fn test_betweenness_centrality() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
let n3 = graph.add_node(TestNode("D".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n1, n3, 1.0, TestEdge("path".to_string()))
.unwrap();
let betweenness = graph.betweenness_centrality().unwrap();
assert_approx_eq(betweenness[&n1], 1.0);
assert_approx_eq(betweenness[&n0], 0.0);
}
#[test]
fn test_betweenness_centrality_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
let n3 = graph.add_node(TestNode("D".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n1, n3, 1.0, TestEdge("path".to_string()))
.unwrap();
let betweenness = graph
.betweenness_centrality_by_types(&["road", "bridge"])
.unwrap();
assert_approx_eq(betweenness[&n1], 1.0);
assert_approx_eq(betweenness[&n0], 0.0);
}
#[test]
fn test_closeness_centrality() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let closeness = graph.closeness_centrality().unwrap();
assert_approx_eq(closeness[&n1], 1.0);
assert_approx_eq(closeness[&n0], 2.0 / 3.0);
}
#[test]
fn test_closeness_centrality_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let closeness = graph
.closeness_centrality_by_types(&["road", "bridge"])
.unwrap();
assert_approx_eq(closeness[&n1], 1.0);
assert_approx_eq(closeness[&n0], 2.0 / 3.0);
}
#[test]
fn test_eigenvector_centrality() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.3, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.3, TestEdge("bridge".to_string()))
.unwrap();
let centrality = graph.eigenvector_centrality(1000, 1e-8).unwrap();
assert!(centrality[&n1] > centrality[&n0]);
assert!(centrality[&n1] > centrality[&n2]);
}
#[test]
fn test_eigenvector_centrality_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.3, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.3, TestEdge("bridge".to_string()))
.unwrap();
let centrality = graph
.eigenvector_centrality_by_types(&["road", "bridge"], 1000, 1e-8)
.unwrap();
assert!(centrality[&n1] > centrality[&n0]);
assert!(centrality[&n1] > centrality[&n2]);
}
#[test]
fn test_pagerank() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n1, 2.0, TestEdge("path".to_string()))
.unwrap();
let pagerank = graph.pagerank(0.85, 100, 1e-6).unwrap();
assert!(pagerank[&n1] > pagerank[&n0]);
assert!(pagerank[&n1] > pagerank[&n2]);
}
#[test]
fn test_pagerank_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n1, 2.0, TestEdge("path".to_string()))
.unwrap();
let pagerank = graph
.pagerank_by_types(&["road", "bridge"], 0.85, 100, 1e-6)
.unwrap();
assert!(pagerank[&n1] > pagerank[&n0]);
assert!(pagerank[&n1] > pagerank[&n2]);
}
#[test]
fn test_katz_centrality() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let katz = graph.katz_centrality(0.1, 1.0, 100, 1e-6).unwrap();
assert!(katz[&n1] > katz[&n0]);
assert!(katz[&n1] > katz[&n2]);
}
#[test]
fn test_katz_centrality_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let katz = graph
.katz_centrality_by_types(&["road", "bridge"], 0.1, 1.0, 100, 1e-6)
.unwrap();
assert!(katz[&n1] > katz[&n0]);
assert!(katz[&n1] > katz[&n2]);
}
#[test]
fn test_empty_graph() {
let graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
assert!(graph.degree_centrality().unwrap().is_empty());
assert!(graph.betweenness_centrality().unwrap().is_empty());
assert!(graph.closeness_centrality().unwrap().is_empty());
assert!(graph.eigenvector_centrality(100, 1e-6).unwrap().is_empty());
assert!(graph.pagerank(0.85, 100, 1e-6).unwrap().is_empty());
assert!(graph
.katz_centrality(0.1, 1.0, 100, 1e-6)
.unwrap()
.is_empty());
}
}