rust_igraph/algorithms/properties/
szeged_index.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::comparison_chain,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20use std::collections::VecDeque;
21
22pub fn szeged_index(graph: &Graph) -> IgraphResult<u64> {
41 let n = graph.vcount() as usize;
42 if n == 0 {
43 return Ok(0);
44 }
45
46 let dist = all_pairs_bfs(graph, n);
47 let mut sz: u64 = 0;
48
49 for (u, v) in graph.edges() {
50 let ui = u as usize;
51 let vi = v as usize;
52 if ui == vi {
53 continue;
54 }
55 let (nu, nv) = count_closer(&dist, n, ui, vi);
56 sz = sz.saturating_add((nu as u64).saturating_mul(nv as u64));
57 }
58
59 Ok(sz)
60}
61
62pub fn revised_szeged_index(graph: &Graph) -> IgraphResult<f64> {
79 let n = graph.vcount() as usize;
80 if n == 0 {
81 return Ok(0.0);
82 }
83
84 let dist = all_pairs_bfs(graph, n);
85 let mut sz_star: f64 = 0.0;
86
87 for (u, v) in graph.edges() {
88 let ui = u as usize;
89 let vi = v as usize;
90 if ui == vi {
91 continue;
92 }
93 let (nu, nv, n0) = count_closer_with_equidistant(&dist, n, ui, vi);
94 let su = nu as f64 + n0 as f64 / 2.0;
95 let sv = nv as f64 + n0 as f64 / 2.0;
96 sz_star += su * sv;
97 }
98
99 Ok(sz_star)
100}
101
102pub fn pi_index(graph: &Graph) -> IgraphResult<u64> {
121 let n = graph.vcount() as usize;
122 if n == 0 {
123 return Ok(0);
124 }
125
126 let dist = all_pairs_bfs(graph, n);
127 let mut pi: u64 = 0;
128
129 for (u, v) in graph.edges() {
130 let ui = u as usize;
131 let vi = v as usize;
132 if ui == vi {
133 continue;
134 }
135 let (nu, nv) = count_closer(&dist, n, ui, vi);
136 pi = pi.saturating_add((nu as u64).saturating_add(nv as u64));
137 }
138
139 Ok(pi)
140}
141
142fn count_closer(dist: &[u32], n: usize, u: usize, v: usize) -> (usize, usize) {
143 let mut nu = 0_usize;
144 let mut nv = 0_usize;
145 for w in 0..n {
146 let du = dist[u * n + w];
147 let dv = dist[v * n + w];
148 if du == u32::MAX || dv == u32::MAX {
149 continue;
150 }
151 if du < dv {
152 nu += 1;
153 } else if dv < du {
154 nv += 1;
155 }
156 }
157 (nu, nv)
158}
159
160fn count_closer_with_equidistant(
161 dist: &[u32],
162 n: usize,
163 u: usize,
164 v: usize,
165) -> (usize, usize, usize) {
166 let mut nu = 0_usize;
167 let mut nv = 0_usize;
168 let mut n0 = 0_usize;
169 for w in 0..n {
170 let du = dist[u * n + w];
171 let dv = dist[v * n + w];
172 if du == u32::MAX || dv == u32::MAX {
173 continue;
174 }
175 if du < dv {
176 nu += 1;
177 } else if dv < du {
178 nv += 1;
179 } else {
180 n0 += 1;
181 }
182 }
183 (nu, nv, n0)
184}
185
186fn all_pairs_bfs(graph: &Graph, n: usize) -> Vec<u32> {
187 let adj = build_adj_list(graph, n);
188 let mut dist = vec![u32::MAX; n * n];
189 for src in 0..n {
190 bfs_distances(&adj, n, src, &mut dist);
191 }
192 dist
193}
194
195fn build_adj_list(graph: &Graph, n: usize) -> Vec<Vec<usize>> {
196 let mut adj = vec![Vec::new(); n];
197 for (u, v) in graph.edges() {
198 let ui = u as usize;
199 let vi = v as usize;
200 adj[ui].push(vi);
201 if !graph.is_directed() && ui != vi {
202 adj[vi].push(ui);
203 }
204 }
205 adj
206}
207
208fn bfs_distances(adj: &[Vec<usize>], n: usize, src: usize, dist: &mut [u32]) {
209 dist[src * n + src] = 0;
210 let mut queue = VecDeque::new();
211 queue.push_back(src);
212 while let Some(v) = queue.pop_front() {
213 let d = dist[src * n + v];
214 for &w in &adj[v] {
215 if dist[src * n + w] == u32::MAX {
216 dist[src * n + w] = d.saturating_add(1);
217 queue.push_back(w);
218 }
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn single_edge() -> Graph {
228 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
229 }
230
231 fn path3() -> Graph {
232 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
233 }
234
235 fn path4() -> Graph {
236 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
237 }
238
239 fn k3() -> Graph {
240 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
241 }
242
243 fn k4() -> Graph {
244 Graph::from_edges(
245 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
246 false,
247 Some(4),
248 )
249 .unwrap()
250 }
251
252 fn cycle4() -> Graph {
253 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
254 }
255
256 fn cycle6() -> Graph {
257 Graph::from_edges(
258 &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)],
259 false,
260 Some(6),
261 )
262 .unwrap()
263 }
264
265 fn star5() -> Graph {
266 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
267 }
268
269 #[test]
272 fn sz_empty() {
273 let g = Graph::with_vertices(0);
274 assert_eq!(szeged_index(&g).unwrap(), 0);
275 }
276
277 #[test]
278 fn sz_no_edges() {
279 let g = Graph::with_vertices(3);
280 assert_eq!(szeged_index(&g).unwrap(), 0);
281 }
282
283 #[test]
284 fn sz_single_edge() {
285 assert_eq!(szeged_index(&single_edge()).unwrap(), 1);
287 }
288
289 #[test]
290 fn sz_path3() {
291 assert_eq!(szeged_index(&path3()).unwrap(), 4);
295 }
296
297 #[test]
298 fn sz_path4() {
299 assert_eq!(szeged_index(&path4()).unwrap(), 10);
315 }
316
317 #[test]
318 fn sz_k3() {
319 assert_eq!(szeged_index(&k3()).unwrap(), 3);
326 }
327
328 #[test]
329 fn sz_k4() {
330 assert_eq!(szeged_index(&k4()).unwrap(), 6);
334 }
335
336 #[test]
337 fn sz_cycle4() {
338 assert_eq!(szeged_index(&cycle4()).unwrap(), 16);
346 }
347
348 #[test]
349 fn sz_star5() {
350 assert_eq!(szeged_index(&star5()).unwrap(), 16);
355 }
356
357 #[test]
358 fn sz_cycle6() {
359 assert_eq!(szeged_index(&cycle6()).unwrap(), 54);
371 }
372
373 #[test]
376 fn rsz_empty() {
377 let g = Graph::with_vertices(0);
378 assert!((revised_szeged_index(&g).unwrap() - 0.0).abs() < 1e-10);
379 }
380
381 #[test]
382 fn rsz_single_edge() {
383 assert!((revised_szeged_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
385 }
386
387 #[test]
388 fn rsz_k3() {
389 let r = revised_szeged_index(&k3()).unwrap();
393 assert!((r - 6.75).abs() < 1e-10);
394 }
395
396 #[test]
397 fn rsz_path3() {
398 let r = revised_szeged_index(&path3()).unwrap();
411 assert!((r - 4.0).abs() < 1e-10);
412 }
413
414 #[test]
415 fn rsz_equals_sz_for_trees() {
416 for g in &[single_edge(), path3(), path4(), star5()] {
420 let sz = szeged_index(g).unwrap() as f64;
421 let rsz = revised_szeged_index(g).unwrap();
422 assert!(
423 (sz - rsz).abs() < 1e-10,
424 "Sz={sz}, Sz*={rsz} should be equal for trees"
425 );
426 }
427 }
428
429 #[test]
430 fn rsz_geq_sz() {
431 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), cycle6()] {
433 let sz = szeged_index(g).unwrap() as f64;
434 let rsz = revised_szeged_index(g).unwrap();
435 assert!(rsz >= sz - 1e-10, "Sz*={rsz} < Sz={sz}");
436 }
437 }
438
439 #[test]
442 fn pi_empty() {
443 let g = Graph::with_vertices(0);
444 assert_eq!(pi_index(&g).unwrap(), 0);
445 }
446
447 #[test]
448 fn pi_single_edge() {
449 assert_eq!(pi_index(&single_edge()).unwrap(), 2);
451 }
452
453 #[test]
454 fn pi_path3() {
455 assert_eq!(pi_index(&path3()).unwrap(), 6);
459 }
460
461 #[test]
462 fn pi_k3() {
463 assert_eq!(pi_index(&k3()).unwrap(), 6);
466 }
467
468 #[test]
469 fn pi_cycle4() {
470 assert_eq!(pi_index(&cycle4()).unwrap(), 16);
473 }
474
475 #[test]
476 fn pi_star5() {
477 assert_eq!(pi_index(&star5()).unwrap(), 20);
480 }
481
482 #[test]
485 fn szeged_leq_wiener_squared_over_m() {
486 for g in &[
489 single_edge(),
490 path3(),
491 path4(),
492 k3(),
493 k4(),
494 cycle4(),
495 star5(),
496 ] {
497 let sz = szeged_index(g).unwrap() as f64;
498 let w = crate::algorithms::properties::distance_spectrum::wiener_index(g).unwrap();
499 assert!(
500 sz >= w - 1e-10,
501 "Szeged {sz} < Wiener {w}, violates Sz >= W"
502 );
503 }
504 }
505
506 #[test]
507 fn pi_equals_sum_nu_nv() {
508 for g in &[path3(), k3(), cycle4(), star5()] {
511 let n = g.vcount() as usize;
512 let dist = all_pairs_bfs(g, n);
513 let mut sum_product: u64 = 0;
514 let mut sum_sum: u64 = 0;
515 for (u, v) in g.edges() {
516 let ui = u as usize;
517 let vi = v as usize;
518 if ui == vi {
519 continue;
520 }
521 let (nu, nv) = count_closer(&dist, n, ui, vi);
522 sum_product += (nu as u64) * (nv as u64);
523 sum_sum += (nu as u64) + (nv as u64);
524 }
525 assert_eq!(sum_product, szeged_index(g).unwrap());
526 assert_eq!(sum_sum, pi_index(g).unwrap());
527 }
528 }
529
530 #[test]
531 fn szeged_equals_wiener_for_trees() {
532 for g in &[single_edge(), path3(), path4(), star5()] {
534 let sz = szeged_index(g).unwrap() as f64;
535 let w = crate::algorithms::properties::distance_spectrum::wiener_index(g).unwrap();
536 assert!((sz - w).abs() < 1e-10, "Szeged {sz} != Wiener {w} for tree");
537 }
538 }
539}