Skip to main content

transit_grid/algorithms/
mod.rs

1//! Module providing traits and implementations for shortest path computations
2//! on a transit network. The two main traits defined are `ShortestPath` and `ShortestPathWithAccessability`.
3//!
4//! `ShortestPath` trait is used for computing shortest path between two nodes in a network.
5//!
6//! `ShortestPathWithAccessability` extends `ShortestPath` and takes into account the accessibility
7//! of nodes while computing the shortest path. This is useful in scenarios where certain nodes
8//! in the network may not be accessible and need to be avoided.
9//!
10//! The module also provides implementations of these traits for `TransitNetwork` struct.
11//! It uses A* algorithm from `petgraph` crate for shortest path computation.
12
13use std::cmp::Ordering;
14
15use geo::CoordNum;
16use petgraph::{algo::astar, visit::EdgeRef};
17
18use crate::{
19    core::{Accessability, NodeId, TransitEdge},
20    graphs::TransitNetwork,
21};
22
23pub mod edge_length;
24
25/// `ShortestPath` trait provides functionality to compute shortest path in a network.
26///
27/// It is generic over two types `R` and `T`. `R` represents some properties of the network (like weights or capacities),
28/// and `T` represents the numerical type for calculations (like distances or costs).
29///
30/// Implementations of `ShortestPath` provide a method `find_shortest_path()` that takes the starting and destination nodes,
31/// and returns the shortest path from the start to the destination node as a vector of node IDs.
32pub trait ShortestPath<R, T> {
33    /// Finds the shortest path from the start node to the destination node.
34    ///
35    /// # Arguments
36    ///
37    /// * `from` - The ID of the starting node.
38    /// * `to` - The ID of the destination node.
39    ///
40    /// # Returns
41    ///
42    /// * `Option<Vec<NodeId>>` - If a path exists, returns the path as a vector of Node IDs, where the first element
43    ///   is the start node and the last is the destination node. If no path exists, returns None.
44    ///
45    fn find_shortest_path(&self, from: NodeId, to: NodeId) -> Option<Vec<NodeId>>;
46}
47
48/// This trait provides methods for finding the shortest path between two nodes in a graph
49/// with consideration for the accessibility of nodes.
50///
51/// # Type Parameters
52///
53/// * `R`: The type that represents the route or connection between nodes.
54/// * `T`: The type that represents the coordinate number used in the nodes. This should implement the `CoordNum` trait.
55pub trait ShortestPathWithAccessability<R, T: CoordNum> {
56    /// Calculates the cost of traversing from one node to another, considering the accessibility of the destination node.
57    ///
58    /// # Arguments
59    ///
60    /// * `from` - The ID of the node from where the traversal starts.
61    /// * `to` - The ID of the node to which the traversal ends.
62    /// * `accessability` - A reference to the accessibility information for nodes in the network.
63    /// * `edge_cost` - A mutable reference to a function that calculates the cost of traversing an edge.
64    ///
65    /// # Returns
66    ///
67    /// * `f64` - The cost of the traversal from `from` node to `to` node.
68    ///
69    fn calc_edge_cost<F>(
70        &self,
71        from: NodeId,
72        to: NodeId,
73        accessability: &Accessability,
74        edge_cost: &mut F,
75    ) -> f64
76    where
77        F: FnMut(TransitEdge<T>) -> f64;
78
79    /// Finds the shortest path between two nodes considering the accessibility of nodes.
80    ///
81    /// # Arguments
82    ///
83    /// * `from` - The ID of the starting node.
84    /// * `to` - The ID of the destination node.
85    /// * `accessability` - The accessibility information for nodes in the network.
86    /// * `edge_cost` - A function to calculate the cost of traversing an edge.
87    ///
88    /// # Returns
89    ///
90    /// * `Option<(f64, Vec<NodeId>)>` - A tuple containing the length of the shortest path and the nodes in the path, or None if no path exists.
91    ///
92    fn find_shortest_path_with_accessability<F>(
93        &self,
94        from: NodeId,
95        to: NodeId,
96        accessability: Accessability,
97        edge_cost: F,
98    ) -> Option<(f64, Vec<NodeId>)>
99    where
100        F: FnMut(TransitEdge<T>) -> f64;
101}
102
103impl<R: Copy, T: CoordNum> ShortestPath<R, T> for TransitNetwork<R, T> {
104    fn find_shortest_path(&self, from: NodeId, to: NodeId) -> Option<Vec<NodeId>> {
105        self.find_shortest_path_with_accessability(
106            from,
107            to,
108            Accessability::UnreachableNodes(vec![]),
109            |_edge| 1.0,
110        )
111        .map(|(_, path)| path)
112    }
113}
114
115impl<R: Copy, T: CoordNum> ShortestPathWithAccessability<R, T> for TransitNetwork<R, T> {
116    // Function to calculate edge cost
117    fn calc_edge_cost<F>(
118        &self,
119        from: NodeId,
120        to: NodeId,
121        accessability: &Accessability,
122        edge_cost: &mut F,
123    ) -> f64
124    where
125        F: FnMut(TransitEdge<T>) -> f64,
126    {
127        if let Accessability::UnreachableNodes(reachable_nodes) = accessability
128            && reachable_nodes.contains(&to) {
129                return f64::INFINITY;
130            }
131        let from = self.physical_graph.id_to_index(from);
132        let to = self.physical_graph.id_to_index(to);
133        if let (Some(from), Some(to)) = (from, to) {
134            let edge = self.physical_graph.graph.find_edge(*from, *to).unwrap();
135            edge_cost(self.physical_graph.graph[edge].clone())
136        } else {
137            f64::INFINITY
138        }
139    }
140
141    fn find_shortest_path_with_accessability<F>(
142        &self,
143        from: NodeId,
144        to: NodeId,
145        accessability: Accessability,
146        mut edge_cost: F,
147    ) -> Option<(f64, Vec<NodeId>)>
148    where
149        F: FnMut(TransitEdge<T>) -> f64,
150    {
151        let start = self.topology_graph.id_to_index(from);
152        let goal = self.topology_graph.id_to_index(to);
153        if let (Some(start), Some(goal)) = (start, goal) {
154            let path1 = astar(
155                &self.topology_graph.graph,
156                start.0,
157                |finish| finish == goal.0 || finish == goal.1,
158                |edge| {
159                    self.calc_edge_cost(
160                        *self.topology_graph.index_to_id(edge.source()).unwrap(),
161                        *self.topology_graph.index_to_id(edge.target()).unwrap(),
162                        &accessability,
163                        &mut edge_cost,
164                    )
165                },
166                |_| 0.,
167            );
168            let path2 = astar(
169                &self.topology_graph.graph,
170                start.1,
171                |finish| finish == goal.0 || finish == goal.1,
172                |edge| {
173                    self.calc_edge_cost(
174                        *self.topology_graph.index_to_id(edge.source()).unwrap(),
175                        *self.topology_graph.index_to_id(edge.target()).unwrap(),
176                        &accessability,
177                        &mut edge_cost,
178                    )
179                },
180                |_| 1.,
181            );
182
183            let best = path1
184                .into_iter()
185                .chain(path2)
186                .min_by(|(cost1, _), (cost2, _)| {
187                    cost1.partial_cmp(cost2).unwrap_or(Ordering::Equal)
188                });
189
190            best.map(|(cost, path)| {
191                let path = path
192                    .into_iter()
193                    .map(|index| *self.topology_graph.index_to_id(index).unwrap())
194                    .collect();
195
196                (cost, path)
197            })
198        } else {
199            None
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use crate::{core::TransitNode, operations::TransitNetworkModifier};
207
208    use super::*;
209    use geo::{coord, point, LineString};
210
211    #[test]
212    fn test_shortest_path() {
213        // Create a new TransitNetwork
214        let mut network = TransitNetwork::new();
215
216        // Define some nodes
217        let node0 = TransitNode {
218            id: 0,
219            location: point!(x: 0.0, y: 0.0),
220        };
221
222        let node1 = TransitNode {
223            id: 1,
224            location: point!(x: 1.0, y: 1.0),
225        };
226
227        let node2 = TransitNode {
228            id: 2,
229            location: point!(x: 2.0, y: 2.0),
230        };
231
232        let node3 = TransitNode {
233            id: 3,
234            location: point!(x: 3.0, y: 3.0),
235        };
236
237        let node4 = TransitNode {
238            id: 4,
239            location: point!(x: 4.0, y: 4.0),
240        };
241
242        // Add nodes to the network
243        network.add_node(node0);
244        network.add_node(node1);
245        network.add_node(node2);
246        network.add_node(node3);
247        network.add_node(node4);
248
249        // Define edges
250        let edge01 = TransitEdge {
251            id: 1,
252            source: 0,
253            target: 1,
254            length: 1.0,
255            path: LineString(vec![coord! {x: 0.0, y: 0.0}, coord! {x: 1.0, y: 1.0}]),
256        };
257        let edge12 = TransitEdge {
258            id: 12,
259            source: 1,
260            target: 2,
261            length: 1.0,
262            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 2.0, y: 2.0}]),
263        };
264        let edge23 = TransitEdge {
265            id: 23,
266            source: 2,
267            target: 3,
268            length: 1.0,
269            path: LineString(vec![coord! {x: 2.0, y: 2.0}, coord! {x: 3.0, y: 3.0}]),
270        };
271        let edge34 = TransitEdge {
272            id: 34,
273            source: 3,
274            target: 4,
275            length: 1.0,
276            path: LineString(vec![coord! {x: 3.0, y: 3.0}, coord! {x: 4.0, y: 4.0}]),
277        };
278
279        // Add edges to the network
280        network.add_edge(edge01);
281        network.add_edge_with_accessibility(edge12, Accessability::ReachableNodes(vec![0]));
282        network.add_edge_with_accessibility(edge23, Accessability::ReachableNodes(vec![1]));
283        network.add_edge_with_accessibility(edge34, Accessability::ReachableNodes(vec![2]));
284
285        let result = network.find_shortest_path(0, 4);
286        assert_eq!(result, Some(vec![0, 1, 2, 3, 4])); // Expected shortest path is 0 -> 1 -> 2 -> 3 -> 4
287    }
288
289    #[test]
290    fn test_shortest_path_with_accessability() {
291        // Create a new TransitNetwork
292
293        let mut network = TransitNetwork::new();
294
295        // Define some nodes
296        let node0 = TransitNode {
297            id: 0,
298            location: point!(x: 0.0, y: 0.0),
299        };
300
301        let node1 = TransitNode {
302            id: 1,
303            location: point!(x: 1.0, y: 1.0),
304        };
305
306        let node2 = TransitNode {
307            id: 2,
308            location: point!(x: 2.0, y: 2.0),
309        };
310
311        let node3 = TransitNode {
312            id: 3,
313            location: point!(x: 3.0, y: 3.0),
314        };
315
316        let node4 = TransitNode {
317            id: 4,
318            location: point!(x: 4.0, y: 4.0),
319        };
320
321        let node5 = TransitNode {
322            id: 5,
323            location: point!(x: 5.0, y: 5.0),
324        };
325
326        // Add nodes to the network
327        network.add_node(node0);
328        network.add_node(node1);
329        network.add_node(node2);
330        network.add_node(node3);
331        network.add_node(node4);
332        network.add_node(node5);
333
334        // Define edges
335        let edge01 = TransitEdge {
336            id: 1,
337            source: 0,
338            target: 1,
339            length: 1.0,
340            path: LineString(vec![coord! {x: 0.0, y: 0.0}, coord! {x: 1.0, y: 1.0}]),
341        };
342        let edge02 = TransitEdge {
343            id: 2,
344            source: 0,
345            target: 2,
346            length: 1.0,
347            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 2.0, y: 2.0}]),
348        };
349        let edge13 = TransitEdge {
350            id: 13,
351            source: 1,
352            target: 3,
353            length: 1.0,
354            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 3.0, y: 3.0}]),
355        };
356        let edge14 = TransitEdge {
357            id: 14,
358            source: 1,
359            target: 4,
360            length: 1.0,
361            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 4.0, y: 4.0}]),
362        };
363        let edge25 = TransitEdge {
364            id: 25,
365            source: 2,
366            target: 5,
367            length: 1.0,
368            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 4.0, y: 4.0}]),
369        };
370        let edge45 = TransitEdge {
371            id: 45,
372            source: 5,
373            target: 4,
374            length: 1.0,
375            path: LineString(vec![coord! {x: 1.0, y: 1.0}, coord! {x: 4.0, y: 4.0}]),
376        };
377
378        // Add edges to the network
379        network.add_edge(edge01);
380        network.add_edge_with_accessibility(edge14, Accessability::ReachableNodes(vec![0]));
381        network.add_edge_with_accessibility(edge02, Accessability::ReachableNodes(vec![0]));
382        network.add_edge_with_accessibility(edge13, Accessability::ReachableNodes(vec![0]));
383        network.add_edge_with_accessibility(edge25, Accessability::ReachableNodes(vec![1]));
384        network.add_edge_with_accessibility(edge45, Accessability::ReachableNodes(vec![2, 1]));
385
386        // Define edge cost function
387        let edge_cost = |_edge: TransitEdge<f32>| 1.0;
388
389        // Test case 1: No node is unreachable
390        let result = network.find_shortest_path_with_accessability(
391            0,                                       // from node 0
392            4,                                       // to node 4
393            Accessability::UnreachableNodes(vec![]), // no unreachable node
394            edge_cost,
395        );
396        assert_eq!(result, Some((2.0, vec![0, 1, 4]))); // Expected shortest path is 0 -> 1 -> 4
397
398        // Test case 2: Node 4 is unreachable
399        let result = network.find_shortest_path_with_accessability(
400            3, // from node 2
401            4, // to node 4
402            Accessability::UnreachableNodes(vec![]),
403            edge_cost,
404        );
405        assert_eq!(result, None); // Expected result is None as there is no path to node 4
406
407        let result = network.find_shortest_path_with_accessability(
408            0,                                        // from node 0
409            4,                                        // to node 4
410            Accessability::UnreachableNodes(vec![1]), // no unreachable node
411            edge_cost,
412        );
413        assert_eq!(result, Some((3.0, vec![0, 2, 5, 4]))); // Expected shortest path is 0 -> 1 -> 4
414        let not_exist = 99;
415        assert!(network
416            .find_shortest_path_with_accessability(
417                0,
418                not_exist,
419                Accessability::UnreachableNodes(vec![]),
420                edge_cost
421            )
422            .is_none());
423    }
424}