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(|&current| 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, |&current| 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}