hypergraph/core/vertices/get_dijkstra_connections.rs
1use std::{
2 cmp::Ordering,
3 collections::{
4 BinaryHeap,
5 HashMap,
6 },
7 fmt::Debug,
8};
9
10use rayon::prelude::*;
11
12use crate::{
13 HyperedgeIndex,
14 HyperedgeTrait,
15 Hypergraph,
16 VertexIndex,
17 VertexTrait,
18 errors::HypergraphError,
19};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22struct Visitor {
23 distance: usize,
24 index: usize,
25}
26
27impl Visitor {
28 fn new(distance: usize, index: usize) -> Self {
29 Self { distance, index }
30 }
31}
32
33// Use a custom implementation of Ord as we want a min-heap BinaryHeap.
34impl Ord for Visitor {
35 fn cmp(&self, other: &Visitor) -> Ordering {
36 other
37 .distance
38 .cmp(&self.distance)
39 .then_with(|| self.distance.cmp(&other.distance))
40 }
41}
42
43impl PartialOrd for Visitor {
44 fn partial_cmp(&self, other: &Visitor) -> Option<Ordering> {
45 Some(self.cmp(other))
46 }
47}
48
49#[allow(clippy::type_complexity)]
50impl<V, HE> Hypergraph<V, HE>
51where
52 V: VertexTrait,
53 HE: HyperedgeTrait,
54{
55 /// Gets a list of the cheapest path of vertices between two vertices as a
56 /// vector of tuples of the form `(VertexIndex, Option<HyperedgeIndex>)`
57 /// where the second member is the hyperedge that has been traversed to
58 /// reach the vertex.
59 /// Please note that the initial tuple holds `None` as hyperedge since none
60 /// has been traversed yet.
61 /// The implementation of the algorithm is partially based on:
62 /// <https://doc.rust-lang.org/std/collections/binary_heap/#examples>
63 pub fn get_dijkstra_connections(
64 &self,
65 from: VertexIndex,
66 to: VertexIndex,
67 ) -> Result<Vec<(VertexIndex, Option<HyperedgeIndex>)>, HypergraphError<V, HE>> {
68 // Get the internal indexes of the vertices.
69 let internal_from = self.get_internal_vertex(from)?;
70 let internal_to = self.get_internal_vertex(to)?;
71
72 // Keep track of the distances.
73 let mut distances = HashMap::new();
74
75 let mut maybe_traversed_hyperedge_by_vertex = HashMap::new();
76
77 // Create an empty binary heap.
78 let mut to_traverse = BinaryHeap::new();
79
80 // Initialize the first vertex to zero.
81 distances.insert(internal_from, 0);
82
83 // Push the first cursor to the heap.
84 to_traverse.push(Visitor::new(0, internal_from));
85
86 // Keep track of the traversal path.
87 let mut path = Vec::<VertexIndex>::new();
88
89 while let Some(Visitor { distance, index }) = to_traverse.pop() {
90 // End of the traversal.
91 if index == internal_to {
92 // Inject the target vertex.
93 path.push(self.get_vertex(internal_to)?);
94
95 return Ok(path
96 .into_par_iter()
97 .map(|vertex_index| {
98 (
99 vertex_index,
100 maybe_traversed_hyperedge_by_vertex
101 .get(&vertex_index)
102 .and_then(|¤t| current),
103 )
104 })
105 .collect());
106 }
107
108 // Skip if a better path has already been found.
109 if distance > distances[&index] {
110 continue;
111 }
112
113 // Get the VertexIndex associated with the internal index.
114 // Proceed by finding all the adjacent vertices as a hashmap whose
115 // keys are VertexIndex and values are a vector of HyperedgeIndex.
116 let mapped_index = self.get_vertex(index)?;
117 let indexes = self.get_full_adjacent_vertices_from(mapped_index)?;
118
119 // For every connected vertex, try to find the lowest distance.
120 for (vertex_index, hyperedge_indexes) in indexes {
121 let internal_vertex_index = self.get_internal_vertex(vertex_index)?;
122
123 let mut min_cost = usize::MAX;
124 let mut best_hyperedge: Option<HyperedgeIndex> = None;
125
126 // Get the lower cost out of all the hyperedges.
127 for hyperedge_index in hyperedge_indexes {
128 let hyperedge_weight = self.get_hyperedge_weight(hyperedge_index)?;
129
130 // Use the trait implementation to get the associated cost
131 // of the hyperedge.
132 let cost = hyperedge_weight.to_owned().into();
133
134 if cost < min_cost {
135 min_cost = cost;
136 best_hyperedge = Some(hyperedge_index);
137
138 break;
139 }
140 }
141
142 // Prepare the next visitor.
143 let next = Visitor::new(distance + min_cost, internal_vertex_index);
144
145 // Check if this is the shorter distance.
146 let is_shorter = distances
147 .get(&next.index)
148 .map_or(true, |¤t| next.distance < current);
149
150 // If so, add it to the frontier and continue.
151 if is_shorter {
152 maybe_traversed_hyperedge_by_vertex.insert(vertex_index, best_hyperedge);
153
154 // Update the path traversal accordingly.
155 // Keep vertex indexes unique.
156 if !path
157 .par_iter()
158 .any(|current_index| mapped_index == *current_index)
159 {
160 path.push(mapped_index);
161 }
162
163 // Push it to the heap.
164 to_traverse.push(next);
165
166 // Relaxation, we have now found a better way
167 distances.insert(internal_vertex_index, next.distance);
168 }
169 }
170 }
171
172 // If we reach this point, this means that there's no solution.
173 // Return an empty vector.
174 Ok(vec![])
175 }
176}