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 parent = p.parents[cur as usize].expect("inbound edge implies parent exists");
348 vs.push(cur);
349 cur = parent;
350 }
351 vs.push(cur); vs.reverse();
353 es.reverse();
354 Ok(Some((vs, es)))
355}
356
357pub fn dijkstra_distances_cutoff(
368 graph: &Graph,
369 source: VertexId,
370 weights: &[f64],
371 cutoff: Option<f64>,
372) -> IgraphResult<Vec<Option<f64>>> {
373 validate_weights(graph, weights)?;
374 if let Some(c) = cutoff {
375 if c.is_nan() {
376 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
377 }
378 }
379 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, DijkstraMode::Out)?;
380 let mut out = dist_vec_to_options(dist);
381 if let Some(c) = cutoff {
382 for d in &mut out {
388 if let Some(v) = *d {
389 if v > c {
390 *d = None;
391 }
392 }
393 }
394 }
395 Ok(out)
396}
397
398pub fn dijkstra_distances_multi(
407 graph: &Graph,
408 sources: &[VertexId],
409 weights: &[f64],
410 cutoff: Option<f64>,
411) -> IgraphResult<Vec<Vec<Option<f64>>>> {
412 validate_weights(graph, weights)?;
413 if let Some(c) = cutoff {
414 if c.is_nan() {
415 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
416 }
417 }
418 let mut out = Vec::with_capacity(sources.len());
419 for &s in sources {
420 out.push(dijkstra_distances_cutoff(graph, s, weights, cutoff)?);
421 }
422 Ok(out)
423}
424
425pub fn dijkstra_distances_with_mode(
455 graph: &Graph,
456 source: VertexId,
457 weights: &[f64],
458 mode: DijkstraMode,
459) -> IgraphResult<Vec<Option<f64>>> {
460 validate_weights(graph, weights)?;
461 let (dist, _) = dijkstra_inner(graph, &[source], weights, None, false, mode)?;
462 Ok(dist_vec_to_options(dist))
463}
464
465pub fn dijkstra_paths_with_mode(
468 graph: &Graph,
469 source: VertexId,
470 weights: &[f64],
471 mode: DijkstraMode,
472) -> IgraphResult<DijkstraPaths> {
473 validate_weights(graph, weights)?;
474 let (dist, inbound) = dijkstra_inner(graph, &[source], weights, None, true, mode)?;
475 let mut parents = vec![None; graph.vcount() as usize];
476 for v in 0..graph.vcount() {
477 if let Some(eid) = inbound[v as usize] {
478 let other = graph.edge_other(eid, v)?;
479 parents[v as usize] = Some(other);
480 }
481 }
482 Ok(DijkstraPaths {
483 distances: dist_vec_to_options(dist),
484 parents,
485 inbound_edges: inbound,
486 })
487}
488
489pub fn dijkstra_path_to_with_mode(
492 graph: &Graph,
493 source: VertexId,
494 target: VertexId,
495 weights: &[f64],
496 mode: DijkstraMode,
497) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
498 let n = graph.vcount();
499 if target >= n {
500 return Err(IgraphError::VertexOutOfRange { id: target, n });
501 }
502 let p = dijkstra_paths_with_mode(graph, source, weights, mode)?;
503 if p.distances[target as usize].is_none() {
504 return Ok(None);
505 }
506 let mut vs = Vec::new();
507 let mut es = Vec::new();
508 let mut cur = target;
509 while let Some(eid) = p.inbound_edges[cur as usize] {
510 es.push(eid);
511 let parent = p.parents[cur as usize].expect("inbound edge implies parent exists");
512 vs.push(cur);
513 cur = parent;
514 }
515 vs.push(cur);
516 vs.reverse();
517 es.reverse();
518 Ok(Some((vs, es)))
519}
520
521pub fn dijkstra_distances_cutoff_with_mode(
524 graph: &Graph,
525 source: VertexId,
526 weights: &[f64],
527 cutoff: Option<f64>,
528 mode: DijkstraMode,
529) -> IgraphResult<Vec<Option<f64>>> {
530 validate_weights(graph, weights)?;
531 if let Some(c) = cutoff {
532 if c.is_nan() {
533 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
534 }
535 }
536 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, mode)?;
537 let mut out = dist_vec_to_options(dist);
538 if let Some(c) = cutoff {
539 for d in &mut out {
540 if let Some(v) = *d {
541 if v > c {
542 *d = None;
543 }
544 }
545 }
546 }
547 Ok(out)
548}
549
550pub fn dijkstra_distances_multi_with_mode(
552 graph: &Graph,
553 sources: &[VertexId],
554 weights: &[f64],
555 cutoff: Option<f64>,
556 mode: DijkstraMode,
557) -> IgraphResult<Vec<Vec<Option<f64>>>> {
558 validate_weights(graph, weights)?;
559 if let Some(c) = cutoff {
560 if c.is_nan() {
561 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
562 }
563 }
564 let mut out = Vec::with_capacity(sources.len());
565 for &s in sources {
566 out.push(dijkstra_distances_cutoff_with_mode(
567 graph, s, weights, cutoff, mode,
568 )?);
569 }
570 Ok(out)
571}
572
573#[derive(Debug, Clone, PartialEq)]
580pub struct DijkstraAllPaths {
581 pub vertex_paths: Vec<Vec<Vec<VertexId>>>,
585 pub edge_paths: Vec<Vec<Vec<EdgeId>>>,
589 pub nrgeo: Vec<u64>,
592}
593
594fn cmp_eps(a: f64, b: f64) -> i32 {
599 const EPS: f64 = 1e-10; if !a.is_finite() || !b.is_finite() {
601 return match a.total_cmp(&b) {
605 Ordering::Equal => 0,
606 Ordering::Greater => 1,
607 Ordering::Less => -1,
608 };
609 }
610 let scale = a.abs().max(b.abs()).max(1.0);
611 let diff = a - b;
612 if diff.abs() <= EPS * scale {
613 0
614 } else if diff > 0.0 {
615 1
616 } else {
617 -1
618 }
619}
620
621pub fn dijkstra_all_shortest_paths(
648 graph: &Graph,
649 source: VertexId,
650 weights: &[f64],
651 mode: DijkstraMode,
652) -> IgraphResult<DijkstraAllPaths> {
653 let n = graph.vcount();
654 if source >= n {
655 return Err(IgraphError::VertexOutOfRange { id: source, n });
656 }
657 validate_weights(graph, weights)?;
658
659 let n_us = n as usize;
660 let mut dist = vec![f64::INFINITY; n_us];
661 let mut parents: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
665 let mut parent_eids: Vec<Vec<EdgeId>> = vec![Vec::new(); n_us];
666 let mut order: Vec<VertexId> = Vec::new();
670 let mut visited = vec![false; n_us];
671
672 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
673 dist[source as usize] = 0.0;
674 heap.push(Frontier(0.0, source));
675
676 while let Some(Frontier(d, v)) = heap.pop() {
677 let v_us = v as usize;
678 if visited[v_us] {
679 continue;
680 }
681 visited[v_us] = true;
682 order.push(v);
683
684 for eid in incident_for_mode(graph, v, mode)? {
685 let w = weights[eid as usize];
686 if !w.is_finite() {
687 continue;
688 }
689 let other = graph.edge_other(eid as EdgeId, v)?;
690 let altdist = d + w;
691 let curdist = dist[other as usize];
692 let cmp = cmp_eps(curdist, altdist);
693 if curdist.is_infinite() {
694 dist[other as usize] = altdist;
696 parents[other as usize].clear();
697 parents[other as usize].push(v);
698 parent_eids[other as usize].clear();
699 parent_eids[other as usize].push(eid);
700 heap.push(Frontier(altdist, other));
701 } else if cmp == 0 && w > 0.0 {
702 parents[other as usize].push(v);
706 parent_eids[other as usize].push(eid);
707 } else if cmp > 0 {
708 dist[other as usize] = altdist;
710 parents[other as usize].clear();
711 parents[other as usize].push(v);
712 parent_eids[other as usize].clear();
713 parent_eids[other as usize].push(eid);
714 heap.push(Frontier(altdist, other));
715 }
716 }
717 }
718
719 let mut nrgeo: Vec<u64> = vec![0; n_us];
723 if dist[source as usize].is_finite() {
724 nrgeo[source as usize] = 1;
725 }
726 for &v in order.iter().skip(1) {
727 let mut acc: u64 = 0;
728 for &p in &parents[v as usize] {
729 acc = acc.saturating_add(nrgeo[p as usize]);
730 }
731 nrgeo[v as usize] = acc;
732 }
733
734 let mut vertex_paths: Vec<Vec<Vec<VertexId>>> = vec![Vec::new(); n_us];
738 let mut edge_paths: Vec<Vec<Vec<EdgeId>>> = vec![Vec::new(); n_us];
739 if dist[source as usize].is_finite() {
740 vertex_paths[source as usize].push(vec![source]);
741 edge_paths[source as usize].push(Vec::new());
742 }
743 for &v in order.iter().skip(1) {
746 let v_us = v as usize;
747 let parent_count = parents[v_us].len();
748 for i in 0..parent_count {
749 let p = parents[v_us][i];
750 let e = parent_eids[v_us][i];
751 let par_paths_len = vertex_paths[p as usize].len();
755 for j in 0..par_paths_len {
756 let mut vp = vertex_paths[p as usize][j].clone();
757 vp.push(v);
758 vertex_paths[v_us].push(vp);
759 let mut ep = edge_paths[p as usize][j].clone();
760 ep.push(e);
761 edge_paths[v_us].push(ep);
762 }
763 }
764 }
765
766 Ok(DijkstraAllPaths {
767 vertex_paths,
768 edge_paths,
769 nrgeo,
770 })
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 #[test]
778 fn empty_graph_source_out_of_range() {
779 let g = Graph::with_vertices(0);
780 assert!(dijkstra_distances(&g, 0, &[]).is_err());
781 }
782
783 #[test]
784 fn singleton_distance_zero() {
785 let g = Graph::with_vertices(1);
786 assert_eq!(dijkstra_distances(&g, 0, &[]).unwrap(), vec![Some(0.0)]);
787 }
788
789 #[test]
790 fn unreachable_yields_none() {
791 let mut g = Graph::with_vertices(3);
792 g.add_edge(0, 1).unwrap();
793 let d = dijkstra_distances(&g, 0, &[1.0]).unwrap();
794 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
795 }
796
797 #[test]
798 fn shortcut_via_smaller_weights() {
799 let mut g = Graph::with_vertices(3);
800 g.add_edge(0, 1).unwrap();
801 g.add_edge(0, 2).unwrap();
802 g.add_edge(1, 2).unwrap();
803 let d = dijkstra_distances(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
804 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(3.0)]);
805 }
806
807 #[test]
808 fn directed_respects_edge_direction() {
809 let mut g = Graph::new(3, true).unwrap();
810 g.add_edge(0, 1).unwrap();
811 g.add_edge(2, 1).unwrap();
812 let d = dijkstra_distances(&g, 0, &[1.0, 1.0]).unwrap();
813 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
815 }
816
817 #[test]
818 fn weights_size_mismatch_errors() {
819 let mut g = Graph::with_vertices(2);
820 g.add_edge(0, 1).unwrap();
821 assert!(dijkstra_distances(&g, 0, &[]).is_err());
822 assert!(dijkstra_distances(&g, 0, &[1.0, 2.0]).is_err());
823 }
824
825 #[test]
826 fn negative_weight_errors() {
827 let mut g = Graph::with_vertices(2);
828 g.add_edge(0, 1).unwrap();
829 assert!(dijkstra_distances(&g, 0, &[-1.0]).is_err());
830 }
831
832 #[test]
833 fn nan_weight_errors() {
834 let mut g = Graph::with_vertices(2);
835 g.add_edge(0, 1).unwrap();
836 assert!(dijkstra_distances(&g, 0, &[f64::NAN]).is_err());
837 }
838
839 #[test]
840 fn infinite_weight_errors() {
841 let mut g = Graph::with_vertices(2);
842 g.add_edge(0, 1).unwrap();
843 assert!(dijkstra_distances(&g, 0, &[f64::INFINITY]).is_err());
844 }
845
846 #[test]
847 fn zero_weights_match_bfs_distance() {
848 let mut g = Graph::with_vertices(5);
850 for u in 0..4u32 {
851 g.add_edge(u, u + 1).unwrap();
852 }
853 let w = vec![1.0; 4];
854 let d = dijkstra_distances(&g, 0, &w).unwrap();
855 assert_eq!(
856 d,
857 vec![Some(0.0), Some(1.0), Some(2.0), Some(3.0), Some(4.0)]
858 );
859 }
860
861 #[test]
862 fn parallel_edges_pick_minimum_weight() {
863 let mut g = Graph::with_vertices(2);
864 g.add_edge(0, 1).unwrap();
865 g.add_edge(0, 1).unwrap();
866 let d = dijkstra_distances(&g, 0, &[5.0, 1.5]).unwrap();
867 assert_eq!(d, vec![Some(0.0), Some(1.5)]);
868 }
869
870 #[test]
871 fn star_graph_distances() {
872 let mut g = Graph::with_vertices(5);
874 g.add_edge(0, 1).unwrap();
875 g.add_edge(0, 2).unwrap();
876 g.add_edge(0, 3).unwrap();
877 g.add_edge(0, 4).unwrap();
878 let w = vec![1.0, 2.5, 0.5, 7.0];
879 let d = dijkstra_distances(&g, 0, &w).unwrap();
880 assert_eq!(
881 d,
882 vec![Some(0.0), Some(1.0), Some(2.5), Some(0.5), Some(7.0)]
883 );
884 }
885
886 fn shortcut_triangle() -> (Graph, Vec<f64>) {
889 let mut g = Graph::with_vertices(3);
890 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])
894 }
895
896 #[test]
897 fn paths_distances_match_dijkstra_distances() {
898 let (g, w) = shortcut_triangle();
899 let p = dijkstra_paths(&g, 0, &w).unwrap();
900 assert_eq!(p.distances, dijkstra_distances(&g, 0, &w).unwrap());
901 }
902
903 #[test]
904 fn paths_parents_and_inbound_edges_record_spt() {
905 let (g, w) = shortcut_triangle();
906 let p = dijkstra_paths(&g, 0, &w).unwrap();
907 assert_eq!(p.parents[0], None);
909 assert_eq!(p.inbound_edges[0], None);
910 assert_eq!(p.parents[1], Some(0));
912 assert_eq!(p.inbound_edges[1], Some(0));
913 assert_eq!(p.parents[2], Some(1));
915 assert_eq!(p.inbound_edges[2], Some(2));
916 }
917
918 #[test]
919 fn paths_unreachable_has_none_parent() {
920 let mut g = Graph::with_vertices(3);
921 g.add_edge(0, 1).unwrap();
922 let p = dijkstra_paths(&g, 0, &[1.0]).unwrap();
923 assert_eq!(p.distances, vec![Some(0.0), Some(1.0), None]);
924 assert_eq!(p.parents, vec![None, Some(0), None]);
925 assert_eq!(p.inbound_edges, vec![None, Some(0), None]);
926 }
927
928 #[test]
929 fn path_to_returns_vertex_and_edge_chain() {
930 let (g, w) = shortcut_triangle();
931 let (vs, es) = dijkstra_path_to(&g, 0, 2, &w).unwrap().unwrap();
932 assert_eq!(vs, vec![0, 1, 2]);
933 assert_eq!(es, vec![0, 2]);
934 }
935
936 #[test]
937 fn path_to_self_is_singleton() {
938 let (g, w) = shortcut_triangle();
939 let (vs, es) = dijkstra_path_to(&g, 0, 0, &w).unwrap().unwrap();
940 assert_eq!(vs, vec![0]);
941 assert!(es.is_empty());
942 }
943
944 #[test]
945 fn path_to_unreachable_is_none() {
946 let mut g = Graph::with_vertices(3);
947 g.add_edge(0, 1).unwrap();
948 assert_eq!(dijkstra_path_to(&g, 0, 2, &[1.0]).unwrap(), None);
949 }
950
951 #[test]
952 fn path_to_target_out_of_range_errors() {
953 let (g, w) = shortcut_triangle();
954 assert!(dijkstra_path_to(&g, 0, 99, &w).is_err());
955 }
956
957 #[test]
958 fn cutoff_masks_distances_above_cutoff() {
959 let mut g = Graph::with_vertices(5);
960 for i in 0..4u32 {
961 g.add_edge(i, i + 1).unwrap();
962 }
963 let w = vec![1.0; 4];
964 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(2.0)).unwrap();
965 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(2.0), None, None]);
967 }
968
969 #[test]
970 fn cutoff_none_matches_unbounded_dijkstra() {
971 let (g, w) = shortcut_triangle();
972 assert_eq!(
973 dijkstra_distances_cutoff(&g, 0, &w, None).unwrap(),
974 dijkstra_distances(&g, 0, &w).unwrap()
975 );
976 }
977
978 #[test]
979 fn cutoff_zero_keeps_only_source() {
980 let (g, w) = shortcut_triangle();
981 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(0.0)).unwrap();
982 assert_eq!(d, vec![Some(0.0), None, None]);
983 }
984
985 #[test]
986 fn cutoff_nan_errors() {
987 let (g, w) = shortcut_triangle();
988 assert!(dijkstra_distances_cutoff(&g, 0, &w, Some(f64::NAN)).is_err());
989 }
990
991 #[test]
992 fn multi_source_yields_per_source_distances() {
993 let (g, w) = shortcut_triangle();
994 let r = dijkstra_distances_multi(&g, &[0, 1], &w, None).unwrap();
995 assert_eq!(r.len(), 2);
996 assert_eq!(r[0], dijkstra_distances(&g, 0, &w).unwrap());
997 assert_eq!(r[1], dijkstra_distances(&g, 1, &w).unwrap());
998 }
999
1000 #[test]
1001 fn multi_source_empty_list_yields_empty_result() {
1002 let (g, w) = shortcut_triangle();
1003 let r = dijkstra_distances_multi(&g, &[], &w, None).unwrap();
1004 assert!(r.is_empty());
1005 }
1006
1007 #[test]
1008 fn multi_source_propagates_cutoff() {
1009 let mut g = Graph::with_vertices(4);
1010 for i in 0..3u32 {
1011 g.add_edge(i, i + 1).unwrap();
1012 }
1013 let w = vec![1.0; 3];
1014 let r = dijkstra_distances_multi(&g, &[0, 3], &w, Some(1.0)).unwrap();
1015 assert_eq!(r[0], vec![Some(0.0), Some(1.0), None, None]);
1016 assert_eq!(r[1], vec![None, None, Some(1.0), Some(0.0)]);
1017 }
1018
1019 #[test]
1020 fn multi_source_invalid_vertex_errors() {
1021 let (g, w) = shortcut_triangle();
1022 assert!(dijkstra_distances_multi(&g, &[0, 99], &w, None).is_err());
1023 }
1024
1025 fn directed_path_3() -> (Graph, Vec<f64>) {
1028 let mut g = Graph::new(3, true).unwrap();
1029 g.add_edge(0, 1).unwrap();
1030 g.add_edge(1, 2).unwrap();
1031 (g, vec![1.0, 2.0])
1032 }
1033
1034 #[test]
1035 fn legacy_apis_match_with_mode_out() {
1036 let (g, w) = shortcut_triangle();
1037 assert_eq!(
1038 dijkstra_distances(&g, 0, &w).unwrap(),
1039 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap()
1040 );
1041 let p = dijkstra_paths(&g, 0, &w).unwrap();
1042 let pm = dijkstra_paths_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap();
1043 assert_eq!(p, pm);
1044 }
1045
1046 #[test]
1047 fn directed_path_in_mode_reverses_reachability() {
1048 let (g, w) = directed_path_3();
1049 assert_eq!(
1051 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap(),
1052 vec![Some(0.0), Some(1.0), Some(3.0)]
1053 );
1054 assert_eq!(
1056 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::In).unwrap(),
1057 vec![Some(0.0), None, None]
1058 );
1059 assert_eq!(
1061 dijkstra_distances_with_mode(&g, 2, &w, DijkstraMode::All).unwrap(),
1062 vec![Some(3.0), Some(2.0), Some(0.0)]
1063 );
1064 }
1065
1066 #[test]
1067 fn undirected_modes_agree() {
1068 let (g, w) = shortcut_triangle();
1069 for m in [DijkstraMode::Out, DijkstraMode::In, DijkstraMode::All] {
1070 assert_eq!(
1071 dijkstra_distances_with_mode(&g, 0, &w, m).unwrap(),
1072 vec![Some(0.0), Some(1.0), Some(3.0)],
1073 "mode {m:?}"
1074 );
1075 }
1076 }
1077
1078 #[test]
1079 fn paths_with_mode_in_records_reverse_parents() {
1080 let (g, w) = directed_path_3();
1081 let p = dijkstra_paths_with_mode(&g, 2, &w, DijkstraMode::In).unwrap();
1083 assert_eq!(p.distances, vec![Some(3.0), Some(2.0), Some(0.0)]);
1084 assert_eq!(p.parents[2], None);
1085 assert_eq!(p.parents[1], Some(2));
1086 assert_eq!(p.parents[0], Some(1));
1087 }
1088
1089 #[test]
1090 fn path_to_with_mode_directed_in() {
1091 let (g, w) = directed_path_3();
1092 let (vs, es) = dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::In)
1093 .unwrap()
1094 .unwrap();
1095 assert_eq!(vs, vec![2, 1, 0]);
1096 assert_eq!(es, vec![1, 0]);
1097 }
1098
1099 #[test]
1100 fn path_to_with_mode_unreachable_via_out() {
1101 let (g, w) = directed_path_3();
1102 assert_eq!(
1104 dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::Out).unwrap(),
1105 None
1106 );
1107 }
1108
1109 #[test]
1110 fn cutoff_with_mode_masks_above() {
1111 let (g, w) = directed_path_3();
1112 let d =
1113 dijkstra_distances_cutoff_with_mode(&g, 0, &w, Some(1.5), DijkstraMode::Out).unwrap();
1114 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
1115 }
1116
1117 #[test]
1118 fn multi_with_mode_sources_independent() {
1119 let (g, w) = directed_path_3();
1120 let r =
1121 dijkstra_distances_multi_with_mode(&g, &[0, 2], &w, None, DijkstraMode::All).unwrap();
1122 assert_eq!(r[0], vec![Some(0.0), Some(1.0), Some(3.0)]);
1123 assert_eq!(r[1], vec![Some(3.0), Some(2.0), Some(0.0)]);
1124 }
1125
1126 #[test]
1129 fn all_paths_diamond_yields_two_geodesics() {
1130 let mut g = Graph::with_vertices(4);
1133 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();
1138 assert_eq!(r.nrgeo, vec![1, 1, 1, 2]);
1139 assert_eq!(r.vertex_paths[0], vec![vec![0]]);
1140 assert_eq!(r.edge_paths[0], vec![Vec::<EdgeId>::new()]);
1141 assert_eq!(r.vertex_paths[3].len(), 2);
1142 let mut paths: Vec<Vec<u32>> = r.vertex_paths[3].clone();
1143 paths.sort();
1144 assert_eq!(paths, vec![vec![0, 1, 3], vec![0, 2, 3]]);
1145 }
1146
1147 #[test]
1148 fn all_paths_unique_returns_single_chain() {
1149 let (g, w) = shortcut_triangle();
1150 let r = dijkstra_all_shortest_paths(&g, 0, &w, DijkstraMode::Out).unwrap();
1151 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1152 assert_eq!(r.vertex_paths[2], vec![vec![0, 1, 2]]);
1153 assert_eq!(r.edge_paths[2], vec![vec![0, 2]]);
1154 }
1155
1156 #[test]
1157 fn all_paths_unreachable_emits_empty() {
1158 let mut g = Graph::with_vertices(3);
1159 g.add_edge(0, 1).unwrap();
1160 let r = dijkstra_all_shortest_paths(&g, 0, &[1.0], DijkstraMode::Out).unwrap();
1161 assert_eq!(r.nrgeo, vec![1, 1, 0]);
1162 assert!(r.vertex_paths[2].is_empty());
1163 assert!(r.edge_paths[2].is_empty());
1164 }
1165
1166 #[test]
1167 fn all_paths_directed_in_mode() {
1168 let (g, w) = directed_path_3();
1169 let r = dijkstra_all_shortest_paths(&g, 2, &w, DijkstraMode::In).unwrap();
1170 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1171 assert_eq!(r.vertex_paths[0], vec![vec![2, 1, 0]]);
1172 assert_eq!(r.edge_paths[0], vec![vec![1, 0]]);
1173 }
1174
1175 #[test]
1176 fn all_paths_invalid_source_errors() {
1177 let (g, _) = shortcut_triangle();
1178 assert!(dijkstra_all_shortest_paths(&g, 99, &[1.0; 3], DijkstraMode::Out).is_err());
1179 }
1180
1181 #[test]
1182 fn all_paths_zero_weight_alt_is_dropped_by_guard() {
1183 let mut g = Graph::with_vertices(3);
1189 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();
1193 assert_eq!(r.nrgeo[2], 1);
1194 assert_eq!(r.vertex_paths[2], vec![vec![0, 2]]);
1195 }
1196}