rust_igraph/algorithms/properties/
schultz_index.rs1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23fn all_pairs_bfs(graph: &Graph, n: usize) -> Vec<u32> {
24 let mut dist = vec![u32::MAX; n * n];
25 for s in 0..n {
26 dist[s * n + s] = 0;
27 let mut queue = std::collections::VecDeque::new();
28 queue.push_back(s as u32);
29 while let Some(u) = queue.pop_front() {
30 let d_u = dist[s * n + u as usize];
31 if let Ok(nbs) = graph.neighbors(u) {
32 for nb in nbs {
33 let idx = s * n + nb as usize;
34 if dist[idx] == u32::MAX {
35 dist[idx] = d_u + 1;
36 queue.push_back(nb);
37 }
38 }
39 }
40 }
41 }
42 dist
43}
44
45pub fn schultz_index(graph: &Graph) -> IgraphResult<u64> {
63 let n = graph.vcount() as usize;
64 if n == 0 {
65 return Ok(0);
66 }
67
68 let dist = all_pairs_bfs(graph, n);
69 let mut deg = vec![0_usize; n];
70 for v in 0..n as u32 {
71 deg[v as usize] = graph.degree(v)?;
72 }
73
74 let mut s: u64 = 0;
75 for u in 0..n {
76 for v in (u + 1)..n {
77 let d = dist[u * n + v];
78 if d == u32::MAX {
79 continue;
80 }
81 let sum_deg = (deg[u] as u64).saturating_add(deg[v] as u64);
82 s = s.saturating_add(sum_deg.saturating_mul(u64::from(d)));
83 }
84 }
85
86 Ok(s)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn single_edge() -> Graph {
94 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
95 }
96
97 fn path3() -> Graph {
98 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
99 }
100
101 fn path4() -> Graph {
102 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
103 }
104
105 fn path5() -> Graph {
106 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
107 }
108
109 fn k3() -> Graph {
110 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
111 }
112
113 fn k4() -> Graph {
114 Graph::from_edges(
115 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
116 false,
117 Some(4),
118 )
119 .unwrap()
120 }
121
122 fn cycle4() -> Graph {
123 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
124 }
125
126 fn cycle5() -> Graph {
127 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
128 }
129
130 fn cycle6() -> Graph {
131 Graph::from_edges(
132 &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)],
133 false,
134 Some(6),
135 )
136 .unwrap()
137 }
138
139 fn star5() -> Graph {
140 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
141 }
142
143 fn paw() -> Graph {
144 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
145 }
146
147 #[test]
148 fn schultz_empty() {
149 let g = Graph::with_vertices(0);
150 assert_eq!(schultz_index(&g).unwrap(), 0);
151 }
152
153 #[test]
154 fn schultz_single_vertex() {
155 let g = Graph::with_vertices(1);
156 assert_eq!(schultz_index(&g).unwrap(), 0);
157 }
158
159 #[test]
160 fn schultz_isolated() {
161 let g = Graph::with_vertices(5);
162 assert_eq!(schultz_index(&g).unwrap(), 0);
163 }
164
165 #[test]
166 fn schultz_single_edge() {
167 assert_eq!(schultz_index(&single_edge()).unwrap(), 2);
169 }
170
171 #[test]
172 fn schultz_path3() {
173 assert_eq!(schultz_index(&path3()).unwrap(), 10);
176 }
177
178 #[test]
179 fn schultz_path4() {
180 assert_eq!(schultz_index(&path4()).unwrap(), 28);
185 }
186
187 #[test]
188 fn schultz_path5() {
189 assert_eq!(schultz_index(&path5()).unwrap(), 60);
196 }
197
198 #[test]
199 fn schultz_k3() {
200 assert_eq!(schultz_index(&k3()).unwrap(), 12);
203 }
204
205 #[test]
206 fn schultz_k4() {
207 assert_eq!(schultz_index(&k4()).unwrap(), 36);
210 }
211
212 #[test]
213 fn schultz_cycle4() {
214 assert_eq!(schultz_index(&cycle4()).unwrap(), 32);
220 }
221
222 #[test]
223 fn schultz_cycle5() {
224 assert_eq!(schultz_index(&cycle5()).unwrap(), 60);
228 }
229
230 #[test]
231 fn schultz_cycle6() {
232 assert_eq!(schultz_index(&cycle6()).unwrap(), 108);
238 }
239
240 #[test]
241 fn schultz_star5() {
242 assert_eq!(schultz_index(&star5()).unwrap(), 44);
247 }
248
249 #[test]
250 fn schultz_paw() {
251 assert_eq!(schultz_index(&paw()).unwrap(), 30);
256 }
257
258 #[test]
259 fn schultz_disconnected() {
260 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
262 assert_eq!(schultz_index(&g).unwrap(), 4);
265 }
266
267 #[test]
268 fn schultz_regular_formula() {
269 for g in &[k3(), k4(), cycle4(), cycle5(), cycle6()] {
271 let r = g.degree(0).unwrap() as u64;
272 let n = g.vcount() as usize;
273 let dist = all_pairs_bfs(g, n);
274 let mut w: u64 = 0;
275 for u in 0..n {
276 for v in (u + 1)..n {
277 let d = dist[u * n + v];
278 if d != u32::MAX {
279 w += u64::from(d);
280 }
281 }
282 }
283 let expected = 2 * r * w;
284 assert_eq!(schultz_index(g).unwrap(), expected, "r={r}, W={w}");
285 }
286 }
287
288 #[test]
289 fn schultz_nonneg() {
290 for g in &[
291 single_edge(),
292 path3(),
293 path4(),
294 k3(),
295 k4(),
296 cycle4(),
297 star5(),
298 paw(),
299 ] {
300 assert!(schultz_index(g).unwrap() > 0);
301 }
302 }
303
304 #[test]
305 fn schultz_vs_gutman_regular() {
306 use crate::algorithms::properties::mostar_index::gutman_index;
309 for g in &[k3(), k4(), cycle4(), cycle5()] {
310 let r = g.degree(0).unwrap() as u64;
311 let s = schultz_index(g).unwrap();
312 let gut = gutman_index(g).unwrap();
313 assert_eq!(s * r, 2 * gut, "r={r}");
314 }
315 }
316
317 #[test]
318 fn schultz_geq_wiener_times_2() {
319 for g in &[single_edge(), path3(), k3(), cycle4(), star5()] {
321 let n = g.vcount() as usize;
322 let dist = all_pairs_bfs(g, n);
323 let mut w: u64 = 0;
324 for u in 0..n {
325 for v in (u + 1)..n {
326 let d = dist[u * n + v];
327 if d != u32::MAX {
328 w += u64::from(d);
329 }
330 }
331 }
332 assert!(schultz_index(g).unwrap() >= 2 * w);
333 }
334 }
335}