1#![allow(
16 clippy::cast_possible_truncation,
17 clippy::cast_precision_loss,
18 clippy::many_single_char_names,
19 clippy::needless_range_loop,
20 clippy::too_many_lines
21)]
22
23use crate::core::{Graph, IgraphResult};
24use std::collections::VecDeque;
25
26pub fn eccentric_connectivity_index(graph: &Graph) -> IgraphResult<u64> {
44 let n = graph.vcount() as usize;
45 if n == 0 {
46 return Ok(0);
47 }
48
49 let ecc = compute_eccentricities(graph, n);
50 let mut xi: u64 = 0;
51
52 for v in 0..n as u32 {
53 let deg = graph.degree(v)? as u64;
54 xi = xi.saturating_add(deg.saturating_mul(u64::from(ecc[v as usize])));
55 }
56
57 Ok(xi)
58}
59
60pub fn total_eccentricity(graph: &Graph) -> IgraphResult<u64> {
74 let n = graph.vcount() as usize;
75 if n == 0 {
76 return Ok(0);
77 }
78
79 let ecc = compute_eccentricities(graph, n);
80 let mut total: u64 = 0;
81 for &e in &ecc {
82 total = total.saturating_add(u64::from(e));
83 }
84
85 Ok(total)
86}
87
88pub fn connective_eccentricity_index(graph: &Graph) -> IgraphResult<f64> {
105 let n = graph.vcount() as usize;
106 if n == 0 {
107 return Ok(0.0);
108 }
109
110 let ecc = compute_eccentricities(graph, n);
111 let mut xi = 0.0_f64;
112
113 for v in 0..n as u32 {
114 let e = ecc[v as usize];
115 if e == 0 {
116 continue;
117 }
118 let deg = graph.degree(v)? as f64;
119 xi += deg / f64::from(e);
120 }
121
122 Ok(xi)
123}
124
125fn compute_eccentricities(graph: &Graph, n: usize) -> Vec<u32> {
126 let adj = build_adj_list(graph, n);
127 let mut ecc = vec![0_u32; n];
128
129 for src in 0..n {
130 let dist = bfs_from(&adj, n, src);
131 let mut max_d = 0_u32;
132 for &d in &dist {
133 if d != u32::MAX && d > max_d {
134 max_d = d;
135 }
136 }
137 ecc[src] = max_d;
138 }
139
140 ecc
141}
142
143fn build_adj_list(graph: &Graph, n: usize) -> Vec<Vec<usize>> {
144 let mut adj = vec![Vec::new(); n];
145 for (u, v) in graph.edges() {
146 let ui = u as usize;
147 let vi = v as usize;
148 adj[ui].push(vi);
149 if !graph.is_directed() && ui != vi {
150 adj[vi].push(ui);
151 }
152 }
153 adj
154}
155
156fn bfs_from(adj: &[Vec<usize>], n: usize, src: usize) -> Vec<u32> {
157 let mut dist = vec![u32::MAX; n];
158 dist[src] = 0;
159 let mut queue = VecDeque::new();
160 queue.push_back(src);
161 while let Some(v) = queue.pop_front() {
162 let d = dist[v];
163 for &w in &adj[v] {
164 if dist[w] == u32::MAX {
165 dist[w] = d.saturating_add(1);
166 queue.push_back(w);
167 }
168 }
169 }
170 dist
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 fn single_edge() -> Graph {
178 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
179 }
180
181 fn path3() -> Graph {
182 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
183 }
184
185 fn path4() -> Graph {
186 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
187 }
188
189 fn path5() -> Graph {
190 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
191 }
192
193 fn k3() -> Graph {
194 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
195 }
196
197 fn k4() -> Graph {
198 Graph::from_edges(
199 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
200 false,
201 Some(4),
202 )
203 .unwrap()
204 }
205
206 fn cycle4() -> Graph {
207 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
208 }
209
210 fn cycle5() -> Graph {
211 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
212 }
213
214 fn star5() -> Graph {
215 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
216 }
217
218 #[test]
221 fn eci_empty() {
222 let g = Graph::with_vertices(0);
223 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 0);
224 }
225
226 #[test]
227 fn eci_single_vertex() {
228 let g = Graph::with_vertices(1);
229 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 0);
230 }
231
232 #[test]
233 fn eci_no_edges() {
234 let g = Graph::with_vertices(3);
235 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 0);
236 }
237
238 #[test]
239 fn eci_single_edge() {
240 assert_eq!(eccentric_connectivity_index(&single_edge()).unwrap(), 2);
242 }
243
244 #[test]
245 fn eci_path3() {
246 assert_eq!(eccentric_connectivity_index(&path3()).unwrap(), 6);
248 }
249
250 #[test]
251 fn eci_path4() {
252 assert_eq!(eccentric_connectivity_index(&path4()).unwrap(), 14);
255 }
256
257 #[test]
258 fn eci_path5() {
259 assert_eq!(eccentric_connectivity_index(&path5()).unwrap(), 24);
262 }
263
264 #[test]
265 fn eci_k3() {
266 assert_eq!(eccentric_connectivity_index(&k3()).unwrap(), 6);
268 }
269
270 #[test]
271 fn eci_k4() {
272 assert_eq!(eccentric_connectivity_index(&k4()).unwrap(), 12);
274 }
275
276 #[test]
277 fn eci_cycle4() {
278 assert_eq!(eccentric_connectivity_index(&cycle4()).unwrap(), 16);
280 }
281
282 #[test]
283 fn eci_cycle5() {
284 assert_eq!(eccentric_connectivity_index(&cycle5()).unwrap(), 20);
286 }
287
288 #[test]
289 fn eci_star5() {
290 assert_eq!(eccentric_connectivity_index(&star5()).unwrap(), 12);
293 }
294
295 #[test]
296 fn eci_with_isolated() {
297 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
300 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 2);
301 }
302
303 #[test]
304 fn eci_two_components() {
305 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
309 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 4);
310 }
311
312 #[test]
313 fn eci_complete_formula() {
314 for n in 2_u32..=6 {
316 let edges: Vec<(u32, u32)> = (0..n)
317 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
318 .collect();
319 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
320 assert_eq!(
321 eccentric_connectivity_index(&g).unwrap(),
322 u64::from(n) * u64::from(n - 1)
323 );
324 }
325 }
326
327 #[test]
328 fn eci_diamond() {
329 let g =
333 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap();
334 assert_eq!(eccentric_connectivity_index(&g).unwrap(), 14);
335 }
336
337 #[test]
340 fn te_empty() {
341 let g = Graph::with_vertices(0);
342 assert_eq!(total_eccentricity(&g).unwrap(), 0);
343 }
344
345 #[test]
346 fn te_single_vertex() {
347 let g = Graph::with_vertices(1);
348 assert_eq!(total_eccentricity(&g).unwrap(), 0);
349 }
350
351 #[test]
352 fn te_single_edge() {
353 assert_eq!(total_eccentricity(&single_edge()).unwrap(), 2);
354 }
355
356 #[test]
357 fn te_path3() {
358 assert_eq!(total_eccentricity(&path3()).unwrap(), 5);
360 }
361
362 #[test]
363 fn te_path4() {
364 assert_eq!(total_eccentricity(&path4()).unwrap(), 10);
366 }
367
368 #[test]
369 fn te_k3() {
370 assert_eq!(total_eccentricity(&k3()).unwrap(), 3);
372 }
373
374 #[test]
375 fn te_k4() {
376 assert_eq!(total_eccentricity(&k4()).unwrap(), 4);
378 }
379
380 #[test]
381 fn te_cycle4() {
382 assert_eq!(total_eccentricity(&cycle4()).unwrap(), 8);
384 }
385
386 #[test]
387 fn te_cycle5() {
388 assert_eq!(total_eccentricity(&cycle5()).unwrap(), 10);
390 }
391
392 #[test]
393 fn te_star5() {
394 assert_eq!(total_eccentricity(&star5()).unwrap(), 9);
396 }
397
398 #[test]
399 fn te_complete_formula() {
400 for n in 2_u32..=6 {
402 let edges: Vec<(u32, u32)> = (0..n)
403 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
404 .collect();
405 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
406 assert_eq!(total_eccentricity(&g).unwrap(), u64::from(n));
407 }
408 }
409
410 #[test]
413 fn cei_empty() {
414 let g = Graph::with_vertices(0);
415 assert!((connective_eccentricity_index(&g).unwrap() - 0.0).abs() < 1e-10);
416 }
417
418 #[test]
419 fn cei_single_vertex() {
420 let g = Graph::with_vertices(1);
421 assert!((connective_eccentricity_index(&g).unwrap() - 0.0).abs() < 1e-10);
422 }
423
424 #[test]
425 fn cei_single_edge() {
426 assert!((connective_eccentricity_index(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
428 }
429
430 #[test]
431 fn cei_path3() {
432 assert!((connective_eccentricity_index(&path3()).unwrap() - 3.0).abs() < 1e-10);
434 }
435
436 #[test]
437 fn cei_path4() {
438 let c = connective_eccentricity_index(&path4()).unwrap();
441 assert!((c - 8.0 / 3.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn cei_k3() {
446 assert!((connective_eccentricity_index(&k3()).unwrap() - 6.0).abs() < 1e-10);
448 }
449
450 #[test]
451 fn cei_k4() {
452 assert!((connective_eccentricity_index(&k4()).unwrap() - 12.0).abs() < 1e-10);
454 }
455
456 #[test]
457 fn cei_cycle4() {
458 assert!((connective_eccentricity_index(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
460 }
461
462 #[test]
463 fn cei_star5() {
464 assert!((connective_eccentricity_index(&star5()).unwrap() - 6.0).abs() < 1e-10);
467 }
468
469 #[test]
470 fn cei_with_isolated() {
471 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
474 assert!((connective_eccentricity_index(&g).unwrap() - 2.0).abs() < 1e-10);
475 }
476
477 #[test]
478 fn cei_complete_formula() {
479 for n in 2_u32..=6 {
481 let edges: Vec<(u32, u32)> = (0..n)
482 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
483 .collect();
484 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
485 assert!(
486 (connective_eccentricity_index(&g).unwrap() - f64::from(n) * f64::from(n - 1))
487 .abs()
488 < 1e-10
489 );
490 }
491 }
492
493 #[test]
496 fn eci_geq_total_ecc() {
497 for g in &[
500 single_edge(),
501 path3(),
502 path4(),
503 k3(),
504 k4(),
505 cycle4(),
506 star5(),
507 ] {
508 let xi = eccentric_connectivity_index(g).unwrap();
509 let te = total_eccentricity(g).unwrap();
510 assert!(xi >= te, "ξ^c={xi} < ζ={te}");
511 }
512 }
513
514 #[test]
515 fn eci_equals_2m_for_kn() {
516 for n in 2_u32..=6 {
518 let edges: Vec<(u32, u32)> = (0..n)
519 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
520 .collect();
521 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
522 assert_eq!(
523 eccentric_connectivity_index(&g).unwrap(),
524 2 * g.ecount() as u64
525 );
526 }
527 }
528
529 #[test]
530 fn cei_leq_eci_for_ecc_geq_1() {
531 for g in &[
533 single_edge(),
534 path3(),
535 path4(),
536 k3(),
537 k4(),
538 cycle4(),
539 star5(),
540 ] {
541 let xi = eccentric_connectivity_index(g).unwrap() as f64;
542 let ce = connective_eccentricity_index(g).unwrap();
543 assert!(ce <= xi + 1e-10, "ξ^ce={ce} > ξ^c={xi}");
544 }
545 }
546
547 #[test]
548 fn eci_path_formula() {
549 for n in 2_u32..=8 {
552 let edges: Vec<(u32, u32)> = (0..n - 1).map(|i| (i, i + 1)).collect();
553 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
554
555 let mut expected: u64 = 0;
556 for i in 0..n {
557 let ecc = std::cmp::max(i, n - 1 - i);
558 let deg = if i == 0 || i == n - 1 { 1_u64 } else { 2_u64 };
559 expected += deg * u64::from(ecc);
560 }
561 assert_eq!(eccentric_connectivity_index(&g).unwrap(), expected);
562 }
563 }
564
565 #[test]
566 fn te_path_formula() {
567 for n in 2_u32..=8 {
569 let edges: Vec<(u32, u32)> = (0..n - 1).map(|i| (i, i + 1)).collect();
570 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
571
572 let mut expected: u64 = 0;
573 for i in 0..n {
574 expected += u64::from(std::cmp::max(i, n - 1 - i));
575 }
576 assert_eq!(total_eccentricity(&g).unwrap(), expected);
577 }
578 }
579}