use hashbrown::HashSet;
use petgraph::visit::{IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, NodeIndexable};
use rayon::prelude::*;
use std::hash::Hash;
fn graph_node_triangles<G>(graph: G, node: G::NodeId) -> (usize, usize)
where
G: IntoEdges,
G::NodeId: Hash + Eq,
{
let node_neighbors: HashSet<G::NodeId> = graph.neighbors(node).filter(|v| *v != node).collect();
let triangles: usize = node_neighbors
.iter()
.map(|&v| {
graph
.neighbors(v)
.filter(|&x| (x != v) && node_neighbors.contains(&x))
.count()
})
.sum();
let triples = match node_neighbors.len() {
0 => 0,
d => (d * (d - 1)) / 2,
};
(triangles / 2, triples)
}
pub fn graph_transitivity<G>(graph: G) -> f64
where
G: NodeIndexable + IntoEdges + IntoNodeIdentifiers + Send + Sync,
G::NodeId: Hash + Eq + Send + Sync,
{
let nodes: Vec<_> = graph.node_identifiers().collect();
let (triangles, triples) = nodes
.par_iter()
.map(|node| graph_node_triangles(graph, *node))
.reduce(
|| (0, 0),
|(sumx, sumy), (resx, resy)| (sumx + resx, sumy + resy),
);
match triangles {
0 => 0.0,
_ => triangles as f64 / triples as f64,
}
}
fn digraph_node_triangles<G>(graph: G, node: G::NodeId) -> (usize, usize)
where
G: IntoNeighborsDirected,
G::NodeId: Hash + Eq,
{
let out_neighbors: HashSet<G::NodeId> = graph
.neighbors_directed(node, petgraph::Direction::Outgoing)
.filter(|v| *v != node)
.collect();
let in_neighbors: HashSet<G::NodeId> = graph
.neighbors_directed(node, petgraph::Direction::Incoming)
.filter(|v| *v != node)
.collect();
let triangles: usize = out_neighbors
.iter()
.chain(in_neighbors.iter())
.map(|v| {
graph
.neighbors_directed(*v, petgraph::Direction::Outgoing)
.chain(graph.neighbors_directed(*v, petgraph::Direction::Incoming))
.map(|x| {
((x != *v) && out_neighbors.contains(&x)) as usize
+ ((x != *v) && in_neighbors.contains(&x)) as usize
})
.sum::<usize>()
})
.sum();
let d_tot = in_neighbors.len() + out_neighbors.len();
let d_bil = out_neighbors.intersection(&in_neighbors).count();
let triples = match d_tot {
0 => 0,
_ => d_tot * (d_tot - 1) - 2 * d_bil,
};
(triangles / 2, triples)
}
pub fn digraph_transitivity<G>(graph: G) -> f64
where
G: NodeIndexable + IntoNodeIdentifiers + IntoNeighborsDirected + Send + Sync,
G::NodeId: Hash + Eq + Send + Sync,
{
let nodes: Vec<_> = graph.node_identifiers().collect();
let (triangles, triples) = nodes
.par_iter()
.map(|node| digraph_node_triangles(graph, *node))
.reduce(
|| (0, 0),
|(sumx, sumy), (resx, resy)| (sumx + resx, sumy + resy),
);
match triangles {
0 => 0.0,
_ => triangles as f64 / triples as f64,
}
}
#[cfg(test)]
mod test_transitivity {
use petgraph::{
graph::{DiGraph, UnGraph},
Graph,
};
use super::{
digraph_node_triangles, digraph_transitivity, graph_node_triangles, graph_transitivity,
};
#[test]
fn test_node_triangles() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(5, 6);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
let e = graph.add_node(());
graph.extend_with_edges([(a, b), (b, c), (a, c), (a, d), (c, d), (d, e)]);
assert_eq!(graph_node_triangles(&graph, a), (2, 3));
}
#[test]
fn test_node_triangles_disconnected() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(1, 0);
let a = graph.add_node(());
assert_eq!(graph_node_triangles(&graph, a), (0, 0));
}
#[test]
fn test_transitivity() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(5, 5);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
let e = graph.add_node(());
graph.extend_with_edges([(a, b), (a, c), (a, d), (a, e), (b, c)]);
assert_eq!(graph_transitivity(&graph), 3. / 8.);
}
#[test]
fn test_transitivity_triangle() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(3, 3);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
graph.extend_with_edges([(a, b), (a, c), (b, c)]);
assert_eq!(graph_transitivity(&graph), 1.0)
}
#[test]
fn test_transitivity_star() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(5, 4);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
let e = graph.add_node(());
graph.extend_with_edges([(a, b), (a, c), (a, d), (a, e)]);
assert_eq!(graph_transitivity(&graph), 0.0)
}
#[test]
fn test_transitivity_empty() {
let graph: UnGraph<(), ()> = Graph::with_capacity(0, 0);
assert_eq!(graph_transitivity(&graph), 0.0)
}
#[test]
fn test_transitivity_disconnected() {
let mut graph: UnGraph<(), ()> = Graph::with_capacity(2, 1);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_node(());
graph.add_edge(a, b, ());
assert_eq!(graph_transitivity(&graph), 0.0)
}
#[test]
fn test_two_directed_node_triangles() {
let mut graph: DiGraph<(), ()> = Graph::with_capacity(5, 7);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
let e = graph.add_node(());
graph.extend_with_edges([(a, b), (b, c), (c, a), (a, c), (c, d), (d, a), (d, e)]);
assert_eq!(digraph_node_triangles(&graph, a), (4, 10));
}
#[test]
fn test_directed_node_triangles_disconnected() {
let mut graph: DiGraph<(), ()> = Graph::with_capacity(1, 0);
let a = graph.add_node(());
assert_eq!(graph_node_triangles(&graph, a), (0, 0));
}
#[test]
fn test_transitivity_directed() {
let mut graph: DiGraph<(), ()> = Graph::with_capacity(5, 4);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
let d = graph.add_node(());
graph.add_node(());
graph.extend_with_edges([(a, b), (a, c), (a, d), (b, c)]);
assert_eq!(digraph_transitivity(&graph), 3. / 10.);
}
#[test]
fn test_transitivity_triangle_directed() {
let mut graph: DiGraph<(), ()> = Graph::with_capacity(3, 3);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
graph.extend_with_edges([(a, b), (a, c), (b, c)]);
assert_eq!(digraph_transitivity(&graph), 0.5);
}
#[test]
fn test_transitivity_fulltriangle_directed() {
let mut graph: DiGraph<(), ()> = Graph::with_capacity(3, 6);
let a = graph.add_node(());
let b = graph.add_node(());
let c = graph.add_node(());
graph.extend_with_edges([(a, b), (b, a), (a, c), (c, a), (b, c), (c, b)]);
assert_eq!(digraph_transitivity(&graph), 1.0);
}
#[test]
fn test_transitivity_empty_directed() {
let graph: DiGraph<(), ()> = Graph::with_capacity(0, 0);
assert_eq!(digraph_transitivity(&graph), 0.0);
}
}