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 next_dist = dist + 1;
160        for nb in graph.neighbors_iter(cur)? {
161            let nb_idx = nb as usize;
162            if !visited[nb_idx] {
163                visited[nb_idx] = true;
164                result[row_offset + nb_idx] = Some(next_dist);
165                queue.push_back((nb, next_dist));
166            }
167        }
168    }
169
170    Ok(())
171}
172
173/// BFS from `source` using a pre-built adjacency list.
174fn bfs_distances_with_adj(
175    adj: &[Vec<VertexId>],
176    source: VertexId,
177    n_us: usize,
178    result: &mut [Option<u32>],
179) {
180    let src_us = source as usize;
181    let row_offset = src_us * n_us;
182
183    let mut visited = vec![false; n_us];
184    let mut queue = VecDeque::new();
185
186    visited[src_us] = true;
187    result[row_offset + src_us] = Some(0);
188    queue.push_back((source, 0u32));
189
190    while let Some((cur, dist)) = queue.pop_front() {
191        let next_dist = dist + 1;
192        for &nb in &adj[cur as usize] {
193            let nb_idx = nb as usize;
194            if !visited[nb_idx] {
195                visited[nb_idx] = true;
196                result[row_offset + nb_idx] = Some(next_dist);
197                queue.push_back((nb, next_dist));
198            }
199        }
200    }
201}
202
203fn build_out_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
204    let m =
205        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
206    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
207    for eid in 0..m {
208        let (from, to) = graph.edge(eid)?;
209        adj[from as usize].push(to);
210    }
211    Ok(adj)
212}
213
214fn build_in_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
215    let m =
216        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
217    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
218    for eid in 0..m {
219        let (from, to) = graph.edge(eid)?;
220        adj[to as usize].push(from);
221    }
222    Ok(adj)
223}
224
225fn build_all_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
226    let m =
227        u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
228    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
229    for eid in 0..m {
230        let (from, to) = graph.edge(eid)?;
231        adj[from as usize].push(to);
232        if from != to {
233            adj[to as usize].push(from);
234        }
235    }
236    Ok(adj)
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn empty_graph() {
245        let g = Graph::with_vertices(0);
246        let d = distances_all(&g).unwrap();
247        assert!(d.is_empty());
248    }
249
250    #[test]
251    fn singleton() {
252        let g = Graph::with_vertices(1);
253        let d = distances_all(&g).unwrap();
254        assert_eq!(d, vec![Some(0)]);
255    }
256
257    #[test]
258    fn path_graph() {
259        let mut g = Graph::with_vertices(4);
260        g.add_edge(0, 1).unwrap();
261        g.add_edge(1, 2).unwrap();
262        g.add_edge(2, 3).unwrap();
263        let d = distances_all(&g).unwrap();
264        let n = 4usize;
265        assert_eq!(d[0], Some(0)); // row 0, col 0
266        assert_eq!(d[1], Some(1)); // row 0, col 1
267        assert_eq!(d[2], Some(2)); // row 0, col 2
268        assert_eq!(d[3], Some(3)); // row 0, col 3
269        assert_eq!(d[3 * n], Some(3)); // row 3, col 0
270        assert_eq!(d[n + 3], Some(2)); // row 1, col 3
271    }
272
273    #[test]
274    fn triangle() {
275        let mut g = Graph::with_vertices(3);
276        g.add_edge(0, 1).unwrap();
277        g.add_edge(1, 2).unwrap();
278        g.add_edge(2, 0).unwrap();
279        let d = distances_all(&g).unwrap();
280        let n = 3;
281        for i in 0..n {
282            assert_eq!(d[i * n + i], Some(0));
283            for j in 0..n {
284                if i != j {
285                    assert_eq!(d[i * n + j], Some(1));
286                }
287            }
288        }
289    }
290
291    #[test]
292    fn two_components() {
293        let mut g = Graph::with_vertices(4);
294        g.add_edge(0, 1).unwrap();
295        g.add_edge(2, 3).unwrap();
296        let d = distances_all(&g).unwrap();
297        let n = 4usize;
298        assert_eq!(d[1], Some(1)); // row 0, col 1
299        assert_eq!(d[2 * n + 3], Some(1));
300        assert_eq!(d[2], None); // row 0, col 2
301        assert_eq!(d[n + 3], None); // row 1, col 3
302    }
303
304    #[test]
305    fn cycle_5() {
306        let mut g = Graph::with_vertices(5);
307        g.add_edge(0, 1).unwrap();
308        g.add_edge(1, 2).unwrap();
309        g.add_edge(2, 3).unwrap();
310        g.add_edge(3, 4).unwrap();
311        g.add_edge(4, 0).unwrap();
312        let d = distances_all(&g).unwrap();
313        // Row 0: distances from vertex 0
314        assert_eq!(d[0], Some(0));
315        assert_eq!(d[1], Some(1));
316        assert_eq!(d[2], Some(2));
317        assert_eq!(d[3], Some(2));
318        assert_eq!(d[4], Some(1));
319    }
320
321    #[test]
322    fn directed_out() {
323        let mut g = Graph::new(3, true).unwrap();
324        g.add_edge(0, 1).unwrap();
325        g.add_edge(1, 2).unwrap();
326        let d = distances_all(&g).unwrap();
327        let n = 3usize;
328        assert_eq!(d[2], Some(2)); // row 0, col 2
329        assert_eq!(d[2 * n], None); // row 2, col 0
330        assert_eq!(d[n], None); // row 1, col 0
331    }
332
333    #[test]
334    fn directed_in_mode() {
335        let mut g = Graph::new(3, true).unwrap();
336        g.add_edge(0, 1).unwrap();
337        g.add_edge(1, 2).unwrap();
338        let d = distances_all_with_mode(&g, DistancesMode::In).unwrap();
339        let n = 3usize;
340        // Following incoming edges: from 2 we can reach 1 (in 1 hop) and 0 (in 2 hops)
341        assert_eq!(d[2 * n + 1], Some(1));
342        assert_eq!(d[2 * n], Some(2)); // row 2, col 0
343        assert_eq!(d[1], None); // row 0, col 1: 0 has no incoming
344    }
345
346    #[test]
347    fn directed_all_mode() {
348        let mut g = Graph::new(3, true).unwrap();
349        g.add_edge(0, 1).unwrap();
350        g.add_edge(1, 2).unwrap();
351        let d = distances_all_with_mode(&g, DistancesMode::All).unwrap();
352        let n = 3;
353        // All mode: treat as undirected
354        assert_eq!(d[2], Some(2));
355        assert_eq!(d[2 * n], Some(2));
356    }
357
358    #[test]
359    fn symmetric_undirected() {
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        let d = distances_all(&g).unwrap();
366        let n = 5;
367        for i in 0..n {
368            for j in 0..n {
369                assert_eq!(d[i * n + j], d[j * n + i], "not symmetric at ({i},{j})");
370            }
371        }
372    }
373
374    #[test]
375    fn isolated_vertices() {
376        let g = Graph::with_vertices(3);
377        let d = distances_all(&g).unwrap();
378        let n = 3;
379        for i in 0..n {
380            assert_eq!(d[i * n + i], Some(0));
381            for j in 0..n {
382                if i != j {
383                    assert_eq!(d[i * n + j], None);
384                }
385            }
386        }
387    }
388
389    #[test]
390    fn oracle_star() {
391        // Star: center 0, leaves 1,2,3.
392        // Verified against python-igraph.
393        let mut g = Graph::with_vertices(4);
394        g.add_edge(0, 1).unwrap();
395        g.add_edge(0, 2).unwrap();
396        g.add_edge(0, 3).unwrap();
397        let d = distances_all(&g).unwrap();
398        let n = 4;
399        // Center to leaves: 1
400        for item in d.iter().take(4).skip(1) {
401            assert_eq!(*item, Some(1));
402        }
403        // Leaf to leaf: 2
404        assert_eq!(d[n + 2], Some(2));
405        assert_eq!(d[n + 3], Some(2));
406        assert_eq!(d[2 * n + 3], Some(2));
407    }
408}