Skip to main content

rust_igraph/algorithms/paths/
distances_all.rs

1//! All-pairs unweighted shortest distances (ALGO-SP-058).
2//!
3//! Counterpart of `igraph_distances()` (multi-source mode) from
4//! `references/igraph/src/paths/unweighted.c`.
5//!
6//! Computes shortest-path distances between all pairs of vertices
7//! using BFS from each source. Returns an n×n flat matrix.
8
9use std::collections::VecDeque;
10
11use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
12
13/// All-pairs unweighted shortest distances.
14///
15/// Returns a flat `Vec<Option<u32>>` of length `n * n` in row-major
16/// order, where `result[i * n + j]` is the shortest-path distance
17/// from vertex `i` to vertex `j`. `None` means unreachable.
18///
19/// For undirected graphs, the matrix is symmetric. For directed
20/// graphs, follows outgoing edges by default; use
21/// [`distances_all_with_mode`] for direction control.
22///
23/// # Errors
24///
25/// Returns an error if internal BFS encounters an issue (should not
26/// happen for valid graphs).
27///
28/// # Examples
29///
30/// ```
31/// use rust_igraph::{Graph, distances_all};
32///
33/// // Triangle: all distances are 0 or 1.
34/// let mut g = Graph::with_vertices(3);
35/// g.add_edge(0, 1).unwrap();
36/// g.add_edge(1, 2).unwrap();
37/// g.add_edge(2, 0).unwrap();
38/// let d = distances_all(&g).unwrap();
39/// assert_eq!(d[0 * 3 + 1], Some(1)); // 0→1
40/// assert_eq!(d[0 * 3 + 2], Some(1)); // 0→2
41/// assert_eq!(d[1 * 3 + 2], Some(1)); // 1→2
42/// assert_eq!(d[0 * 3 + 0], Some(0)); // self
43/// ```
44pub fn distances_all(graph: &Graph) -> IgraphResult<Vec<Option<u32>>> {
45    let n = graph.vcount();
46    let n_us = n as usize;
47
48    if n == 0 {
49        return Ok(Vec::new());
50    }
51
52    let mut result = vec![
53        None;
54        n_us.checked_mul(n_us).ok_or_else(|| {
55            IgraphError::InvalidArgument("distances_all: n*n overflows usize".into())
56        })?
57    ];
58
59    if graph.is_directed() {
60        let adj = build_out_adj(graph, n_us)?;
61        for src in 0..n {
62            bfs_distances_with_adj(&adj, src, n_us, &mut result);
63        }
64    } else {
65        for src in 0..n {
66            bfs_distances_undirected(graph, src, n_us, &mut result)?;
67        }
68    }
69
70    Ok(result)
71}
72
73/// Direction mode for [`distances_all_with_mode`].
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum DistancesMode {
76    /// Follow outgoing edges.
77    Out,
78    /// Follow incoming edges.
79    In,
80    /// Ignore edge direction.
81    All,
82}
83
84/// All-pairs shortest distances with direction control.
85///
86/// For undirected graphs, `mode` is ignored.
87///
88/// # Examples
89///
90/// ```
91/// use rust_igraph::{Graph, distances_all_with_mode, DistancesMode};
92///
93/// // Directed: 0→1→2
94/// let mut g = Graph::new(3, true).unwrap();
95/// g.add_edge(0, 1).unwrap();
96/// g.add_edge(1, 2).unwrap();
97/// let d = distances_all_with_mode(&g, DistancesMode::Out).unwrap();
98/// assert_eq!(d[0 * 3 + 2], Some(2)); // 0→1→2
99/// assert_eq!(d[2 * 3 + 0], None);    // 2 cannot reach 0
100/// let d_in = distances_all_with_mode(&g, DistancesMode::In).unwrap();
101/// assert_eq!(d_in[2 * 3 + 0], Some(2)); // follow incoming from 2
102/// ```
103pub fn distances_all_with_mode(
104    graph: &Graph,
105    mode: DistancesMode,
106) -> IgraphResult<Vec<Option<u32>>> {
107    let n = graph.vcount();
108    let n_us = n as usize;
109
110    if n == 0 {
111        return Ok(Vec::new());
112    }
113
114    let mut result = vec![
115        None;
116        n_us.checked_mul(n_us).ok_or_else(|| {
117            IgraphError::InvalidArgument("distances_all_with_mode: n*n overflows usize".into())
118        })?
119    ];
120
121    if !graph.is_directed() {
122        for src in 0..n {
123            bfs_distances_undirected(graph, src, n_us, &mut result)?;
124        }
125        return Ok(result);
126    }
127
128    let adj = match mode {
129        DistancesMode::Out => build_out_adj(graph, n_us)?,
130        DistancesMode::In => build_in_adj(graph, n_us)?,
131        DistancesMode::All => build_all_adj(graph, n_us)?,
132    };
133
134    for src in 0..n {
135        bfs_distances_with_adj(&adj, src, n_us, &mut result);
136    }
137
138    Ok(result)
139}
140
141/// BFS from `source` using `graph.neighbors()` (undirected).
142fn bfs_distances_undirected(
143    graph: &Graph,
144    source: VertexId,
145    n_us: usize,
146    result: &mut [Option<u32>],
147) -> IgraphResult<()> {
148    let src_us = source as usize;
149    let row_offset = src_us * n_us;
150
151    let mut visited = vec![false; n_us];
152    let mut queue = VecDeque::new();
153
154    visited[src_us] = true;
155    result[row_offset + src_us] = Some(0);
156    queue.push_back((source, 0u32));
157
158    while let Some((cur, dist)) = queue.pop_front() {
159        let neighbors = graph.neighbors(cur)?;
160        let next_dist = dist + 1;
161        for &nb in &neighbors {
162            let nb_idx = nb as usize;
163            if !visited[nb_idx] {
164                visited[nb_idx] = true;
165                result[row_offset + nb_idx] = Some(next_dist);
166                queue.push_back((nb, next_dist));
167            }
168        }
169    }
170
171    Ok(())
172}
173
174/// BFS from `source` using a pre-built adjacency list.
175fn bfs_distances_with_adj(
176    adj: &[Vec<VertexId>],
177    source: VertexId,
178    n_us: usize,
179    result: &mut [Option<u32>],
180) {
181    let src_us = source as usize;
182    let row_offset = src_us * n_us;
183
184    let mut visited = vec![false; n_us];
185    let mut queue = VecDeque::new();
186
187    visited[src_us] = true;
188    result[row_offset + src_us] = Some(0);
189    queue.push_back((source, 0u32));
190
191    while let Some((cur, dist)) = queue.pop_front() {
192        let next_dist = dist + 1;
193        for &nb in &adj[cur as usize] {
194            let nb_idx = nb as usize;
195            if !visited[nb_idx] {
196                visited[nb_idx] = true;
197                result[row_offset + nb_idx] = Some(next_dist);
198                queue.push_back((nb, next_dist));
199            }
200        }
201    }
202}
203
204fn build_out_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
205    let m =
206        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
207    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
208    for eid in 0..m {
209        let (from, to) = graph.edge(eid)?;
210        adj[from as usize].push(to);
211    }
212    Ok(adj)
213}
214
215fn build_in_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
216    let m =
217        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
218    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
219    for eid in 0..m {
220        let (from, to) = graph.edge(eid)?;
221        adj[to as usize].push(from);
222    }
223    Ok(adj)
224}
225
226fn build_all_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
227    let m =
228        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
229    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
230    for eid in 0..m {
231        let (from, to) = graph.edge(eid)?;
232        adj[from as usize].push(to);
233        if from != to {
234            adj[to as usize].push(from);
235        }
236    }
237    Ok(adj)
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn empty_graph() {
246        let g = Graph::with_vertices(0);
247        let d = distances_all(&g).unwrap();
248        assert!(d.is_empty());
249    }
250
251    #[test]
252    fn singleton() {
253        let g = Graph::with_vertices(1);
254        let d = distances_all(&g).unwrap();
255        assert_eq!(d, vec![Some(0)]);
256    }
257
258    #[test]
259    fn path_graph() {
260        let mut g = Graph::with_vertices(4);
261        g.add_edge(0, 1).unwrap();
262        g.add_edge(1, 2).unwrap();
263        g.add_edge(2, 3).unwrap();
264        let d = distances_all(&g).unwrap();
265        let n = 4usize;
266        assert_eq!(d[0], Some(0)); // row 0, col 0
267        assert_eq!(d[1], Some(1)); // row 0, col 1
268        assert_eq!(d[2], Some(2)); // row 0, col 2
269        assert_eq!(d[3], Some(3)); // row 0, col 3
270        assert_eq!(d[3 * n], Some(3)); // row 3, col 0
271        assert_eq!(d[n + 3], Some(2)); // row 1, col 3
272    }
273
274    #[test]
275    fn triangle() {
276        let mut g = Graph::with_vertices(3);
277        g.add_edge(0, 1).unwrap();
278        g.add_edge(1, 2).unwrap();
279        g.add_edge(2, 0).unwrap();
280        let d = distances_all(&g).unwrap();
281        let n = 3;
282        for i in 0..n {
283            assert_eq!(d[i * n + i], Some(0));
284            for j in 0..n {
285                if i != j {
286                    assert_eq!(d[i * n + j], Some(1));
287                }
288            }
289        }
290    }
291
292    #[test]
293    fn two_components() {
294        let mut g = Graph::with_vertices(4);
295        g.add_edge(0, 1).unwrap();
296        g.add_edge(2, 3).unwrap();
297        let d = distances_all(&g).unwrap();
298        let n = 4usize;
299        assert_eq!(d[1], Some(1)); // row 0, col 1
300        assert_eq!(d[2 * n + 3], Some(1));
301        assert_eq!(d[2], None); // row 0, col 2
302        assert_eq!(d[n + 3], None); // row 1, col 3
303    }
304
305    #[test]
306    fn cycle_5() {
307        let mut g = Graph::with_vertices(5);
308        g.add_edge(0, 1).unwrap();
309        g.add_edge(1, 2).unwrap();
310        g.add_edge(2, 3).unwrap();
311        g.add_edge(3, 4).unwrap();
312        g.add_edge(4, 0).unwrap();
313        let d = distances_all(&g).unwrap();
314        // Row 0: distances from vertex 0
315        assert_eq!(d[0], Some(0));
316        assert_eq!(d[1], Some(1));
317        assert_eq!(d[2], Some(2));
318        assert_eq!(d[3], Some(2));
319        assert_eq!(d[4], Some(1));
320    }
321
322    #[test]
323    fn directed_out() {
324        let mut g = Graph::new(3, true).unwrap();
325        g.add_edge(0, 1).unwrap();
326        g.add_edge(1, 2).unwrap();
327        let d = distances_all(&g).unwrap();
328        let n = 3usize;
329        assert_eq!(d[2], Some(2)); // row 0, col 2
330        assert_eq!(d[2 * n], None); // row 2, col 0
331        assert_eq!(d[n], None); // row 1, col 0
332    }
333
334    #[test]
335    fn directed_in_mode() {
336        let mut g = Graph::new(3, true).unwrap();
337        g.add_edge(0, 1).unwrap();
338        g.add_edge(1, 2).unwrap();
339        let d = distances_all_with_mode(&g, DistancesMode::In).unwrap();
340        let n = 3usize;
341        // Following incoming edges: from 2 we can reach 1 (in 1 hop) and 0 (in 2 hops)
342        assert_eq!(d[2 * n + 1], Some(1));
343        assert_eq!(d[2 * n], Some(2)); // row 2, col 0
344        assert_eq!(d[1], None); // row 0, col 1: 0 has no incoming
345    }
346
347    #[test]
348    fn directed_all_mode() {
349        let mut g = Graph::new(3, true).unwrap();
350        g.add_edge(0, 1).unwrap();
351        g.add_edge(1, 2).unwrap();
352        let d = distances_all_with_mode(&g, DistancesMode::All).unwrap();
353        let n = 3;
354        // All mode: treat as undirected
355        assert_eq!(d[2], Some(2));
356        assert_eq!(d[2 * n], Some(2));
357    }
358
359    #[test]
360    fn symmetric_undirected() {
361        let mut g = Graph::with_vertices(5);
362        g.add_edge(0, 1).unwrap();
363        g.add_edge(1, 2).unwrap();
364        g.add_edge(2, 3).unwrap();
365        g.add_edge(3, 4).unwrap();
366        let d = distances_all(&g).unwrap();
367        let n = 5;
368        for i in 0..n {
369            for j in 0..n {
370                assert_eq!(d[i * n + j], d[j * n + i], "not symmetric at ({i},{j})");
371            }
372        }
373    }
374
375    #[test]
376    fn isolated_vertices() {
377        let g = Graph::with_vertices(3);
378        let d = distances_all(&g).unwrap();
379        let n = 3;
380        for i in 0..n {
381            assert_eq!(d[i * n + i], Some(0));
382            for j in 0..n {
383                if i != j {
384                    assert_eq!(d[i * n + j], None);
385                }
386            }
387        }
388    }
389
390    #[test]
391    fn oracle_star() {
392        // Star: center 0, leaves 1,2,3.
393        // Verified against python-igraph.
394        let mut g = Graph::with_vertices(4);
395        g.add_edge(0, 1).unwrap();
396        g.add_edge(0, 2).unwrap();
397        g.add_edge(0, 3).unwrap();
398        let d = distances_all(&g).unwrap();
399        let n = 4;
400        // Center to leaves: 1
401        for item in d.iter().take(4).skip(1) {
402            assert_eq!(*item, Some(1));
403        }
404        // Leaf to leaf: 2
405        assert_eq!(d[n + 2], Some(2));
406        assert_eq!(d[n + 3], Some(2));
407        assert_eq!(d[2 * n + 3], Some(2));
408    }
409}