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