traitgraph_algo/dijkstra/
mod.rs

1use crate::dijkstra::epoch_array_dijkstra_node_weight_array::EpochNodeWeightArray;
2use crate::dijkstra::performance_counters::DijkstraPerformanceData;
3use std::collections::BinaryHeap;
4use std::fmt::Debug;
5use std::marker::PhantomData;
6use std::ops::Add;
7use traitgraph::index::{GraphIndex, NodeIndex};
8use traitgraph::interface::{GraphBase, StaticGraph};
9
10mod dijkstra_weight_implementations;
11
12/// Using an epoched array as [NodeWeightArray].
13pub mod epoch_array_dijkstra_node_weight_array;
14/// Contains the implementation of the [NodeWeightArray] as [hashbrown::HashMap].
15#[cfg(feature = "hashbrown_dijkstra_node_weight_array")]
16pub mod hashbrown_dijkstra_node_weight_array;
17
18/// Performance counters for Dijkstra's algorithm.
19pub mod performance_counters;
20
21/// A Dijkstra implementation with a set of common optimisations.
22pub type DefaultDijkstra<Graph, WeightType> = Dijkstra<
23    Graph,
24    WeightType,
25    EpochNodeWeightArray<WeightType>,
26    BinaryHeap<std::cmp::Reverse<(WeightType, <Graph as GraphBase>::NodeIndex)>>,
27>;
28
29/// A weight-type usable in Dijkstra's algorithm.
30pub trait DijkstraWeight: Ord + Add<Output = Self> + Sized + Clone {
31    /// The infinity value of this type.
32    fn infinity() -> Self;
33
34    /// The zero value of this type.
35    fn zero() -> Self;
36}
37
38/// Edge data that has a weight usable for shortest path computation.
39pub trait DijkstraWeightedEdgeData<WeightType: DijkstraWeight> {
40    /// The weight of the edge.
41    fn weight(&self) -> WeightType;
42}
43
44impl<WeightType: DijkstraWeight + Copy> DijkstraWeightedEdgeData<WeightType> for WeightType {
45    #[inline]
46    fn weight(&self) -> WeightType {
47        *self
48    }
49}
50
51/// An array to store minimal node weights for Dijkstra's algorithm.
52pub trait NodeWeightArray<WeightType> {
53    /// Create a new NodeWeightArray of given size.
54    fn new(size: usize) -> Self;
55
56    /// Returns the current weight of the given node index.
57    fn get(&self, node_index: usize) -> WeightType;
58
59    /// Returns the current weight of the given node index as mutable reference.
60    fn get_mut<'this: 'result, 'result>(
61        &'this mut self,
62        node_index: usize,
63    ) -> &'result mut WeightType;
64
65    /// Sets the current weight of the given node index.
66    fn set(&mut self, node_index: usize, weight: WeightType);
67
68    /// Resets the weights of all node indices to infinity
69    fn clear(&mut self);
70
71    /// Returns the number of nodes whose weight is stored in the data structure.
72    fn size(&self) -> usize;
73}
74
75impl<WeightType: DijkstraWeight + Copy> NodeWeightArray<WeightType> for Vec<WeightType> {
76    fn new(size: usize) -> Self {
77        vec![WeightType::infinity(); size]
78    }
79
80    #[inline]
81    fn get(&self, node_index: usize) -> WeightType {
82        self[node_index]
83    }
84
85    #[inline]
86    fn get_mut<'this: 'result, 'result>(
87        &'this mut self,
88        node_index: usize,
89    ) -> &'result mut WeightType {
90        &mut self[node_index]
91    }
92
93    #[inline]
94    fn set(&mut self, node_index: usize, weight: WeightType) {
95        self[node_index] = weight;
96    }
97
98    fn clear(&mut self) {
99        for entry in self.iter_mut() {
100            *entry = WeightType::infinity();
101        }
102    }
103
104    #[inline]
105    fn size(&self) -> usize {
106        self.len()
107    }
108}
109
110/// A data structure that decides whether a given node index is a target of the current Dijkstra search.
111pub trait DijkstraTargetMap<Graph: GraphBase> {
112    /// Returns true if the given node index is a target of the current Dijkstra search.
113    fn is_target(&self, node_index: Graph::NodeIndex) -> bool;
114}
115
116impl<Graph: GraphBase> DijkstraTargetMap<Graph> for Vec<bool> {
117    fn is_target(&self, node_index: Graph::NodeIndex) -> bool {
118        self[node_index.as_usize()]
119    }
120}
121
122impl<IndexType: Sized + Eq, Graph: GraphBase<NodeIndex = NodeIndex<IndexType>>>
123    DijkstraTargetMap<Graph> for NodeIndex<IndexType>
124{
125    fn is_target(&self, node_index: Graph::NodeIndex) -> bool {
126        *self == node_index
127    }
128}
129
130/// A min-heap used in Dijkstra's shortest path algorithm.
131pub trait DijkstraHeap<WeightType, IndexType>: Default {
132    /// Insert an index-weight pair into the heap.
133    fn insert(&mut self, weight: WeightType, index: IndexType);
134
135    /// Remove the weight and index with the smallest weight from the heap.
136    fn remove_min(&mut self) -> Option<(WeightType, IndexType)>;
137
138    /// Remove all entries from the heap.
139    fn clear(&mut self);
140
141    /// Returns the number of nodes the heap currently has space for.
142    fn size(&mut self) -> usize;
143}
144
145impl<WeightType: Ord, IndexType: Ord> DijkstraHeap<WeightType, IndexType>
146    for BinaryHeap<std::cmp::Reverse<(WeightType, IndexType)>>
147{
148    fn insert(&mut self, weight: WeightType, index: IndexType) {
149        self.push(std::cmp::Reverse((weight, index)));
150    }
151
152    fn remove_min(&mut self) -> Option<(WeightType, IndexType)> {
153        self.pop().map(|packed| packed.0)
154    }
155
156    fn clear(&mut self) {
157        self.clear()
158    }
159
160    fn size(&mut self) -> usize {
161        self.len()
162    }
163}
164
165/// The exhaustiveness of an execution of Dijkstra's algorithm.
166/// This can be complete, or partial because of reaching performance limits.
167///
168/// Note that the `max_weight` parameter is not a performance limit, but a limit on the search space.
169#[derive(Debug, Clone, Eq, PartialEq)]
170pub enum DijkstraExhaustiveness {
171    /// The search exhausted the search space.
172    Complete,
173    /// The search was aborted early because the node weight data structure grew too large.
174    PartialNodeWeights,
175    /// The search was aborted early because the heap grew too large.
176    PartialHeap,
177}
178
179/// The final status of an execution of Dijkstra's algorithm.
180#[derive(Debug, Clone, Eq, PartialEq)]
181pub struct DijkstraStatus<DijkstraPerformance: DijkstraPerformanceData> {
182    /// The exhaustiveness of the search.
183    pub exhaustiveness: DijkstraExhaustiveness,
184    /// The performance data collected during execution.
185    pub performance_data: DijkstraPerformance,
186}
187
188/// Data structure for Dijkstra's shortest path algorithm.
189///
190/// This variant of Dijkstra's algorithm supports only computing the length of a shortest path, and not the shortest path itself.
191/// Therefore it does not need an array of back pointers for each node, saving a bit of memory.
192pub struct Dijkstra<
193    Graph: GraphBase,
194    WeightType: DijkstraWeight,
195    NodeWeights: NodeWeightArray<WeightType>,
196    Heap: DijkstraHeap<WeightType, Graph::NodeIndex>,
197> {
198    heap: Heap,
199    // back_pointers: Vec<Graph::OptionalNodeIndex>,
200    node_weights: NodeWeights,
201    graph: PhantomData<Graph>,
202    _weight_type_phantom: PhantomData<WeightType>,
203}
204
205impl<
206        WeightType: DijkstraWeight + Eq + Debug,
207        EdgeData: DijkstraWeightedEdgeData<WeightType>,
208        Graph: StaticGraph<EdgeData = EdgeData>,
209        NodeWeights: NodeWeightArray<WeightType>,
210        Heap: DijkstraHeap<WeightType, Graph::NodeIndex>,
211    > Dijkstra<Graph, WeightType, NodeWeights, Heap>
212{
213    /// Create the data structures for the given graph.
214    pub fn new(graph: &Graph) -> Self {
215        Self {
216            heap: Default::default(),
217            // back_pointers: vec![Default::default(); graph.node_count()],
218            node_weights: NodeWeights::new(graph.node_count()),
219            graph: Default::default(),
220            _weight_type_phantom: Default::default(),
221        }
222    }
223
224    /// Compute the shortest paths from source to all targets, with given maximum weight.
225    ///
226    /// **max_node_weight_data_size:** the maximum number of nodes for which a weight can be stored before the search aborts.
227    #[inline(never)]
228    #[allow(clippy::too_many_arguments)]
229    pub fn shortest_path_lens<
230        TargetMap: DijkstraTargetMap<Graph>,
231        DijkstraPerformance: DijkstraPerformanceData,
232    >(
233        &mut self,
234        graph: &Graph,
235        source: Graph::NodeIndex,
236        targets: &TargetMap,
237        target_amount: usize,
238        max_weight: WeightType,
239        forbid_source_target: bool,
240        distances: &mut Vec<(Graph::NodeIndex, WeightType)>,
241        max_node_weight_data_size: usize,
242        max_heap_data_size: usize,
243        mut performance_data: DijkstraPerformance,
244    ) -> DijkstraStatus<DijkstraPerformance> {
245        //println!("Shortest path lens of {}", source.as_usize());
246        self.heap.insert(WeightType::zero(), source);
247        //self.back_pointers[source.as_usize()] = source.into();
248        self.node_weights.set(source.as_usize(), WeightType::zero());
249        distances.clear();
250        let mut exhaustiveness = DijkstraExhaustiveness::Complete;
251
252        //let max_iterations = self.graph.node_count();
253        while let Some((weight, node_index)) = self.heap.remove_min() {
254            performance_data.add_iteration();
255            //println!("Finalising node {}", node_index.as_usize());
256            // Check if the node was already processed
257            let actual_weight = self.node_weights.get(node_index.as_usize());
258            if actual_weight < weight {
259                performance_data.add_unnecessary_heap_element();
260                continue;
261            }
262            debug_assert_eq!(actual_weight, weight);
263
264            // Check if we are still lower than or equal to max_weight
265            if weight > max_weight {
266                //println!("Aborting early by max_weight after {}/{} iterations of which {} are unnecessary", iterations, max_iterations, unnecessary_iterations);
267                break;
268            }
269
270            // Check if we found a target
271            if targets.is_target(node_index) && (!forbid_source_target || node_index != source) {
272                distances.push((node_index, weight.clone()));
273
274                // Check if we already found all paths
275                if distances.len() == target_amount {
276                    //println!("Aborting early after finding all targets");
277                    break;
278                }
279            }
280
281            // Relax neighbors
282            for out_neighbor in graph.out_neighbors(node_index) {
283                let new_neighbor_weight =
284                    weight.clone() + graph.edge_data(out_neighbor.edge_id).weight();
285                let neighbor_weight = self.node_weights.get_mut(out_neighbor.node_id.as_usize());
286                if new_neighbor_weight < *neighbor_weight {
287                    *neighbor_weight = new_neighbor_weight.clone();
288                    self.heap.insert(new_neighbor_weight, out_neighbor.node_id);
289                    //self.back_pointers[out_neighbor.node_id.as_usize()] = node_index.into();
290                }
291            }
292
293            performance_data.record_heap_size(self.heap.size());
294            performance_data.record_distance_array_size(self.node_weights.size());
295
296            if self.node_weights.size() > max_node_weight_data_size {
297                exhaustiveness = DijkstraExhaustiveness::PartialNodeWeights;
298                break;
299            } else if self.heap.size() > max_heap_data_size {
300                exhaustiveness = DijkstraExhaustiveness::PartialHeap;
301                break;
302            }
303        }
304
305        self.heap.clear();
306        /*for back_pointer in &mut self.back_pointers {
307            *back_pointer = Default::default();
308        }*/
309        self.node_weights.clear();
310        performance_data.finish_dijkstra();
311        DijkstraStatus {
312            exhaustiveness,
313            performance_data,
314        }
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use crate::dijkstra::performance_counters::NoopDijkstraPerformanceCounter;
321    use crate::dijkstra::DefaultDijkstra;
322    use traitgraph::implementation::petgraph_impl::PetGraph;
323    use traitgraph::interface::MutableGraphContainer;
324
325    #[test]
326    fn test_dijkstra_simple() {
327        let mut graph = PetGraph::new();
328        let n1 = graph.add_node(());
329        let n2 = graph.add_node(());
330        let n3 = graph.add_node(());
331        graph.add_edge(n1, n2, 2);
332        graph.add_edge(n2, n3, 2);
333        graph.add_edge(n1, n3, 5);
334
335        let mut dijkstra = DefaultDijkstra::new(&graph);
336        let mut distances = Vec::new();
337        let mut targets = vec![false, false, true];
338        dijkstra.shortest_path_lens(
339            &graph,
340            n1,
341            &targets,
342            1,
343            6,
344            false,
345            &mut distances,
346            usize::MAX,
347            usize::MAX,
348            NoopDijkstraPerformanceCounter,
349        );
350        debug_assert_eq!(distances, vec![(n3, 4)]);
351
352        dijkstra.shortest_path_lens(
353            &graph,
354            n1,
355            &targets,
356            1,
357            6,
358            false,
359            &mut distances,
360            usize::MAX,
361            usize::MAX,
362            NoopDijkstraPerformanceCounter,
363        );
364        debug_assert_eq!(distances, vec![(n3, 4)]);
365
366        dijkstra.shortest_path_lens(
367            &graph,
368            n2,
369            &targets,
370            1,
371            6,
372            false,
373            &mut distances,
374            usize::MAX,
375            usize::MAX,
376            NoopDijkstraPerformanceCounter,
377        );
378        debug_assert_eq!(distances, vec![(n3, 2)]);
379
380        dijkstra.shortest_path_lens(
381            &graph,
382            n3,
383            &targets,
384            1,
385            6,
386            false,
387            &mut distances,
388            usize::MAX,
389            usize::MAX,
390            NoopDijkstraPerformanceCounter,
391        );
392        debug_assert_eq!(distances, vec![(n3, 0)]);
393
394        targets = vec![false, true, false];
395        dijkstra.shortest_path_lens(
396            &graph,
397            n3,
398            &targets,
399            1,
400            6,
401            false,
402            &mut distances,
403            usize::MAX,
404            usize::MAX,
405            NoopDijkstraPerformanceCounter,
406        );
407        debug_assert_eq!(distances, vec![]);
408    }
409
410    #[test]
411    fn test_dijkstra_cycle() {
412        let mut graph = PetGraph::new();
413        let n1 = graph.add_node(());
414        let n2 = graph.add_node(());
415        let n3 = graph.add_node(());
416        graph.add_edge(n1, n2, 2);
417        graph.add_edge(n2, n3, 2);
418        graph.add_edge(n3, n1, 5);
419
420        let mut dijkstra = DefaultDijkstra::new(&graph);
421        let mut distances = Vec::new();
422        let targets = vec![false, false, true];
423        dijkstra.shortest_path_lens(
424            &graph,
425            n1,
426            &targets,
427            1,
428            6,
429            false,
430            &mut distances,
431            usize::MAX,
432            usize::MAX,
433            NoopDijkstraPerformanceCounter,
434        );
435        debug_assert_eq!(distances, vec![(n3, 4)]);
436    }
437}