1use std::cmp::Ordering;
29use std::collections::BinaryHeap;
30
31use crate::core::graph::EdgeId;
32use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
33
34#[derive(Copy, Clone)]
38struct Frontier(f64, VertexId);
39
40impl PartialEq for Frontier {
41 fn eq(&self, other: &Self) -> bool {
42 self.0 == other.0 && self.1 == other.1
43 }
44}
45impl Eq for Frontier {}
46impl Ord for Frontier {
47 fn cmp(&self, other: &Self) -> Ordering {
48 other.0.total_cmp(&self.0).then(other.1.cmp(&self.1))
51 }
52}
53impl PartialOrd for Frontier {
54 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55 Some(self.cmp(other))
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum DijkstraMode {
66 Out,
69 In,
71 All,
74}
75
76pub(super) fn incident_for_mode(
79 graph: &Graph,
80 v: VertexId,
81 mode: DijkstraMode,
82) -> IgraphResult<Vec<EdgeId>> {
83 if !graph.is_directed() {
84 return graph.incident(v);
85 }
86 match mode {
87 DijkstraMode::Out => graph.incident(v),
88 DijkstraMode::In => graph.incident_in(v),
89 DijkstraMode::All => {
90 let mut out = graph.incident(v)?;
91 out.extend(graph.incident_in(v)?);
92 Ok(out)
93 }
94 }
95}
96
97pub(super) fn validate_weights(graph: &Graph, weights: &[f64]) -> IgraphResult<()> {
100 let m = graph.ecount();
101 if weights.len() != m {
102 return Err(IgraphError::InvalidArgument(format!(
103 "weights vector size ({}) differs from edge count ({})",
104 weights.len(),
105 m
106 )));
107 }
108 for (e, &w) in weights.iter().enumerate() {
109 if w.is_nan() {
110 return Err(IgraphError::InvalidArgument(format!(
111 "weight at edge {e} is NaN"
112 )));
113 }
114 if w < 0.0 {
115 return Err(IgraphError::InvalidArgument(format!(
116 "weight at edge {e} is negative ({w}); Dijkstra requires non-negative weights"
117 )));
118 }
119 if !w.is_finite() {
120 return Err(IgraphError::InvalidArgument(format!(
121 "weight at edge {e} is not finite ({w})"
122 )));
123 }
124 }
125 Ok(())
126}
127
128fn dijkstra_inner(
134 graph: &Graph,
135 sources: &[VertexId],
136 weights: &[f64],
137 cutoff: Option<f64>,
138 record_inbound: bool,
139 mode: DijkstraMode,
140) -> IgraphResult<(Vec<f64>, Vec<Option<EdgeId>>)> {
141 let n = graph.vcount();
142 let n_us = n as usize;
143 let mut dist = vec![f64::INFINITY; n_us];
144 let mut inbound: Vec<Option<EdgeId>> = if record_inbound {
145 vec![None; n_us]
146 } else {
147 Vec::new()
148 };
149 let mut visited = vec![false; n_us];
150
151 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
152 for &s in sources {
153 if s >= n {
154 return Err(IgraphError::VertexOutOfRange { id: s, n });
155 }
156 if dist[s as usize] > 0.0 {
157 dist[s as usize] = 0.0;
158 heap.push(Frontier(0.0, s));
159 }
160 }
161
162 while let Some(Frontier(d, v)) = heap.pop() {
163 let v_us = v as usize;
164 if visited[v_us] {
165 continue;
166 }
167 if let Some(c) = cutoff {
168 if d > c {
169 continue;
170 }
171 }
172 visited[v_us] = true;
173
174 for eid in incident_for_mode(graph, v, mode)? {
175 let w = weights[eid as usize];
176 if !w.is_finite() {
177 continue;
178 }
179 let other = graph.edge_other(eid as EdgeId, v)?;
180 let nd = d + w;
181 if nd < dist[other as usize] {
182 dist[other as usize] = nd;
183 if record_inbound {
184 inbound[other as usize] = Some(eid as EdgeId);
185 }
186 heap.push(Frontier(nd, other));
187 }
188 }
189 }
190
191 Ok((dist, inbound))
192}
193
194fn dist_vec_to_options(dist: Vec<f64>) -> Vec<Option<f64>> {
195 dist.into_iter()
196 .map(|d| if d.is_infinite() { None } else { Some(d) })
197 .collect()
198}
199
200pub fn dijkstra_distances(
226 graph: &Graph,
227 source: VertexId,
228 weights: &[f64],
229) -> IgraphResult<Vec<Option<f64>>> {
230 validate_weights(graph, weights)?;
231 let (dist, _) = dijkstra_inner(graph, &[source], weights, None, false, DijkstraMode::Out)?;
232 Ok(dist_vec_to_options(dist))
233}
234
235#[derive(Debug, Clone, PartialEq)]
248pub struct DijkstraPaths {
249 pub distances: Vec<Option<f64>>,
252 pub parents: Vec<Option<VertexId>>,
256 pub inbound_edges: Vec<Option<EdgeId>>,
260}
261
262pub fn dijkstra_paths(
285 graph: &Graph,
286 source: VertexId,
287 weights: &[f64],
288) -> IgraphResult<DijkstraPaths> {
289 validate_weights(graph, weights)?;
290 let (dist, inbound) = dijkstra_inner(graph, &[source], weights, None, true, DijkstraMode::Out)?;
291 let mut parents = vec![None; graph.vcount() as usize];
292 for v in 0..graph.vcount() {
293 if let Some(eid) = inbound[v as usize] {
294 let other = graph.edge_other(eid, v)?;
295 parents[v as usize] = Some(other);
296 }
297 }
298 Ok(DijkstraPaths {
299 distances: dist_vec_to_options(dist),
300 parents,
301 inbound_edges: inbound,
302 })
303}
304
305pub fn dijkstra_path_to(
328 graph: &Graph,
329 source: VertexId,
330 target: VertexId,
331 weights: &[f64],
332) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
333 let n = graph.vcount();
334 if target >= n {
335 return Err(IgraphError::VertexOutOfRange { id: target, n });
336 }
337 let p = dijkstra_paths(graph, source, weights)?;
338 if p.distances[target as usize].is_none() {
339 return Ok(None);
340 }
341 let mut vs = Vec::new();
343 let mut es = Vec::new();
344 let mut cur = target;
345 while let Some(eid) = p.inbound_edges[cur as usize] {
346 es.push(eid);
347 let Some(parent) = p.parents[cur as usize] else {
348 return Err(IgraphError::Internal(
349 "inbound edge exists but parent is None",
350 ));
351 };
352 vs.push(cur);
353 cur = parent;
354 }
355 vs.push(cur); vs.reverse();
357 es.reverse();
358 Ok(Some((vs, es)))
359}
360
361pub fn dijkstra_distances_cutoff(
386 graph: &Graph,
387 source: VertexId,
388 weights: &[f64],
389 cutoff: Option<f64>,
390) -> IgraphResult<Vec<Option<f64>>> {
391 validate_weights(graph, weights)?;
392 if let Some(c) = cutoff {
393 if c.is_nan() {
394 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
395 }
396 }
397 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, DijkstraMode::Out)?;
398 let mut out = dist_vec_to_options(dist);
399 if let Some(c) = cutoff {
400 for d in &mut out {
406 if let Some(v) = *d {
407 if v > c {
408 *d = None;
409 }
410 }
411 }
412 }
413 Ok(out)
414}
415
416pub fn dijkstra_distances_multi(
438 graph: &Graph,
439 sources: &[VertexId],
440 weights: &[f64],
441 cutoff: Option<f64>,
442) -> IgraphResult<Vec<Vec<Option<f64>>>> {
443 validate_weights(graph, weights)?;
444 if let Some(c) = cutoff {
445 if c.is_nan() {
446 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
447 }
448 }
449 let mut out = Vec::with_capacity(sources.len());
450 for &s in sources {
451 out.push(dijkstra_distances_cutoff(graph, s, weights, cutoff)?);
452 }
453 Ok(out)
454}
455
456pub fn dijkstra_distances_with_mode(
486 graph: &Graph,
487 source: VertexId,
488 weights: &[f64],
489 mode: DijkstraMode,
490) -> IgraphResult<Vec<Option<f64>>> {
491 validate_weights(graph, weights)?;
492 let (dist, _) = dijkstra_inner(graph, &[source], weights, None, false, mode)?;
493 Ok(dist_vec_to_options(dist))
494}
495
496pub fn dijkstra_paths_with_mode(
511 graph: &Graph,
512 source: VertexId,
513 weights: &[f64],
514 mode: DijkstraMode,
515) -> IgraphResult<DijkstraPaths> {
516 validate_weights(graph, weights)?;
517 let (dist, inbound) = dijkstra_inner(graph, &[source], weights, None, true, mode)?;
518 let mut parents = vec![None; graph.vcount() as usize];
519 for v in 0..graph.vcount() {
520 if let Some(eid) = inbound[v as usize] {
521 let other = graph.edge_other(eid, v)?;
522 parents[v as usize] = Some(other);
523 }
524 }
525 Ok(DijkstraPaths {
526 distances: dist_vec_to_options(dist),
527 parents,
528 inbound_edges: inbound,
529 })
530}
531
532pub fn dijkstra_path_to_with_mode(
548 graph: &Graph,
549 source: VertexId,
550 target: VertexId,
551 weights: &[f64],
552 mode: DijkstraMode,
553) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
554 let n = graph.vcount();
555 if target >= n {
556 return Err(IgraphError::VertexOutOfRange { id: target, n });
557 }
558 let p = dijkstra_paths_with_mode(graph, source, weights, mode)?;
559 if p.distances[target as usize].is_none() {
560 return Ok(None);
561 }
562 let mut vs = Vec::new();
563 let mut es = Vec::new();
564 let mut cur = target;
565 while let Some(eid) = p.inbound_edges[cur as usize] {
566 es.push(eid);
567 let Some(parent) = p.parents[cur as usize] else {
568 return Err(IgraphError::Internal(
569 "inbound edge exists but parent is None",
570 ));
571 };
572 vs.push(cur);
573 cur = parent;
574 }
575 vs.push(cur);
576 vs.reverse();
577 es.reverse();
578 Ok(Some((vs, es)))
579}
580
581pub fn dijkstra_distances_cutoff_with_mode(
598 graph: &Graph,
599 source: VertexId,
600 weights: &[f64],
601 cutoff: Option<f64>,
602 mode: DijkstraMode,
603) -> IgraphResult<Vec<Option<f64>>> {
604 validate_weights(graph, weights)?;
605 if let Some(c) = cutoff {
606 if c.is_nan() {
607 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
608 }
609 }
610 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, mode)?;
611 let mut out = dist_vec_to_options(dist);
612 if let Some(c) = cutoff {
613 for d in &mut out {
614 if let Some(v) = *d {
615 if v > c {
616 *d = None;
617 }
618 }
619 }
620 }
621 Ok(out)
622}
623
624pub fn dijkstra_distances_multi_with_mode(
641 graph: &Graph,
642 sources: &[VertexId],
643 weights: &[f64],
644 cutoff: Option<f64>,
645 mode: DijkstraMode,
646) -> IgraphResult<Vec<Vec<Option<f64>>>> {
647 validate_weights(graph, weights)?;
648 if let Some(c) = cutoff {
649 if c.is_nan() {
650 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
651 }
652 }
653 let mut out = Vec::with_capacity(sources.len());
654 for &s in sources {
655 out.push(dijkstra_distances_cutoff_with_mode(
656 graph, s, weights, cutoff, mode,
657 )?);
658 }
659 Ok(out)
660}
661
662#[derive(Debug, Clone, PartialEq)]
669pub struct DijkstraAllPaths {
670 pub vertex_paths: Vec<Vec<Vec<VertexId>>>,
674 pub edge_paths: Vec<Vec<Vec<EdgeId>>>,
678 pub nrgeo: Vec<u64>,
681}
682
683fn cmp_eps(a: f64, b: f64) -> i32 {
688 const EPS: f64 = 1e-10; if !a.is_finite() || !b.is_finite() {
690 return match a.total_cmp(&b) {
694 Ordering::Equal => 0,
695 Ordering::Greater => 1,
696 Ordering::Less => -1,
697 };
698 }
699 let scale = a.abs().max(b.abs()).max(1.0);
700 let diff = a - b;
701 if diff.abs() <= EPS * scale {
702 0
703 } else if diff > 0.0 {
704 1
705 } else {
706 -1
707 }
708}
709
710pub fn dijkstra_all_shortest_paths(
737 graph: &Graph,
738 source: VertexId,
739 weights: &[f64],
740 mode: DijkstraMode,
741) -> IgraphResult<DijkstraAllPaths> {
742 let n = graph.vcount();
743 if source >= n {
744 return Err(IgraphError::VertexOutOfRange { id: source, n });
745 }
746 validate_weights(graph, weights)?;
747
748 let n_us = n as usize;
749 let mut dist = vec![f64::INFINITY; n_us];
750 let mut parents: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
754 let mut parent_eids: Vec<Vec<EdgeId>> = vec![Vec::new(); n_us];
755 let mut order: Vec<VertexId> = Vec::new();
759 let mut visited = vec![false; n_us];
760
761 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
762 dist[source as usize] = 0.0;
763 heap.push(Frontier(0.0, source));
764
765 while let Some(Frontier(d, v)) = heap.pop() {
766 let v_us = v as usize;
767 if visited[v_us] {
768 continue;
769 }
770 visited[v_us] = true;
771 order.push(v);
772
773 for eid in incident_for_mode(graph, v, mode)? {
774 let w = weights[eid as usize];
775 if !w.is_finite() {
776 continue;
777 }
778 let other = graph.edge_other(eid as EdgeId, v)?;
779 let altdist = d + w;
780 let curdist = dist[other as usize];
781 let cmp = cmp_eps(curdist, altdist);
782 if curdist.is_infinite() {
783 dist[other as usize] = altdist;
785 parents[other as usize].clear();
786 parents[other as usize].push(v);
787 parent_eids[other as usize].clear();
788 parent_eids[other as usize].push(eid);
789 heap.push(Frontier(altdist, other));
790 } else if cmp == 0 && w > 0.0 {
791 parents[other as usize].push(v);
795 parent_eids[other as usize].push(eid);
796 } else if cmp > 0 {
797 dist[other as usize] = altdist;
799 parents[other as usize].clear();
800 parents[other as usize].push(v);
801 parent_eids[other as usize].clear();
802 parent_eids[other as usize].push(eid);
803 heap.push(Frontier(altdist, other));
804 }
805 }
806 }
807
808 let mut nrgeo: Vec<u64> = vec![0; n_us];
812 if dist[source as usize].is_finite() {
813 nrgeo[source as usize] = 1;
814 }
815 for &v in order.iter().skip(1) {
816 let mut acc: u64 = 0;
817 for &p in &parents[v as usize] {
818 acc = acc.saturating_add(nrgeo[p as usize]);
819 }
820 nrgeo[v as usize] = acc;
821 }
822
823 let mut vertex_paths: Vec<Vec<Vec<VertexId>>> = vec![Vec::new(); n_us];
827 let mut edge_paths: Vec<Vec<Vec<EdgeId>>> = vec![Vec::new(); n_us];
828 if dist[source as usize].is_finite() {
829 vertex_paths[source as usize].push(vec![source]);
830 edge_paths[source as usize].push(Vec::new());
831 }
832 for &v in order.iter().skip(1) {
835 let v_us = v as usize;
836 let parent_count = parents[v_us].len();
837 for i in 0..parent_count {
838 let p = parents[v_us][i];
839 let e = parent_eids[v_us][i];
840 let par_paths_len = vertex_paths[p as usize].len();
844 for j in 0..par_paths_len {
845 let mut vp = vertex_paths[p as usize][j].clone();
846 vp.push(v);
847 vertex_paths[v_us].push(vp);
848 let mut ep = edge_paths[p as usize][j].clone();
849 ep.push(e);
850 edge_paths[v_us].push(ep);
851 }
852 }
853 }
854
855 Ok(DijkstraAllPaths {
856 vertex_paths,
857 edge_paths,
858 nrgeo,
859 })
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn empty_graph_source_out_of_range() {
868 let g = Graph::with_vertices(0);
869 assert!(dijkstra_distances(&g, 0, &[]).is_err());
870 }
871
872 #[test]
873 fn singleton_distance_zero() {
874 let g = Graph::with_vertices(1);
875 assert_eq!(dijkstra_distances(&g, 0, &[]).unwrap(), vec![Some(0.0)]);
876 }
877
878 #[test]
879 fn unreachable_yields_none() {
880 let mut g = Graph::with_vertices(3);
881 g.add_edge(0, 1).unwrap();
882 let d = dijkstra_distances(&g, 0, &[1.0]).unwrap();
883 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
884 }
885
886 #[test]
887 fn shortcut_via_smaller_weights() {
888 let mut g = Graph::with_vertices(3);
889 g.add_edge(0, 1).unwrap();
890 g.add_edge(0, 2).unwrap();
891 g.add_edge(1, 2).unwrap();
892 let d = dijkstra_distances(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
893 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(3.0)]);
894 }
895
896 #[test]
897 fn directed_respects_edge_direction() {
898 let mut g = Graph::new(3, true).unwrap();
899 g.add_edge(0, 1).unwrap();
900 g.add_edge(2, 1).unwrap();
901 let d = dijkstra_distances(&g, 0, &[1.0, 1.0]).unwrap();
902 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
904 }
905
906 #[test]
907 fn weights_size_mismatch_errors() {
908 let mut g = Graph::with_vertices(2);
909 g.add_edge(0, 1).unwrap();
910 assert!(dijkstra_distances(&g, 0, &[]).is_err());
911 assert!(dijkstra_distances(&g, 0, &[1.0, 2.0]).is_err());
912 }
913
914 #[test]
915 fn negative_weight_errors() {
916 let mut g = Graph::with_vertices(2);
917 g.add_edge(0, 1).unwrap();
918 assert!(dijkstra_distances(&g, 0, &[-1.0]).is_err());
919 }
920
921 #[test]
922 fn nan_weight_errors() {
923 let mut g = Graph::with_vertices(2);
924 g.add_edge(0, 1).unwrap();
925 assert!(dijkstra_distances(&g, 0, &[f64::NAN]).is_err());
926 }
927
928 #[test]
929 fn infinite_weight_errors() {
930 let mut g = Graph::with_vertices(2);
931 g.add_edge(0, 1).unwrap();
932 assert!(dijkstra_distances(&g, 0, &[f64::INFINITY]).is_err());
933 }
934
935 #[test]
936 fn zero_weights_match_bfs_distance() {
937 let mut g = Graph::with_vertices(5);
939 for u in 0..4u32 {
940 g.add_edge(u, u + 1).unwrap();
941 }
942 let w = vec![1.0; 4];
943 let d = dijkstra_distances(&g, 0, &w).unwrap();
944 assert_eq!(
945 d,
946 vec![Some(0.0), Some(1.0), Some(2.0), Some(3.0), Some(4.0)]
947 );
948 }
949
950 #[test]
951 fn parallel_edges_pick_minimum_weight() {
952 let mut g = Graph::with_vertices(2);
953 g.add_edge(0, 1).unwrap();
954 g.add_edge(0, 1).unwrap();
955 let d = dijkstra_distances(&g, 0, &[5.0, 1.5]).unwrap();
956 assert_eq!(d, vec![Some(0.0), Some(1.5)]);
957 }
958
959 #[test]
960 fn star_graph_distances() {
961 let mut g = Graph::with_vertices(5);
963 g.add_edge(0, 1).unwrap();
964 g.add_edge(0, 2).unwrap();
965 g.add_edge(0, 3).unwrap();
966 g.add_edge(0, 4).unwrap();
967 let w = vec![1.0, 2.5, 0.5, 7.0];
968 let d = dijkstra_distances(&g, 0, &w).unwrap();
969 assert_eq!(
970 d,
971 vec![Some(0.0), Some(1.0), Some(2.5), Some(0.5), Some(7.0)]
972 );
973 }
974
975 fn shortcut_triangle() -> (Graph, Vec<f64>) {
978 let mut g = Graph::with_vertices(3);
979 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 2).unwrap(); (g, vec![1.0, 4.0, 2.0])
983 }
984
985 #[test]
986 fn paths_distances_match_dijkstra_distances() {
987 let (g, w) = shortcut_triangle();
988 let p = dijkstra_paths(&g, 0, &w).unwrap();
989 assert_eq!(p.distances, dijkstra_distances(&g, 0, &w).unwrap());
990 }
991
992 #[test]
993 fn paths_parents_and_inbound_edges_record_spt() {
994 let (g, w) = shortcut_triangle();
995 let p = dijkstra_paths(&g, 0, &w).unwrap();
996 assert_eq!(p.parents[0], None);
998 assert_eq!(p.inbound_edges[0], None);
999 assert_eq!(p.parents[1], Some(0));
1001 assert_eq!(p.inbound_edges[1], Some(0));
1002 assert_eq!(p.parents[2], Some(1));
1004 assert_eq!(p.inbound_edges[2], Some(2));
1005 }
1006
1007 #[test]
1008 fn paths_unreachable_has_none_parent() {
1009 let mut g = Graph::with_vertices(3);
1010 g.add_edge(0, 1).unwrap();
1011 let p = dijkstra_paths(&g, 0, &[1.0]).unwrap();
1012 assert_eq!(p.distances, vec![Some(0.0), Some(1.0), None]);
1013 assert_eq!(p.parents, vec![None, Some(0), None]);
1014 assert_eq!(p.inbound_edges, vec![None, Some(0), None]);
1015 }
1016
1017 #[test]
1018 fn path_to_returns_vertex_and_edge_chain() {
1019 let (g, w) = shortcut_triangle();
1020 let (vs, es) = dijkstra_path_to(&g, 0, 2, &w).unwrap().unwrap();
1021 assert_eq!(vs, vec![0, 1, 2]);
1022 assert_eq!(es, vec![0, 2]);
1023 }
1024
1025 #[test]
1026 fn path_to_self_is_singleton() {
1027 let (g, w) = shortcut_triangle();
1028 let (vs, es) = dijkstra_path_to(&g, 0, 0, &w).unwrap().unwrap();
1029 assert_eq!(vs, vec![0]);
1030 assert!(es.is_empty());
1031 }
1032
1033 #[test]
1034 fn path_to_unreachable_is_none() {
1035 let mut g = Graph::with_vertices(3);
1036 g.add_edge(0, 1).unwrap();
1037 assert_eq!(dijkstra_path_to(&g, 0, 2, &[1.0]).unwrap(), None);
1038 }
1039
1040 #[test]
1041 fn path_to_target_out_of_range_errors() {
1042 let (g, w) = shortcut_triangle();
1043 assert!(dijkstra_path_to(&g, 0, 99, &w).is_err());
1044 }
1045
1046 #[test]
1047 fn cutoff_masks_distances_above_cutoff() {
1048 let mut g = Graph::with_vertices(5);
1049 for i in 0..4u32 {
1050 g.add_edge(i, i + 1).unwrap();
1051 }
1052 let w = vec![1.0; 4];
1053 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(2.0)).unwrap();
1054 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(2.0), None, None]);
1056 }
1057
1058 #[test]
1059 fn cutoff_none_matches_unbounded_dijkstra() {
1060 let (g, w) = shortcut_triangle();
1061 assert_eq!(
1062 dijkstra_distances_cutoff(&g, 0, &w, None).unwrap(),
1063 dijkstra_distances(&g, 0, &w).unwrap()
1064 );
1065 }
1066
1067 #[test]
1068 fn cutoff_zero_keeps_only_source() {
1069 let (g, w) = shortcut_triangle();
1070 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(0.0)).unwrap();
1071 assert_eq!(d, vec![Some(0.0), None, None]);
1072 }
1073
1074 #[test]
1075 fn cutoff_nan_errors() {
1076 let (g, w) = shortcut_triangle();
1077 assert!(dijkstra_distances_cutoff(&g, 0, &w, Some(f64::NAN)).is_err());
1078 }
1079
1080 #[test]
1081 fn multi_source_yields_per_source_distances() {
1082 let (g, w) = shortcut_triangle();
1083 let r = dijkstra_distances_multi(&g, &[0, 1], &w, None).unwrap();
1084 assert_eq!(r.len(), 2);
1085 assert_eq!(r[0], dijkstra_distances(&g, 0, &w).unwrap());
1086 assert_eq!(r[1], dijkstra_distances(&g, 1, &w).unwrap());
1087 }
1088
1089 #[test]
1090 fn multi_source_empty_list_yields_empty_result() {
1091 let (g, w) = shortcut_triangle();
1092 let r = dijkstra_distances_multi(&g, &[], &w, None).unwrap();
1093 assert!(r.is_empty());
1094 }
1095
1096 #[test]
1097 fn multi_source_propagates_cutoff() {
1098 let mut g = Graph::with_vertices(4);
1099 for i in 0..3u32 {
1100 g.add_edge(i, i + 1).unwrap();
1101 }
1102 let w = vec![1.0; 3];
1103 let r = dijkstra_distances_multi(&g, &[0, 3], &w, Some(1.0)).unwrap();
1104 assert_eq!(r[0], vec![Some(0.0), Some(1.0), None, None]);
1105 assert_eq!(r[1], vec![None, None, Some(1.0), Some(0.0)]);
1106 }
1107
1108 #[test]
1109 fn multi_source_invalid_vertex_errors() {
1110 let (g, w) = shortcut_triangle();
1111 assert!(dijkstra_distances_multi(&g, &[0, 99], &w, None).is_err());
1112 }
1113
1114 fn directed_path_3() -> (Graph, Vec<f64>) {
1117 let mut g = Graph::new(3, true).unwrap();
1118 g.add_edge(0, 1).unwrap();
1119 g.add_edge(1, 2).unwrap();
1120 (g, vec![1.0, 2.0])
1121 }
1122
1123 #[test]
1124 fn legacy_apis_match_with_mode_out() {
1125 let (g, w) = shortcut_triangle();
1126 assert_eq!(
1127 dijkstra_distances(&g, 0, &w).unwrap(),
1128 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap()
1129 );
1130 let p = dijkstra_paths(&g, 0, &w).unwrap();
1131 let pm = dijkstra_paths_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap();
1132 assert_eq!(p, pm);
1133 }
1134
1135 #[test]
1136 fn directed_path_in_mode_reverses_reachability() {
1137 let (g, w) = directed_path_3();
1138 assert_eq!(
1140 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap(),
1141 vec![Some(0.0), Some(1.0), Some(3.0)]
1142 );
1143 assert_eq!(
1145 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::In).unwrap(),
1146 vec![Some(0.0), None, None]
1147 );
1148 assert_eq!(
1150 dijkstra_distances_with_mode(&g, 2, &w, DijkstraMode::All).unwrap(),
1151 vec![Some(3.0), Some(2.0), Some(0.0)]
1152 );
1153 }
1154
1155 #[test]
1156 fn undirected_modes_agree() {
1157 let (g, w) = shortcut_triangle();
1158 for m in [DijkstraMode::Out, DijkstraMode::In, DijkstraMode::All] {
1159 assert_eq!(
1160 dijkstra_distances_with_mode(&g, 0, &w, m).unwrap(),
1161 vec![Some(0.0), Some(1.0), Some(3.0)],
1162 "mode {m:?}"
1163 );
1164 }
1165 }
1166
1167 #[test]
1168 fn paths_with_mode_in_records_reverse_parents() {
1169 let (g, w) = directed_path_3();
1170 let p = dijkstra_paths_with_mode(&g, 2, &w, DijkstraMode::In).unwrap();
1172 assert_eq!(p.distances, vec![Some(3.0), Some(2.0), Some(0.0)]);
1173 assert_eq!(p.parents[2], None);
1174 assert_eq!(p.parents[1], Some(2));
1175 assert_eq!(p.parents[0], Some(1));
1176 }
1177
1178 #[test]
1179 fn path_to_with_mode_directed_in() {
1180 let (g, w) = directed_path_3();
1181 let (vs, es) = dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::In)
1182 .unwrap()
1183 .unwrap();
1184 assert_eq!(vs, vec![2, 1, 0]);
1185 assert_eq!(es, vec![1, 0]);
1186 }
1187
1188 #[test]
1189 fn path_to_with_mode_unreachable_via_out() {
1190 let (g, w) = directed_path_3();
1191 assert_eq!(
1193 dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::Out).unwrap(),
1194 None
1195 );
1196 }
1197
1198 #[test]
1199 fn cutoff_with_mode_masks_above() {
1200 let (g, w) = directed_path_3();
1201 let d =
1202 dijkstra_distances_cutoff_with_mode(&g, 0, &w, Some(1.5), DijkstraMode::Out).unwrap();
1203 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
1204 }
1205
1206 #[test]
1207 fn multi_with_mode_sources_independent() {
1208 let (g, w) = directed_path_3();
1209 let r =
1210 dijkstra_distances_multi_with_mode(&g, &[0, 2], &w, None, DijkstraMode::All).unwrap();
1211 assert_eq!(r[0], vec![Some(0.0), Some(1.0), Some(3.0)]);
1212 assert_eq!(r[1], vec![Some(3.0), Some(2.0), Some(0.0)]);
1213 }
1214
1215 #[test]
1218 fn all_paths_diamond_yields_two_geodesics() {
1219 let mut g = Graph::with_vertices(4);
1222 g.add_edge(0, 1).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(1, 3).unwrap(); g.add_edge(2, 3).unwrap(); let r = dijkstra_all_shortest_paths(&g, 0, &[1.0; 4], DijkstraMode::Out).unwrap();
1227 assert_eq!(r.nrgeo, vec![1, 1, 1, 2]);
1228 assert_eq!(r.vertex_paths[0], vec![vec![0]]);
1229 assert_eq!(r.edge_paths[0], vec![Vec::<EdgeId>::new()]);
1230 assert_eq!(r.vertex_paths[3].len(), 2);
1231 let mut paths: Vec<Vec<u32>> = r.vertex_paths[3].clone();
1232 paths.sort();
1233 assert_eq!(paths, vec![vec![0, 1, 3], vec![0, 2, 3]]);
1234 }
1235
1236 #[test]
1237 fn all_paths_unique_returns_single_chain() {
1238 let (g, w) = shortcut_triangle();
1239 let r = dijkstra_all_shortest_paths(&g, 0, &w, DijkstraMode::Out).unwrap();
1240 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1241 assert_eq!(r.vertex_paths[2], vec![vec![0, 1, 2]]);
1242 assert_eq!(r.edge_paths[2], vec![vec![0, 2]]);
1243 }
1244
1245 #[test]
1246 fn all_paths_unreachable_emits_empty() {
1247 let mut g = Graph::with_vertices(3);
1248 g.add_edge(0, 1).unwrap();
1249 let r = dijkstra_all_shortest_paths(&g, 0, &[1.0], DijkstraMode::Out).unwrap();
1250 assert_eq!(r.nrgeo, vec![1, 1, 0]);
1251 assert!(r.vertex_paths[2].is_empty());
1252 assert!(r.edge_paths[2].is_empty());
1253 }
1254
1255 #[test]
1256 fn all_paths_directed_in_mode() {
1257 let (g, w) = directed_path_3();
1258 let r = dijkstra_all_shortest_paths(&g, 2, &w, DijkstraMode::In).unwrap();
1259 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1260 assert_eq!(r.vertex_paths[0], vec![vec![2, 1, 0]]);
1261 assert_eq!(r.edge_paths[0], vec![vec![1, 0]]);
1262 }
1263
1264 #[test]
1265 fn all_paths_invalid_source_errors() {
1266 let (g, _) = shortcut_triangle();
1267 assert!(dijkstra_all_shortest_paths(&g, 99, &[1.0; 3], DijkstraMode::Out).is_err());
1268 }
1269
1270 #[test]
1271 fn all_paths_zero_weight_alt_is_dropped_by_guard() {
1272 let mut g = Graph::with_vertices(3);
1278 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 2).unwrap(); let r = dijkstra_all_shortest_paths(&g, 0, &[1.0, 0.0, 1.0], DijkstraMode::Out).unwrap();
1282 assert_eq!(r.nrgeo[2], 1);
1283 assert_eq!(r.vertex_paths[2], vec![vec![0, 2]]);
1284 }
1285}