1use std::collections::VecDeque;
24
25use crate::algorithms::paths::distances::distances;
26use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum EccMode {
35 Out,
40 In,
42 All,
45}
46
47fn bfs_distances(graph: &Graph, source: VertexId, mode: EccMode) -> IgraphResult<Vec<Option<u32>>> {
51 let n = graph.vcount();
52 if n == 0 {
53 return Ok(Vec::new());
54 }
55 if source >= n {
56 return Err(IgraphError::VertexOutOfRange { id: source, n });
57 }
58
59 let n_us = n as usize;
60 let mut dist: Vec<Option<u32>> = vec![None; n_us];
61 let mut queue: VecDeque<VertexId> = VecDeque::new();
62 dist[source as usize] = Some(0);
63 queue.push_back(source);
64
65 let directed = graph.is_directed();
66 while let Some(v) = queue.pop_front() {
67 let next = dist[v as usize]
68 .expect("dequeued vertex has been visited")
69 .checked_add(1)
70 .ok_or(IgraphError::Internal(
71 "distance overflow (graph diameter exceeds u32::MAX)",
72 ))?;
73 let neighbours: Vec<VertexId> = if directed {
74 match mode {
75 EccMode::Out => graph.out_neighbors_vec(v)?,
76 EccMode::In => graph.in_neighbors_vec(v)?,
77 EccMode::All => {
78 let mut out = graph.out_neighbors_vec(v)?;
79 out.extend(graph.in_neighbors_vec(v)?);
80 out
81 }
82 }
83 } else {
84 graph.neighbors(v)?
87 };
88 for w in neighbours {
89 if dist[w as usize].is_none() {
90 dist[w as usize] = Some(next);
91 queue.push_back(w);
92 }
93 }
94 }
95
96 Ok(dist)
97}
98
99pub fn eccentricity(graph: &Graph) -> IgraphResult<Vec<u32>> {
110 let n = graph.vcount();
111 let mut out = vec![0u32; n as usize];
112 for v in 0..n {
113 let d = distances(graph, v)?;
114 let max = d.iter().filter_map(|x| *x).max().unwrap_or(0);
115 out[v as usize] = max;
116 }
117 Ok(out)
118}
119
120pub fn radius(graph: &Graph) -> IgraphResult<Option<u32>> {
126 let n = graph.vcount();
127 if n == 0 {
128 return Ok(None);
129 }
130 let ecc = eccentricity(graph)?;
131 Ok(ecc.into_iter().min())
132}
133
134pub fn diameter(graph: &Graph) -> IgraphResult<Option<u32>> {
154 let n = graph.vcount();
155 if n == 0 {
156 return Ok(None);
157 }
158 let ecc = eccentricity(graph)?;
159 Ok(ecc.into_iter().max())
160}
161
162pub fn eccentricity_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Vec<u32>> {
188 let n = graph.vcount();
189 let mut out = vec![0u32; n as usize];
190 for v in 0..n {
191 let d = bfs_distances(graph, v, mode)?;
192 let max = d.iter().filter_map(|x| *x).max().unwrap_or(0);
193 out[v as usize] = max;
194 }
195 Ok(out)
196}
197
198pub fn radius_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Option<u32>> {
202 if graph.vcount() == 0 {
203 return Ok(None);
204 }
205 let ecc = eccentricity_with_mode(graph, mode)?;
206 Ok(ecc.into_iter().min())
207}
208
209pub fn diameter_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Option<u32>> {
214 if graph.vcount() == 0 {
215 return Ok(None);
216 }
217 let ecc = eccentricity_with_mode(graph, mode)?;
218 Ok(ecc.into_iter().max())
219}
220
221fn ecc_mode_to_dijkstra(mode: EccMode) -> crate::algorithms::paths::dijkstra::DijkstraMode {
228 use crate::algorithms::paths::dijkstra::DijkstraMode;
229 match mode {
230 EccMode::Out => DijkstraMode::Out,
231 EccMode::In => DijkstraMode::In,
232 EccMode::All => DijkstraMode::All,
233 }
234}
235
236pub fn eccentricity_weighted_with_mode(
260 graph: &Graph,
261 weights: &[f64],
262 mode: EccMode,
263) -> IgraphResult<Vec<f64>> {
264 use crate::algorithms::paths::dijkstra::dijkstra_distances_with_mode;
265 let n = graph.vcount();
266 let mut out = vec![0.0_f64; n as usize];
267 for v in 0..n {
268 let d = dijkstra_distances_with_mode(graph, v, weights, ecc_mode_to_dijkstra(mode))?;
269 let max = d
270 .iter()
271 .filter_map(|x| *x)
272 .fold(0.0_f64, |a, b| if b > a { b } else { a });
273 out[v as usize] = max;
274 }
275 Ok(out)
276}
277
278pub fn radius_weighted_with_mode(
282 graph: &Graph,
283 weights: &[f64],
284 mode: EccMode,
285) -> IgraphResult<Option<f64>> {
286 if graph.vcount() == 0 {
287 return Ok(None);
288 }
289 let ecc = eccentricity_weighted_with_mode(graph, weights, mode)?;
290 Ok(ecc
291 .into_iter()
292 .fold(None, |acc, x| Some(acc.map_or(x, |a: f64| a.min(x)))))
293}
294
295pub fn diameter_weighted_with_mode(
300 graph: &Graph,
301 weights: &[f64],
302 mode: EccMode,
303) -> IgraphResult<Option<f64>> {
304 if graph.vcount() == 0 {
305 return Ok(None);
306 }
307 let ecc = eccentricity_weighted_with_mode(graph, weights, mode)?;
308 Ok(ecc
309 .into_iter()
310 .fold(None, |acc, x| Some(acc.map_or(x, |a: f64| a.max(x)))))
311}
312
313pub fn eccentricity_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Vec<f64>> {
316 eccentricity_weighted_with_mode(graph, weights, EccMode::Out)
317}
318
319pub fn radius_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Option<f64>> {
321 radius_weighted_with_mode(graph, weights, EccMode::Out)
322}
323
324pub fn diameter_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Option<f64>> {
326 diameter_weighted_with_mode(graph, weights, EccMode::Out)
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn empty_graph_radii_are_none() {
335 let g = Graph::with_vertices(0);
336 assert_eq!(radius(&g).unwrap(), None);
337 assert_eq!(diameter(&g).unwrap(), None);
338 assert!(eccentricity(&g).unwrap().is_empty());
339 }
340
341 #[test]
342 fn singleton_has_zero_eccentricity() {
343 let g = Graph::with_vertices(1);
344 assert_eq!(eccentricity(&g).unwrap(), vec![0]);
345 assert_eq!(radius(&g).unwrap(), Some(0));
346 assert_eq!(diameter(&g).unwrap(), Some(0));
347 }
348
349 #[test]
350 fn isolated_vertices_each_have_eccentricity_zero() {
351 let g = Graph::with_vertices(5);
352 assert_eq!(eccentricity(&g).unwrap(), vec![0; 5]);
353 assert_eq!(radius(&g).unwrap(), Some(0));
354 assert_eq!(diameter(&g).unwrap(), Some(0));
355 }
356
357 #[test]
358 fn path_5_diameter_4_radius_2() {
359 let mut g = Graph::with_vertices(5);
360 for i in 0..4 {
361 g.add_edge(i, i + 1).unwrap();
362 }
363 assert_eq!(eccentricity(&g).unwrap(), vec![4, 3, 2, 3, 4]);
364 assert_eq!(radius(&g).unwrap(), Some(2));
365 assert_eq!(diameter(&g).unwrap(), Some(4));
366 }
367
368 #[test]
369 fn cycle_4_eccentricity_uniform_2() {
370 let mut g = Graph::with_vertices(4);
371 for i in 0..4u32 {
372 g.add_edge(i, (i + 1) % 4).unwrap();
373 }
374 assert_eq!(eccentricity(&g).unwrap(), vec![2, 2, 2, 2]);
375 assert_eq!(radius(&g).unwrap(), Some(2));
376 assert_eq!(diameter(&g).unwrap(), Some(2));
377 }
378
379 #[test]
380 fn star_centre_has_eccentricity_1_leaves_have_2() {
381 let mut g = Graph::with_vertices(4);
383 for v in 1..4 {
384 g.add_edge(0, v).unwrap();
385 }
386 assert_eq!(eccentricity(&g).unwrap(), vec![1, 2, 2, 2]);
387 assert_eq!(radius(&g).unwrap(), Some(1));
388 assert_eq!(diameter(&g).unwrap(), Some(2));
389 }
390
391 #[test]
392 fn disconnected_components_use_max_within_components() {
393 let mut g = Graph::with_vertices(5);
397 g.add_edge(0, 1).unwrap();
398 g.add_edge(1, 2).unwrap();
399 g.add_edge(3, 4).unwrap();
400 assert_eq!(eccentricity(&g).unwrap(), vec![2, 1, 2, 1, 1]);
401 assert_eq!(radius(&g).unwrap(), Some(1));
402 assert_eq!(diameter(&g).unwrap(), Some(2));
403 }
404
405 #[test]
406 fn directed_path_uses_out_edges() {
407 let mut g = Graph::new(3, true).unwrap();
409 g.add_edge(0, 1).unwrap();
410 g.add_edge(1, 2).unwrap();
411 assert_eq!(eccentricity(&g).unwrap(), vec![2, 1, 0]);
412 assert_eq!(diameter(&g).unwrap(), Some(2));
413 }
414
415 #[test]
416 fn self_loop_does_not_inflate_eccentricity() {
417 let mut g = Graph::with_vertices(2);
419 g.add_edge(0, 0).unwrap();
420 g.add_edge(0, 1).unwrap();
421 assert_eq!(eccentricity(&g).unwrap(), vec![1, 1]);
422 assert_eq!(diameter(&g).unwrap(), Some(1));
423 }
424
425 #[test]
428 fn legacy_apis_match_with_mode_out() {
429 let mut g = Graph::with_vertices(5);
432 for i in 0..4 {
433 g.add_edge(i, i + 1).unwrap();
434 }
435 assert_eq!(
436 eccentricity(&g).unwrap(),
437 eccentricity_with_mode(&g, EccMode::Out).unwrap()
438 );
439 assert_eq!(
440 radius(&g).unwrap(),
441 radius_with_mode(&g, EccMode::Out).unwrap()
442 );
443 assert_eq!(
444 diameter(&g).unwrap(),
445 diameter_with_mode(&g, EccMode::Out).unwrap()
446 );
447 }
448
449 #[test]
450 fn undirected_all_modes_agree() {
451 let mut g = Graph::with_vertices(4);
452 g.add_edge(0, 1).unwrap();
453 g.add_edge(1, 2).unwrap();
454 g.add_edge(2, 3).unwrap();
455 for m in [EccMode::Out, EccMode::In, EccMode::All] {
456 assert_eq!(
457 eccentricity_with_mode(&g, m).unwrap(),
458 vec![3, 2, 2, 3],
459 "mode {m:?}"
460 );
461 assert_eq!(radius_with_mode(&g, m).unwrap(), Some(2));
462 assert_eq!(diameter_with_mode(&g, m).unwrap(), Some(3));
463 }
464 }
465
466 #[test]
467 fn directed_path_in_mode_reverses() {
468 let mut g = Graph::new(3, true).unwrap();
470 g.add_edge(0, 1).unwrap();
471 g.add_edge(1, 2).unwrap();
472 assert_eq!(
473 eccentricity_with_mode(&g, EccMode::Out).unwrap(),
474 vec![2, 1, 0]
475 );
476 assert_eq!(
477 eccentricity_with_mode(&g, EccMode::In).unwrap(),
478 vec![0, 1, 2]
479 );
480 }
481
482 #[test]
483 fn directed_path_all_mode_treats_undirected() {
484 let mut g = Graph::new(3, true).unwrap();
485 g.add_edge(0, 1).unwrap();
486 g.add_edge(1, 2).unwrap();
487 assert_eq!(
489 eccentricity_with_mode(&g, EccMode::All).unwrap(),
490 vec![2, 1, 2]
491 );
492 assert_eq!(radius_with_mode(&g, EccMode::All).unwrap(), Some(1));
493 assert_eq!(diameter_with_mode(&g, EccMode::All).unwrap(), Some(2));
494 }
495
496 #[test]
497 fn directed_cycle_all_modes_uniform() {
498 let mut g = Graph::new(3, true).unwrap();
502 g.add_edge(0, 1).unwrap();
503 g.add_edge(1, 2).unwrap();
504 g.add_edge(2, 0).unwrap();
505 assert_eq!(
506 eccentricity_with_mode(&g, EccMode::Out).unwrap(),
507 vec![2, 2, 2]
508 );
509 assert_eq!(
510 eccentricity_with_mode(&g, EccMode::In).unwrap(),
511 vec![2, 2, 2]
512 );
513 assert_eq!(
514 eccentricity_with_mode(&g, EccMode::All).unwrap(),
515 vec![1, 1, 1]
516 );
517 }
518
519 #[test]
520 fn directed_disconnected_in_out_is_zero_for_sinks_sources() {
521 let mut g = Graph::new(3, true).unwrap();
523 g.add_edge(0, 1).unwrap();
524 assert_eq!(
526 eccentricity_with_mode(&g, EccMode::Out).unwrap(),
527 vec![1, 0, 0]
528 );
529 assert_eq!(
531 eccentricity_with_mode(&g, EccMode::In).unwrap(),
532 vec![0, 1, 0]
533 );
534 assert_eq!(
536 eccentricity_with_mode(&g, EccMode::All).unwrap(),
537 vec![1, 1, 0]
538 );
539 }
540
541 #[test]
542 fn radius_diameter_match_min_max_of_eccentricity() {
543 let mut g = Graph::new(4, true).unwrap();
544 g.add_edge(0, 1).unwrap();
545 g.add_edge(1, 2).unwrap();
546 g.add_edge(2, 3).unwrap();
547 g.add_edge(3, 0).unwrap();
548 for m in [EccMode::Out, EccMode::In, EccMode::All] {
549 let ecc = eccentricity_with_mode(&g, m).unwrap();
550 assert_eq!(radius_with_mode(&g, m).unwrap(), ecc.iter().copied().min());
551 assert_eq!(
552 diameter_with_mode(&g, m).unwrap(),
553 ecc.iter().copied().max()
554 );
555 }
556 }
557
558 #[test]
559 fn empty_graph_with_mode_is_none() {
560 let g_u = Graph::with_vertices(0);
561 let g_d = Graph::new(0, true).unwrap();
562 for m in [EccMode::Out, EccMode::In, EccMode::All] {
563 assert_eq!(radius_with_mode(&g_u, m).unwrap(), None);
564 assert_eq!(diameter_with_mode(&g_u, m).unwrap(), None);
565 assert!(eccentricity_with_mode(&g_u, m).unwrap().is_empty());
566 assert_eq!(radius_with_mode(&g_d, m).unwrap(), None);
567 assert_eq!(diameter_with_mode(&g_d, m).unwrap(), None);
568 assert!(eccentricity_with_mode(&g_d, m).unwrap().is_empty());
569 }
570 }
571
572 #[test]
575 fn weighted_path_eccentricity() {
576 let mut g = Graph::with_vertices(3);
579 g.add_edge(0, 1).unwrap();
580 g.add_edge(1, 2).unwrap();
581 let r = eccentricity_weighted(&g, &[1.0, 2.5]).unwrap();
582 assert_eq!(r, vec![3.5, 2.5, 3.5]);
583 assert_eq!(radius_weighted(&g, &[1.0, 2.5]).unwrap(), Some(2.5));
584 assert_eq!(diameter_weighted(&g, &[1.0, 2.5]).unwrap(), Some(3.5));
585 }
586
587 #[test]
588 fn weighted_singleton_zero() {
589 let g = Graph::with_vertices(1);
590 assert_eq!(eccentricity_weighted(&g, &[]).unwrap(), vec![0.0]);
591 assert_eq!(radius_weighted(&g, &[]).unwrap(), Some(0.0));
592 assert_eq!(diameter_weighted(&g, &[]).unwrap(), Some(0.0));
593 }
594
595 #[test]
596 fn weighted_isolated_vertices_zero() {
597 let g = Graph::with_vertices(4);
598 assert_eq!(eccentricity_weighted(&g, &[]).unwrap(), vec![0.0; 4]);
599 assert_eq!(radius_weighted(&g, &[]).unwrap(), Some(0.0));
600 assert_eq!(diameter_weighted(&g, &[]).unwrap(), Some(0.0));
601 }
602
603 #[test]
604 fn weighted_disconnected_unconn_true_semantics() {
605 let mut g = Graph::with_vertices(4);
608 g.add_edge(0, 1).unwrap();
609 g.add_edge(2, 3).unwrap();
610 let r = eccentricity_weighted(&g, &[1.0, 4.0]).unwrap();
611 assert_eq!(r, vec![1.0, 1.0, 4.0, 4.0]);
612 assert_eq!(radius_weighted(&g, &[1.0, 4.0]).unwrap(), Some(1.0));
614 assert_eq!(diameter_weighted(&g, &[1.0, 4.0]).unwrap(), Some(4.0));
615 }
616
617 #[test]
618 fn weighted_directed_in_mode_reverses() {
619 let mut g = Graph::new(3, true).unwrap();
622 g.add_edge(0, 1).unwrap();
623 g.add_edge(1, 2).unwrap();
624 let w = vec![1.0, 2.0];
625 assert_eq!(
626 eccentricity_weighted_with_mode(&g, &w, EccMode::Out).unwrap(),
627 vec![3.0, 2.0, 0.0]
628 );
629 assert_eq!(
630 eccentricity_weighted_with_mode(&g, &w, EccMode::In).unwrap(),
631 vec![0.0, 1.0, 3.0]
632 );
633 assert_eq!(
634 eccentricity_weighted_with_mode(&g, &w, EccMode::All).unwrap(),
635 vec![3.0, 2.0, 3.0]
636 );
637 }
638
639 #[test]
640 fn weighted_undirected_modes_agree() {
641 let mut g = Graph::with_vertices(3);
644 g.add_edge(0, 1).unwrap();
645 g.add_edge(1, 2).unwrap();
646 g.add_edge(0, 2).unwrap();
647 let w = vec![1.0, 2.0, 4.0];
648 for m in [EccMode::Out, EccMode::In, EccMode::All] {
649 assert_eq!(
650 eccentricity_weighted_with_mode(&g, &w, m).unwrap(),
651 vec![3.0, 2.0, 3.0],
652 "mode {m:?}"
653 );
654 }
655 }
656
657 #[test]
658 fn weighted_negative_weight_errors() {
659 let mut g = Graph::with_vertices(2);
660 g.add_edge(0, 1).unwrap();
661 assert!(eccentricity_weighted(&g, &[-1.0]).is_err());
662 }
663
664 #[test]
665 fn weighted_empty_graph_radius_diameter_none() {
666 let g = Graph::with_vertices(0);
667 assert_eq!(radius_weighted(&g, &[]).unwrap(), None);
668 assert_eq!(diameter_weighted(&g, &[]).unwrap(), None);
669 assert!(eccentricity_weighted(&g, &[]).unwrap().is_empty());
670 }
671
672 #[test]
673 fn weighted_with_mode_default_matches_out() {
674 let mut g = Graph::with_vertices(3);
675 g.add_edge(0, 1).unwrap();
676 g.add_edge(1, 2).unwrap();
677 let w = vec![1.0, 2.5];
678 assert_eq!(
679 eccentricity_weighted(&g, &w).unwrap(),
680 eccentricity_weighted_with_mode(&g, &w, EccMode::Out).unwrap()
681 );
682 }
683}