Skip to main content

rust_igraph/algorithms/paths/
get_shortest_paths_dijkstra.rs

1//! Weighted shortest paths via Dijkstra (ALGO-SP-038).
2//!
3//! Counterpart of `igraph_get_shortest_paths_dijkstra()` from
4//! `references/igraph/src/paths/dijkstra.c:336`.
5//!
6//! Returns one shortest path (vertex and edge sequences) from a source
7//! vertex to every vertex in the graph, using weighted Dijkstra.
8
9use crate::core::graph::EdgeId;
10use crate::core::{Graph, IgraphResult, VertexId};
11
12use super::dijkstra::{DijkstraMode, DijkstraPaths, dijkstra_paths, dijkstra_paths_with_mode};
13
14/// Result of [`get_shortest_paths_dijkstra`]: vertex and edge paths
15/// from a single source to all vertices.
16#[derive(Debug, Clone, PartialEq)]
17pub struct ShortestPathsDijkstra {
18    /// `vertex_paths[v]` is the vertex sequence of a shortest path from
19    /// `source` to `v` (inclusive at both ends). Empty if `v` is
20    /// unreachable.
21    pub vertex_paths: Vec<Vec<VertexId>>,
22    /// `edge_paths[v]` is the edge-id sequence of a shortest path from
23    /// `source` to `v`. Empty if `v` is unreachable.
24    pub edge_paths: Vec<Vec<EdgeId>>,
25}
26
27/// Returns one shortest path from `source` to every vertex in the
28/// graph, using weighted edges (Dijkstra's algorithm).
29///
30/// For directed graphs, follows edges in the outgoing direction by
31/// default. Use [`get_shortest_paths_dijkstra_with_mode`] to control
32/// direction.
33///
34/// # Errors
35///
36/// - `InvalidArgument` if `source >= vcount()`, or weights are invalid
37///   (wrong length, negative, or NaN).
38///
39/// # Examples
40///
41/// ```
42/// use rust_igraph::{Graph, get_shortest_paths_dijkstra};
43///
44/// // Path 0-1-2-3, weights [1, 1, 1, 10] — last edge is a heavy shortcut.
45/// let mut g = Graph::with_vertices(4);
46/// g.add_edge(0, 1).unwrap();
47/// g.add_edge(1, 2).unwrap();
48/// g.add_edge(2, 3).unwrap();
49/// g.add_edge(0, 3).unwrap(); // heavy
50/// let r = get_shortest_paths_dijkstra(&g, 0, &[1.0, 1.0, 1.0, 10.0]).unwrap();
51/// assert_eq!(r.vertex_paths[3], vec![0, 1, 2, 3]);
52/// assert_eq!(r.edge_paths[3], vec![0, 1, 2]);
53/// ```
54pub fn get_shortest_paths_dijkstra(
55    graph: &Graph,
56    source: VertexId,
57    weights: &[f64],
58) -> IgraphResult<ShortestPathsDijkstra> {
59    let dp = dijkstra_paths(graph, source, weights)?;
60    Ok(build_all_paths(graph, source, &dp))
61}
62
63/// Mode-aware [`get_shortest_paths_dijkstra`].
64///
65/// For directed graphs, `mode` controls traversal direction. For
66/// undirected graphs, `mode` is ignored.
67///
68/// # Examples
69///
70/// ```
71/// use rust_igraph::{Graph, get_shortest_paths_dijkstra_with_mode, DijkstraMode};
72///
73/// // Directed 0→1→2 with unit weights.
74/// let mut g = Graph::new(3, true).unwrap();
75/// g.add_edge(0, 1).unwrap();
76/// g.add_edge(1, 2).unwrap();
77/// let r = get_shortest_paths_dijkstra_with_mode(&g, 0, &[1.0, 1.0], DijkstraMode::Out).unwrap();
78/// assert_eq!(r.vertex_paths[2], vec![0, 1, 2]);
79/// assert_eq!(r.edge_paths[2], vec![0, 1]);
80/// ```
81pub fn get_shortest_paths_dijkstra_with_mode(
82    graph: &Graph,
83    source: VertexId,
84    weights: &[f64],
85    mode: DijkstraMode,
86) -> IgraphResult<ShortestPathsDijkstra> {
87    let dp = dijkstra_paths_with_mode(graph, source, weights, mode)?;
88    Ok(build_all_paths(graph, source, &dp))
89}
90
91fn build_all_paths(graph: &Graph, source: VertexId, dp: &DijkstraPaths) -> ShortestPathsDijkstra {
92    let n = graph.vcount() as usize;
93    let mut vertex_paths: Vec<Vec<VertexId>> = Vec::with_capacity(n);
94    let mut edge_paths: Vec<Vec<EdgeId>> = Vec::with_capacity(n);
95
96    for v in 0..n {
97        #[allow(clippy::cast_possible_truncation)]
98        let v_id = v as u32;
99        if dp.distances[v].is_none() {
100            vertex_paths.push(Vec::new());
101            edge_paths.push(Vec::new());
102            continue;
103        }
104        if v_id == source {
105            vertex_paths.push(vec![source]);
106            edge_paths.push(Vec::new());
107            continue;
108        }
109        let mut vs = Vec::new();
110        let mut es = Vec::new();
111        let mut cur = v_id;
112        while cur != source {
113            vs.push(cur);
114            if let Some(eid) = dp.inbound_edges[cur as usize] {
115                es.push(eid);
116            }
117            match dp.parents.get(cur as usize).copied().flatten() {
118                Some(p) => cur = p,
119                None => break,
120            }
121        }
122        vs.push(source);
123        vs.reverse();
124        es.reverse();
125        vertex_paths.push(vs);
126        edge_paths.push(es);
127    }
128
129    ShortestPathsDijkstra {
130        vertex_paths,
131        edge_paths,
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn singleton() {
141        let g = Graph::with_vertices(1);
142        let r = get_shortest_paths_dijkstra(&g, 0, &[]).unwrap();
143        assert_eq!(r.vertex_paths, vec![vec![0]]);
144        assert_eq!(r.edge_paths, vec![vec![]]);
145    }
146
147    #[test]
148    fn path_graph_unit_weights() {
149        let mut g = Graph::with_vertices(4);
150        g.add_edge(0, 1).unwrap();
151        g.add_edge(1, 2).unwrap();
152        g.add_edge(2, 3).unwrap();
153        let r = get_shortest_paths_dijkstra(&g, 0, &[1.0, 1.0, 1.0]).unwrap();
154        assert_eq!(r.vertex_paths[0], vec![0]);
155        assert_eq!(r.vertex_paths[1], vec![0, 1]);
156        assert_eq!(r.vertex_paths[2], vec![0, 1, 2]);
157        assert_eq!(r.vertex_paths[3], vec![0, 1, 2, 3]);
158        assert_eq!(r.edge_paths[0], vec![]);
159        assert_eq!(r.edge_paths[1], vec![0]);
160        assert_eq!(r.edge_paths[2], vec![0, 1]);
161        assert_eq!(r.edge_paths[3], vec![0, 1, 2]);
162    }
163
164    #[test]
165    fn shortcut_with_high_weight() {
166        let mut g = Graph::with_vertices(4);
167        g.add_edge(0, 1).unwrap(); // e0: weight 1
168        g.add_edge(1, 2).unwrap(); // e1: weight 1
169        g.add_edge(2, 3).unwrap(); // e2: weight 1
170        g.add_edge(0, 3).unwrap(); // e3: weight 10
171        let r = get_shortest_paths_dijkstra(&g, 0, &[1.0, 1.0, 1.0, 10.0]).unwrap();
172        assert_eq!(r.vertex_paths[3], vec![0, 1, 2, 3]);
173        assert_eq!(r.edge_paths[3], vec![0, 1, 2]);
174    }
175
176    #[test]
177    fn shortcut_with_low_weight() {
178        let mut g = Graph::with_vertices(4);
179        g.add_edge(0, 1).unwrap(); // e0: weight 5
180        g.add_edge(1, 2).unwrap(); // e1: weight 5
181        g.add_edge(2, 3).unwrap(); // e2: weight 5
182        g.add_edge(0, 3).unwrap(); // e3: weight 1
183        let r = get_shortest_paths_dijkstra(&g, 0, &[5.0, 5.0, 5.0, 1.0]).unwrap();
184        assert_eq!(r.vertex_paths[3], vec![0, 3]);
185        assert_eq!(r.edge_paths[3], vec![3]);
186    }
187
188    #[test]
189    fn disconnected_graph() {
190        let mut g = Graph::with_vertices(4);
191        g.add_edge(0, 1).unwrap();
192        g.add_edge(2, 3).unwrap();
193        let r = get_shortest_paths_dijkstra(&g, 0, &[1.0, 1.0]).unwrap();
194        assert_eq!(r.vertex_paths[0], vec![0]);
195        assert_eq!(r.vertex_paths[1], vec![0, 1]);
196        assert!(r.vertex_paths[2].is_empty());
197        assert!(r.vertex_paths[3].is_empty());
198        assert!(r.edge_paths[2].is_empty());
199        assert!(r.edge_paths[3].is_empty());
200    }
201
202    #[test]
203    fn source_out_of_range() {
204        let g = Graph::with_vertices(3);
205        assert!(get_shortest_paths_dijkstra(&g, 5, &[]).is_err());
206    }
207
208    #[test]
209    fn directed_out() {
210        let mut g = Graph::new(3, true).unwrap();
211        g.add_edge(0, 1).unwrap();
212        g.add_edge(1, 2).unwrap();
213        let r =
214            get_shortest_paths_dijkstra_with_mode(&g, 0, &[1.0, 1.0], DijkstraMode::Out).unwrap();
215        assert_eq!(r.vertex_paths[2], vec![0, 1, 2]);
216        assert_eq!(r.edge_paths[2], vec![0, 1]);
217    }
218
219    #[test]
220    fn directed_in() {
221        let mut g = Graph::new(3, true).unwrap();
222        g.add_edge(0, 1).unwrap();
223        g.add_edge(1, 2).unwrap();
224        let r =
225            get_shortest_paths_dijkstra_with_mode(&g, 2, &[1.0, 1.0], DijkstraMode::In).unwrap();
226        assert_eq!(r.vertex_paths[0], vec![2, 1, 0]);
227        assert_eq!(r.edge_paths[0], vec![1, 0]);
228    }
229
230    #[test]
231    fn directed_unreachable() {
232        let mut g = Graph::new(3, true).unwrap();
233        g.add_edge(0, 1).unwrap();
234        g.add_edge(1, 2).unwrap();
235        let r =
236            get_shortest_paths_dijkstra_with_mode(&g, 2, &[1.0, 1.0], DijkstraMode::Out).unwrap();
237        assert_eq!(r.vertex_paths[2], vec![2]);
238        assert!(r.vertex_paths[0].is_empty());
239        assert!(r.vertex_paths[1].is_empty());
240    }
241
242    #[test]
243    fn triangle_nonuniform_weights() {
244        let mut g = Graph::with_vertices(3);
245        g.add_edge(0, 1).unwrap(); // e0: weight 1
246        g.add_edge(0, 2).unwrap(); // e1: weight 4
247        g.add_edge(1, 2).unwrap(); // e2: weight 2
248        let r = get_shortest_paths_dijkstra(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
249        // 0→2 directly costs 4, but 0→1→2 costs 3.
250        assert_eq!(r.vertex_paths[2], vec![0, 1, 2]);
251        assert_eq!(r.edge_paths[2], vec![0, 2]);
252    }
253
254    #[test]
255    fn edge_path_lengths_consistent() {
256        let mut g = Graph::with_vertices(5);
257        g.add_edge(0, 1).unwrap();
258        g.add_edge(1, 2).unwrap();
259        g.add_edge(2, 3).unwrap();
260        g.add_edge(3, 4).unwrap();
261        let r = get_shortest_paths_dijkstra(&g, 0, &[1.0; 4]).unwrap();
262        for v in 0..5 {
263            if r.vertex_paths[v].is_empty() {
264                assert!(r.edge_paths[v].is_empty());
265            } else {
266                assert_eq!(
267                    r.edge_paths[v].len() + 1,
268                    r.vertex_paths[v].len(),
269                    "edge count should be vertex count - 1 for vertex {v}"
270                );
271            }
272        }
273    }
274}