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
76fn incident_for_mode(graph: &Graph, v: VertexId, mode: DijkstraMode) -> IgraphResult<Vec<EdgeId>> {
79 if !graph.is_directed() {
80 return graph.incident(v);
81 }
82 match mode {
83 DijkstraMode::Out => graph.incident(v),
84 DijkstraMode::In => graph.incident_in(v),
85 DijkstraMode::All => {
86 let mut out = graph.incident(v)?;
87 out.extend(graph.incident_in(v)?);
88 Ok(out)
89 }
90 }
91}
92
93fn validate_weights(graph: &Graph, weights: &[f64]) -> IgraphResult<()> {
96 let m = graph.ecount();
97 if weights.len() != m {
98 return Err(IgraphError::InvalidArgument(format!(
99 "weights vector size ({}) differs from edge count ({})",
100 weights.len(),
101 m
102 )));
103 }
104 for (e, &w) in weights.iter().enumerate() {
105 if w.is_nan() {
106 return Err(IgraphError::InvalidArgument(format!(
107 "weight at edge {e} is NaN"
108 )));
109 }
110 if w < 0.0 {
111 return Err(IgraphError::InvalidArgument(format!(
112 "weight at edge {e} is negative ({w}); Dijkstra requires non-negative weights"
113 )));
114 }
115 if !w.is_finite() {
116 return Err(IgraphError::InvalidArgument(format!(
117 "weight at edge {e} is not finite ({w})"
118 )));
119 }
120 }
121 Ok(())
122}
123
124fn dijkstra_inner(
130 graph: &Graph,
131 sources: &[VertexId],
132 weights: &[f64],
133 cutoff: Option<f64>,
134 record_inbound: bool,
135 mode: DijkstraMode,
136) -> IgraphResult<(Vec<f64>, Vec<Option<EdgeId>>)> {
137 let n = graph.vcount();
138 let n_us = n as usize;
139 let mut dist = vec![f64::INFINITY; n_us];
140 let mut inbound: Vec<Option<EdgeId>> = if record_inbound {
141 vec![None; n_us]
142 } else {
143 Vec::new()
144 };
145 let mut visited = vec![false; n_us];
146
147 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
148 for &s in sources {
149 if s >= n {
150 return Err(IgraphError::VertexOutOfRange { id: s, n });
151 }
152 if dist[s as usize] > 0.0 {
153 dist[s as usize] = 0.0;
154 heap.push(Frontier(0.0, s));
155 }
156 }
157
158 while let Some(Frontier(d, v)) = heap.pop() {
159 let v_us = v as usize;
160 if visited[v_us] {
161 continue;
162 }
163 if let Some(c) = cutoff {
164 if d > c {
165 continue;
166 }
167 }
168 visited[v_us] = true;
169
170 for eid in incident_for_mode(graph, v, mode)? {
171 let w = weights[eid as usize];
172 if !w.is_finite() {
173 continue;
174 }
175 let other = graph.edge_other(eid as EdgeId, v)?;
176 let nd = d + w;
177 if nd < dist[other as usize] {
178 dist[other as usize] = nd;
179 if record_inbound {
180 inbound[other as usize] = Some(eid as EdgeId);
181 }
182 heap.push(Frontier(nd, other));
183 }
184 }
185 }
186
187 Ok((dist, inbound))
188}
189
190fn dist_vec_to_options(dist: Vec<f64>) -> Vec<Option<f64>> {
191 dist.into_iter()
192 .map(|d| if d.is_infinite() { None } else { Some(d) })
193 .collect()
194}
195
196pub fn dijkstra_distances(
222 graph: &Graph,
223 source: VertexId,
224 weights: &[f64],
225) -> IgraphResult<Vec<Option<f64>>> {
226 validate_weights(graph, weights)?;
227 let (dist, _) = dijkstra_inner(graph, &[source], weights, None, false, DijkstraMode::Out)?;
228 Ok(dist_vec_to_options(dist))
229}
230
231#[derive(Debug, Clone, PartialEq)]
244pub struct DijkstraPaths {
245 pub distances: Vec<Option<f64>>,
248 pub parents: Vec<Option<VertexId>>,
252 pub inbound_edges: Vec<Option<EdgeId>>,
256}
257
258pub fn dijkstra_paths(
281 graph: &Graph,
282 source: VertexId,
283 weights: &[f64],
284) -> IgraphResult<DijkstraPaths> {
285 validate_weights(graph, weights)?;
286 let (dist, inbound) = dijkstra_inner(graph, &[source], weights, None, true, DijkstraMode::Out)?;
287 let mut parents = vec![None; graph.vcount() as usize];
288 for v in 0..graph.vcount() {
289 if let Some(eid) = inbound[v as usize] {
290 let other = graph.edge_other(eid, v)?;
291 parents[v as usize] = Some(other);
292 }
293 }
294 Ok(DijkstraPaths {
295 distances: dist_vec_to_options(dist),
296 parents,
297 inbound_edges: inbound,
298 })
299}
300
301pub fn dijkstra_path_to(
324 graph: &Graph,
325 source: VertexId,
326 target: VertexId,
327 weights: &[f64],
328) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
329 let n = graph.vcount();
330 if target >= n {
331 return Err(IgraphError::VertexOutOfRange { id: target, n });
332 }
333 let p = dijkstra_paths(graph, source, weights)?;
334 if p.distances[target as usize].is_none() {
335 return Ok(None);
336 }
337 let mut vs = Vec::new();
339 let mut es = Vec::new();
340 let mut cur = target;
341 while let Some(eid) = p.inbound_edges[cur as usize] {
342 es.push(eid);
343 let parent = p.parents[cur as usize].expect("inbound edge implies parent exists");
344 vs.push(cur);
345 cur = parent;
346 }
347 vs.push(cur); vs.reverse();
349 es.reverse();
350 Ok(Some((vs, es)))
351}
352
353pub fn dijkstra_distances_cutoff(
364 graph: &Graph,
365 source: VertexId,
366 weights: &[f64],
367 cutoff: Option<f64>,
368) -> IgraphResult<Vec<Option<f64>>> {
369 validate_weights(graph, weights)?;
370 if let Some(c) = cutoff {
371 if c.is_nan() {
372 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
373 }
374 }
375 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, DijkstraMode::Out)?;
376 let mut out = dist_vec_to_options(dist);
377 if let Some(c) = cutoff {
378 for d in &mut out {
384 if let Some(v) = *d {
385 if v > c {
386 *d = None;
387 }
388 }
389 }
390 }
391 Ok(out)
392}
393
394pub fn dijkstra_distances_multi(
403 graph: &Graph,
404 sources: &[VertexId],
405 weights: &[f64],
406 cutoff: Option<f64>,
407) -> IgraphResult<Vec<Vec<Option<f64>>>> {
408 validate_weights(graph, weights)?;
409 if let Some(c) = cutoff {
410 if c.is_nan() {
411 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
412 }
413 }
414 let mut out = Vec::with_capacity(sources.len());
415 for &s in sources {
416 out.push(dijkstra_distances_cutoff(graph, s, weights, cutoff)?);
417 }
418 Ok(out)
419}
420
421pub fn dijkstra_distances_with_mode(
451 graph: &Graph,
452 source: VertexId,
453 weights: &[f64],
454 mode: DijkstraMode,
455) -> IgraphResult<Vec<Option<f64>>> {
456 validate_weights(graph, weights)?;
457 let (dist, _) = dijkstra_inner(graph, &[source], weights, None, false, mode)?;
458 Ok(dist_vec_to_options(dist))
459}
460
461pub fn dijkstra_paths_with_mode(
464 graph: &Graph,
465 source: VertexId,
466 weights: &[f64],
467 mode: DijkstraMode,
468) -> IgraphResult<DijkstraPaths> {
469 validate_weights(graph, weights)?;
470 let (dist, inbound) = dijkstra_inner(graph, &[source], weights, None, true, mode)?;
471 let mut parents = vec![None; graph.vcount() as usize];
472 for v in 0..graph.vcount() {
473 if let Some(eid) = inbound[v as usize] {
474 let other = graph.edge_other(eid, v)?;
475 parents[v as usize] = Some(other);
476 }
477 }
478 Ok(DijkstraPaths {
479 distances: dist_vec_to_options(dist),
480 parents,
481 inbound_edges: inbound,
482 })
483}
484
485pub fn dijkstra_path_to_with_mode(
488 graph: &Graph,
489 source: VertexId,
490 target: VertexId,
491 weights: &[f64],
492 mode: DijkstraMode,
493) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
494 let n = graph.vcount();
495 if target >= n {
496 return Err(IgraphError::VertexOutOfRange { id: target, n });
497 }
498 let p = dijkstra_paths_with_mode(graph, source, weights, mode)?;
499 if p.distances[target as usize].is_none() {
500 return Ok(None);
501 }
502 let mut vs = Vec::new();
503 let mut es = Vec::new();
504 let mut cur = target;
505 while let Some(eid) = p.inbound_edges[cur as usize] {
506 es.push(eid);
507 let parent = p.parents[cur as usize].expect("inbound edge implies parent exists");
508 vs.push(cur);
509 cur = parent;
510 }
511 vs.push(cur);
512 vs.reverse();
513 es.reverse();
514 Ok(Some((vs, es)))
515}
516
517pub fn dijkstra_distances_cutoff_with_mode(
520 graph: &Graph,
521 source: VertexId,
522 weights: &[f64],
523 cutoff: Option<f64>,
524 mode: DijkstraMode,
525) -> IgraphResult<Vec<Option<f64>>> {
526 validate_weights(graph, weights)?;
527 if let Some(c) = cutoff {
528 if c.is_nan() {
529 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
530 }
531 }
532 let (dist, _) = dijkstra_inner(graph, &[source], weights, cutoff, false, mode)?;
533 let mut out = dist_vec_to_options(dist);
534 if let Some(c) = cutoff {
535 for d in &mut out {
536 if let Some(v) = *d {
537 if v > c {
538 *d = None;
539 }
540 }
541 }
542 }
543 Ok(out)
544}
545
546pub fn dijkstra_distances_multi_with_mode(
548 graph: &Graph,
549 sources: &[VertexId],
550 weights: &[f64],
551 cutoff: Option<f64>,
552 mode: DijkstraMode,
553) -> IgraphResult<Vec<Vec<Option<f64>>>> {
554 validate_weights(graph, weights)?;
555 if let Some(c) = cutoff {
556 if c.is_nan() {
557 return Err(IgraphError::InvalidArgument("cutoff is NaN".into()));
558 }
559 }
560 let mut out = Vec::with_capacity(sources.len());
561 for &s in sources {
562 out.push(dijkstra_distances_cutoff_with_mode(
563 graph, s, weights, cutoff, mode,
564 )?);
565 }
566 Ok(out)
567}
568
569#[derive(Debug, Clone, PartialEq)]
576pub struct DijkstraAllPaths {
577 pub vertex_paths: Vec<Vec<Vec<VertexId>>>,
581 pub edge_paths: Vec<Vec<Vec<EdgeId>>>,
585 pub nrgeo: Vec<u64>,
588}
589
590fn cmp_eps(a: f64, b: f64) -> i32 {
595 const EPS: f64 = 1e-10; if !a.is_finite() || !b.is_finite() {
597 return match a.total_cmp(&b) {
601 Ordering::Equal => 0,
602 Ordering::Greater => 1,
603 Ordering::Less => -1,
604 };
605 }
606 let scale = a.abs().max(b.abs()).max(1.0);
607 let diff = a - b;
608 if diff.abs() <= EPS * scale {
609 0
610 } else if diff > 0.0 {
611 1
612 } else {
613 -1
614 }
615}
616
617pub fn dijkstra_all_shortest_paths(
644 graph: &Graph,
645 source: VertexId,
646 weights: &[f64],
647 mode: DijkstraMode,
648) -> IgraphResult<DijkstraAllPaths> {
649 let n = graph.vcount();
650 if source >= n {
651 return Err(IgraphError::VertexOutOfRange { id: source, n });
652 }
653 validate_weights(graph, weights)?;
654
655 let n_us = n as usize;
656 let mut dist = vec![f64::INFINITY; n_us];
657 let mut parents: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
661 let mut parent_eids: Vec<Vec<EdgeId>> = vec![Vec::new(); n_us];
662 let mut order: Vec<VertexId> = Vec::new();
666 let mut visited = vec![false; n_us];
667
668 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
669 dist[source as usize] = 0.0;
670 heap.push(Frontier(0.0, source));
671
672 while let Some(Frontier(d, v)) = heap.pop() {
673 let v_us = v as usize;
674 if visited[v_us] {
675 continue;
676 }
677 visited[v_us] = true;
678 order.push(v);
679
680 for eid in incident_for_mode(graph, v, mode)? {
681 let w = weights[eid as usize];
682 if !w.is_finite() {
683 continue;
684 }
685 let other = graph.edge_other(eid as EdgeId, v)?;
686 let altdist = d + w;
687 let curdist = dist[other as usize];
688 let cmp = cmp_eps(curdist, altdist);
689 if curdist.is_infinite() {
690 dist[other as usize] = altdist;
692 parents[other as usize].clear();
693 parents[other as usize].push(v);
694 parent_eids[other as usize].clear();
695 parent_eids[other as usize].push(eid);
696 heap.push(Frontier(altdist, other));
697 } else if cmp == 0 && w > 0.0 {
698 parents[other as usize].push(v);
702 parent_eids[other as usize].push(eid);
703 } else if cmp > 0 {
704 dist[other as usize] = altdist;
706 parents[other as usize].clear();
707 parents[other as usize].push(v);
708 parent_eids[other as usize].clear();
709 parent_eids[other as usize].push(eid);
710 heap.push(Frontier(altdist, other));
711 }
712 }
713 }
714
715 let mut nrgeo: Vec<u64> = vec![0; n_us];
719 if dist[source as usize].is_finite() {
720 nrgeo[source as usize] = 1;
721 }
722 for &v in order.iter().skip(1) {
723 let mut acc: u64 = 0;
724 for &p in &parents[v as usize] {
725 acc = acc.saturating_add(nrgeo[p as usize]);
726 }
727 nrgeo[v as usize] = acc;
728 }
729
730 let mut vertex_paths: Vec<Vec<Vec<VertexId>>> = vec![Vec::new(); n_us];
734 let mut edge_paths: Vec<Vec<Vec<EdgeId>>> = vec![Vec::new(); n_us];
735 if dist[source as usize].is_finite() {
736 vertex_paths[source as usize].push(vec![source]);
737 edge_paths[source as usize].push(Vec::new());
738 }
739 for &v in order.iter().skip(1) {
742 let v_us = v as usize;
743 let parent_count = parents[v_us].len();
744 for i in 0..parent_count {
745 let p = parents[v_us][i];
746 let e = parent_eids[v_us][i];
747 let par_paths_len = vertex_paths[p as usize].len();
751 for j in 0..par_paths_len {
752 let mut vp = vertex_paths[p as usize][j].clone();
753 vp.push(v);
754 vertex_paths[v_us].push(vp);
755 let mut ep = edge_paths[p as usize][j].clone();
756 ep.push(e);
757 edge_paths[v_us].push(ep);
758 }
759 }
760 }
761
762 Ok(DijkstraAllPaths {
763 vertex_paths,
764 edge_paths,
765 nrgeo,
766 })
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772
773 #[test]
774 fn empty_graph_source_out_of_range() {
775 let g = Graph::with_vertices(0);
776 assert!(dijkstra_distances(&g, 0, &[]).is_err());
777 }
778
779 #[test]
780 fn singleton_distance_zero() {
781 let g = Graph::with_vertices(1);
782 assert_eq!(dijkstra_distances(&g, 0, &[]).unwrap(), vec![Some(0.0)]);
783 }
784
785 #[test]
786 fn unreachable_yields_none() {
787 let mut g = Graph::with_vertices(3);
788 g.add_edge(0, 1).unwrap();
789 let d = dijkstra_distances(&g, 0, &[1.0]).unwrap();
790 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
791 }
792
793 #[test]
794 fn shortcut_via_smaller_weights() {
795 let mut g = Graph::with_vertices(3);
796 g.add_edge(0, 1).unwrap();
797 g.add_edge(0, 2).unwrap();
798 g.add_edge(1, 2).unwrap();
799 let d = dijkstra_distances(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
800 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(3.0)]);
801 }
802
803 #[test]
804 fn directed_respects_edge_direction() {
805 let mut g = Graph::new(3, true).unwrap();
806 g.add_edge(0, 1).unwrap();
807 g.add_edge(2, 1).unwrap();
808 let d = dijkstra_distances(&g, 0, &[1.0, 1.0]).unwrap();
809 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
811 }
812
813 #[test]
814 fn weights_size_mismatch_errors() {
815 let mut g = Graph::with_vertices(2);
816 g.add_edge(0, 1).unwrap();
817 assert!(dijkstra_distances(&g, 0, &[]).is_err());
818 assert!(dijkstra_distances(&g, 0, &[1.0, 2.0]).is_err());
819 }
820
821 #[test]
822 fn negative_weight_errors() {
823 let mut g = Graph::with_vertices(2);
824 g.add_edge(0, 1).unwrap();
825 assert!(dijkstra_distances(&g, 0, &[-1.0]).is_err());
826 }
827
828 #[test]
829 fn nan_weight_errors() {
830 let mut g = Graph::with_vertices(2);
831 g.add_edge(0, 1).unwrap();
832 assert!(dijkstra_distances(&g, 0, &[f64::NAN]).is_err());
833 }
834
835 #[test]
836 fn infinite_weight_errors() {
837 let mut g = Graph::with_vertices(2);
838 g.add_edge(0, 1).unwrap();
839 assert!(dijkstra_distances(&g, 0, &[f64::INFINITY]).is_err());
840 }
841
842 #[test]
843 fn zero_weights_match_bfs_distance() {
844 let mut g = Graph::with_vertices(5);
846 for u in 0..4u32 {
847 g.add_edge(u, u + 1).unwrap();
848 }
849 let w = vec![1.0; 4];
850 let d = dijkstra_distances(&g, 0, &w).unwrap();
851 assert_eq!(
852 d,
853 vec![Some(0.0), Some(1.0), Some(2.0), Some(3.0), Some(4.0)]
854 );
855 }
856
857 #[test]
858 fn parallel_edges_pick_minimum_weight() {
859 let mut g = Graph::with_vertices(2);
860 g.add_edge(0, 1).unwrap();
861 g.add_edge(0, 1).unwrap();
862 let d = dijkstra_distances(&g, 0, &[5.0, 1.5]).unwrap();
863 assert_eq!(d, vec![Some(0.0), Some(1.5)]);
864 }
865
866 #[test]
867 fn star_graph_distances() {
868 let mut g = Graph::with_vertices(5);
870 g.add_edge(0, 1).unwrap();
871 g.add_edge(0, 2).unwrap();
872 g.add_edge(0, 3).unwrap();
873 g.add_edge(0, 4).unwrap();
874 let w = vec![1.0, 2.5, 0.5, 7.0];
875 let d = dijkstra_distances(&g, 0, &w).unwrap();
876 assert_eq!(
877 d,
878 vec![Some(0.0), Some(1.0), Some(2.5), Some(0.5), Some(7.0)]
879 );
880 }
881
882 fn shortcut_triangle() -> (Graph, Vec<f64>) {
885 let mut g = Graph::with_vertices(3);
886 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])
890 }
891
892 #[test]
893 fn paths_distances_match_dijkstra_distances() {
894 let (g, w) = shortcut_triangle();
895 let p = dijkstra_paths(&g, 0, &w).unwrap();
896 assert_eq!(p.distances, dijkstra_distances(&g, 0, &w).unwrap());
897 }
898
899 #[test]
900 fn paths_parents_and_inbound_edges_record_spt() {
901 let (g, w) = shortcut_triangle();
902 let p = dijkstra_paths(&g, 0, &w).unwrap();
903 assert_eq!(p.parents[0], None);
905 assert_eq!(p.inbound_edges[0], None);
906 assert_eq!(p.parents[1], Some(0));
908 assert_eq!(p.inbound_edges[1], Some(0));
909 assert_eq!(p.parents[2], Some(1));
911 assert_eq!(p.inbound_edges[2], Some(2));
912 }
913
914 #[test]
915 fn paths_unreachable_has_none_parent() {
916 let mut g = Graph::with_vertices(3);
917 g.add_edge(0, 1).unwrap();
918 let p = dijkstra_paths(&g, 0, &[1.0]).unwrap();
919 assert_eq!(p.distances, vec![Some(0.0), Some(1.0), None]);
920 assert_eq!(p.parents, vec![None, Some(0), None]);
921 assert_eq!(p.inbound_edges, vec![None, Some(0), None]);
922 }
923
924 #[test]
925 fn path_to_returns_vertex_and_edge_chain() {
926 let (g, w) = shortcut_triangle();
927 let (vs, es) = dijkstra_path_to(&g, 0, 2, &w).unwrap().unwrap();
928 assert_eq!(vs, vec![0, 1, 2]);
929 assert_eq!(es, vec![0, 2]);
930 }
931
932 #[test]
933 fn path_to_self_is_singleton() {
934 let (g, w) = shortcut_triangle();
935 let (vs, es) = dijkstra_path_to(&g, 0, 0, &w).unwrap().unwrap();
936 assert_eq!(vs, vec![0]);
937 assert!(es.is_empty());
938 }
939
940 #[test]
941 fn path_to_unreachable_is_none() {
942 let mut g = Graph::with_vertices(3);
943 g.add_edge(0, 1).unwrap();
944 assert_eq!(dijkstra_path_to(&g, 0, 2, &[1.0]).unwrap(), None);
945 }
946
947 #[test]
948 fn path_to_target_out_of_range_errors() {
949 let (g, w) = shortcut_triangle();
950 assert!(dijkstra_path_to(&g, 0, 99, &w).is_err());
951 }
952
953 #[test]
954 fn cutoff_masks_distances_above_cutoff() {
955 let mut g = Graph::with_vertices(5);
956 for i in 0..4u32 {
957 g.add_edge(i, i + 1).unwrap();
958 }
959 let w = vec![1.0; 4];
960 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(2.0)).unwrap();
961 assert_eq!(d, vec![Some(0.0), Some(1.0), Some(2.0), None, None]);
963 }
964
965 #[test]
966 fn cutoff_none_matches_unbounded_dijkstra() {
967 let (g, w) = shortcut_triangle();
968 assert_eq!(
969 dijkstra_distances_cutoff(&g, 0, &w, None).unwrap(),
970 dijkstra_distances(&g, 0, &w).unwrap()
971 );
972 }
973
974 #[test]
975 fn cutoff_zero_keeps_only_source() {
976 let (g, w) = shortcut_triangle();
977 let d = dijkstra_distances_cutoff(&g, 0, &w, Some(0.0)).unwrap();
978 assert_eq!(d, vec![Some(0.0), None, None]);
979 }
980
981 #[test]
982 fn cutoff_nan_errors() {
983 let (g, w) = shortcut_triangle();
984 assert!(dijkstra_distances_cutoff(&g, 0, &w, Some(f64::NAN)).is_err());
985 }
986
987 #[test]
988 fn multi_source_yields_per_source_distances() {
989 let (g, w) = shortcut_triangle();
990 let r = dijkstra_distances_multi(&g, &[0, 1], &w, None).unwrap();
991 assert_eq!(r.len(), 2);
992 assert_eq!(r[0], dijkstra_distances(&g, 0, &w).unwrap());
993 assert_eq!(r[1], dijkstra_distances(&g, 1, &w).unwrap());
994 }
995
996 #[test]
997 fn multi_source_empty_list_yields_empty_result() {
998 let (g, w) = shortcut_triangle();
999 let r = dijkstra_distances_multi(&g, &[], &w, None).unwrap();
1000 assert!(r.is_empty());
1001 }
1002
1003 #[test]
1004 fn multi_source_propagates_cutoff() {
1005 let mut g = Graph::with_vertices(4);
1006 for i in 0..3u32 {
1007 g.add_edge(i, i + 1).unwrap();
1008 }
1009 let w = vec![1.0; 3];
1010 let r = dijkstra_distances_multi(&g, &[0, 3], &w, Some(1.0)).unwrap();
1011 assert_eq!(r[0], vec![Some(0.0), Some(1.0), None, None]);
1012 assert_eq!(r[1], vec![None, None, Some(1.0), Some(0.0)]);
1013 }
1014
1015 #[test]
1016 fn multi_source_invalid_vertex_errors() {
1017 let (g, w) = shortcut_triangle();
1018 assert!(dijkstra_distances_multi(&g, &[0, 99], &w, None).is_err());
1019 }
1020
1021 fn directed_path_3() -> (Graph, Vec<f64>) {
1024 let mut g = Graph::new(3, true).unwrap();
1025 g.add_edge(0, 1).unwrap();
1026 g.add_edge(1, 2).unwrap();
1027 (g, vec![1.0, 2.0])
1028 }
1029
1030 #[test]
1031 fn legacy_apis_match_with_mode_out() {
1032 let (g, w) = shortcut_triangle();
1033 assert_eq!(
1034 dijkstra_distances(&g, 0, &w).unwrap(),
1035 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap()
1036 );
1037 let p = dijkstra_paths(&g, 0, &w).unwrap();
1038 let pm = dijkstra_paths_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap();
1039 assert_eq!(p, pm);
1040 }
1041
1042 #[test]
1043 fn directed_path_in_mode_reverses_reachability() {
1044 let (g, w) = directed_path_3();
1045 assert_eq!(
1047 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::Out).unwrap(),
1048 vec![Some(0.0), Some(1.0), Some(3.0)]
1049 );
1050 assert_eq!(
1052 dijkstra_distances_with_mode(&g, 0, &w, DijkstraMode::In).unwrap(),
1053 vec![Some(0.0), None, None]
1054 );
1055 assert_eq!(
1057 dijkstra_distances_with_mode(&g, 2, &w, DijkstraMode::All).unwrap(),
1058 vec![Some(3.0), Some(2.0), Some(0.0)]
1059 );
1060 }
1061
1062 #[test]
1063 fn undirected_modes_agree() {
1064 let (g, w) = shortcut_triangle();
1065 for m in [DijkstraMode::Out, DijkstraMode::In, DijkstraMode::All] {
1066 assert_eq!(
1067 dijkstra_distances_with_mode(&g, 0, &w, m).unwrap(),
1068 vec![Some(0.0), Some(1.0), Some(3.0)],
1069 "mode {m:?}"
1070 );
1071 }
1072 }
1073
1074 #[test]
1075 fn paths_with_mode_in_records_reverse_parents() {
1076 let (g, w) = directed_path_3();
1077 let p = dijkstra_paths_with_mode(&g, 2, &w, DijkstraMode::In).unwrap();
1079 assert_eq!(p.distances, vec![Some(3.0), Some(2.0), Some(0.0)]);
1080 assert_eq!(p.parents[2], None);
1081 assert_eq!(p.parents[1], Some(2));
1082 assert_eq!(p.parents[0], Some(1));
1083 }
1084
1085 #[test]
1086 fn path_to_with_mode_directed_in() {
1087 let (g, w) = directed_path_3();
1088 let (vs, es) = dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::In)
1089 .unwrap()
1090 .unwrap();
1091 assert_eq!(vs, vec![2, 1, 0]);
1092 assert_eq!(es, vec![1, 0]);
1093 }
1094
1095 #[test]
1096 fn path_to_with_mode_unreachable_via_out() {
1097 let (g, w) = directed_path_3();
1098 assert_eq!(
1100 dijkstra_path_to_with_mode(&g, 2, 0, &w, DijkstraMode::Out).unwrap(),
1101 None
1102 );
1103 }
1104
1105 #[test]
1106 fn cutoff_with_mode_masks_above() {
1107 let (g, w) = directed_path_3();
1108 let d =
1109 dijkstra_distances_cutoff_with_mode(&g, 0, &w, Some(1.5), DijkstraMode::Out).unwrap();
1110 assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
1111 }
1112
1113 #[test]
1114 fn multi_with_mode_sources_independent() {
1115 let (g, w) = directed_path_3();
1116 let r =
1117 dijkstra_distances_multi_with_mode(&g, &[0, 2], &w, None, DijkstraMode::All).unwrap();
1118 assert_eq!(r[0], vec![Some(0.0), Some(1.0), Some(3.0)]);
1119 assert_eq!(r[1], vec![Some(3.0), Some(2.0), Some(0.0)]);
1120 }
1121
1122 #[test]
1125 fn all_paths_diamond_yields_two_geodesics() {
1126 let mut g = Graph::with_vertices(4);
1129 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();
1134 assert_eq!(r.nrgeo, vec![1, 1, 1, 2]);
1135 assert_eq!(r.vertex_paths[0], vec![vec![0]]);
1136 assert_eq!(r.edge_paths[0], vec![Vec::<EdgeId>::new()]);
1137 assert_eq!(r.vertex_paths[3].len(), 2);
1138 let mut paths: Vec<Vec<u32>> = r.vertex_paths[3].clone();
1139 paths.sort();
1140 assert_eq!(paths, vec![vec![0, 1, 3], vec![0, 2, 3]]);
1141 }
1142
1143 #[test]
1144 fn all_paths_unique_returns_single_chain() {
1145 let (g, w) = shortcut_triangle();
1146 let r = dijkstra_all_shortest_paths(&g, 0, &w, DijkstraMode::Out).unwrap();
1147 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1148 assert_eq!(r.vertex_paths[2], vec![vec![0, 1, 2]]);
1149 assert_eq!(r.edge_paths[2], vec![vec![0, 2]]);
1150 }
1151
1152 #[test]
1153 fn all_paths_unreachable_emits_empty() {
1154 let mut g = Graph::with_vertices(3);
1155 g.add_edge(0, 1).unwrap();
1156 let r = dijkstra_all_shortest_paths(&g, 0, &[1.0], DijkstraMode::Out).unwrap();
1157 assert_eq!(r.nrgeo, vec![1, 1, 0]);
1158 assert!(r.vertex_paths[2].is_empty());
1159 assert!(r.edge_paths[2].is_empty());
1160 }
1161
1162 #[test]
1163 fn all_paths_directed_in_mode() {
1164 let (g, w) = directed_path_3();
1165 let r = dijkstra_all_shortest_paths(&g, 2, &w, DijkstraMode::In).unwrap();
1166 assert_eq!(r.nrgeo, vec![1, 1, 1]);
1167 assert_eq!(r.vertex_paths[0], vec![vec![2, 1, 0]]);
1168 assert_eq!(r.edge_paths[0], vec![vec![1, 0]]);
1169 }
1170
1171 #[test]
1172 fn all_paths_invalid_source_errors() {
1173 let (g, _) = shortcut_triangle();
1174 assert!(dijkstra_all_shortest_paths(&g, 99, &[1.0; 3], DijkstraMode::Out).is_err());
1175 }
1176
1177 #[test]
1178 fn all_paths_zero_weight_alt_is_dropped_by_guard() {
1179 let mut g = Graph::with_vertices(3);
1185 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();
1189 assert_eq!(r.nrgeo[2], 1);
1190 assert_eq!(r.vertex_paths[2], vec![vec![0, 2]]);
1191 }
1192}