graph_cycles/
lib.rs

1//! Find all cycles in a graph
2//!
3//! A naive implementation of Johnson's algorithm to find all cycles
4//! in a graph. Based on [petgraph](https://github.com/petgraph/petgraph).
5//!
6//! # Example
7//!
8//! The triangle graph has exactly one cycle, namely the full graph itself.
9//!
10//! ```rust
11//! use graph_cycles::Cycles;
12//! use petgraph::graph::Graph;
13//!
14//! let g = Graph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 0)]);
15//!
16//! // find all cycles
17//! let cycles = g.cycles();
18//! assert_eq!(cycles.len(), 1);
19//! assert_eq!(cycles[0].len(), 3);
20//!
21//! // print each cycle in turn
22//! g.visit_all_cycles(|_g, c| {
23//!    println!("Found new cycle with vertices {c:?}");
24//! });
25//! ```
26//!
27//! # Caveats
28//!
29//! This crate is essentially untested.
30//!
31//! # References
32//!
33//! Donald B. Johnson,
34//! Finding all the elementary circuits of a directed graph,
35//! SIAM Journal on Computing, 1975.
36//!
37use std::ops::ControlFlow;
38
39use ahash::AHashSet;
40use petgraph::{
41    algo::tarjan_scc,
42    visit::{GraphBase, IntoNeighbors, IntoNodeIdentifiers, NodeIndexable},
43};
44
45/// Trait for identifying cycles in a graph
46pub trait Cycles {
47    /// The node identifier of the underlying graph
48    type NodeId;
49
50    /// Apply the `visitor` to each cycle until we are told to stop
51    ///
52    /// The first argument passed to the visitor is a reference to the
53    /// graph and the second one a slice with all nodes that form the
54    /// cycle. If at any point the visitor returns
55    /// `ControlFlow::Break(b)` this function stops visiting any
56    /// further cycles and returns `Some(b)`. Otherwise the return
57    /// value is `None`.
58    fn visit_cycles<F, B>(&self, visitor: F) -> Option<B>
59    where
60        F: FnMut(&Self, &[Self::NodeId]) -> ControlFlow<B>;
61
62    /// Apply the `visitor` to each cycle until we are told to stop
63    ///
64    /// The first argument passed to the visitor is a reference to the
65    /// graph and the second one a slice with all nodes that form the
66    /// cycle.
67    fn visit_all_cycles<F>(&self, mut visitor: F)
68    where
69        F: FnMut(&Self, &[Self::NodeId]),
70    {
71        self.visit_cycles(|g, n| {
72            visitor(g, n);
73            ControlFlow::<(), ()>::Continue(())
74        });
75    }
76
77    /// Find all cycles
78    ///
79    /// Each element of the returned `Vec` is a `Vec` of all nodes in one cycle.
80    fn cycles(&self) -> Vec<Vec<Self::NodeId>>;
81}
82
83impl<Graph: GraphBase> Cycles for Graph
84where
85    for<'a> &'a Graph: IntoNodeIdentifiers
86        + IntoNeighbors
87        + NodeIndexable
88        + GraphBase<NodeId = <Graph as GraphBase>::NodeId>,
89{
90    type NodeId = <Graph as GraphBase>::NodeId;
91    fn visit_cycles<F, B>(&self, mut visitor: F) -> Option<B>
92    where
93        F: FnMut(&Graph, &[Self::NodeId]) -> ControlFlow<B>,
94    {
95        for component in tarjan_scc(self) {
96            let mut finder = CycleFinder::new(self, component);
97            if let ControlFlow::Break(b) = finder.visit(&mut visitor) {
98                return Some(b);
99            }
100        }
101        None
102    }
103    fn cycles(&self) -> Vec<Vec<Self::NodeId>> {
104        let mut cycles = Vec::new();
105        self.visit_cycles(|_, cycle| {
106            cycles.push(cycle.to_vec());
107            ControlFlow::<(), ()>::Continue(())
108        });
109        cycles
110    }
111}
112
113#[derive(Clone, Debug, Eq, PartialEq)]
114struct CycleFinder<G, N> {
115    graph: G,
116    scc: Vec<N>,
117    blocked: Vec<bool>,
118    b: Vec<AHashSet<usize>>,
119    stack: Vec<N>,
120    s: usize,
121}
122
123impl<G> CycleFinder<G, G::NodeId>
124where
125    G: IntoNodeIdentifiers + IntoNeighbors + NodeIndexable,
126{
127    fn new(graph: G, scc: Vec<G::NodeId>) -> Self {
128        let num_vertices = scc.len();
129        Self {
130            graph,
131            scc,
132            blocked: vec![false; num_vertices],
133            b: vec![Default::default(); num_vertices],
134            stack: Default::default(),
135            s: Default::default(),
136        }
137    }
138
139    fn visit<F, B>(&mut self, visitor: &mut F) -> ControlFlow<B>
140    where
141        F: FnMut(G, &[G::NodeId]) -> ControlFlow<B>,
142    {
143        // cycle finding algorithm from
144        for s in 0..self.scc.len() {
145            self.s = s;
146            self.blocked[s..].fill(false);
147            for b in &mut self.b[s + 1..] {
148                b.clear();
149            }
150            if let ControlFlow::Break(b) = self.circuit(s, visitor) {
151                return ControlFlow::Break(b);
152            }
153            self.blocked[s] = true;
154        }
155        ControlFlow::Continue(())
156    }
157
158    fn circuit<B, F>(
159        &mut self,
160        v: usize,
161        visitor: &mut F,
162    ) -> ControlFlow<B, bool>
163    where
164        F: FnMut(G, &[G::NodeId]) -> ControlFlow<B>,
165    {
166        let mut f = false;
167        self.stack.push(self.scc[v]);
168        self.blocked[v] = true;
169
170        // L1:
171        for w in self.adjacent_vertices(v) {
172            if w == self.s {
173                if let ControlFlow::Break(b) = visitor(self.graph, &self.stack)
174                {
175                    return ControlFlow::Break(b);
176                }
177                f = true;
178            } else if !self.blocked[w]
179                && matches!(
180                    self.circuit(w, visitor),
181                    ControlFlow::Continue(true)
182                )
183            {
184                f = true;
185            }
186        }
187
188        // L2:
189        if f {
190            self.unblock(v)
191        } else {
192            for w in self.adjacent_vertices(v) {
193                self.b[w].insert(v);
194            }
195        }
196
197        self.stack.pop(); // v
198        ControlFlow::Continue(f)
199    }
200
201    fn unblock(&mut self, v: usize) {
202        self.blocked[v] = false;
203        let tmp = self.b[v].clone();
204        for w in tmp {
205            if self.blocked[w] {
206                self.unblock(w)
207            }
208        }
209        self.b[v].clear()
210    }
211
212    fn adjacent_vertices(&self, v: usize) -> Vec<usize> {
213        self.graph
214            .neighbors(self.scc[v])
215            .filter_map(|n| self.scc.iter().position(|v| *v == n))
216            .collect()
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use petgraph::{Directed, graph::Graph, graphmap::GraphMap};
223
224    #[test]
225    fn test_graph() {
226        let g: Graph<usize, (), Directed> = Graph::new();
227        crate::Cycles::cycles(&g);
228    }
229
230    #[test]
231    fn test_graph_map() {
232        let g: GraphMap<usize, (), Directed> = GraphMap::new();
233        crate::Cycles::cycles(&g);
234    }
235}