Skip to main content

rust_igraph/algorithms/paths/
distances.rs

1//! Unweighted single-source shortest-path distances (ALGO-SP-006).
2//!
3//! Counterpart of `igraph_distances(_, NULL, _, single_from, all_to, IGRAPH_OUT)`
4//! from `references/igraph/src/paths/unweighted.c:273-325` (reduced single-source
5//! single-mode-OUT slice). The full multi-source / multi-mode / cutoff matrix
6//! variant ships in a follow-up AWU.
7//!
8//! Algorithm: BFS from `source`; the first time a vertex `v` is dequeued
9//! its distance is the shortest-path length from `source` (because BFS
10//! discovers vertices in non-decreasing distance order). Vertices never
11//! reached get `None`.
12//!
13//! Result type is `Vec<Option<u32>>`, mirroring upstream's
14//! `IGRAPH_INFINITY` sentinel. Conversion to a `f64` matrix with `inf` for
15//! unreachable is the caller's responsibility.
16
17use std::collections::VecDeque;
18
19use crate::core::{Graph, IgraphResult, VertexId};
20
21/// Unweighted shortest-path lengths from `source` to every vertex.
22///
23/// `result[v] == Some(d)` if `v` is reachable from `source` in `d`
24/// edges (`d == 0` for the source itself); `None` if `v` is in a
25/// different connected component (undirected) or has no out-path from
26/// `source` (directed).
27///
28/// Counterpart of `igraph_distances(_, NULL, _, single_from, all_to,
29/// IGRAPH_OUT)`. For directed graphs traversal follows out-edges
30/// (matching `IGRAPH_OUT`); for undirected graphs every edge counts
31/// both ways.
32///
33/// # Examples
34///
35/// ```
36/// use rust_igraph::{Graph, distances};
37///
38/// // Undirected path 0-1-2-3, plus an isolated vertex 4.
39/// let mut g = Graph::with_vertices(5);
40/// g.add_edge(0, 1).unwrap();
41/// g.add_edge(1, 2).unwrap();
42/// g.add_edge(2, 3).unwrap();
43///
44/// let d = distances(&g, 0).unwrap();
45/// assert_eq!(d, vec![Some(0), Some(1), Some(2), Some(3), None]);
46/// ```
47pub fn distances(graph: &Graph, source: VertexId) -> IgraphResult<Vec<Option<u32>>> {
48    let n = graph.vcount();
49    if n == 0 {
50        return Ok(Vec::new());
51    }
52    // `neighbors()` will reject an out-of-range `source`.
53    graph.neighbors(source)?;
54
55    let n_us = n as usize;
56    let mut dist: Vec<Option<u32>> = vec![None; n_us];
57    let mut queue: VecDeque<VertexId> = VecDeque::new();
58
59    dist[source as usize] = Some(0);
60    queue.push_back(source);
61
62    while let Some(v) = queue.pop_front() {
63        let next = dist[v as usize]
64            .expect("dequeued vertex has been visited")
65            .checked_add(1)
66            .ok_or(crate::core::IgraphError::Internal(
67                "distance overflow (graph diameter exceeds u32::MAX)",
68            ))?;
69        for w in graph.neighbors(v)? {
70            if dist[w as usize].is_none() {
71                dist[w as usize] = Some(next);
72                queue.push_back(w);
73            }
74        }
75    }
76
77    Ok(dist)
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn empty_graph_yields_empty_distances() {
86        let g = Graph::with_vertices(0);
87        let d = distances(&g, 0);
88        // No source can be valid in an empty graph, but per the contract
89        // we short-circuit and return an empty Vec without consulting
90        // `source`. (Matches the BFS Phase-0 behaviour.)
91        assert_eq!(d.unwrap(), Vec::<Option<u32>>::new());
92    }
93
94    #[test]
95    fn single_vertex_distance_to_self_is_zero() {
96        let g = Graph::with_vertices(1);
97        let d = distances(&g, 0).unwrap();
98        assert_eq!(d, vec![Some(0)]);
99    }
100
101    #[test]
102    fn invalid_source_errors() {
103        let g = Graph::with_vertices(3);
104        let err = distances(&g, 5);
105        assert!(err.is_err(), "expected error for source >= vcount");
106    }
107
108    #[test]
109    fn path_distances_increase_by_one() {
110        // 0 - 1 - 2 - 3 - 4 (undirected path).
111        let mut g = Graph::with_vertices(5);
112        for i in 0..4 {
113            g.add_edge(i, i + 1).unwrap();
114        }
115        let d = distances(&g, 0).unwrap();
116        assert_eq!(d, vec![Some(0), Some(1), Some(2), Some(3), Some(4)]);
117    }
118
119    #[test]
120    fn isolated_components_are_unreachable() {
121        // {0,1,2} - {3,4}.
122        let mut g = Graph::with_vertices(5);
123        g.add_edge(0, 1).unwrap();
124        g.add_edge(1, 2).unwrap();
125        g.add_edge(3, 4).unwrap();
126        let d = distances(&g, 0).unwrap();
127        assert_eq!(d, vec![Some(0), Some(1), Some(2), None, None]);
128    }
129
130    #[test]
131    fn self_loop_does_not_affect_distance() {
132        // Self-loop on the source plus 0-1.
133        let mut g = Graph::with_vertices(2);
134        g.add_edge(0, 0).unwrap();
135        g.add_edge(0, 1).unwrap();
136        let d = distances(&g, 0).unwrap();
137        assert_eq!(d, vec![Some(0), Some(1)]);
138    }
139
140    #[test]
141    fn parallel_edges_do_not_affect_distance() {
142        // Three copies of (0,1).
143        let mut g = Graph::with_vertices(2);
144        g.add_edge(0, 1).unwrap();
145        g.add_edge(0, 1).unwrap();
146        g.add_edge(0, 1).unwrap();
147        let d = distances(&g, 0).unwrap();
148        assert_eq!(d, vec![Some(0), Some(1)]);
149    }
150
151    #[test]
152    fn directed_graph_uses_out_edges() {
153        // 0 -> 1 -> 2: forward distances 0,1,2; from 2 only 2 reachable.
154        let mut g = Graph::new(3, true).unwrap();
155        g.add_edge(0, 1).unwrap();
156        g.add_edge(1, 2).unwrap();
157        let d_from_zero = distances(&g, 0).unwrap();
158        assert_eq!(d_from_zero, vec![Some(0), Some(1), Some(2)]);
159        let d_from_two = distances(&g, 2).unwrap();
160        assert_eq!(d_from_two, vec![None, None, Some(0)]);
161    }
162
163    #[test]
164    fn cycle_distances_are_minimum_of_two_directions() {
165        // Undirected 6-cycle: 0-1-2-3-4-5-0. From 0:
166        //   1 → 1,  2 → 2,  3 → 3,  4 → 2,  5 → 1.
167        let mut g = Graph::with_vertices(6);
168        for i in 0..5 {
169            g.add_edge(i, i + 1).unwrap();
170        }
171        g.add_edge(5, 0).unwrap();
172        let d = distances(&g, 0).unwrap();
173        assert_eq!(
174            d,
175            vec![Some(0), Some(1), Some(2), Some(3), Some(2), Some(1)]
176        );
177    }
178}