use std::collections::VecDeque;
use super::Adjacency;
#[must_use]
pub fn average_clustering(g: &Adjacency) -> f32 {
if g.n == 0 {
return 0.0;
}
let mut sum = 0.0f32;
for i in 0..g.n {
let nbrs: Vec<usize> = g.und[i].iter().map(|&(j, _)| j).collect();
let k = nbrs.len();
if k < 2 {
continue;
}
let mut links = 0usize;
for (a, &u) in nbrs.iter().enumerate() {
for &v in &nbrs[a + 1..] {
if g.und[u].iter().any(|&(w, _)| w == v) {
links += 1;
}
}
}
sum += 2.0 * links as f32 / (k * (k - 1)) as f32;
}
sum / g.n as f32
}
#[must_use]
pub fn transitivity(g: &Adjacency) -> f32 {
let mut triangles = 0usize;
let mut triads = 0usize;
for i in 0..g.n {
let nbrs: Vec<usize> = g.und[i].iter().map(|&(j, _)| j).collect();
let k = nbrs.len();
if k >= 2 {
triads += k * (k - 1) / 2;
}
for (a, &u) in nbrs.iter().enumerate() {
for &v in &nbrs[a + 1..] {
if g.und[u].iter().any(|&(w, _)| w == v) {
triangles += 1;
}
}
}
}
if triads == 0 {
0.0
} else {
triangles as f32 / triads as f32
}
}
fn bfs_dist(g: &Adjacency, src: usize) -> Vec<usize> {
let mut dist = vec![usize::MAX; g.n];
let mut q = VecDeque::new();
dist[src] = 0;
q.push_back(src);
while let Some(v) = q.pop_front() {
for &(w, _) in &g.und[v] {
if dist[w] == usize::MAX {
dist[w] = dist[v] + 1;
q.push_back(w);
}
}
}
dist
}
#[must_use]
pub fn diameter(g: &Adjacency) -> usize {
let mut d = 0;
for s in 0..g.n {
for dist in bfs_dist(g, s) {
if dist != usize::MAX {
d = d.max(dist);
}
}
}
d
}
#[must_use]
pub fn average_path_length(g: &Adjacency) -> f32 {
let mut total = 0u64;
let mut pairs = 0u64;
for s in 0..g.n {
for (t, dist) in bfs_dist(g, s).into_iter().enumerate() {
if t != s && dist != usize::MAX {
total += dist as u64;
pairs += 1;
}
}
}
if pairs == 0 {
0.0
} else {
total as f32 / pairs as f32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triangle_is_fully_clustered() {
let g = Adjacency::from_edges(3, &[(0, 1), (1, 2), (2, 0)]);
assert!((average_clustering(&g) - 1.0).abs() < 1e-6);
assert!((transitivity(&g) - 1.0).abs() < 1e-6);
assert_eq!(diameter(&g), 1);
assert!((average_path_length(&g) - 1.0).abs() < 1e-6);
}
#[test]
fn path_graph_has_no_clustering() {
let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 3)]);
assert!(average_clustering(&g) < 1e-6);
assert_eq!(diameter(&g), 3);
assert!((average_path_length(&g) - 20.0 / 12.0).abs() < 1e-4);
}
#[test]
fn disconnected_ignores_infinite_pairs() {
let g = Adjacency::from_edges(4, &[(0, 1), (2, 3)]);
assert_eq!(diameter(&g), 1);
assert!((average_path_length(&g) - 1.0).abs() < 1e-6);
}
#[test]
fn empty_graph_is_zero() {
let g = Adjacency::from_edges(0, &[]);
assert_eq!(average_clustering(&g), 0.0);
assert_eq!(diameter(&g), 0);
assert_eq!(average_path_length(&g), 0.0);
}
}