1use std::collections::BinaryHeap;
7use std::sync::OnceLock;
8
9use graphos_common::types::{NodeId, Value};
10use graphos_common::utils::error::{Error, Result};
11use graphos_common::utils::hash::FxHashMap;
12use graphos_core::graph::Direction;
13use graphos_core::graph::lpg::LpgStore;
14
15use super::super::{AlgorithmResult, ParameterDef, ParameterType, Parameters};
16use super::traits::{GraphAlgorithm, MinScored};
17
18fn extract_weight(
26 store: &LpgStore,
27 edge_id: graphos_common::types::EdgeId,
28 weight_prop: Option<&str>,
29) -> f64 {
30 if let Some(prop_name) = weight_prop {
31 if let Some(edge) = store.get_edge(edge_id) {
32 if let Some(value) = edge.get_property(prop_name) {
33 return match value {
34 Value::Int64(i) => *i as f64,
35 Value::Float64(f) => *f,
36 _ => 1.0,
37 };
38 }
39 }
40 }
41 1.0
42}
43
44#[derive(Debug, Clone)]
50pub struct DijkstraResult {
51 pub distances: FxHashMap<NodeId, f64>,
53 pub predecessors: FxHashMap<NodeId, NodeId>,
55}
56
57impl DijkstraResult {
58 pub fn path_to(&self, source: NodeId, target: NodeId) -> Option<Vec<NodeId>> {
62 if !self.distances.contains_key(&target) {
63 return None;
64 }
65
66 let mut path = Vec::new();
67 let mut current = target;
68
69 while current != source {
70 path.push(current);
71 current = *self.predecessors.get(¤t)?;
72 }
73 path.push(source);
74 path.reverse();
75
76 Some(path)
77 }
78
79 pub fn distance_to(&self, target: NodeId) -> Option<f64> {
81 self.distances.get(&target).copied()
82 }
83}
84
85pub fn dijkstra(store: &LpgStore, source: NodeId, weight_property: Option<&str>) -> DijkstraResult {
101 let mut distances: FxHashMap<NodeId, f64> = FxHashMap::default();
102 let mut predecessors: FxHashMap<NodeId, NodeId> = FxHashMap::default();
103 let mut heap: BinaryHeap<MinScored<f64, NodeId>> = BinaryHeap::new();
104
105 if store.get_node(source).is_none() {
107 return DijkstraResult {
108 distances,
109 predecessors,
110 };
111 }
112
113 distances.insert(source, 0.0);
114 heap.push(MinScored::new(0.0, source));
115
116 while let Some(MinScored(dist, node)) = heap.pop() {
117 if let Some(&best) = distances.get(&node) {
119 if dist > best {
120 continue;
121 }
122 }
123
124 for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
126 let weight = extract_weight(store, edge_id, weight_property);
127 let new_dist = dist + weight;
128
129 let is_better = distances
130 .get(&neighbor)
131 .map_or(true, |¤t| new_dist < current);
132
133 if is_better {
134 distances.insert(neighbor, new_dist);
135 predecessors.insert(neighbor, node);
136 heap.push(MinScored::new(new_dist, neighbor));
137 }
138 }
139 }
140
141 DijkstraResult {
142 distances,
143 predecessors,
144 }
145}
146
147pub fn dijkstra_path(
151 store: &LpgStore,
152 source: NodeId,
153 target: NodeId,
154 weight_property: Option<&str>,
155) -> Option<(f64, Vec<NodeId>)> {
156 let mut distances: FxHashMap<NodeId, f64> = FxHashMap::default();
157 let mut predecessors: FxHashMap<NodeId, NodeId> = FxHashMap::default();
158 let mut heap: BinaryHeap<MinScored<f64, NodeId>> = BinaryHeap::new();
159
160 if store.get_node(source).is_none() || store.get_node(target).is_none() {
162 return None;
163 }
164
165 distances.insert(source, 0.0);
166 heap.push(MinScored::new(0.0, source));
167
168 while let Some(MinScored(dist, node)) = heap.pop() {
169 if node == target {
171 let mut path = Vec::new();
173 let mut current = target;
174 while current != source {
175 path.push(current);
176 current = *predecessors.get(¤t)?;
177 }
178 path.push(source);
179 path.reverse();
180 return Some((dist, path));
181 }
182
183 if let Some(&best) = distances.get(&node) {
185 if dist > best {
186 continue;
187 }
188 }
189
190 for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
192 let weight = extract_weight(store, edge_id, weight_property);
193 let new_dist = dist + weight;
194
195 let is_better = distances
196 .get(&neighbor)
197 .map_or(true, |¤t| new_dist < current);
198
199 if is_better {
200 distances.insert(neighbor, new_dist);
201 predecessors.insert(neighbor, node);
202 heap.push(MinScored::new(new_dist, neighbor));
203 }
204 }
205 }
206
207 None }
209
210pub fn astar<H>(
232 store: &LpgStore,
233 source: NodeId,
234 target: NodeId,
235 weight_property: Option<&str>,
236 heuristic: H,
237) -> Option<(f64, Vec<NodeId>)>
238where
239 H: Fn(NodeId) -> f64,
240{
241 let mut g_score: FxHashMap<NodeId, f64> = FxHashMap::default();
242 let mut predecessors: FxHashMap<NodeId, NodeId> = FxHashMap::default();
243 let mut heap: BinaryHeap<MinScored<f64, NodeId>> = BinaryHeap::new();
244
245 if store.get_node(source).is_none() || store.get_node(target).is_none() {
247 return None;
248 }
249
250 g_score.insert(source, 0.0);
251 let f_score = heuristic(source);
252 heap.push(MinScored::new(f_score, source));
253
254 while let Some(MinScored(_, node)) = heap.pop() {
255 if node == target {
256 let mut path = Vec::new();
258 let mut current = target;
259 while current != source {
260 path.push(current);
261 current = *predecessors.get(¤t)?;
262 }
263 path.push(source);
264 path.reverse();
265 return Some((*g_score.get(&target)?, path));
266 }
267
268 let current_g = *g_score.get(&node).unwrap_or(&f64::INFINITY);
269
270 for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
272 let weight = extract_weight(store, edge_id, weight_property);
273 let tentative_g = current_g + weight;
274
275 let is_better = g_score
276 .get(&neighbor)
277 .map_or(true, |¤t| tentative_g < current);
278
279 if is_better {
280 predecessors.insert(neighbor, node);
281 g_score.insert(neighbor, tentative_g);
282 let f = tentative_g + heuristic(neighbor);
283 heap.push(MinScored::new(f, neighbor));
284 }
285 }
286 }
287
288 None }
290
291#[derive(Debug, Clone)]
297pub struct BellmanFordResult {
298 pub distances: FxHashMap<NodeId, f64>,
300 pub predecessors: FxHashMap<NodeId, NodeId>,
302 pub has_negative_cycle: bool,
304 source: NodeId,
306}
307
308impl BellmanFordResult {
309 pub fn path_to(&self, target: NodeId) -> Option<Vec<NodeId>> {
311 if !self.distances.contains_key(&target) {
312 return None;
313 }
314
315 let mut path = vec![target];
316 let mut current = target;
317
318 while current != self.source {
319 let pred = self.predecessors.get(¤t)?;
320 path.push(*pred);
321 current = *pred;
322 }
323
324 path.reverse();
325 Some(path)
326 }
327}
328
329pub fn bellman_ford(
348 store: &LpgStore,
349 source: NodeId,
350 weight_property: Option<&str>,
351) -> BellmanFordResult {
352 let mut distances: FxHashMap<NodeId, f64> = FxHashMap::default();
353 let mut predecessors: FxHashMap<NodeId, NodeId> = FxHashMap::default();
354
355 if store.get_node(source).is_none() {
357 return BellmanFordResult {
358 distances,
359 predecessors,
360 has_negative_cycle: false,
361 source,
362 };
363 }
364
365 let nodes: Vec<NodeId> = store.node_ids();
367 let edges: Vec<(NodeId, NodeId, graphos_common::types::EdgeId)> = nodes
368 .iter()
369 .flat_map(|&node| {
370 store
371 .edges_from(node, Direction::Outgoing)
372 .map(move |(neighbor, edge_id)| (node, neighbor, edge_id))
373 })
374 .collect();
375
376 let n = nodes.len();
377
378 distances.insert(source, 0.0);
380
381 for _ in 0..n.saturating_sub(1) {
383 let mut changed = false;
384 for &(u, v, edge_id) in &edges {
385 if let Some(&dist_u) = distances.get(&u) {
386 let weight = extract_weight(store, edge_id, weight_property);
387 let new_dist = dist_u + weight;
388
389 let is_better = distances
390 .get(&v)
391 .map_or(true, |¤t| new_dist < current);
392
393 if is_better {
394 distances.insert(v, new_dist);
395 predecessors.insert(v, u);
396 changed = true;
397 }
398 }
399 }
400 if !changed {
401 break; }
403 }
404
405 let mut has_negative_cycle = false;
407 for &(u, v, edge_id) in &edges {
408 if let Some(&dist_u) = distances.get(&u) {
409 let weight = extract_weight(store, edge_id, weight_property);
410 if let Some(&dist_v) = distances.get(&v) {
411 if dist_u + weight < dist_v {
412 has_negative_cycle = true;
413 break;
414 }
415 }
416 }
417 }
418
419 BellmanFordResult {
420 distances,
421 predecessors,
422 has_negative_cycle,
423 source,
424 }
425}
426
427#[derive(Debug, Clone)]
433pub struct FloydWarshallResult {
434 distances: Vec<Vec<f64>>,
436 next: Vec<Vec<Option<usize>>>,
438 node_to_index: FxHashMap<NodeId, usize>,
440 index_to_node: Vec<NodeId>,
442}
443
444impl FloydWarshallResult {
445 pub fn distance(&self, from: NodeId, to: NodeId) -> Option<f64> {
447 let i = *self.node_to_index.get(&from)?;
448 let j = *self.node_to_index.get(&to)?;
449 let dist = self.distances[i][j];
450 if dist == f64::INFINITY {
451 None
452 } else {
453 Some(dist)
454 }
455 }
456
457 pub fn path(&self, from: NodeId, to: NodeId) -> Option<Vec<NodeId>> {
459 let i = *self.node_to_index.get(&from)?;
460 let j = *self.node_to_index.get(&to)?;
461
462 if self.distances[i][j] == f64::INFINITY {
463 return None;
464 }
465
466 let mut path = vec![from];
467 let mut current = i;
468
469 while current != j {
470 current = self.next[current][j]?;
471 path.push(self.index_to_node[current]);
472 }
473
474 Some(path)
475 }
476
477 pub fn has_negative_cycle(&self) -> bool {
479 for i in 0..self.distances.len() {
480 if self.distances[i][i] < 0.0 {
481 return true;
482 }
483 }
484 false
485 }
486
487 pub fn nodes(&self) -> &[NodeId] {
489 &self.index_to_node
490 }
491}
492
493pub fn floyd_warshall(store: &LpgStore, weight_property: Option<&str>) -> FloydWarshallResult {
508 let nodes: Vec<NodeId> = store.node_ids();
509 let n = nodes.len();
510
511 let mut node_to_index: FxHashMap<NodeId, usize> = FxHashMap::default();
513 for (idx, &node) in nodes.iter().enumerate() {
514 node_to_index.insert(node, idx);
515 }
516
517 let mut distances = vec![vec![f64::INFINITY; n]; n];
519 let mut next: Vec<Vec<Option<usize>>> = vec![vec![None; n]; n];
520
521 for i in 0..n {
523 distances[i][i] = 0.0;
524 }
525
526 for (idx, &node) in nodes.iter().enumerate() {
528 for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
529 if let Some(&neighbor_idx) = node_to_index.get(&neighbor) {
530 let weight = extract_weight(store, edge_id, weight_property);
531 if weight < distances[idx][neighbor_idx] {
532 distances[idx][neighbor_idx] = weight;
533 next[idx][neighbor_idx] = Some(neighbor_idx);
534 }
535 }
536 }
537 }
538
539 for k in 0..n {
541 for i in 0..n {
542 for j in 0..n {
543 let through_k = distances[i][k] + distances[k][j];
544 if through_k < distances[i][j] {
545 distances[i][j] = through_k;
546 next[i][j] = next[i][k];
547 }
548 }
549 }
550 }
551
552 FloydWarshallResult {
553 distances,
554 next,
555 node_to_index,
556 index_to_node: nodes,
557 }
558}
559
560static DIJKSTRA_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
566
567fn dijkstra_params() -> &'static [ParameterDef] {
568 DIJKSTRA_PARAMS.get_or_init(|| {
569 vec![
570 ParameterDef {
571 name: "source".to_string(),
572 description: "Source node ID".to_string(),
573 param_type: ParameterType::NodeId,
574 required: true,
575 default: None,
576 },
577 ParameterDef {
578 name: "target".to_string(),
579 description: "Target node ID (optional, for single-pair shortest path)".to_string(),
580 param_type: ParameterType::NodeId,
581 required: false,
582 default: None,
583 },
584 ParameterDef {
585 name: "weight".to_string(),
586 description: "Edge property name for weights (default: 1.0)".to_string(),
587 param_type: ParameterType::String,
588 required: false,
589 default: None,
590 },
591 ]
592 })
593}
594
595pub struct DijkstraAlgorithm;
597
598impl GraphAlgorithm for DijkstraAlgorithm {
599 fn name(&self) -> &str {
600 "dijkstra"
601 }
602
603 fn description(&self) -> &str {
604 "Dijkstra's shortest path algorithm"
605 }
606
607 fn parameters(&self) -> &[ParameterDef] {
608 dijkstra_params()
609 }
610
611 fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
612 let source_id = params
613 .get_int("source")
614 .ok_or_else(|| Error::InvalidValue("source parameter required".to_string()))?;
615
616 let source = NodeId::new(source_id as u64);
617 let weight_prop = params.get_string("weight");
618
619 if let Some(target_id) = params.get_int("target") {
620 let target = NodeId::new(target_id as u64);
622 match dijkstra_path(store, source, target, weight_prop.as_deref()) {
623 Some((distance, path)) => {
624 let mut result = AlgorithmResult::new(vec![
625 "source".to_string(),
626 "target".to_string(),
627 "distance".to_string(),
628 "path".to_string(),
629 ]);
630
631 let path_str: String = path
632 .iter()
633 .map(|n| n.0.to_string())
634 .collect::<Vec<_>>()
635 .join(" -> ");
636
637 result.add_row(vec![
638 Value::Int64(source.0 as i64),
639 Value::Int64(target.0 as i64),
640 Value::Float64(distance),
641 Value::String(path_str.into()),
642 ]);
643
644 Ok(result)
645 }
646 None => {
647 let mut result = AlgorithmResult::new(vec![
648 "source".to_string(),
649 "target".to_string(),
650 "distance".to_string(),
651 "path".to_string(),
652 ]);
653 result.add_row(vec![
654 Value::Int64(source.0 as i64),
655 Value::Int64(target_id),
656 Value::Null,
657 Value::String("unreachable".into()),
658 ]);
659 Ok(result)
660 }
661 }
662 } else {
663 let dijkstra_result = dijkstra(store, source, weight_prop.as_deref());
665
666 let mut result =
667 AlgorithmResult::new(vec!["node_id".to_string(), "distance".to_string()]);
668
669 for (node, distance) in dijkstra_result.distances {
670 result.add_row(vec![Value::Int64(node.0 as i64), Value::Float64(distance)]);
671 }
672
673 Ok(result)
674 }
675 }
676}
677
678static BELLMAN_FORD_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
680
681fn bellman_ford_params() -> &'static [ParameterDef] {
682 BELLMAN_FORD_PARAMS.get_or_init(|| {
683 vec![
684 ParameterDef {
685 name: "source".to_string(),
686 description: "Source node ID".to_string(),
687 param_type: ParameterType::NodeId,
688 required: true,
689 default: None,
690 },
691 ParameterDef {
692 name: "weight".to_string(),
693 description: "Edge property name for weights (default: 1.0)".to_string(),
694 param_type: ParameterType::String,
695 required: false,
696 default: None,
697 },
698 ]
699 })
700}
701
702pub struct BellmanFordAlgorithm;
704
705impl GraphAlgorithm for BellmanFordAlgorithm {
706 fn name(&self) -> &str {
707 "bellman_ford"
708 }
709
710 fn description(&self) -> &str {
711 "Bellman-Ford shortest path algorithm (handles negative weights)"
712 }
713
714 fn parameters(&self) -> &[ParameterDef] {
715 bellman_ford_params()
716 }
717
718 fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
719 let source_id = params
720 .get_int("source")
721 .ok_or_else(|| Error::InvalidValue("source parameter required".to_string()))?;
722
723 let source = NodeId::new(source_id as u64);
724 let weight_prop = params.get_string("weight");
725
726 let bf_result = bellman_ford(store, source, weight_prop.as_deref());
727
728 let mut result = AlgorithmResult::new(vec![
729 "node_id".to_string(),
730 "distance".to_string(),
731 "has_negative_cycle".to_string(),
732 ]);
733
734 for (node, distance) in bf_result.distances {
735 result.add_row(vec![
736 Value::Int64(node.0 as i64),
737 Value::Float64(distance),
738 Value::Bool(bf_result.has_negative_cycle),
739 ]);
740 }
741
742 Ok(result)
743 }
744}
745
746static FLOYD_WARSHALL_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
748
749fn floyd_warshall_params() -> &'static [ParameterDef] {
750 FLOYD_WARSHALL_PARAMS.get_or_init(|| {
751 vec![ParameterDef {
752 name: "weight".to_string(),
753 description: "Edge property name for weights (default: 1.0)".to_string(),
754 param_type: ParameterType::String,
755 required: false,
756 default: None,
757 }]
758 })
759}
760
761pub struct FloydWarshallAlgorithm;
763
764impl GraphAlgorithm for FloydWarshallAlgorithm {
765 fn name(&self) -> &str {
766 "floyd_warshall"
767 }
768
769 fn description(&self) -> &str {
770 "Floyd-Warshall all-pairs shortest paths algorithm"
771 }
772
773 fn parameters(&self) -> &[ParameterDef] {
774 floyd_warshall_params()
775 }
776
777 fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
778 let weight_prop = params.get_string("weight");
779
780 let fw_result = floyd_warshall(store, weight_prop.as_deref());
781
782 let mut result = AlgorithmResult::new(vec![
783 "source".to_string(),
784 "target".to_string(),
785 "distance".to_string(),
786 ]);
787
788 for (i, &from_node) in fw_result.index_to_node.iter().enumerate() {
790 for (j, &to_node) in fw_result.index_to_node.iter().enumerate() {
791 let dist = fw_result.distances[i][j];
792 if dist < f64::INFINITY {
793 result.add_row(vec![
794 Value::Int64(from_node.0 as i64),
795 Value::Int64(to_node.0 as i64),
796 Value::Float64(dist),
797 ]);
798 }
799 }
800 }
801
802 Ok(result)
803 }
804}
805
806#[cfg(test)]
811mod tests {
812 use super::*;
813
814 fn create_weighted_graph() -> LpgStore {
815 let store = LpgStore::new();
816
817 let n0 = store.create_node(&["Node"]);
827 let n1 = store.create_node(&["Node"]);
828 let n2 = store.create_node(&["Node"]);
829 let n3 = store.create_node(&["Node"]);
830 let n4 = store.create_node(&["Node"]);
831
832 store.create_edge_with_props(n0, n1, "EDGE", [("weight", Value::Float64(2.0))]);
834 store.create_edge_with_props(n1, n2, "EDGE", [("weight", Value::Float64(3.0))]);
835 store.create_edge_with_props(n0, n3, "EDGE", [("weight", Value::Float64(4.0))]);
836 store.create_edge_with_props(n3, n1, "EDGE", [("weight", Value::Float64(1.0))]);
837 store.create_edge_with_props(n2, n4, "EDGE", [("weight", Value::Float64(1.0))]);
838
839 store
840 }
841
842 #[test]
843 fn test_dijkstra_basic() {
844 let store = create_weighted_graph();
845 let result = dijkstra(&store, NodeId::new(0), Some("weight"));
846
847 assert!(result.distances.contains_key(&NodeId::new(0)));
848 assert_eq!(result.distance_to(NodeId::new(0)), Some(0.0));
849 assert!(result.distances.contains_key(&NodeId::new(1)));
850 assert!(result.distances.contains_key(&NodeId::new(2)));
851 }
852
853 #[test]
854 fn test_dijkstra_path() {
855 let store = create_weighted_graph();
856 let result = dijkstra(&store, NodeId::new(0), Some("weight"));
857
858 let path = result.path_to(NodeId::new(0), NodeId::new(2));
859 assert!(path.is_some());
860
861 let path = path.unwrap();
862 assert_eq!(path[0], NodeId::new(0)); assert_eq!(*path.last().unwrap(), NodeId::new(2)); }
865
866 #[test]
867 fn test_dijkstra_single_pair() {
868 let store = create_weighted_graph();
869 let result = dijkstra_path(&store, NodeId::new(0), NodeId::new(4), Some("weight"));
870
871 assert!(result.is_some());
872 let (distance, path) = result.unwrap();
873 assert!(distance > 0.0);
874 assert_eq!(path[0], NodeId::new(0));
875 assert_eq!(*path.last().unwrap(), NodeId::new(4));
876 }
877
878 #[test]
879 fn test_dijkstra_unreachable() {
880 let store = LpgStore::new();
881 let n0 = store.create_node(&["Node"]);
882 let _n1 = store.create_node(&["Node"]); let result = dijkstra_path(&store, n0, NodeId::new(1), None);
885 assert!(result.is_none());
886 }
887
888 #[test]
889 fn test_bellman_ford_basic() {
890 let store = create_weighted_graph();
891 let result = bellman_ford(&store, NodeId::new(0), Some("weight"));
892
893 assert!(!result.has_negative_cycle);
894 assert!(result.distances.contains_key(&NodeId::new(0)));
895 assert_eq!(*result.distances.get(&NodeId::new(0)).unwrap(), 0.0);
896 }
897
898 #[test]
899 fn test_floyd_warshall_basic() {
900 let store = create_weighted_graph();
901 let result = floyd_warshall(&store, Some("weight"));
902
903 assert_eq!(result.distance(NodeId::new(0), NodeId::new(0)), Some(0.0));
905 assert_eq!(result.distance(NodeId::new(1), NodeId::new(1)), Some(0.0));
906
907 assert!(result.distance(NodeId::new(0), NodeId::new(2)).is_some());
909 }
910
911 #[test]
912 fn test_floyd_warshall_path_reconstruction() {
913 let store = create_weighted_graph();
914 let result = floyd_warshall(&store, Some("weight"));
915
916 let path = result.path(NodeId::new(0), NodeId::new(2));
917 assert!(path.is_some());
918
919 let path = path.unwrap();
920 assert_eq!(path[0], NodeId::new(0));
921 assert_eq!(*path.last().unwrap(), NodeId::new(2));
922 }
923
924 #[test]
925 fn test_astar_basic() {
926 let store = create_weighted_graph();
927
928 let heuristic = |_: NodeId| 0.0;
930
931 let result = astar(
932 &store,
933 NodeId::new(0),
934 NodeId::new(4),
935 Some("weight"),
936 heuristic,
937 );
938 assert!(result.is_some());
939
940 let (distance, path) = result.unwrap();
941 assert!(distance > 0.0);
942 assert_eq!(path[0], NodeId::new(0));
943 assert_eq!(*path.last().unwrap(), NodeId::new(4));
944 }
945
946 #[test]
947 fn test_dijkstra_nonexistent_source() {
948 let store = LpgStore::new();
949 let result = dijkstra(&store, NodeId::new(999), None);
950 assert!(result.distances.is_empty());
951 }
952
953 #[test]
954 fn test_unweighted_defaults() {
955 let store = LpgStore::new();
956 let n0 = store.create_node(&["Node"]);
957 let n1 = store.create_node(&["Node"]);
958 let n2 = store.create_node(&["Node"]);
959 store.create_edge(n0, n1, "EDGE");
960 store.create_edge(n1, n2, "EDGE");
961
962 let result = dijkstra(&store, n0, None);
964 assert_eq!(result.distance_to(n1), Some(1.0));
965 assert_eq!(result.distance_to(n2), Some(2.0));
966 }
967}