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(
141 graph: &Graph,
142 source: VertexId,
143 weights: &[f64],
144 mode: DijkstraMode,
145) -> IgraphResult<Vec<Option<f64>>> {
146 let (widths, _) = widest_inner(graph, source, weights, mode)?;
147 Ok(widths
148 .into_iter()
149 .map(|w| {
150 if w == f64::NEG_INFINITY {
151 None
152 } else {
153 Some(w)
154 }
155 })
156 .collect())
157}
158
159fn widest_inner(
165 graph: &Graph,
166 source: VertexId,
167 weights: &[f64],
168 mode: DijkstraMode,
169) -> IgraphResult<(Vec<f64>, Vec<Option<EdgeId>>)> {
170 let n = graph.vcount();
171 if source >= n {
172 return Err(IgraphError::VertexOutOfRange { id: source, n });
173 }
174 validate_weights(graph, weights)?;
175
176 let n_usize = n as usize;
177 let mut widths: Vec<f64> = vec![f64::NEG_INFINITY; n_usize];
178 widths[source as usize] = f64::INFINITY;
179 let mut parent_eid: Vec<Option<EdgeId>> = vec![None; n_usize];
180
181 let mut heap: BinaryHeap<WidestFrontier> = BinaryHeap::new();
182 heap.push(WidestFrontier(f64::INFINITY, source));
183
184 while let Some(WidestFrontier(w, v)) = heap.pop() {
185 if w < widths[v as usize] {
186 continue;
187 }
188
189 let incidents = incident_for_mode(graph, v, mode)?;
190 for eid in incidents {
191 let edge_w = weights[eid as usize];
192 if edge_w == f64::NEG_INFINITY {
193 continue;
194 }
195 let other = graph.edge_other(eid, v)?;
196 let alt = w.min(edge_w);
197 if alt > widths[other as usize] {
198 widths[other as usize] = alt;
199 parent_eid[other as usize] = Some(eid);
200 heap.push(WidestFrontier(alt, other));
201 }
202 }
203 }
204
205 Ok((widths, parent_eid))
206}
207
208pub fn widest_path(
239 graph: &Graph,
240 from: VertexId,
241 to: VertexId,
242 weights: &[f64],
243) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
244 widest_path_with_mode(graph, from, to, weights, DijkstraMode::Out)
245}
246
247#[derive(Debug, Clone, PartialEq)]
256pub struct WidestPaths {
257 pub widths: Vec<Option<f64>>,
260 pub parents: Vec<Option<VertexId>>,
263 pub inbound_edges: Vec<Option<EdgeId>>,
266}
267
268pub fn widest_paths(graph: &Graph, from: VertexId, weights: &[f64]) -> IgraphResult<WidestPaths> {
303 widest_paths_with_mode(graph, from, weights, DijkstraMode::Out)
304}
305
306pub fn widest_paths_with_mode(
311 graph: &Graph,
312 from: VertexId,
313 weights: &[f64],
314 mode: DijkstraMode,
315) -> IgraphResult<WidestPaths> {
316 let (raw_widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
317 let n = raw_widths.len();
318 let mut parents: Vec<Option<VertexId>> = vec![None; n];
319 let widths: Vec<Option<f64>> = raw_widths
320 .iter()
321 .map(|&w| {
322 if w == f64::NEG_INFINITY {
323 None
324 } else {
325 Some(w)
326 }
327 })
328 .collect();
329 for v in 0..n {
333 if let Some(eid) = parent_eid[v] {
334 let v_u32 = u32::try_from(v)
335 .map_err(|_| IgraphError::Internal("vertex index exceeds u32::MAX"))?;
336 let prev = graph.edge_other(eid, v_u32)?;
337 parents[v] = Some(prev);
338 }
339 }
340 Ok(WidestPaths {
341 widths,
342 parents,
343 inbound_edges: parent_eid,
344 })
345}
346
347pub fn widest_path_with_mode(
352 graph: &Graph,
353 from: VertexId,
354 to: VertexId,
355 weights: &[f64],
356 mode: DijkstraMode,
357) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
358 let n = graph.vcount();
359 if to >= n {
360 return Err(IgraphError::VertexOutOfRange { id: to, n });
361 }
362 let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
364 reconstruct_one(graph, from, to, &widths, &parent_eid)
365}
366
367fn reconstruct_one(
371 graph: &Graph,
372 from: VertexId,
373 to: VertexId,
374 widths: &[f64],
375 parent_eid: &[Option<EdgeId>],
376) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
377 if from == to {
378 return Ok(Some((vec![from], Vec::new())));
379 }
380 if widths[to as usize] == f64::NEG_INFINITY {
381 return Ok(None);
382 }
383 let mut edges: Vec<EdgeId> = Vec::new();
384 let mut vertices: Vec<VertexId> = vec![to];
385 let mut cur = to;
386 while cur != from {
387 let eid = parent_eid[cur as usize].ok_or(IgraphError::Internal(
388 "missing parent edge while walking widest path",
389 ))?;
390 let prev = graph.edge_other(eid, cur)?;
391 edges.push(eid);
392 vertices.push(prev);
393 cur = prev;
394 }
395 vertices.reverse();
396 edges.reverse();
397 Ok(Some((vertices, edges)))
398}
399
400pub type WidestPathResult = Option<(Vec<VertexId>, Vec<EdgeId>)>;
405
406pub fn widest_paths_to(
439 graph: &Graph,
440 from: VertexId,
441 targets: &[VertexId],
442 weights: &[f64],
443) -> IgraphResult<Vec<WidestPathResult>> {
444 widest_paths_to_with_mode(graph, from, targets, weights, DijkstraMode::Out)
445}
446
447pub fn widest_paths_to_with_mode(
452 graph: &Graph,
453 from: VertexId,
454 targets: &[VertexId],
455 weights: &[f64],
456 mode: DijkstraMode,
457) -> IgraphResult<Vec<WidestPathResult>> {
458 let n = graph.vcount();
459 for &t in targets {
460 if t >= n {
461 return Err(IgraphError::VertexOutOfRange { id: t, n });
462 }
463 }
464 let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
466 let mut out = Vec::with_capacity(targets.len());
467 for &t in targets {
468 out.push(reconstruct_one(graph, from, t, &widths, &parent_eid)?);
469 }
470 Ok(out)
471}
472
473pub fn widest_path_widths_floyd_warshall(
516 graph: &Graph,
517 weights: &[f64],
518) -> IgraphResult<Vec<Vec<Option<f64>>>> {
519 widest_path_widths_floyd_warshall_with_mode(graph, weights, DijkstraMode::Out)
520}
521
522pub fn widest_path_widths_floyd_warshall_with_mode(
535 graph: &Graph,
536 weights: &[f64],
537 mode: DijkstraMode,
538) -> IgraphResult<Vec<Vec<Option<f64>>>> {
539 validate_weights(graph, weights)?;
540 let vcount = graph.vcount();
541 let n_us = vcount as usize;
542
543 let effective_mode = if graph.is_directed() {
545 mode
546 } else {
547 DijkstraMode::All
548 };
549 let (use_out, use_in) = match effective_mode {
550 DijkstraMode::Out => (true, false),
551 DijkstraMode::In => (false, true),
552 DijkstraMode::All => (true, true),
553 };
554
555 let mut mat: Vec<Vec<f64>> = vec![vec![f64::NEG_INFINITY; n_us]; n_us];
557 for (i, row) in mat.iter_mut().enumerate().take(n_us) {
558 row[i] = f64::INFINITY;
559 }
560
561 for (e, &w) in weights.iter().enumerate() {
563 if w == f64::NEG_INFINITY {
564 continue;
565 }
566 let eid = u32::try_from(e)
567 .map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX in FW widest"))?;
568 let (s, t) = graph.edge(eid)?;
569 let (si, ti) = (s as usize, t as usize);
570 if use_out && mat[si][ti] < w {
571 mat[si][ti] = w;
572 }
573 if use_in && mat[ti][si] < w {
574 mat[ti][si] = w;
575 }
576 }
577
578 #[allow(clippy::needless_range_loop)]
583 for k in 0..n_us {
584 for j in 0..n_us {
585 let width_kj = mat[k][j];
586 if j == k || width_kj == f64::NEG_INFINITY {
587 continue;
588 }
589 for i in 0..n_us {
590 if i == j || i == k {
591 continue;
592 }
593 let alt = mat[i][k].min(width_kj);
594 if alt > mat[i][j] {
595 mat[i][j] = alt;
596 }
597 }
598 }
599 }
600
601 Ok(mat
602 .into_iter()
603 .map(|row| {
604 row.into_iter()
605 .map(|w| {
606 if w == f64::NEG_INFINITY {
607 None
608 } else {
609 Some(w)
610 }
611 })
612 .collect()
613 })
614 .collect())
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn triangle_picks_direct_edge_when_wider() {
623 let mut g = Graph::with_vertices(3);
624 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();
628 assert_eq!(w[0], Some(f64::INFINITY));
630 assert_eq!(w[1], Some(2.0));
632 assert_eq!(w[2], Some(4.0));
634 }
635
636 #[test]
637 fn chain_bottleneck_is_minimum_weight() {
638 let mut g = Graph::with_vertices(4);
640 g.add_edge(0, 1).unwrap();
641 g.add_edge(1, 2).unwrap();
642 g.add_edge(2, 3).unwrap();
643 let w = widest_path_widths(&g, 0, &[5.0, 1.0, 3.0]).unwrap();
644 assert_eq!(w[0], Some(f64::INFINITY));
645 assert_eq!(w[1], Some(5.0));
646 assert_eq!(w[2], Some(1.0));
648 assert_eq!(w[3], Some(1.0));
650 }
651
652 #[test]
653 fn unreachable_vertex_is_none() {
654 let mut g = Graph::with_vertices(4);
655 g.add_edge(0, 1).unwrap();
656 g.add_edge(2, 3).unwrap();
657 let w = widest_path_widths(&g, 0, &[2.0, 3.0]).unwrap();
658 assert_eq!(w[0], Some(f64::INFINITY));
659 assert_eq!(w[1], Some(2.0));
660 assert_eq!(w[2], None);
661 assert_eq!(w[3], None);
662 }
663
664 #[test]
665 fn negative_infinity_edge_ignored() {
666 let mut g = Graph::with_vertices(3);
669 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
671 let w = widest_path_widths(&g, 0, &[f64::NEG_INFINITY, 1.0]).unwrap();
672 assert_eq!(w[0], Some(f64::INFINITY));
673 assert_eq!(w[1], None); assert_eq!(w[2], None);
675 }
676
677 #[test]
678 fn directed_out_mode_default() {
679 let mut g = Graph::new(3, true).unwrap();
681 g.add_edge(0, 1).unwrap();
682 g.add_edge(1, 2).unwrap();
683 let w = widest_path_widths(&g, 0, &[5.0, 3.0]).unwrap();
684 assert_eq!(w[0], Some(f64::INFINITY));
685 assert_eq!(w[1], Some(5.0));
686 assert_eq!(w[2], Some(3.0));
687 }
688
689 #[test]
690 fn directed_in_mode_reverses() {
691 let mut g = Graph::new(3, true).unwrap();
693 g.add_edge(0, 1).unwrap();
694 g.add_edge(1, 2).unwrap();
695 let w = widest_path_widths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
696 assert_eq!(w[0], Some(3.0));
697 assert_eq!(w[1], Some(3.0));
698 assert_eq!(w[2], Some(f64::INFINITY));
699 }
700
701 #[test]
702 fn source_out_of_range_errors() {
703 let g = Graph::with_vertices(3);
704 let err = widest_path_widths(&g, 99, &[]).unwrap_err();
705 assert!(matches!(
706 err,
707 IgraphError::VertexOutOfRange { id: 99, n: 3 }
708 ));
709 }
710
711 #[test]
712 fn nan_weight_errors() {
713 let mut g = Graph::with_vertices(2);
714 g.add_edge(0, 1).unwrap();
715 let err = widest_path_widths(&g, 0, &[f64::NAN]).unwrap_err();
716 assert!(matches!(err, IgraphError::InvalidArgument(_)));
717 }
718
719 #[test]
720 fn weights_size_mismatch_errors() {
721 let mut g = Graph::with_vertices(2);
722 g.add_edge(0, 1).unwrap();
723 let err = widest_path_widths(&g, 0, &[1.0, 2.0]).unwrap_err();
724 assert!(matches!(err, IgraphError::InvalidArgument(_)));
725 }
726
727 #[test]
728 fn empty_graph_no_edges() {
729 let g = Graph::with_vertices(3);
730 let w = widest_path_widths(&g, 0, &[]).unwrap();
731 assert_eq!(w[0], Some(f64::INFINITY));
732 assert_eq!(w[1], None);
733 assert_eq!(w[2], None);
734 }
735
736 #[test]
737 fn negative_weights_allowed_as_bottleneck() {
738 let mut g = Graph::with_vertices(3);
741 g.add_edge(0, 1).unwrap();
742 g.add_edge(1, 2).unwrap();
743 let w = widest_path_widths(&g, 0, &[-1.0, 5.0]).unwrap();
744 assert_eq!(w[0], Some(f64::INFINITY));
745 assert_eq!(w[1], Some(-1.0));
746 assert_eq!(w[2], Some(-1.0));
748 }
749
750 #[test]
751 fn multiple_parallel_edges_pick_widest() {
752 let mut g = Graph::with_vertices(2);
753 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();
757 assert_eq!(w[0], Some(f64::INFINITY));
758 assert_eq!(w[1], Some(5.0));
759 }
760
761 #[test]
764 fn widest_path_triangle_via_shortcut() {
765 let mut g = Graph::with_vertices(3);
768 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])
772 .unwrap()
773 .expect("0→1 is reachable");
774 assert_eq!(vs, vec![0, 2, 1]);
775 assert_eq!(es, vec![1, 2]);
776 }
777
778 #[test]
779 fn widest_path_direct_edge_when_widest() {
780 let mut g = Graph::with_vertices(3);
781 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])
785 .unwrap()
786 .expect("0→2 reachable");
787 assert_eq!(vs, vec![0, 2]);
788 assert_eq!(es, vec![1]);
789 }
790
791 #[test]
792 fn widest_path_self_target_returns_trivial() {
793 let mut g = Graph::with_vertices(3);
794 g.add_edge(0, 1).unwrap();
795 let (vs, es) = widest_path(&g, 0, 0, &[5.0]).unwrap().unwrap();
796 assert_eq!(vs, vec![0]);
797 assert!(es.is_empty());
798 }
799
800 #[test]
801 fn widest_path_unreachable_target_returns_none() {
802 let mut g = Graph::with_vertices(4);
803 g.add_edge(0, 1).unwrap();
804 g.add_edge(2, 3).unwrap();
805 let result = widest_path(&g, 0, 2, &[1.0, 1.0]).unwrap();
806 assert!(result.is_none());
807 }
808
809 #[test]
810 fn widest_path_chain_returns_full_chain() {
811 let mut g = Graph::with_vertices(4);
813 g.add_edge(0, 1).unwrap();
814 g.add_edge(1, 2).unwrap();
815 g.add_edge(2, 3).unwrap();
816 let (vs, es) = widest_path(&g, 0, 3, &[1.0, 1.0, 1.0]).unwrap().unwrap();
817 assert_eq!(vs, vec![0, 1, 2, 3]);
818 assert_eq!(es, vec![0, 1, 2]);
819 }
820
821 #[test]
822 fn widest_path_directed_respects_direction() {
823 let mut g = Graph::new(3, true).unwrap();
825 g.add_edge(0, 1).unwrap();
826 g.add_edge(1, 2).unwrap();
827 let (vs, _) = widest_path(&g, 0, 2, &[5.0, 3.0]).unwrap().unwrap();
828 assert_eq!(vs, vec![0, 1, 2]);
829 assert!(widest_path(&g, 2, 0, &[5.0, 3.0]).unwrap().is_none());
831 let (vs, _) = widest_path_with_mode(&g, 2, 0, &[5.0, 3.0], DijkstraMode::In)
833 .unwrap()
834 .unwrap();
835 assert_eq!(vs, vec![2, 1, 0]);
836 }
837
838 #[test]
839 fn widest_path_from_out_of_range_errors() {
840 let g = Graph::with_vertices(3);
841 let err = widest_path(&g, 99, 0, &[]).unwrap_err();
842 assert!(matches!(
843 err,
844 IgraphError::VertexOutOfRange { id: 99, n: 3 }
845 ));
846 }
847
848 #[test]
849 fn widest_path_to_out_of_range_errors() {
850 let g = Graph::with_vertices(3);
851 let err = widest_path(&g, 0, 99, &[]).unwrap_err();
852 assert!(matches!(
853 err,
854 IgraphError::VertexOutOfRange { id: 99, n: 3 }
855 ));
856 }
857
858 #[test]
859 fn widest_path_negative_infinity_edge_breaks_chain() {
860 let mut g = Graph::with_vertices(3);
862 g.add_edge(0, 1).unwrap();
863 g.add_edge(1, 2).unwrap();
864 let r = widest_path(&g, 0, 2, &[f64::NEG_INFINITY, 1.0]).unwrap();
865 assert!(r.is_none());
866 }
867
868 #[test]
871 fn fw_widest_triangle_matches_dijkstra_per_source() {
872 let mut g = Graph::with_vertices(3);
873 g.add_edge(0, 1).unwrap();
874 g.add_edge(0, 2).unwrap();
875 g.add_edge(1, 2).unwrap();
876 let weights = [1.0, 4.0, 2.0];
877 let fw = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
878 for u in 0..3u32 {
880 let dij = widest_path_widths(&g, u, &weights).unwrap();
881 assert_eq!(fw[u as usize], dij, "row {u} mismatch");
882 }
883 }
884
885 #[test]
886 fn fw_widest_diagonal_is_infinity() {
887 let g = Graph::with_vertices(3);
888 let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
889 for (i, row) in m.iter().enumerate() {
890 for (j, entry) in row.iter().enumerate() {
891 if i == j {
892 assert_eq!(*entry, Some(f64::INFINITY));
893 } else {
894 assert_eq!(*entry, None);
895 }
896 }
897 }
898 }
899
900 #[test]
901 fn fw_widest_unreachable_components() {
902 let mut g = Graph::with_vertices(4);
903 g.add_edge(0, 1).unwrap();
904 g.add_edge(2, 3).unwrap();
905 let m = widest_path_widths_floyd_warshall(&g, &[5.0, 7.0]).unwrap();
906 assert_eq!(m[0][1], Some(5.0));
907 assert_eq!(m[0][2], None);
908 assert_eq!(m[2][3], Some(7.0));
909 assert_eq!(m[1][3], None);
910 }
911
912 #[test]
913 fn fw_widest_directed_respects_mode() {
914 let mut g = Graph::new(3, true).unwrap();
915 g.add_edge(0, 1).unwrap();
916 g.add_edge(1, 2).unwrap();
917 let weights = [5.0, 3.0];
918 let out = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
920 assert_eq!(out[0][1], Some(5.0));
921 assert_eq!(out[0][2], Some(3.0));
922 assert_eq!(out[2][0], None);
923 let in_m =
925 widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::In).unwrap();
926 assert_eq!(in_m[0][1], None);
927 assert_eq!(in_m[2][0], Some(3.0));
928 let all =
930 widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::All).unwrap();
931 assert_eq!(all[0][2], Some(3.0));
932 assert_eq!(all[2][0], Some(3.0));
933 }
934
935 #[test]
936 fn fw_widest_parallel_edges_keep_widest() {
937 let mut g = Graph::with_vertices(2);
938 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();
942 assert_eq!(m[0][1], Some(5.0));
943 assert_eq!(m[1][0], Some(5.0));
944 }
945
946 #[test]
947 fn fw_widest_negative_infinity_edge_ignored() {
948 let mut g = Graph::with_vertices(3);
949 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
951 let m = widest_path_widths_floyd_warshall(&g, &[f64::NEG_INFINITY, 1.0]).unwrap();
952 assert_eq!(m[0][1], None);
954 assert_eq!(m[0][2], None);
955 assert_eq!(m[1][2], Some(1.0));
956 }
957
958 #[test]
959 fn fw_widest_nan_weight_errors() {
960 let mut g = Graph::with_vertices(2);
961 g.add_edge(0, 1).unwrap();
962 let err = widest_path_widths_floyd_warshall(&g, &[f64::NAN]).unwrap_err();
963 assert!(matches!(err, IgraphError::InvalidArgument(_)));
964 }
965
966 #[test]
967 fn fw_widest_empty_graph_empty_matrix() {
968 let g = Graph::with_vertices(0);
969 let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
970 assert!(m.is_empty());
971 }
972
973 #[test]
976 fn widest_paths_to_triangle_two_targets() {
977 let mut g = Graph::with_vertices(3);
978 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();
982 assert_eq!(paths.len(), 2);
983 let (vs1, es1) = paths[0].as_ref().unwrap();
985 assert_eq!(vs1, &vec![0, 2, 1]);
986 assert_eq!(es1, &vec![1, 2]);
987 let (vs2, es2) = paths[1].as_ref().unwrap();
989 assert_eq!(vs2, &vec![0, 2]);
990 assert_eq!(es2, &vec![1]);
991 }
992
993 #[test]
994 fn widest_paths_to_includes_unreachable_as_none() {
995 let mut g = Graph::with_vertices(4);
996 g.add_edge(0, 1).unwrap();
997 g.add_edge(2, 3).unwrap();
998 let paths = widest_paths_to(&g, 0, &[1, 2, 3], &[1.0, 1.0]).unwrap();
1000 assert!(paths[0].is_some());
1001 assert!(paths[1].is_none());
1002 assert!(paths[2].is_none());
1003 }
1004
1005 #[test]
1006 fn widest_paths_to_empty_targets_returns_empty() {
1007 let g = Graph::with_vertices(3);
1008 let paths = widest_paths_to(&g, 0, &[], &[]).unwrap();
1009 assert!(paths.is_empty());
1010 }
1011
1012 #[test]
1013 fn widest_paths_to_self_target_is_trivial() {
1014 let mut g = Graph::with_vertices(3);
1015 g.add_edge(0, 1).unwrap();
1016 let paths = widest_paths_to(&g, 1, &[1, 0], &[5.0]).unwrap();
1018 let (vs0, es0) = paths[0].as_ref().unwrap();
1019 assert_eq!(vs0, &vec![1]);
1020 assert!(es0.is_empty());
1021 let (vs1, es1) = paths[1].as_ref().unwrap();
1023 assert_eq!(vs1, &vec![1, 0]);
1024 assert_eq!(es1, &vec![0]);
1025 }
1026
1027 #[test]
1028 fn widest_paths_to_duplicate_targets_return_same_path() {
1029 let mut g = Graph::with_vertices(3);
1030 g.add_edge(0, 1).unwrap();
1031 g.add_edge(1, 2).unwrap();
1032 let paths = widest_paths_to(&g, 0, &[2, 2, 2], &[5.0, 3.0]).unwrap();
1033 assert_eq!(paths.len(), 3);
1034 for p in &paths {
1035 let (vs, _) = p.as_ref().unwrap();
1036 assert_eq!(vs, &vec![0, 1, 2]);
1037 }
1038 }
1039
1040 #[test]
1041 fn widest_paths_to_target_out_of_range_errors() {
1042 let g = Graph::with_vertices(3);
1043 let err = widest_paths_to(&g, 0, &[1, 99], &[]).unwrap_err();
1044 assert!(matches!(
1045 err,
1046 IgraphError::VertexOutOfRange { id: 99, n: 3 }
1047 ));
1048 }
1049
1050 #[test]
1051 fn widest_paths_to_directed_in_mode_reverses() {
1052 let mut g = Graph::new(3, true).unwrap();
1053 g.add_edge(0, 1).unwrap();
1054 g.add_edge(1, 2).unwrap();
1055 let paths =
1057 widest_paths_to_with_mode(&g, 2, &[1, 0], &[5.0, 3.0], DijkstraMode::In).unwrap();
1058 let (vs1, _) = paths[0].as_ref().unwrap();
1059 assert_eq!(vs1, &vec![2, 1]);
1060 let (vs0, _) = paths[1].as_ref().unwrap();
1061 assert_eq!(vs0, &vec![2, 1, 0]);
1062 }
1063
1064 #[test]
1065 fn widest_paths_to_negative_infinity_edge_blocks_target() {
1066 let mut g = Graph::with_vertices(3);
1067 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
1069 let paths = widest_paths_to(&g, 0, &[1, 2], &[f64::NEG_INFINITY, 1.0]).unwrap();
1070 assert!(paths[0].is_none());
1071 assert!(paths[1].is_none());
1072 }
1073
1074 #[test]
1077 fn widest_paths_triangle_struct_fields_consistent() {
1078 let mut g = Graph::with_vertices(3);
1079 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();
1083 assert_eq!(sp.widths[0], Some(f64::INFINITY));
1085 assert_eq!(sp.parents[0], None);
1086 assert_eq!(sp.inbound_edges[0], None);
1087 assert_eq!(sp.widths[2], Some(4.0));
1089 assert_eq!(sp.parents[2], Some(0));
1090 assert_eq!(sp.inbound_edges[2], Some(1));
1091 assert_eq!(sp.widths[1], Some(2.0));
1093 assert_eq!(sp.parents[1], Some(2));
1094 assert_eq!(sp.inbound_edges[1], Some(2));
1095 }
1096
1097 #[test]
1098 fn widest_paths_widths_match_widest_path_widths() {
1099 let mut g = Graph::with_vertices(4);
1101 g.add_edge(0, 1).unwrap();
1102 g.add_edge(1, 2).unwrap();
1103 g.add_edge(2, 3).unwrap();
1104 let weights = [5.0, 1.0, 3.0];
1105 let sp = widest_paths(&g, 0, &weights).unwrap();
1106 let standalone = widest_path_widths(&g, 0, &weights).unwrap();
1107 assert_eq!(sp.widths, standalone);
1108 }
1109
1110 #[test]
1111 fn widest_paths_unreachable_has_none_in_all_three_fields() {
1112 let mut g = Graph::with_vertices(4);
1113 g.add_edge(0, 1).unwrap();
1114 g.add_edge(2, 3).unwrap();
1115 let sp = widest_paths(&g, 0, &[5.0, 7.0]).unwrap();
1116 assert_eq!(sp.widths[2], None);
1118 assert_eq!(sp.parents[2], None);
1119 assert_eq!(sp.inbound_edges[2], None);
1120 assert_eq!(sp.parents[1], Some(0));
1122 assert_eq!(sp.inbound_edges[1], Some(0));
1123 }
1124
1125 #[test]
1126 fn widest_paths_directed_in_mode() {
1127 let mut g = Graph::new(3, true).unwrap();
1128 g.add_edge(0, 1).unwrap();
1129 g.add_edge(1, 2).unwrap();
1130 let sp = widest_paths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
1132 assert_eq!(sp.widths[2], Some(f64::INFINITY));
1133 assert_eq!(sp.widths[1], Some(3.0));
1134 assert_eq!(sp.widths[0], Some(3.0));
1135 assert_eq!(sp.parents[1], Some(2));
1138 assert_eq!(sp.parents[0], Some(1));
1139 }
1140
1141 #[test]
1142 fn widest_paths_source_out_of_range_errors() {
1143 let g = Graph::with_vertices(3);
1144 let err = widest_paths(&g, 99, &[]).unwrap_err();
1145 assert!(matches!(
1146 err,
1147 IgraphError::VertexOutOfRange { id: 99, n: 3 }
1148 ));
1149 }
1150
1151 #[test]
1152 fn widest_paths_nan_weight_errors() {
1153 let mut g = Graph::with_vertices(2);
1154 g.add_edge(0, 1).unwrap();
1155 let err = widest_paths(&g, 0, &[f64::NAN]).unwrap_err();
1156 assert!(matches!(err, IgraphError::InvalidArgument(_)));
1157 }
1158
1159 #[test]
1160 fn widest_paths_spt_endpoints_match_widest_path_chain() {
1161 let mut g = Graph::with_vertices(4);
1164 g.add_edge(0, 1).unwrap();
1165 g.add_edge(1, 2).unwrap();
1166 g.add_edge(2, 3).unwrap();
1167 let weights = [5.0, 1.0, 3.0];
1168 let sp = widest_paths(&g, 0, &weights).unwrap();
1169 let path = widest_path(&g, 0, 3, &weights).unwrap().unwrap();
1170 let mut reconstructed: Vec<u32> = vec![3];
1172 let mut cur = 3usize;
1173 while let Some(prev) = sp.parents[cur] {
1174 reconstructed.push(prev);
1175 cur = prev as usize;
1176 }
1177 reconstructed.reverse();
1178 assert_eq!(reconstructed, path.0);
1179 }
1180}