Skip to main content

rust_igraph/algorithms/paths/
all_shortest_paths.rs

1//! All shortest paths from a single source (ALGO-SP-059).
2//!
3//! Counterpart of `igraph_get_all_shortest_paths()` from
4//! `references/igraph/src/paths/all_shortest_paths.c`.
5//!
6//! Returns every shortest path from a source vertex to all reachable
7//! targets, using BFS with multi-parent tracking.
8
9use std::collections::VecDeque;
10
11use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
12
13/// Direction mode for [`get_all_shortest_paths`] and
14/// [`get_all_shortest_paths_with_mode`].
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum AllShortestPathsMode {
17    /// Follow outgoing edges.
18    Out,
19    /// Follow incoming edges.
20    In,
21    /// Ignore edge direction.
22    All,
23}
24
25/// Result of [`get_all_shortest_paths`] /
26/// [`get_all_shortest_paths_with_mode`].
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct AllShortestPaths {
29    /// All shortest paths grouped by target vertex. `paths[v]` is the
30    /// list of all shortest paths from the source to vertex `v`. If `v`
31    /// is unreachable, `paths[v]` is empty.
32    pub paths: Vec<Vec<Vec<VertexId>>>,
33    /// Number of shortest paths to each vertex. `nrgeo[v]` is the count
34    /// of shortest paths from source to `v` (0 if unreachable, 1 for
35    /// the source itself).
36    pub nrgeo: Vec<u64>,
37}
38
39/// Find all shortest paths from `source` to every reachable vertex.
40///
41/// Uses BFS (unweighted edges). For directed graphs, follows outgoing
42/// edges by default. Use [`get_all_shortest_paths_with_mode`] for
43/// direction control.
44///
45/// # Errors
46///
47/// - `InvalidArgument` if `source >= vcount()`.
48///
49/// # Examples
50///
51/// ```
52/// use rust_igraph::{Graph, get_all_shortest_paths};
53///
54/// // Diamond: 0-1, 0-2, 1-3, 2-3. Two shortest paths from 0 to 3.
55/// let mut g = Graph::with_vertices(4);
56/// g.add_edge(0, 1).unwrap();
57/// g.add_edge(0, 2).unwrap();
58/// g.add_edge(1, 3).unwrap();
59/// g.add_edge(2, 3).unwrap();
60/// let r = get_all_shortest_paths(&g, 0).unwrap();
61/// assert_eq!(r.paths[3].len(), 2); // two paths to vertex 3
62/// assert_eq!(r.nrgeo[3], 2);
63/// assert_eq!(r.paths[0], vec![vec![0]]); // self-path
64/// ```
65pub fn get_all_shortest_paths(graph: &Graph, source: VertexId) -> IgraphResult<AllShortestPaths> {
66    if graph.is_directed() {
67        return get_all_shortest_paths_directed(graph, source, DirectedMode::Out);
68    }
69    get_all_shortest_paths_undirected(graph, source)
70}
71
72/// Find all shortest paths from `source` with direction control.
73///
74/// For undirected graphs, `mode` is ignored.
75///
76/// # Errors
77///
78/// - `InvalidArgument` if `source >= vcount()`.
79///
80/// # Examples
81///
82/// ```
83/// use rust_igraph::{Graph, get_all_shortest_paths_with_mode, AllShortestPathsMode};
84///
85/// // Directed diamond: 0→1, 0→2, 1→3, 2→3.
86/// let mut g = Graph::new(4, true).unwrap();
87/// g.add_edge(0, 1).unwrap();
88/// g.add_edge(0, 2).unwrap();
89/// g.add_edge(1, 3).unwrap();
90/// g.add_edge(2, 3).unwrap();
91/// let r = get_all_shortest_paths_with_mode(&g, 0, AllShortestPathsMode::Out).unwrap();
92/// assert_eq!(r.paths[3].len(), 2);
93/// // In mode from vertex 3
94/// let r_in = get_all_shortest_paths_with_mode(&g, 3, AllShortestPathsMode::In).unwrap();
95/// assert_eq!(r_in.paths[0].len(), 2); // two paths back to 0
96/// ```
97pub fn get_all_shortest_paths_with_mode(
98    graph: &Graph,
99    source: VertexId,
100    mode: AllShortestPathsMode,
101) -> IgraphResult<AllShortestPaths> {
102    if !graph.is_directed() {
103        return get_all_shortest_paths_undirected(graph, source);
104    }
105    let dir_mode = match mode {
106        AllShortestPathsMode::Out => DirectedMode::Out,
107        AllShortestPathsMode::In => DirectedMode::In,
108        AllShortestPathsMode::All => DirectedMode::All,
109    };
110    get_all_shortest_paths_directed(graph, source, dir_mode)
111}
112
113#[derive(Clone, Copy)]
114enum DirectedMode {
115    Out,
116    In,
117    All,
118}
119
120fn get_all_shortest_paths_undirected(
121    graph: &Graph,
122    source: VertexId,
123) -> IgraphResult<AllShortestPaths> {
124    let n = graph.vcount();
125    if source >= n {
126        return Err(IgraphError::InvalidArgument(format!(
127            "get_all_shortest_paths: source {source} out of range (vcount={n})"
128        )));
129    }
130
131    let n_us = n as usize;
132    let mut dist: Vec<Option<u32>> = vec![None; n_us];
133    let mut parents: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
134    let mut nrgeo: Vec<u64> = vec![0; n_us];
135
136    dist[source as usize] = Some(0);
137    nrgeo[source as usize] = 1;
138
139    let mut queue = VecDeque::new();
140    queue.push_back(source);
141
142    while let Some(cur) = queue.pop_front() {
143        let cur_dist = dist[cur as usize].unwrap_or(0);
144        let next_dist = cur_dist + 1;
145        let neighbors = graph.neighbors(cur)?;
146        for &nb in &neighbors {
147            let nb_idx = nb as usize;
148            let nb_dist = dist[nb_idx];
149            match nb_dist {
150                None => {
151                    dist[nb_idx] = Some(next_dist);
152                    parents[nb_idx].push(cur);
153                    nrgeo[nb_idx] = nrgeo[cur as usize];
154                    queue.push_back(nb);
155                }
156                Some(d) if d == next_dist => {
157                    parents[nb_idx].push(cur);
158                    nrgeo[nb_idx] += nrgeo[cur as usize];
159                }
160                _ => {}
161            }
162        }
163    }
164
165    let paths = enumerate_all_paths(source, n_us, &parents, &dist);
166    Ok(AllShortestPaths { paths, nrgeo })
167}
168
169fn get_all_shortest_paths_directed(
170    graph: &Graph,
171    source: VertexId,
172    mode: DirectedMode,
173) -> IgraphResult<AllShortestPaths> {
174    let n = graph.vcount();
175    if source >= n {
176        return Err(IgraphError::InvalidArgument(format!(
177            "get_all_shortest_paths: source {source} out of range (vcount={n})"
178        )));
179    }
180
181    let n_us = n as usize;
182    let adj = build_adj(graph, n_us, mode)?;
183
184    let mut dist: Vec<Option<u32>> = vec![None; n_us];
185    let mut parents: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
186    let mut nrgeo: Vec<u64> = vec![0; n_us];
187
188    dist[source as usize] = Some(0);
189    nrgeo[source as usize] = 1;
190
191    let mut queue = VecDeque::new();
192    queue.push_back(source);
193
194    while let Some(cur) = queue.pop_front() {
195        let cur_dist = dist[cur as usize].unwrap_or(0);
196        let next_dist = cur_dist + 1;
197        for &nb in &adj[cur as usize] {
198            let nb_idx = nb as usize;
199            let nb_dist = dist[nb_idx];
200            match nb_dist {
201                None => {
202                    dist[nb_idx] = Some(next_dist);
203                    parents[nb_idx].push(cur);
204                    nrgeo[nb_idx] = nrgeo[cur as usize];
205                    queue.push_back(nb);
206                }
207                Some(d) if d == next_dist => {
208                    parents[nb_idx].push(cur);
209                    nrgeo[nb_idx] += nrgeo[cur as usize];
210                }
211                _ => {}
212            }
213        }
214    }
215
216    let paths = enumerate_all_paths(source, n_us, &parents, &dist);
217    Ok(AllShortestPaths { paths, nrgeo })
218}
219
220fn build_adj(graph: &Graph, n_us: usize, mode: DirectedMode) -> IgraphResult<Vec<Vec<VertexId>>> {
221    let m =
222        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
223    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
224    for eid in 0..m {
225        let (from, to) = graph.edge(eid)?;
226        match mode {
227            DirectedMode::Out => adj[from as usize].push(to),
228            DirectedMode::In => adj[to as usize].push(from),
229            DirectedMode::All => {
230                adj[from as usize].push(to);
231                if from != to {
232                    adj[to as usize].push(from);
233                }
234            }
235        }
236    }
237    Ok(adj)
238}
239
240fn enumerate_all_paths(
241    source: VertexId,
242    n_us: usize,
243    parents: &[Vec<VertexId>],
244    dist: &[Option<u32>],
245) -> Vec<Vec<Vec<VertexId>>> {
246    let mut result: Vec<Vec<Vec<VertexId>>> = vec![Vec::new(); n_us];
247
248    result[source as usize] = vec![vec![source]];
249
250    if n_us <= 1 {
251        return result;
252    }
253
254    // Process vertices in BFS order (by distance).
255    let mut order: Vec<(u32, usize)> = Vec::with_capacity(n_us);
256    for (v, d) in dist.iter().enumerate() {
257        if let Some(dv) = d {
258            order.push((*dv, v));
259        }
260    }
261    order.sort_unstable();
262
263    for &(_, v) in &order {
264        #[allow(clippy::cast_possible_truncation)]
265        let v_id = v as u32;
266        if v_id == source {
267            continue;
268        }
269        let mut paths_to_v: Vec<Vec<VertexId>> = Vec::new();
270        for &p in &parents[v] {
271            for parent_path in &result[p as usize] {
272                let mut path = parent_path.clone();
273                path.push(v_id);
274                paths_to_v.push(path);
275            }
276        }
277        result[v] = paths_to_v;
278    }
279
280    result
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn empty_graph_err() {
289        let g = Graph::with_vertices(0);
290        assert!(get_all_shortest_paths(&g, 0).is_err());
291    }
292
293    #[test]
294    fn singleton() {
295        let g = Graph::with_vertices(1);
296        let r = get_all_shortest_paths(&g, 0).unwrap();
297        assert_eq!(r.paths[0], vec![vec![0]]);
298        assert_eq!(r.nrgeo[0], 1);
299    }
300
301    #[test]
302    fn path_graph_unique() {
303        let mut g = Graph::with_vertices(4);
304        g.add_edge(0, 1).unwrap();
305        g.add_edge(1, 2).unwrap();
306        g.add_edge(2, 3).unwrap();
307        let r = get_all_shortest_paths(&g, 0).unwrap();
308        assert_eq!(r.paths[0], vec![vec![0]]);
309        assert_eq!(r.paths[1], vec![vec![0, 1]]);
310        assert_eq!(r.paths[2], vec![vec![0, 1, 2]]);
311        assert_eq!(r.paths[3], vec![vec![0, 1, 2, 3]]);
312        for v in 0..4 {
313            assert_eq!(r.nrgeo[v], 1);
314        }
315    }
316
317    #[test]
318    fn diamond_two_paths() {
319        // 0-1, 0-2, 1-3, 2-3: two shortest paths from 0 to 3.
320        let mut g = Graph::with_vertices(4);
321        g.add_edge(0, 1).unwrap();
322        g.add_edge(0, 2).unwrap();
323        g.add_edge(1, 3).unwrap();
324        g.add_edge(2, 3).unwrap();
325        let r = get_all_shortest_paths(&g, 0).unwrap();
326        assert_eq!(r.nrgeo[3], 2);
327        assert_eq!(r.paths[3].len(), 2);
328        let mut sorted_paths = r.paths[3].clone();
329        sorted_paths.sort();
330        assert_eq!(sorted_paths, vec![vec![0, 1, 3], vec![0, 2, 3]]);
331    }
332
333    #[test]
334    fn two_components_unreachable() {
335        let mut g = Graph::with_vertices(4);
336        g.add_edge(0, 1).unwrap();
337        g.add_edge(2, 3).unwrap();
338        let r = get_all_shortest_paths(&g, 0).unwrap();
339        assert_eq!(r.paths[1], vec![vec![0, 1]]);
340        assert!(r.paths[2].is_empty());
341        assert!(r.paths[3].is_empty());
342        assert_eq!(r.nrgeo[2], 0);
343        assert_eq!(r.nrgeo[3], 0);
344    }
345
346    #[test]
347    fn cycle_5_multiple_shortest() {
348        // C5: 0-1-2-3-4-0. From 0 to 3: two paths of length 2
349        // (0-4-3 and 0-1-2-3 are NOT same length; 0-4-3 is length 2, 0-1-2-3 is length 3)
350        // Actually from 0: paths to 3 are 0-1-2-3 (len 3) and 0-4-3 (len 2).
351        // Wait: 0-4-3. Edge 4-0 means 0's neighbors include 4. Edge 3-4.
352        // Dist from 0: 1→{1,4}, 2→{2,3}, so shortest to 3 is 2 via 0-4-3.
353        // But also 0-1-2 is dist 2 to vertex 2; and 2-3 is dist 1.
354        // So dist(0,3) = 2 via 0-4-3. But 0-1-2-3 = dist 3. So only ONE shortest path.
355        // Actually edges are 0-1, 1-2, 2-3, 3-4, 4-0.
356        // From 0: neighbors are 1 and 4 (dist 1).
357        // From 1: neighbor 2 (dist 2). From 4: neighbor 3 (dist 2).
358        // So vertex 3 reached at dist 2 via 4. Vertex 2 reached at dist 2 via 1.
359        // From 2: neighbor 3 at dist 3 > 2 (already found). So path to 3: only 0-4-3.
360        let mut g = Graph::with_vertices(5);
361        g.add_edge(0, 1).unwrap();
362        g.add_edge(1, 2).unwrap();
363        g.add_edge(2, 3).unwrap();
364        g.add_edge(3, 4).unwrap();
365        g.add_edge(4, 0).unwrap();
366        let r = get_all_shortest_paths(&g, 0).unwrap();
367        assert_eq!(r.nrgeo[1], 1);
368        assert_eq!(r.nrgeo[4], 1);
369        assert_eq!(r.nrgeo[2], 1); // only via 0-1-2
370        assert_eq!(r.nrgeo[3], 1); // only via 0-4-3
371    }
372
373    #[test]
374    fn grid_multiple_paths() {
375        // 2x3 grid:
376        // 0-1-2
377        // |   |
378        // 3-4-5
379        // From 0 to 5: shortest is length 3.
380        // Paths: 0-1-2-5 and 0-3-4-5 and 0-1-4-5 and 0-3-4-... wait.
381        // Edges: 0-1, 1-2, 0-3, 2-5, 3-4, 4-5
382        // Plus 1-4 for full grid? No. Let's add 1-4 to make it a full grid.
383        // Actually let me construct it carefully:
384        // 0-1, 1-2 (top row), 3-4, 4-5 (bottom row), 0-3, 1-4, 2-5 (columns)
385        let mut g = Graph::with_vertices(6);
386        g.add_edge(0, 1).unwrap();
387        g.add_edge(1, 2).unwrap();
388        g.add_edge(3, 4).unwrap();
389        g.add_edge(4, 5).unwrap();
390        g.add_edge(0, 3).unwrap();
391        g.add_edge(1, 4).unwrap();
392        g.add_edge(2, 5).unwrap();
393        let r = get_all_shortest_paths(&g, 0).unwrap();
394        // Dist to 5: 0→1→2→5 (3), 0→1→4→5 (3), 0→3→4→5 (3)
395        assert_eq!(r.nrgeo[5], 3);
396        assert_eq!(r.paths[5].len(), 3);
397        let mut sorted = r.paths[5].clone();
398        sorted.sort();
399        assert_eq!(
400            sorted,
401            vec![vec![0, 1, 2, 5], vec![0, 1, 4, 5], vec![0, 3, 4, 5]]
402        );
403    }
404
405    #[test]
406    fn directed_out() {
407        // Directed diamond: 0→1, 0→2, 1→3, 2→3.
408        let mut g = Graph::new(4, true).unwrap();
409        g.add_edge(0, 1).unwrap();
410        g.add_edge(0, 2).unwrap();
411        g.add_edge(1, 3).unwrap();
412        g.add_edge(2, 3).unwrap();
413        let r = get_all_shortest_paths(&g, 0).unwrap();
414        assert_eq!(r.nrgeo[3], 2);
415        assert_eq!(r.paths[3].len(), 2);
416    }
417
418    #[test]
419    fn directed_in() {
420        // Directed diamond: 0→1, 0→2, 1→3, 2→3. From 3, In mode.
421        let mut g = Graph::new(4, true).unwrap();
422        g.add_edge(0, 1).unwrap();
423        g.add_edge(0, 2).unwrap();
424        g.add_edge(1, 3).unwrap();
425        g.add_edge(2, 3).unwrap();
426        let r = get_all_shortest_paths_with_mode(&g, 3, AllShortestPathsMode::In).unwrap();
427        assert_eq!(r.nrgeo[0], 2);
428        assert_eq!(r.paths[0].len(), 2);
429        let mut sorted = r.paths[0].clone();
430        sorted.sort();
431        assert_eq!(sorted, vec![vec![3, 1, 0], vec![3, 2, 0]]);
432    }
433
434    #[test]
435    fn directed_all() {
436        // 0→1→2. All mode from 2.
437        let mut g = Graph::new(3, true).unwrap();
438        g.add_edge(0, 1).unwrap();
439        g.add_edge(1, 2).unwrap();
440        let r = get_all_shortest_paths_with_mode(&g, 2, AllShortestPathsMode::All).unwrap();
441        assert_eq!(r.paths[0], vec![vec![2, 1, 0]]);
442        assert_eq!(r.paths[1], vec![vec![2, 1]]);
443    }
444
445    #[test]
446    fn directed_unreachable() {
447        // 0→1→2. Out mode from 2.
448        let mut g = Graph::new(3, true).unwrap();
449        g.add_edge(0, 1).unwrap();
450        g.add_edge(1, 2).unwrap();
451        let r = get_all_shortest_paths(&g, 2).unwrap();
452        assert_eq!(r.paths[2], vec![vec![2]]);
453        assert!(r.paths[0].is_empty());
454        assert!(r.paths[1].is_empty());
455    }
456
457    #[test]
458    fn source_out_of_range() {
459        let g = Graph::with_vertices(3);
460        assert!(get_all_shortest_paths(&g, 5).is_err());
461    }
462
463    #[test]
464    fn self_loop_no_effect() {
465        let mut g = Graph::with_vertices(3);
466        g.add_edge(0, 1).unwrap();
467        g.add_edge(1, 1).unwrap();
468        g.add_edge(1, 2).unwrap();
469        let r = get_all_shortest_paths(&g, 0).unwrap();
470        assert_eq!(r.paths[2], vec![vec![0, 1, 2]]);
471        assert_eq!(r.nrgeo[2], 1);
472    }
473
474    #[test]
475    fn nrgeo_star() {
476        // Star: center 0, leaves 1,2,3.
477        let mut g = Graph::with_vertices(4);
478        g.add_edge(0, 1).unwrap();
479        g.add_edge(0, 2).unwrap();
480        g.add_edge(0, 3).unwrap();
481        let r = get_all_shortest_paths(&g, 0).unwrap();
482        assert_eq!(r.nrgeo[0], 1);
483        for leaf in 1u32..4 {
484            assert_eq!(r.nrgeo[leaf as usize], 1);
485            assert_eq!(r.paths[leaf as usize], vec![vec![0, leaf]]);
486        }
487        // From leaf 1: paths to 2 and 3 go through center.
488        let r1 = get_all_shortest_paths(&g, 1).unwrap();
489        assert_eq!(r1.nrgeo[2], 1);
490        assert_eq!(r1.paths[2], vec![vec![1, 0, 2]]);
491    }
492}