petgraph/algo/
simple_paths.rs

1use alloc::vec;
2use core::{
3    hash::{BuildHasher, Hash},
4    iter::{from_fn, FromIterator},
5};
6
7use indexmap::IndexSet;
8
9use crate::{
10    visit::{IntoNeighborsDirected, NodeCount},
11    Direction::Outgoing,
12};
13
14/// Calculate all simple paths with specified constraints from node `from` to node `to`.
15///
16/// A simple path is a path without repeating nodes.
17/// The number of simple paths between a given pair of vertices can grow exponentially,
18/// reaching `O(|V|!)` on complete graphs with `|V|` vertices.
19///
20/// So if you have a large enough graph, be prepared to wait on the results for years.
21/// Or consider extracting only part of the simple paths using the adapter [`Iterator::take`].
22/// Also note, that this algorithm does not check that a path exists between `from` and `to`. This may lead to very long running times and it may be worth it to check if a path exists before running this algorithm on large graphs.
23///
24/// This algorithm is adapted from [NetworkX](https://networkx.github.io/documentation/stable/reference/algorithms/generated/networkx.algorithms.simple_paths.all_simple_paths.html).
25/// # Arguments
26/// * `graph`: an input graph.
27/// * `from`: an initial node of desired paths.
28/// * `to`: a target node of desired paths.
29/// * `min_intermediate_nodes`: the minimum number of nodes in the desired paths.
30/// * `max_intermediate_nodes`: the maximum number of nodes in the desired paths (optional).
31/// # Returns
32/// Returns an iterator that produces all simple paths from `from` node to `to`, which contains at least `min_intermediate_nodes` nodes
33/// and at most `max_intermediate_nodes`, if given, or limited by the graph's order otherwise.
34///
35/// # Complexity
36/// * Time complexity: for computing the first **k** paths, the running time will be **O(k|V| + k|E|)**.
37/// * Auxillary space: **O(|V|)**.
38///
39/// where **|V|** is the number of nodes and **|E|** is the number of edges.
40///
41/// # Example
42/// ```
43/// use std::collections::hash_map::RandomState;
44/// use petgraph::{algo, prelude::*};
45///
46/// let mut graph = DiGraph::<&str, i32>::new();
47///
48/// let a = graph.add_node("a");
49/// let b = graph.add_node("b");
50/// let c = graph.add_node("c");
51/// let d = graph.add_node("d");
52///
53/// graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (a, b, 1), (b, d, 1)]);
54///
55/// let paths = algo::all_simple_paths::<Vec<_>, _, RandomState>(&graph, a, d, 0, None)
56///   .collect::<Vec<_>>();
57///
58/// assert_eq!(paths.len(), 4);
59///
60///
61/// // Take only 2 paths.
62/// let paths = algo::all_simple_paths::<Vec<_>, _, RandomState>(&graph, a, d, 0, None)
63///   .take(2)
64///   .collect::<Vec<_>>();
65///
66/// assert_eq!(paths.len(), 2);
67///
68/// ```
69pub fn all_simple_paths<TargetColl, G, S>(
70    graph: G,
71    from: G::NodeId,
72    to: G::NodeId,
73    min_intermediate_nodes: usize,
74    max_intermediate_nodes: Option<usize>,
75) -> impl Iterator<Item = TargetColl>
76where
77    G: NodeCount,
78    G: IntoNeighborsDirected,
79    G::NodeId: Eq + Hash,
80    TargetColl: FromIterator<G::NodeId>,
81    S: BuildHasher + Default,
82{
83    // how many nodes are allowed in simple path up to target node
84    // it is min/max allowed path length minus one, because it is more appropriate when implementing lookahead
85    // than constantly add 1 to length of current path
86    let max_length = if let Some(l) = max_intermediate_nodes {
87        l + 1
88    } else {
89        graph.node_count() - 1
90    };
91
92    let min_length = min_intermediate_nodes + 1;
93
94    // list of visited nodes
95    let mut visited: IndexSet<G::NodeId, S> = IndexSet::from_iter(Some(from));
96    // list of childs of currently exploring path nodes,
97    // last elem is list of childs of last visited node
98    let mut stack = vec![graph.neighbors_directed(from, Outgoing)];
99
100    from_fn(move || {
101        while let Some(children) = stack.last_mut() {
102            if let Some(child) = children.next() {
103                if visited.len() < max_length {
104                    if child == to {
105                        if visited.len() >= min_length {
106                            let path = visited
107                                .iter()
108                                .cloned()
109                                .chain(Some(to))
110                                .collect::<TargetColl>();
111                            return Some(path);
112                        }
113                    } else if !visited.contains(&child) {
114                        visited.insert(child);
115                        stack.push(graph.neighbors_directed(child, Outgoing));
116                    }
117                } else {
118                    if (child == to || children.any(|v| v == to)) && visited.len() >= min_length {
119                        let path = visited
120                            .iter()
121                            .cloned()
122                            .chain(Some(to))
123                            .collect::<TargetColl>();
124                        return Some(path);
125                    }
126                    stack.pop();
127                    visited.pop();
128                }
129            } else {
130                stack.pop();
131                visited.pop();
132            }
133        }
134        None
135    })
136}
137
138#[cfg(test)]
139mod test {
140    use alloc::{vec, vec::Vec};
141    use core::iter::FromIterator;
142    use std::{collections::hash_map::RandomState, println};
143
144    use hashbrown::HashSet;
145    use itertools::assert_equal;
146
147    use super::all_simple_paths;
148    use crate::{dot::Dot, prelude::DiGraph};
149
150    #[test]
151    fn test_all_simple_paths() {
152        let graph = DiGraph::<i32, i32, _>::from_edges([
153            (0, 1),
154            (0, 2),
155            (0, 3),
156            (1, 2),
157            (1, 3),
158            (2, 3),
159            (2, 4),
160            (3, 2),
161            (3, 4),
162            (4, 2),
163            (4, 5),
164            (5, 2),
165            (5, 3),
166        ]);
167
168        let expexted_simple_paths_0_to_5 = vec![
169            vec![0usize, 1, 2, 3, 4, 5],
170            vec![0, 1, 2, 4, 5],
171            vec![0, 1, 3, 2, 4, 5],
172            vec![0, 1, 3, 4, 5],
173            vec![0, 2, 3, 4, 5],
174            vec![0, 2, 4, 5],
175            vec![0, 3, 2, 4, 5],
176            vec![0, 3, 4, 5],
177        ];
178
179        println!("{}", Dot::new(&graph));
180        let actual_simple_paths_0_to_5: HashSet<Vec<_>> =
181            all_simple_paths::<_, _, RandomState>(&graph, 0u32.into(), 5u32.into(), 0, None)
182                .map(|v: Vec<_>| v.into_iter().map(|i| i.index()).collect())
183                .collect();
184        assert_eq!(actual_simple_paths_0_to_5.len(), 8);
185        assert_eq!(
186            HashSet::from_iter(expexted_simple_paths_0_to_5),
187            actual_simple_paths_0_to_5
188        );
189    }
190
191    #[test]
192    fn test_one_simple_path() {
193        let graph = DiGraph::<i32, i32, _>::from_edges([(0, 1), (2, 1)]);
194
195        let expexted_simple_paths_0_to_1 = &[vec![0usize, 1]];
196        println!("{}", Dot::new(&graph));
197        let actual_simple_paths_0_to_1: Vec<Vec<_>> =
198            all_simple_paths::<_, _, RandomState>(&graph, 0u32.into(), 1u32.into(), 0, None)
199                .map(|v: Vec<_>| v.into_iter().map(|i| i.index()).collect())
200                .collect();
201
202        assert_eq!(actual_simple_paths_0_to_1.len(), 1);
203        assert_equal(expexted_simple_paths_0_to_1, &actual_simple_paths_0_to_1);
204    }
205
206    #[test]
207    fn test_no_simple_paths() {
208        let graph = DiGraph::<i32, i32, _>::from_edges([(0, 1), (2, 1)]);
209
210        println!("{}", Dot::new(&graph));
211        let actual_simple_paths_0_to_2: Vec<Vec<_>> =
212            all_simple_paths::<_, _, RandomState>(&graph, 0u32.into(), 2u32.into(), 0, None)
213                .map(|v: Vec<_>| v.into_iter().map(|i| i.index()).collect())
214                .collect();
215
216        assert_eq!(actual_simple_paths_0_to_2.len(), 0);
217    }
218}