1use std::cmp::Ordering;
26use std::collections::BinaryHeap;
27
28use crate::algorithms::paths::dijkstra::DijkstraMode;
29use crate::core::graph::EdgeId;
30use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
31
32#[derive(Copy, Clone)]
35struct WidestFrontier(f64, VertexId);
36
37impl PartialEq for WidestFrontier {
38 fn eq(&self, other: &Self) -> bool {
39 self.0 == other.0 && self.1 == other.1
40 }
41}
42impl Eq for WidestFrontier {}
43impl Ord for WidestFrontier {
44 fn cmp(&self, other: &Self) -> Ordering {
45 self.0.total_cmp(&other.0).then(other.1.cmp(&self.1))
48 }
49}
50impl PartialOrd for WidestFrontier {
51 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
52 Some(self.cmp(other))
53 }
54}
55
56fn validate_weights(graph: &Graph, weights: &[f64]) -> IgraphResult<()> {
57 let m = graph.ecount();
58 if weights.len() != m {
59 return Err(IgraphError::InvalidArgument(format!(
60 "weights vector size ({}) differs from edge count ({})",
61 weights.len(),
62 m
63 )));
64 }
65 for (e, &w) in weights.iter().enumerate() {
66 if w.is_nan() {
67 return Err(IgraphError::InvalidArgument(format!(
68 "weight at edge {e} is NaN"
69 )));
70 }
71 }
72 Ok(())
73}
74
75fn incident_for_mode(graph: &Graph, v: VertexId, mode: DijkstraMode) -> IgraphResult<Vec<EdgeId>> {
76 if !graph.is_directed() {
77 return graph.incident(v);
78 }
79 match mode {
80 DijkstraMode::Out => graph.incident(v),
81 DijkstraMode::In => graph.incident_in(v),
82 DijkstraMode::All => {
83 let mut out = graph.incident(v)?;
84 out.extend(graph.incident_in(v)?);
85 Ok(out)
86 }
87 }
88}
89
90pub fn widest_path_widths(
127 graph: &Graph,
128 source: VertexId,
129 weights: &[f64],
130) -> IgraphResult<Vec<Option<f64>>> {
131 widest_path_widths_with_mode(graph, source, weights, DijkstraMode::Out)
132}
133
134pub fn widest_path_widths_with_mode(
153 graph: &Graph,
154 source: VertexId,
155 weights: &[f64],
156 mode: DijkstraMode,
157) -> IgraphResult<Vec<Option<f64>>> {
158 let (widths, _) = widest_inner(graph, source, weights, mode)?;
159 Ok(widths
160 .into_iter()
161 .map(|w| {
162 if w == f64::NEG_INFINITY {
163 None
164 } else {
165 Some(w)
166 }
167 })
168 .collect())
169}
170
171fn widest_inner(
177 graph: &Graph,
178 source: VertexId,
179 weights: &[f64],
180 mode: DijkstraMode,
181) -> IgraphResult<(Vec<f64>, Vec<Option<EdgeId>>)> {
182 let n = graph.vcount();
183 if source >= n {
184 return Err(IgraphError::VertexOutOfRange { id: source, n });
185 }
186 validate_weights(graph, weights)?;
187
188 let n_usize = n as usize;
189 let mut widths: Vec<f64> = vec![f64::NEG_INFINITY; n_usize];
190 widths[source as usize] = f64::INFINITY;
191 let mut parent_eid: Vec<Option<EdgeId>> = vec![None; n_usize];
192
193 let mut heap: BinaryHeap<WidestFrontier> = BinaryHeap::new();
194 heap.push(WidestFrontier(f64::INFINITY, source));
195
196 while let Some(WidestFrontier(w, v)) = heap.pop() {
197 if w < widths[v as usize] {
198 continue;
199 }
200
201 let incidents = incident_for_mode(graph, v, mode)?;
202 for eid in incidents {
203 let edge_w = weights[eid as usize];
204 if edge_w == f64::NEG_INFINITY {
205 continue;
206 }
207 let other = graph.edge_other(eid, v)?;
208 let alt = w.min(edge_w);
209 if alt > widths[other as usize] {
210 widths[other as usize] = alt;
211 parent_eid[other as usize] = Some(eid);
212 heap.push(WidestFrontier(alt, other));
213 }
214 }
215 }
216
217 Ok((widths, parent_eid))
218}
219
220pub fn widest_path(
251 graph: &Graph,
252 from: VertexId,
253 to: VertexId,
254 weights: &[f64],
255) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
256 widest_path_with_mode(graph, from, to, weights, DijkstraMode::Out)
257}
258
259#[derive(Debug, Clone, PartialEq)]
268pub struct WidestPaths {
269 pub widths: Vec<Option<f64>>,
272 pub parents: Vec<Option<VertexId>>,
275 pub inbound_edges: Vec<Option<EdgeId>>,
278}
279
280pub fn widest_paths(graph: &Graph, from: VertexId, weights: &[f64]) -> IgraphResult<WidestPaths> {
315 widest_paths_with_mode(graph, from, weights, DijkstraMode::Out)
316}
317
318pub fn widest_paths_with_mode(
335 graph: &Graph,
336 from: VertexId,
337 weights: &[f64],
338 mode: DijkstraMode,
339) -> IgraphResult<WidestPaths> {
340 let (raw_widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
341 let n = raw_widths.len();
342 let mut parents: Vec<Option<VertexId>> = vec![None; n];
343 let widths: Vec<Option<f64>> = raw_widths
344 .iter()
345 .map(|&w| {
346 if w == f64::NEG_INFINITY {
347 None
348 } else {
349 Some(w)
350 }
351 })
352 .collect();
353 for v in 0..n {
357 if let Some(eid) = parent_eid[v] {
358 let v_u32 = u32::try_from(v)
359 .map_err(|_| IgraphError::Internal("vertex index exceeds u32::MAX"))?;
360 let prev = graph.edge_other(eid, v_u32)?;
361 parents[v] = Some(prev);
362 }
363 }
364 Ok(WidestPaths {
365 widths,
366 parents,
367 inbound_edges: parent_eid,
368 })
369}
370
371pub fn widest_path_with_mode(
388 graph: &Graph,
389 from: VertexId,
390 to: VertexId,
391 weights: &[f64],
392 mode: DijkstraMode,
393) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
394 let n = graph.vcount();
395 if to >= n {
396 return Err(IgraphError::VertexOutOfRange { id: to, n });
397 }
398 let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
400 reconstruct_one(graph, from, to, &widths, &parent_eid)
401}
402
403fn reconstruct_one(
407 graph: &Graph,
408 from: VertexId,
409 to: VertexId,
410 widths: &[f64],
411 parent_eid: &[Option<EdgeId>],
412) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
413 if from == to {
414 return Ok(Some((vec![from], Vec::new())));
415 }
416 if widths[to as usize] == f64::NEG_INFINITY {
417 return Ok(None);
418 }
419 let mut edges: Vec<EdgeId> = Vec::new();
420 let mut vertices: Vec<VertexId> = vec![to];
421 let mut cur = to;
422 while cur != from {
423 let eid = parent_eid[cur as usize].ok_or(IgraphError::Internal(
424 "missing parent edge while walking widest path",
425 ))?;
426 let prev = graph.edge_other(eid, cur)?;
427 edges.push(eid);
428 vertices.push(prev);
429 cur = prev;
430 }
431 vertices.reverse();
432 edges.reverse();
433 Ok(Some((vertices, edges)))
434}
435
436pub type WidestPathResult = Option<(Vec<VertexId>, Vec<EdgeId>)>;
441
442pub fn widest_paths_to(
475 graph: &Graph,
476 from: VertexId,
477 targets: &[VertexId],
478 weights: &[f64],
479) -> IgraphResult<Vec<WidestPathResult>> {
480 widest_paths_to_with_mode(graph, from, targets, weights, DijkstraMode::Out)
481}
482
483pub fn widest_paths_to_with_mode(
500 graph: &Graph,
501 from: VertexId,
502 targets: &[VertexId],
503 weights: &[f64],
504 mode: DijkstraMode,
505) -> IgraphResult<Vec<WidestPathResult>> {
506 let n = graph.vcount();
507 for &t in targets {
508 if t >= n {
509 return Err(IgraphError::VertexOutOfRange { id: t, n });
510 }
511 }
512 let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
514 let mut out = Vec::with_capacity(targets.len());
515 for &t in targets {
516 out.push(reconstruct_one(graph, from, t, &widths, &parent_eid)?);
517 }
518 Ok(out)
519}
520
521pub fn widest_path_widths_floyd_warshall(
564 graph: &Graph,
565 weights: &[f64],
566) -> IgraphResult<Vec<Vec<Option<f64>>>> {
567 widest_path_widths_floyd_warshall_with_mode(graph, weights, DijkstraMode::Out)
568}
569
570pub fn widest_path_widths_floyd_warshall_with_mode(
595 graph: &Graph,
596 weights: &[f64],
597 mode: DijkstraMode,
598) -> IgraphResult<Vec<Vec<Option<f64>>>> {
599 validate_weights(graph, weights)?;
600 let vcount = graph.vcount();
601 let n_us = vcount as usize;
602
603 let effective_mode = if graph.is_directed() {
605 mode
606 } else {
607 DijkstraMode::All
608 };
609 let (use_out, use_in) = match effective_mode {
610 DijkstraMode::Out => (true, false),
611 DijkstraMode::In => (false, true),
612 DijkstraMode::All => (true, true),
613 };
614
615 let mut mat: Vec<Vec<f64>> = vec![vec![f64::NEG_INFINITY; n_us]; n_us];
617 for (i, row) in mat.iter_mut().enumerate().take(n_us) {
618 row[i] = f64::INFINITY;
619 }
620
621 for (e, &w) in weights.iter().enumerate() {
623 if w == f64::NEG_INFINITY {
624 continue;
625 }
626 let eid = u32::try_from(e)
627 .map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX in FW widest"))?;
628 let (s, t) = graph.edge(eid)?;
629 let (si, ti) = (s as usize, t as usize);
630 if use_out && mat[si][ti] < w {
631 mat[si][ti] = w;
632 }
633 if use_in && mat[ti][si] < w {
634 mat[ti][si] = w;
635 }
636 }
637
638 #[allow(clippy::needless_range_loop)]
643 for k in 0..n_us {
644 for j in 0..n_us {
645 let width_kj = mat[k][j];
646 if j == k || width_kj == f64::NEG_INFINITY {
647 continue;
648 }
649 for i in 0..n_us {
650 if i == j || i == k {
651 continue;
652 }
653 let alt = mat[i][k].min(width_kj);
654 if alt > mat[i][j] {
655 mat[i][j] = alt;
656 }
657 }
658 }
659 }
660
661 Ok(mat
662 .into_iter()
663 .map(|row| {
664 row.into_iter()
665 .map(|w| {
666 if w == f64::NEG_INFINITY {
667 None
668 } else {
669 Some(w)
670 }
671 })
672 .collect()
673 })
674 .collect())
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn triangle_picks_direct_edge_when_wider() {
683 let mut g = Graph::with_vertices(3);
684 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); let w = widest_path_widths(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
688 assert_eq!(w[0], Some(f64::INFINITY));
690 assert_eq!(w[1], Some(2.0));
692 assert_eq!(w[2], Some(4.0));
694 }
695
696 #[test]
697 fn chain_bottleneck_is_minimum_weight() {
698 let mut g = Graph::with_vertices(4);
700 g.add_edge(0, 1).unwrap();
701 g.add_edge(1, 2).unwrap();
702 g.add_edge(2, 3).unwrap();
703 let w = widest_path_widths(&g, 0, &[5.0, 1.0, 3.0]).unwrap();
704 assert_eq!(w[0], Some(f64::INFINITY));
705 assert_eq!(w[1], Some(5.0));
706 assert_eq!(w[2], Some(1.0));
708 assert_eq!(w[3], Some(1.0));
710 }
711
712 #[test]
713 fn unreachable_vertex_is_none() {
714 let mut g = Graph::with_vertices(4);
715 g.add_edge(0, 1).unwrap();
716 g.add_edge(2, 3).unwrap();
717 let w = widest_path_widths(&g, 0, &[2.0, 3.0]).unwrap();
718 assert_eq!(w[0], Some(f64::INFINITY));
719 assert_eq!(w[1], Some(2.0));
720 assert_eq!(w[2], None);
721 assert_eq!(w[3], None);
722 }
723
724 #[test]
725 fn negative_infinity_edge_ignored() {
726 let mut g = Graph::with_vertices(3);
729 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
731 let w = widest_path_widths(&g, 0, &[f64::NEG_INFINITY, 1.0]).unwrap();
732 assert_eq!(w[0], Some(f64::INFINITY));
733 assert_eq!(w[1], None); assert_eq!(w[2], None);
735 }
736
737 #[test]
738 fn directed_out_mode_default() {
739 let mut g = Graph::new(3, true).unwrap();
741 g.add_edge(0, 1).unwrap();
742 g.add_edge(1, 2).unwrap();
743 let w = widest_path_widths(&g, 0, &[5.0, 3.0]).unwrap();
744 assert_eq!(w[0], Some(f64::INFINITY));
745 assert_eq!(w[1], Some(5.0));
746 assert_eq!(w[2], Some(3.0));
747 }
748
749 #[test]
750 fn directed_in_mode_reverses() {
751 let mut g = Graph::new(3, true).unwrap();
753 g.add_edge(0, 1).unwrap();
754 g.add_edge(1, 2).unwrap();
755 let w = widest_path_widths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
756 assert_eq!(w[0], Some(3.0));
757 assert_eq!(w[1], Some(3.0));
758 assert_eq!(w[2], Some(f64::INFINITY));
759 }
760
761 #[test]
762 fn source_out_of_range_errors() {
763 let g = Graph::with_vertices(3);
764 let err = widest_path_widths(&g, 99, &[]).unwrap_err();
765 assert!(matches!(
766 err,
767 IgraphError::VertexOutOfRange { id: 99, n: 3 }
768 ));
769 }
770
771 #[test]
772 fn nan_weight_errors() {
773 let mut g = Graph::with_vertices(2);
774 g.add_edge(0, 1).unwrap();
775 let err = widest_path_widths(&g, 0, &[f64::NAN]).unwrap_err();
776 assert!(matches!(err, IgraphError::InvalidArgument(_)));
777 }
778
779 #[test]
780 fn weights_size_mismatch_errors() {
781 let mut g = Graph::with_vertices(2);
782 g.add_edge(0, 1).unwrap();
783 let err = widest_path_widths(&g, 0, &[1.0, 2.0]).unwrap_err();
784 assert!(matches!(err, IgraphError::InvalidArgument(_)));
785 }
786
787 #[test]
788 fn empty_graph_no_edges() {
789 let g = Graph::with_vertices(3);
790 let w = widest_path_widths(&g, 0, &[]).unwrap();
791 assert_eq!(w[0], Some(f64::INFINITY));
792 assert_eq!(w[1], None);
793 assert_eq!(w[2], None);
794 }
795
796 #[test]
797 fn negative_weights_allowed_as_bottleneck() {
798 let mut g = Graph::with_vertices(3);
801 g.add_edge(0, 1).unwrap();
802 g.add_edge(1, 2).unwrap();
803 let w = widest_path_widths(&g, 0, &[-1.0, 5.0]).unwrap();
804 assert_eq!(w[0], Some(f64::INFINITY));
805 assert_eq!(w[1], Some(-1.0));
806 assert_eq!(w[2], Some(-1.0));
808 }
809
810 #[test]
811 fn multiple_parallel_edges_pick_widest() {
812 let mut g = Graph::with_vertices(2);
813 g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); let w = widest_path_widths(&g, 0, &[1.0, 5.0, 3.0]).unwrap();
817 assert_eq!(w[0], Some(f64::INFINITY));
818 assert_eq!(w[1], Some(5.0));
819 }
820
821 #[test]
824 fn widest_path_triangle_via_shortcut() {
825 let mut g = Graph::with_vertices(3);
828 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); let (vs, es) = widest_path(&g, 0, 1, &[1.0, 4.0, 2.0])
832 .unwrap()
833 .expect("0→1 is reachable");
834 assert_eq!(vs, vec![0, 2, 1]);
835 assert_eq!(es, vec![1, 2]);
836 }
837
838 #[test]
839 fn widest_path_direct_edge_when_widest() {
840 let mut g = Graph::with_vertices(3);
841 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); let (vs, es) = widest_path(&g, 0, 2, &[1.0, 4.0, 2.0])
845 .unwrap()
846 .expect("0→2 reachable");
847 assert_eq!(vs, vec![0, 2]);
848 assert_eq!(es, vec![1]);
849 }
850
851 #[test]
852 fn widest_path_self_target_returns_trivial() {
853 let mut g = Graph::with_vertices(3);
854 g.add_edge(0, 1).unwrap();
855 let (vs, es) = widest_path(&g, 0, 0, &[5.0]).unwrap().unwrap();
856 assert_eq!(vs, vec![0]);
857 assert!(es.is_empty());
858 }
859
860 #[test]
861 fn widest_path_unreachable_target_returns_none() {
862 let mut g = Graph::with_vertices(4);
863 g.add_edge(0, 1).unwrap();
864 g.add_edge(2, 3).unwrap();
865 let result = widest_path(&g, 0, 2, &[1.0, 1.0]).unwrap();
866 assert!(result.is_none());
867 }
868
869 #[test]
870 fn widest_path_chain_returns_full_chain() {
871 let mut g = Graph::with_vertices(4);
873 g.add_edge(0, 1).unwrap();
874 g.add_edge(1, 2).unwrap();
875 g.add_edge(2, 3).unwrap();
876 let (vs, es) = widest_path(&g, 0, 3, &[1.0, 1.0, 1.0]).unwrap().unwrap();
877 assert_eq!(vs, vec![0, 1, 2, 3]);
878 assert_eq!(es, vec![0, 1, 2]);
879 }
880
881 #[test]
882 fn widest_path_directed_respects_direction() {
883 let mut g = Graph::new(3, true).unwrap();
885 g.add_edge(0, 1).unwrap();
886 g.add_edge(1, 2).unwrap();
887 let (vs, _) = widest_path(&g, 0, 2, &[5.0, 3.0]).unwrap().unwrap();
888 assert_eq!(vs, vec![0, 1, 2]);
889 assert!(widest_path(&g, 2, 0, &[5.0, 3.0]).unwrap().is_none());
891 let (vs, _) = widest_path_with_mode(&g, 2, 0, &[5.0, 3.0], DijkstraMode::In)
893 .unwrap()
894 .unwrap();
895 assert_eq!(vs, vec![2, 1, 0]);
896 }
897
898 #[test]
899 fn widest_path_from_out_of_range_errors() {
900 let g = Graph::with_vertices(3);
901 let err = widest_path(&g, 99, 0, &[]).unwrap_err();
902 assert!(matches!(
903 err,
904 IgraphError::VertexOutOfRange { id: 99, n: 3 }
905 ));
906 }
907
908 #[test]
909 fn widest_path_to_out_of_range_errors() {
910 let g = Graph::with_vertices(3);
911 let err = widest_path(&g, 0, 99, &[]).unwrap_err();
912 assert!(matches!(
913 err,
914 IgraphError::VertexOutOfRange { id: 99, n: 3 }
915 ));
916 }
917
918 #[test]
919 fn widest_path_negative_infinity_edge_breaks_chain() {
920 let mut g = Graph::with_vertices(3);
922 g.add_edge(0, 1).unwrap();
923 g.add_edge(1, 2).unwrap();
924 let r = widest_path(&g, 0, 2, &[f64::NEG_INFINITY, 1.0]).unwrap();
925 assert!(r.is_none());
926 }
927
928 #[test]
931 fn fw_widest_triangle_matches_dijkstra_per_source() {
932 let mut g = Graph::with_vertices(3);
933 g.add_edge(0, 1).unwrap();
934 g.add_edge(0, 2).unwrap();
935 g.add_edge(1, 2).unwrap();
936 let weights = [1.0, 4.0, 2.0];
937 let fw = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
938 for u in 0..3u32 {
940 let dij = widest_path_widths(&g, u, &weights).unwrap();
941 assert_eq!(fw[u as usize], dij, "row {u} mismatch");
942 }
943 }
944
945 #[test]
946 fn fw_widest_diagonal_is_infinity() {
947 let g = Graph::with_vertices(3);
948 let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
949 for (i, row) in m.iter().enumerate() {
950 for (j, entry) in row.iter().enumerate() {
951 if i == j {
952 assert_eq!(*entry, Some(f64::INFINITY));
953 } else {
954 assert_eq!(*entry, None);
955 }
956 }
957 }
958 }
959
960 #[test]
961 fn fw_widest_unreachable_components() {
962 let mut g = Graph::with_vertices(4);
963 g.add_edge(0, 1).unwrap();
964 g.add_edge(2, 3).unwrap();
965 let m = widest_path_widths_floyd_warshall(&g, &[5.0, 7.0]).unwrap();
966 assert_eq!(m[0][1], Some(5.0));
967 assert_eq!(m[0][2], None);
968 assert_eq!(m[2][3], Some(7.0));
969 assert_eq!(m[1][3], None);
970 }
971
972 #[test]
973 fn fw_widest_directed_respects_mode() {
974 let mut g = Graph::new(3, true).unwrap();
975 g.add_edge(0, 1).unwrap();
976 g.add_edge(1, 2).unwrap();
977 let weights = [5.0, 3.0];
978 let out = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
980 assert_eq!(out[0][1], Some(5.0));
981 assert_eq!(out[0][2], Some(3.0));
982 assert_eq!(out[2][0], None);
983 let in_m =
985 widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::In).unwrap();
986 assert_eq!(in_m[0][1], None);
987 assert_eq!(in_m[2][0], Some(3.0));
988 let all =
990 widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::All).unwrap();
991 assert_eq!(all[0][2], Some(3.0));
992 assert_eq!(all[2][0], Some(3.0));
993 }
994
995 #[test]
996 fn fw_widest_parallel_edges_keep_widest() {
997 let mut g = Graph::with_vertices(2);
998 g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); let m = widest_path_widths_floyd_warshall(&g, &[1.0, 5.0, 3.0]).unwrap();
1002 assert_eq!(m[0][1], Some(5.0));
1003 assert_eq!(m[1][0], Some(5.0));
1004 }
1005
1006 #[test]
1007 fn fw_widest_negative_infinity_edge_ignored() {
1008 let mut g = Graph::with_vertices(3);
1009 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
1011 let m = widest_path_widths_floyd_warshall(&g, &[f64::NEG_INFINITY, 1.0]).unwrap();
1012 assert_eq!(m[0][1], None);
1014 assert_eq!(m[0][2], None);
1015 assert_eq!(m[1][2], Some(1.0));
1016 }
1017
1018 #[test]
1019 fn fw_widest_nan_weight_errors() {
1020 let mut g = Graph::with_vertices(2);
1021 g.add_edge(0, 1).unwrap();
1022 let err = widest_path_widths_floyd_warshall(&g, &[f64::NAN]).unwrap_err();
1023 assert!(matches!(err, IgraphError::InvalidArgument(_)));
1024 }
1025
1026 #[test]
1027 fn fw_widest_empty_graph_empty_matrix() {
1028 let g = Graph::with_vertices(0);
1029 let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
1030 assert!(m.is_empty());
1031 }
1032
1033 #[test]
1036 fn widest_paths_to_triangle_two_targets() {
1037 let mut g = Graph::with_vertices(3);
1038 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); let paths = widest_paths_to(&g, 0, &[1, 2], &[1.0, 4.0, 2.0]).unwrap();
1042 assert_eq!(paths.len(), 2);
1043 let (vs1, es1) = paths[0].as_ref().unwrap();
1045 assert_eq!(vs1, &vec![0, 2, 1]);
1046 assert_eq!(es1, &vec![1, 2]);
1047 let (vs2, es2) = paths[1].as_ref().unwrap();
1049 assert_eq!(vs2, &vec![0, 2]);
1050 assert_eq!(es2, &vec![1]);
1051 }
1052
1053 #[test]
1054 fn widest_paths_to_includes_unreachable_as_none() {
1055 let mut g = Graph::with_vertices(4);
1056 g.add_edge(0, 1).unwrap();
1057 g.add_edge(2, 3).unwrap();
1058 let paths = widest_paths_to(&g, 0, &[1, 2, 3], &[1.0, 1.0]).unwrap();
1060 assert!(paths[0].is_some());
1061 assert!(paths[1].is_none());
1062 assert!(paths[2].is_none());
1063 }
1064
1065 #[test]
1066 fn widest_paths_to_empty_targets_returns_empty() {
1067 let g = Graph::with_vertices(3);
1068 let paths = widest_paths_to(&g, 0, &[], &[]).unwrap();
1069 assert!(paths.is_empty());
1070 }
1071
1072 #[test]
1073 fn widest_paths_to_self_target_is_trivial() {
1074 let mut g = Graph::with_vertices(3);
1075 g.add_edge(0, 1).unwrap();
1076 let paths = widest_paths_to(&g, 1, &[1, 0], &[5.0]).unwrap();
1078 let (vs0, es0) = paths[0].as_ref().unwrap();
1079 assert_eq!(vs0, &vec![1]);
1080 assert!(es0.is_empty());
1081 let (vs1, es1) = paths[1].as_ref().unwrap();
1083 assert_eq!(vs1, &vec![1, 0]);
1084 assert_eq!(es1, &vec![0]);
1085 }
1086
1087 #[test]
1088 fn widest_paths_to_duplicate_targets_return_same_path() {
1089 let mut g = Graph::with_vertices(3);
1090 g.add_edge(0, 1).unwrap();
1091 g.add_edge(1, 2).unwrap();
1092 let paths = widest_paths_to(&g, 0, &[2, 2, 2], &[5.0, 3.0]).unwrap();
1093 assert_eq!(paths.len(), 3);
1094 for p in &paths {
1095 let (vs, _) = p.as_ref().unwrap();
1096 assert_eq!(vs, &vec![0, 1, 2]);
1097 }
1098 }
1099
1100 #[test]
1101 fn widest_paths_to_target_out_of_range_errors() {
1102 let g = Graph::with_vertices(3);
1103 let err = widest_paths_to(&g, 0, &[1, 99], &[]).unwrap_err();
1104 assert!(matches!(
1105 err,
1106 IgraphError::VertexOutOfRange { id: 99, n: 3 }
1107 ));
1108 }
1109
1110 #[test]
1111 fn widest_paths_to_directed_in_mode_reverses() {
1112 let mut g = Graph::new(3, true).unwrap();
1113 g.add_edge(0, 1).unwrap();
1114 g.add_edge(1, 2).unwrap();
1115 let paths =
1117 widest_paths_to_with_mode(&g, 2, &[1, 0], &[5.0, 3.0], DijkstraMode::In).unwrap();
1118 let (vs1, _) = paths[0].as_ref().unwrap();
1119 assert_eq!(vs1, &vec![2, 1]);
1120 let (vs0, _) = paths[1].as_ref().unwrap();
1121 assert_eq!(vs0, &vec![2, 1, 0]);
1122 }
1123
1124 #[test]
1125 fn widest_paths_to_negative_infinity_edge_blocks_target() {
1126 let mut g = Graph::with_vertices(3);
1127 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
1129 let paths = widest_paths_to(&g, 0, &[1, 2], &[f64::NEG_INFINITY, 1.0]).unwrap();
1130 assert!(paths[0].is_none());
1131 assert!(paths[1].is_none());
1132 }
1133
1134 #[test]
1137 fn widest_paths_triangle_struct_fields_consistent() {
1138 let mut g = Graph::with_vertices(3);
1139 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); let sp = widest_paths(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
1143 assert_eq!(sp.widths[0], Some(f64::INFINITY));
1145 assert_eq!(sp.parents[0], None);
1146 assert_eq!(sp.inbound_edges[0], None);
1147 assert_eq!(sp.widths[2], Some(4.0));
1149 assert_eq!(sp.parents[2], Some(0));
1150 assert_eq!(sp.inbound_edges[2], Some(1));
1151 assert_eq!(sp.widths[1], Some(2.0));
1153 assert_eq!(sp.parents[1], Some(2));
1154 assert_eq!(sp.inbound_edges[1], Some(2));
1155 }
1156
1157 #[test]
1158 fn widest_paths_widths_match_widest_path_widths() {
1159 let mut g = Graph::with_vertices(4);
1161 g.add_edge(0, 1).unwrap();
1162 g.add_edge(1, 2).unwrap();
1163 g.add_edge(2, 3).unwrap();
1164 let weights = [5.0, 1.0, 3.0];
1165 let sp = widest_paths(&g, 0, &weights).unwrap();
1166 let standalone = widest_path_widths(&g, 0, &weights).unwrap();
1167 assert_eq!(sp.widths, standalone);
1168 }
1169
1170 #[test]
1171 fn widest_paths_unreachable_has_none_in_all_three_fields() {
1172 let mut g = Graph::with_vertices(4);
1173 g.add_edge(0, 1).unwrap();
1174 g.add_edge(2, 3).unwrap();
1175 let sp = widest_paths(&g, 0, &[5.0, 7.0]).unwrap();
1176 assert_eq!(sp.widths[2], None);
1178 assert_eq!(sp.parents[2], None);
1179 assert_eq!(sp.inbound_edges[2], None);
1180 assert_eq!(sp.parents[1], Some(0));
1182 assert_eq!(sp.inbound_edges[1], Some(0));
1183 }
1184
1185 #[test]
1186 fn widest_paths_directed_in_mode() {
1187 let mut g = Graph::new(3, true).unwrap();
1188 g.add_edge(0, 1).unwrap();
1189 g.add_edge(1, 2).unwrap();
1190 let sp = widest_paths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
1192 assert_eq!(sp.widths[2], Some(f64::INFINITY));
1193 assert_eq!(sp.widths[1], Some(3.0));
1194 assert_eq!(sp.widths[0], Some(3.0));
1195 assert_eq!(sp.parents[1], Some(2));
1198 assert_eq!(sp.parents[0], Some(1));
1199 }
1200
1201 #[test]
1202 fn widest_paths_source_out_of_range_errors() {
1203 let g = Graph::with_vertices(3);
1204 let err = widest_paths(&g, 99, &[]).unwrap_err();
1205 assert!(matches!(
1206 err,
1207 IgraphError::VertexOutOfRange { id: 99, n: 3 }
1208 ));
1209 }
1210
1211 #[test]
1212 fn widest_paths_nan_weight_errors() {
1213 let mut g = Graph::with_vertices(2);
1214 g.add_edge(0, 1).unwrap();
1215 let err = widest_paths(&g, 0, &[f64::NAN]).unwrap_err();
1216 assert!(matches!(err, IgraphError::InvalidArgument(_)));
1217 }
1218
1219 #[test]
1220 fn widest_paths_spt_endpoints_match_widest_path_chain() {
1221 let mut g = Graph::with_vertices(4);
1224 g.add_edge(0, 1).unwrap();
1225 g.add_edge(1, 2).unwrap();
1226 g.add_edge(2, 3).unwrap();
1227 let weights = [5.0, 1.0, 3.0];
1228 let sp = widest_paths(&g, 0, &weights).unwrap();
1229 let path = widest_path(&g, 0, 3, &weights).unwrap().unwrap();
1230 let mut reconstructed: Vec<u32> = vec![3];
1232 let mut cur = 3usize;
1233 while let Some(prev) = sp.parents[cur] {
1234 reconstructed.push(prev);
1235 cur = prev as usize;
1236 }
1237 reconstructed.reverse();
1238 assert_eq!(reconstructed, path.0);
1239 }
1240}