Skip to main content

rust_igraph/algorithms/paths/
distances_from.rs

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