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    graph.neighbors_iter(source)?; // validate source
53
54    let n_us = n as usize;
55    let mut dist: Vec<Option<u32>> = vec![None; n_us];
56    let mut queue: VecDeque<VertexId> = VecDeque::new();
57
58    dist[source as usize] = Some(0);
59    queue.push_back(source);
60
61    while let Some(v) = queue.pop_front() {
62        let next = dist[v as usize]
63            .ok_or(crate::core::IgraphError::Internal(
64                "dequeued vertex has no distance",
65            ))?
66            .checked_add(1)
67            .ok_or(crate::core::IgraphError::Internal(
68                "distance overflow (graph diameter exceeds u32::MAX)",
69            ))?;
70        for w in graph.neighbors_iter(v)? {
71            if dist[w as usize].is_none() {
72                dist[w as usize] = Some(next);
73                queue.push_back(w);
74            }
75        }
76    }
77
78    Ok(dist)
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn empty_graph_yields_empty_distances() {
87        let g = Graph::with_vertices(0);
88        let d = distances(&g, 0);
89        // No source can be valid in an empty graph, but per the contract
90        // we short-circuit and return an empty Vec without consulting
91        // `source`. (Matches the BFS Phase-0 behaviour.)
92        assert_eq!(d.unwrap(), Vec::<Option<u32>>::new());
93    }
94
95    #[test]
96    fn single_vertex_distance_to_self_is_zero() {
97        let g = Graph::with_vertices(1);
98        let d = distances(&g, 0).unwrap();
99        assert_eq!(d, vec![Some(0)]);
100    }
101
102    #[test]
103    fn invalid_source_errors() {
104        let g = Graph::with_vertices(3);
105        let err = distances(&g, 5);
106        assert!(err.is_err(), "expected error for source >= vcount");
107    }
108
109    #[test]
110    fn path_distances_increase_by_one() {
111        // 0 - 1 - 2 - 3 - 4 (undirected path).
112        let mut g = Graph::with_vertices(5);
113        for i in 0..4 {
114            g.add_edge(i, i + 1).unwrap();
115        }
116        let d = distances(&g, 0).unwrap();
117        assert_eq!(d, vec![Some(0), Some(1), Some(2), Some(3), Some(4)]);
118    }
119
120    #[test]
121    fn isolated_components_are_unreachable() {
122        // {0,1,2} - {3,4}.
123        let mut g = Graph::with_vertices(5);
124        g.add_edge(0, 1).unwrap();
125        g.add_edge(1, 2).unwrap();
126        g.add_edge(3, 4).unwrap();
127        let d = distances(&g, 0).unwrap();
128        assert_eq!(d, vec![Some(0), Some(1), Some(2), None, None]);
129    }
130
131    #[test]
132    fn self_loop_does_not_affect_distance() {
133        // Self-loop on the source plus 0-1.
134        let mut g = Graph::with_vertices(2);
135        g.add_edge(0, 0).unwrap();
136        g.add_edge(0, 1).unwrap();
137        let d = distances(&g, 0).unwrap();
138        assert_eq!(d, vec![Some(0), Some(1)]);
139    }
140
141    #[test]
142    fn parallel_edges_do_not_affect_distance() {
143        // Three copies of (0,1).
144        let mut g = Graph::with_vertices(2);
145        g.add_edge(0, 1).unwrap();
146        g.add_edge(0, 1).unwrap();
147        g.add_edge(0, 1).unwrap();
148        let d = distances(&g, 0).unwrap();
149        assert_eq!(d, vec![Some(0), Some(1)]);
150    }
151
152    #[test]
153    fn directed_graph_uses_out_edges() {
154        // 0 -> 1 -> 2: forward distances 0,1,2; from 2 only 2 reachable.
155        let mut g = Graph::new(3, true).unwrap();
156        g.add_edge(0, 1).unwrap();
157        g.add_edge(1, 2).unwrap();
158        let d_from_zero = distances(&g, 0).unwrap();
159        assert_eq!(d_from_zero, vec![Some(0), Some(1), Some(2)]);
160        let d_from_two = distances(&g, 2).unwrap();
161        assert_eq!(d_from_two, vec![None, None, Some(0)]);
162    }
163
164    #[test]
165    fn cycle_distances_are_minimum_of_two_directions() {
166        // Undirected 6-cycle: 0-1-2-3-4-5-0. From 0:
167        //   1 → 1,  2 → 2,  3 → 3,  4 → 2,  5 → 1.
168        let mut g = Graph::with_vertices(6);
169        for i in 0..5 {
170            g.add_edge(i, i + 1).unwrap();
171        }
172        g.add_edge(5, 0).unwrap();
173        let d = distances(&g, 0).unwrap();
174        assert_eq!(
175            d,
176            vec![Some(0), Some(1), Some(2), Some(3), Some(2), Some(1)]
177        );
178    }
179}