1use petgraph::algo::dijkstra;
10use petgraph::visit::EdgeRef;
11use std::cmp::Ordering;
12use std::collections::{BinaryHeap, HashMap};
13use std::hash::Hash;
14
15use crate::base::{DiGraph, EdgeWeight, Graph, Node};
16use crate::error::{GraphError, Result};
17
18#[derive(Debug, Clone)]
20pub struct Path<N: Node + std::fmt::Debug, E: EdgeWeight> {
21 pub nodes: Vec<N>,
23 pub total_weight: E,
25}
26
27#[derive(Debug, Clone)]
29pub struct AStarResult<N: Node + std::fmt::Debug, E: EdgeWeight> {
30 pub path: Vec<N>,
32 pub cost: E,
34}
35
36#[derive(Clone)]
38struct AStarState<N: Node + std::fmt::Debug, E: EdgeWeight> {
39 node: N,
40 cost: E,
41 heuristic: E,
42 path: Vec<N>,
43}
44
45impl<N: Node + std::fmt::Debug, E: EdgeWeight> PartialEq for AStarState<N, E> {
46 fn eq(&self, other: &Self) -> bool {
47 self.node == other.node
48 }
49}
50
51impl<N: Node + std::fmt::Debug, E: EdgeWeight> Eq for AStarState<N, E> {}
52
53impl<N: Node + std::fmt::Debug, E: EdgeWeight + std::ops::Add<Output = E> + Copy + PartialOrd> Ord
54 for AStarState<N, E>
55{
56 fn cmp(&self, other: &Self) -> Ordering {
57 let self_total = self.cost + self.heuristic;
59 let other_total = other.cost + other.heuristic;
60 other_total
61 .partial_cmp(&self_total)
62 .unwrap_or(Ordering::Equal)
63 .then_with(|| {
64 other
65 .cost
66 .partial_cmp(&self.cost)
67 .unwrap_or(Ordering::Equal)
68 })
69 }
70}
71
72impl<N: Node + std::fmt::Debug, E: EdgeWeight + std::ops::Add<Output = E> + Copy + PartialOrd>
73 PartialOrd for AStarState<N, E>
74{
75 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
76 Some(self.cmp(other))
77 }
78}
79
80#[deprecated(
104 since = "0.1.0-beta.2",
105 note = "Use `dijkstra_path` for future compatibility. This function will return PathResult in v1.0"
106)]
107#[allow(dead_code)]
108pub fn shortest_path<N, E, Ix>(
109 graph: &Graph<N, E, Ix>,
110 source: &N,
111 target: &N,
112) -> Result<Option<Path<N, E>>>
113where
114 N: Node + std::fmt::Debug,
115 E: EdgeWeight
116 + scirs2_core::numeric::Zero
117 + scirs2_core::numeric::One
118 + std::ops::Add<Output = E>
119 + PartialOrd
120 + std::marker::Copy
121 + std::fmt::Debug
122 + std::default::Default,
123 Ix: petgraph::graph::IndexType,
124{
125 if !graph.has_node(source) {
127 return Err(GraphError::InvalidGraph(format!(
128 "Source node {source:?} not found"
129 )));
130 }
131 if !graph.has_node(target) {
132 return Err(GraphError::InvalidGraph(format!(
133 "Target node {target:?} not found"
134 )));
135 }
136
137 let source_idx = graph
138 .inner()
139 .node_indices()
140 .find(|&idx| graph.inner()[idx] == *source)
141 .expect("Test: operation failed");
142 let target_idx = graph
143 .inner()
144 .node_indices()
145 .find(|&idx| graph.inner()[idx] == *target)
146 .expect("Test: operation failed");
147
148 let results = dijkstra(graph.inner(), source_idx, Some(target_idx), |e| *e.weight());
150
151 if !results.contains_key(&target_idx) {
153 return Ok(None);
154 }
155
156 let total_weight = results[&target_idx];
157
158 let mut path = Vec::new();
160 let mut current = target_idx;
161
162 path.push(graph.inner()[current].clone());
163
164 while current != source_idx {
166 let min_prev = graph
167 .inner()
168 .edges_directed(current, petgraph::Direction::Incoming)
169 .filter_map(|e| {
170 let from = e.source();
171 let edge_weight = *e.weight();
172
173 if let Some(from_dist) = results.get(&from) {
175 if *from_dist + edge_weight == results[¤t] {
177 return Some((from, *from_dist));
178 }
179 }
180 None
181 })
182 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
183
184 if let Some((prev, _)) = min_prev {
185 current = prev;
186 path.push(graph.inner()[current].clone());
187 } else {
188 return Err(GraphError::AlgorithmError(
190 "Failed to reconstruct path".to_string(),
191 ));
192 }
193 }
194
195 path.reverse();
197
198 Ok(Some(Path {
199 nodes: path,
200 total_weight,
201 }))
202}
203
204#[allow(dead_code)]
239pub fn dijkstra_path<N, E, Ix>(
240 graph: &Graph<N, E, Ix>,
241 source: &N,
242 target: &N,
243) -> Result<Option<Path<N, E>>>
244where
245 N: Node + std::fmt::Debug,
246 E: EdgeWeight
247 + scirs2_core::numeric::Zero
248 + scirs2_core::numeric::One
249 + std::ops::Add<Output = E>
250 + PartialOrd
251 + std::marker::Copy
252 + std::fmt::Debug
253 + std::default::Default,
254 Ix: petgraph::graph::IndexType,
255{
256 #[allow(deprecated)]
257 shortest_path(graph, source, target)
258}
259
260#[deprecated(
267 since = "0.1.0-beta.2",
268 note = "Use `dijkstra_path_digraph` for future compatibility. This function will return PathResult in v1.0"
269)]
270#[allow(dead_code)]
271pub fn shortest_path_digraph<N, E, Ix>(
272 graph: &DiGraph<N, E, Ix>,
273 source: &N,
274 target: &N,
275) -> Result<Option<Path<N, E>>>
276where
277 N: Node + std::fmt::Debug,
278 E: EdgeWeight
279 + scirs2_core::numeric::Zero
280 + scirs2_core::numeric::One
281 + std::ops::Add<Output = E>
282 + PartialOrd
283 + std::marker::Copy
284 + std::fmt::Debug
285 + std::default::Default,
286 Ix: petgraph::graph::IndexType,
287{
288 if !graph.has_node(source) {
291 return Err(GraphError::InvalidGraph(format!(
292 "Source node {source:?} not found"
293 )));
294 }
295 if !graph.has_node(target) {
296 return Err(GraphError::InvalidGraph(format!(
297 "Target node {target:?} not found"
298 )));
299 }
300
301 let source_idx = graph
302 .inner()
303 .node_indices()
304 .find(|&idx| graph.inner()[idx] == *source)
305 .expect("Test: operation failed");
306 let target_idx = graph
307 .inner()
308 .node_indices()
309 .find(|&idx| graph.inner()[idx] == *target)
310 .expect("Test: operation failed");
311
312 let results = dijkstra(graph.inner(), source_idx, Some(target_idx), |e| *e.weight());
314
315 if !results.contains_key(&target_idx) {
317 return Ok(None);
318 }
319
320 let total_weight = results[&target_idx];
321
322 let mut path = Vec::new();
324 let mut current = target_idx;
325
326 path.push(graph.inner()[current].clone());
327
328 while current != source_idx {
330 let min_prev = graph
331 .inner()
332 .edges_directed(current, petgraph::Direction::Incoming)
333 .filter_map(|e| {
334 let from = e.source();
335 let edge_weight = *e.weight();
336
337 if let Some(from_dist) = results.get(&from) {
339 if *from_dist + edge_weight == results[¤t] {
341 return Some((from, *from_dist));
342 }
343 }
344 None
345 })
346 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
347
348 if let Some((prev, _)) = min_prev {
349 current = prev;
350 path.push(graph.inner()[current].clone());
351 } else {
352 return Err(GraphError::AlgorithmError(
354 "Failed to reconstruct path".to_string(),
355 ));
356 }
357 }
358
359 path.reverse();
361
362 Ok(Some(Path {
363 nodes: path,
364 total_weight,
365 }))
366}
367
368#[allow(dead_code)]
389pub fn dijkstra_path_digraph<N, E, Ix>(
390 graph: &DiGraph<N, E, Ix>,
391 source: &N,
392 target: &N,
393) -> Result<Option<Path<N, E>>>
394where
395 N: Node + std::fmt::Debug,
396 E: EdgeWeight
397 + scirs2_core::numeric::Zero
398 + scirs2_core::numeric::One
399 + std::ops::Add<Output = E>
400 + PartialOrd
401 + std::marker::Copy
402 + std::fmt::Debug
403 + std::default::Default,
404 Ix: petgraph::graph::IndexType,
405{
406 #[allow(deprecated)]
407 shortest_path_digraph(graph, source, target)
408}
409
410#[allow(dead_code)]
433pub fn floyd_warshall<N, E, Ix>(
434 graph: &Graph<N, E, Ix>,
435) -> Result<scirs2_core::ndarray::Array2<f64>>
436where
437 N: Node + std::fmt::Debug,
438 E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
439 Ix: petgraph::graph::IndexType,
440{
441 let n = graph.node_count();
442
443 if n == 0 {
444 return Ok(scirs2_core::ndarray::Array2::zeros((0, 0)));
445 }
446
447 let mut dist = scirs2_core::ndarray::Array2::from_elem((n, n), f64::INFINITY);
449
450 for i in 0..n {
452 dist[[i, i]] = 0.0;
453 }
454
455 for edge in graph.inner().edge_references() {
457 let i = edge.source().index();
458 let j = edge.target().index();
459 let weight: f64 = (*edge.weight()).into();
460
461 dist[[i, j]] = weight;
462 dist[[j, i]] = weight;
464 }
465
466 for k in 0..n {
468 for i in 0..n {
469 for j in 0..n {
470 let alt = dist[[i, k]] + dist[[k, j]];
471 if alt < dist[[i, j]] {
472 dist[[i, j]] = alt;
473 }
474 }
475 }
476 }
477
478 Ok(dist)
479}
480
481#[allow(dead_code)]
483pub fn floyd_warshall_digraph<N, E, Ix>(
484 graph: &DiGraph<N, E, Ix>,
485) -> Result<scirs2_core::ndarray::Array2<f64>>
486where
487 N: Node + std::fmt::Debug,
488 E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
489 Ix: petgraph::graph::IndexType,
490{
491 let n = graph.node_count();
492
493 if n == 0 {
494 return Ok(scirs2_core::ndarray::Array2::zeros((0, 0)));
495 }
496
497 let mut dist = scirs2_core::ndarray::Array2::from_elem((n, n), f64::INFINITY);
499
500 for i in 0..n {
502 dist[[i, i]] = 0.0;
503 }
504
505 for edge in graph.inner().edge_references() {
507 let i = edge.source().index();
508 let j = edge.target().index();
509 let weight: f64 = (*edge.weight()).into();
510
511 dist[[i, j]] = weight;
512 }
513
514 for k in 0..n {
516 for i in 0..n {
517 for j in 0..n {
518 let alt = dist[[i, k]] + dist[[k, j]];
519 if alt < dist[[i, j]] {
520 dist[[i, j]] = alt;
521 }
522 }
523 }
524 }
525
526 Ok(dist)
527}
528
529#[allow(dead_code)]
554pub fn astar_search<N, E, Ix, H>(
555 graph: &Graph<N, E, Ix>,
556 source: &N,
557 target: &N,
558 heuristic: H,
559) -> Result<AStarResult<N, E>>
560where
561 N: Node + std::fmt::Debug + Clone + Hash + Eq,
562 E: EdgeWeight
563 + Clone
564 + std::ops::Add<Output = E>
565 + scirs2_core::numeric::Zero
566 + PartialOrd
567 + Copy,
568 Ix: petgraph::graph::IndexType,
569 H: Fn(&N) -> E,
570{
571 if !graph.contains_node(source) || !graph.contains_node(target) {
572 return Err(GraphError::node_not_found("node"));
573 }
574
575 let mut open_set = BinaryHeap::new();
576 let mut g_score: HashMap<N, E> = HashMap::new();
577 let mut came_from: HashMap<N, N> = HashMap::new();
578
579 g_score.insert(source.clone(), E::zero());
580
581 open_set.push(AStarState {
582 node: source.clone(),
583 cost: E::zero(),
584 heuristic: heuristic(source),
585 path: vec![source.clone()],
586 });
587
588 while let Some(current_state) = open_set.pop() {
589 let current = ¤t_state.node;
590
591 if current == target {
592 return Ok(AStarResult {
593 path: current_state.path,
594 cost: current_state.cost,
595 });
596 }
597
598 let current_g = g_score.get(current).cloned().unwrap_or_else(E::zero);
599
600 if let Ok(neighbors) = graph.neighbors(current) {
601 for neighbor in neighbors {
602 if let Ok(edge_weight) = graph.edge_weight(current, &neighbor) {
603 let tentative_g = current_g + edge_weight;
604
605 let current_neighbor_g = g_score.get(&neighbor);
606 if current_neighbor_g.is_none()
607 || tentative_g < *current_neighbor_g.expect("Test: operation failed")
608 {
609 came_from.insert(neighbor.clone(), current.clone());
610 g_score.insert(neighbor.clone(), tentative_g);
611
612 let mut new_path = current_state.path.clone();
613 new_path.push(neighbor.clone());
614
615 open_set.push(AStarState {
616 node: neighbor.clone(),
617 cost: tentative_g,
618 heuristic: heuristic(&neighbor),
619 path: new_path,
620 });
621 }
622 }
623 }
624 }
625 }
626
627 Err(GraphError::NoPath {
628 src_node: format!("{source:?}"),
629 target: format!("{target:?}"),
630 nodes: 0,
631 edges: 0,
632 })
633}
634
635#[allow(dead_code)]
637pub fn astar_search_digraph<N, E, Ix, H>(
638 graph: &DiGraph<N, E, Ix>,
639 source: &N,
640 target: &N,
641 heuristic: H,
642) -> Result<AStarResult<N, E>>
643where
644 N: Node + std::fmt::Debug + Clone + Hash + Eq,
645 E: EdgeWeight
646 + Clone
647 + std::ops::Add<Output = E>
648 + scirs2_core::numeric::Zero
649 + PartialOrd
650 + Copy,
651 Ix: petgraph::graph::IndexType,
652 H: Fn(&N) -> E,
653{
654 if !graph.contains_node(source) || !graph.contains_node(target) {
655 return Err(GraphError::node_not_found("node"));
656 }
657
658 let mut open_set = BinaryHeap::new();
659 let mut g_score: HashMap<N, E> = HashMap::new();
660 let mut came_from: HashMap<N, N> = HashMap::new();
661
662 g_score.insert(source.clone(), E::zero());
663
664 open_set.push(AStarState {
665 node: source.clone(),
666 cost: E::zero(),
667 heuristic: heuristic(source),
668 path: vec![source.clone()],
669 });
670
671 while let Some(current_state) = open_set.pop() {
672 let current = ¤t_state.node;
673
674 if current == target {
675 return Ok(AStarResult {
676 path: current_state.path,
677 cost: current_state.cost,
678 });
679 }
680
681 let current_g = g_score.get(current).cloned().unwrap_or_else(E::zero);
682
683 if let Ok(successors) = graph.successors(current) {
684 for neighbor in successors {
685 if let Ok(edge_weight) = graph.edge_weight(current, &neighbor) {
686 let tentative_g = current_g + edge_weight;
687
688 let current_neighbor_g = g_score.get(&neighbor);
689 if current_neighbor_g.is_none()
690 || tentative_g < *current_neighbor_g.expect("Test: operation failed")
691 {
692 came_from.insert(neighbor.clone(), current.clone());
693 g_score.insert(neighbor.clone(), tentative_g);
694
695 let mut new_path = current_state.path.clone();
696 new_path.push(neighbor.clone());
697
698 open_set.push(AStarState {
699 node: neighbor.clone(),
700 cost: tentative_g,
701 heuristic: heuristic(&neighbor),
702 path: new_path,
703 });
704 }
705 }
706 }
707 }
708 }
709
710 Err(GraphError::NoPath {
711 src_node: format!("{source:?}"),
712 target: format!("{target:?}"),
713 nodes: 0,
714 edges: 0,
715 })
716}
717
718#[allow(dead_code)]
723pub fn k_shortest_paths<N, E, Ix>(
724 graph: &Graph<N, E, Ix>,
725 source: &N,
726 target: &N,
727 k: usize,
728) -> Result<Vec<(f64, Vec<N>)>>
729where
730 N: Node + std::fmt::Debug + Clone + Hash + Eq + Ord,
731 E: EdgeWeight
732 + Into<f64>
733 + Clone
734 + scirs2_core::numeric::Zero
735 + scirs2_core::numeric::One
736 + std::ops::Add<Output = E>
737 + PartialOrd
738 + std::marker::Copy
739 + std::fmt::Debug
740 + std::default::Default,
741 Ix: petgraph::graph::IndexType,
742{
743 if k == 0 {
744 return Ok(vec![]);
745 }
746
747 if !graph.contains_node(source) || !graph.contains_node(target) {
749 return Err(GraphError::node_not_found("node"));
750 }
751
752 let mut paths = Vec::new();
753 let mut candidates = std::collections::BinaryHeap::new();
754
755 match dijkstra_path(graph, source, target) {
757 Ok(Some(path)) => {
758 let weight: f64 = path.total_weight.into();
759 paths.push((weight, path.nodes));
760 }
761 Ok(None) => return Ok(vec![]), Err(e) => return Err(e),
763 }
764
765 for i in 0..k - 1 {
767 if i >= paths.len() {
768 break;
769 }
770
771 let (_, prev_path) = &paths[i];
772
773 for j in 0..prev_path.len() - 1 {
775 let spur_node = &prev_path[j];
776 let root_path = &prev_path[..=j];
777
778 let mut removed_edges = Vec::new();
780
781 for (_, path) in &paths {
783 if path.len() > j && &path[..=j] == root_path && j + 1 < path.len() {
784 removed_edges.push((path[j].clone(), path[j + 1].clone()));
785 }
786 }
787
788 if let Ok((spur_weight, spur_path)) =
790 shortest_path_avoiding_edges(graph, spur_node, target, &removed_edges, root_path)
791 {
792 let mut total_weight = spur_weight;
794 for idx in 0..j {
795 if let Ok(edge_weight) = graph.edge_weight(&prev_path[idx], &prev_path[idx + 1])
796 {
797 let weight: f64 = edge_weight.into();
798 total_weight += weight;
799 }
800 }
801
802 let mut complete_path = root_path[..j].to_vec();
804 complete_path.extend(spur_path);
805
806 candidates.push((
808 std::cmp::Reverse(ordered_float::OrderedFloat(total_weight)),
809 complete_path.clone(),
810 ));
811 }
812 }
813 }
814
815 while paths.len() < k && !candidates.is_empty() {
817 let (std::cmp::Reverse(ordered_float::OrderedFloat(weight)), path) =
818 candidates.pop().expect("Operation failed");
819
820 let is_duplicate = paths.iter().any(|(_, p)| p == &path);
822 if !is_duplicate {
823 paths.push((weight, path));
824 }
825 }
826
827 Ok(paths)
828}
829
830#[allow(dead_code)]
832fn shortest_path_avoiding_edges<N, E, Ix>(
833 graph: &Graph<N, E, Ix>,
834 source: &N,
835 target: &N,
836 avoided_edges: &[(N, N)],
837 excluded_nodes: &[N],
838) -> Result<(f64, Vec<N>)>
839where
840 N: Node + std::fmt::Debug + Clone + Hash + Eq + Ord,
841 E: EdgeWeight + Into<f64>,
842 Ix: petgraph::graph::IndexType,
843{
844 use std::cmp::Reverse;
845
846 let mut distances: HashMap<N, f64> = HashMap::new();
847 let mut previous: HashMap<N, N> = HashMap::new();
848 let mut heap = BinaryHeap::new();
849
850 distances.insert(source.clone(), 0.0);
851 heap.push((Reverse(ordered_float::OrderedFloat(0.0)), source.clone()));
852
853 while let Some((Reverse(ordered_float::OrderedFloat(dist)), node)) = heap.pop() {
854 if &node == target {
855 let mut path = vec![target.clone()];
857 let mut current = target.clone();
858
859 while let Some(prev) = previous.get(¤t) {
860 path.push(prev.clone());
861 current = prev.clone();
862 }
863
864 path.reverse();
865 return Ok((dist, path));
866 }
867
868 if distances.get(&node).is_none_or(|&d| dist > d) {
869 continue;
870 }
871
872 if let Ok(neighbors) = graph.neighbors(&node) {
873 for neighbor in neighbors {
874 if avoided_edges.contains(&(node.clone(), neighbor.clone())) {
876 continue;
877 }
878
879 if &neighbor != source && &neighbor != target && excluded_nodes.contains(&neighbor)
881 {
882 continue;
883 }
884
885 if let Ok(edge_weight) = graph.edge_weight(&node, &neighbor) {
886 let weight: f64 = edge_weight.into();
887 let new_dist = dist + weight;
888
889 if new_dist < *distances.get(&neighbor).unwrap_or(&f64::INFINITY) {
890 distances.insert(neighbor.clone(), new_dist);
891 previous.insert(neighbor.clone(), node.clone());
892 heap.push((Reverse(ordered_float::OrderedFloat(new_dist)), neighbor));
893 }
894 }
895 }
896 }
897 }
898
899 Err(GraphError::NoPath {
900 src_node: format!("{source:?}"),
901 target: format!("{target:?}"),
902 nodes: 0,
903 edges: 0,
904 })
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910
911 #[test]
912 #[allow(deprecated)]
913 fn test_shortest_path() {
914 let mut graph: Graph<i32, f64> = Graph::new();
915
916 graph.add_edge(1, 2, 4.0).expect("Operation failed");
918 graph.add_edge(1, 3, 2.0).expect("Operation failed");
919 graph.add_edge(2, 3, 1.0).expect("Operation failed");
920 graph.add_edge(2, 4, 5.0).expect("Operation failed");
921 graph.add_edge(3, 4, 8.0).expect("Operation failed");
922
923 let path = shortest_path(&graph, &1, &4)
924 .expect("Operation failed")
925 .expect("Test: operation failed");
926
927 assert_eq!(path.total_weight, 8.0);
929 assert_eq!(path.nodes, vec![1, 3, 2, 4]);
930 }
931
932 #[test]
933 fn test_floyd_warshall() {
934 let mut graph: Graph<i32, f64> = Graph::new();
935
936 graph.add_edge(0, 1, 1.0).expect("Operation failed");
938 graph.add_edge(1, 2, 2.0).expect("Operation failed");
939 graph.add_edge(2, 0, 3.0).expect("Operation failed");
940
941 let distances = floyd_warshall(&graph).expect("Operation failed");
942
943 assert_eq!(distances[[0, 0]], 0.0);
945 assert_eq!(distances[[0, 1]], 1.0);
946 assert_eq!(distances[[0, 2]], 3.0); assert_eq!(distances[[1, 0]], 1.0); }
949
950 #[test]
951 fn test_astar_search() {
952 let mut graph: Graph<(i32, i32), f64> = Graph::new();
953
954 graph
956 .add_edge((0, 0), (0, 1), 1.0)
957 .expect("Operation failed");
958 graph
959 .add_edge((0, 1), (1, 1), 1.0)
960 .expect("Operation failed");
961 graph
962 .add_edge((1, 1), (1, 0), 1.0)
963 .expect("Operation failed");
964 graph
965 .add_edge((1, 0), (0, 0), 1.0)
966 .expect("Operation failed");
967
968 let heuristic = |&(x, y): &(i32, i32)| -> f64 { ((1 - x).abs() + (1 - y).abs()) as f64 };
970
971 let result = astar_search(&graph, &(0, 0), &(1, 1), heuristic);
972 let result = result.expect("Test: operation failed");
973 assert_eq!(result.cost, 2.0);
974 assert_eq!(result.path.len(), 3); }
976
977 #[test]
978 fn test_k_shortest_paths() {
979 let mut graph: Graph<char, f64> = Graph::new();
980
981 graph.add_edge('A', 'B', 2.0).expect("Operation failed");
983 graph.add_edge('B', 'D', 2.0).expect("Operation failed");
984 graph.add_edge('A', 'C', 1.0).expect("Operation failed");
985 graph.add_edge('C', 'D', 4.0).expect("Operation failed");
986 graph.add_edge('B', 'C', 1.0).expect("Operation failed");
987
988 let paths = k_shortest_paths(&graph, &'A', &'D', 3).expect("Operation failed");
989
990 assert!(paths.len() >= 2);
991 assert_eq!(paths[0].0, 4.0); assert_eq!(paths[0].1, vec!['A', 'B', 'D']);
993 }
994}