facett_graphview/analysis/
stats.rs1use std::collections::VecDeque;
10
11use super::Adjacency;
12
13#[must_use]
18pub fn average_clustering(g: &Adjacency) -> f32 {
19 if g.n == 0 {
20 return 0.0;
21 }
22 let mut sum = 0.0f32;
23 for i in 0..g.n {
24 let nbrs: Vec<usize> = g.und[i].iter().map(|&(j, _)| j).collect();
25 let k = nbrs.len();
26 if k < 2 {
27 continue;
28 }
29 let mut links = 0usize;
31 for (a, &u) in nbrs.iter().enumerate() {
32 for &v in &nbrs[a + 1..] {
33 if g.und[u].iter().any(|&(w, _)| w == v) {
34 links += 1;
35 }
36 }
37 }
38 sum += 2.0 * links as f32 / (k * (k - 1)) as f32;
39 }
40 sum / g.n as f32
41}
42
43#[must_use]
47pub fn transitivity(g: &Adjacency) -> f32 {
48 let mut triangles = 0usize;
49 let mut triads = 0usize;
50 for i in 0..g.n {
51 let nbrs: Vec<usize> = g.und[i].iter().map(|&(j, _)| j).collect();
52 let k = nbrs.len();
53 if k >= 2 {
54 triads += k * (k - 1) / 2;
55 }
56 for (a, &u) in nbrs.iter().enumerate() {
57 for &v in &nbrs[a + 1..] {
58 if g.und[u].iter().any(|&(w, _)| w == v) {
59 triangles += 1;
60 }
61 }
62 }
63 }
64 if triads == 0 {
65 0.0
66 } else {
67 triangles as f32 / triads as f32
68 }
69}
70
71fn bfs_dist(g: &Adjacency, src: usize) -> Vec<usize> {
73 let mut dist = vec![usize::MAX; g.n];
74 let mut q = VecDeque::new();
75 dist[src] = 0;
76 q.push_back(src);
77 while let Some(v) = q.pop_front() {
78 for &(w, _) in &g.und[v] {
79 if dist[w] == usize::MAX {
80 dist[w] = dist[v] + 1;
81 q.push_back(w);
82 }
83 }
84 }
85 dist
86}
87
88#[must_use]
92pub fn diameter(g: &Adjacency) -> usize {
93 let mut d = 0;
94 for s in 0..g.n {
95 for dist in bfs_dist(g, s) {
96 if dist != usize::MAX {
97 d = d.max(dist);
98 }
99 }
100 }
101 d
102}
103
104#[must_use]
107pub fn average_path_length(g: &Adjacency) -> f32 {
108 let mut total = 0u64;
109 let mut pairs = 0u64;
110 for s in 0..g.n {
111 for (t, dist) in bfs_dist(g, s).into_iter().enumerate() {
112 if t != s && dist != usize::MAX {
113 total += dist as u64;
114 pairs += 1;
115 }
116 }
117 }
118 if pairs == 0 {
119 0.0
120 } else {
121 total as f32 / pairs as f32
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn triangle_is_fully_clustered() {
131 let g = Adjacency::from_edges(3, &[(0, 1), (1, 2), (2, 0)]);
133 assert!((average_clustering(&g) - 1.0).abs() < 1e-6);
134 assert!((transitivity(&g) - 1.0).abs() < 1e-6);
135 assert_eq!(diameter(&g), 1);
136 assert!((average_path_length(&g) - 1.0).abs() < 1e-6);
137 }
138
139 #[test]
140 fn path_graph_has_no_clustering() {
141 let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 3)]);
143 assert!(average_clustering(&g) < 1e-6);
144 assert_eq!(diameter(&g), 3);
145 assert!((average_path_length(&g) - 20.0 / 12.0).abs() < 1e-4);
147 }
148
149 #[test]
150 fn disconnected_ignores_infinite_pairs() {
151 let g = Adjacency::from_edges(4, &[(0, 1), (2, 3)]);
153 assert_eq!(diameter(&g), 1);
154 assert!((average_path_length(&g) - 1.0).abs() < 1e-6);
155 }
156
157 #[test]
158 fn empty_graph_is_zero() {
159 let g = Adjacency::from_edges(0, &[]);
160 assert_eq!(average_clustering(&g), 0.0);
161 assert_eq!(diameter(&g), 0);
162 assert_eq!(average_path_length(&g), 0.0);
163 }
164}