ade_elementary_circuits/
lib.rs

1#[cfg(any(test, feature = "test-utils"))]
2pub mod utils;
3
4use ade_common::INVALID_KEY_SEQUENCE;
5use ade_strongly_connected_components::scc_iterative;
6use ade_traits::{EdgeTrait, GraphViewTrait, NodeTrait};
7use smallvec::SmallVec;
8
9// Johnson's algorithm for finding all elementary circuits in a directed graph
10// SIAM J. Comput., Vol. 4, No. 1, March 1975
11// https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
12pub fn elementary_circuits<N: NodeTrait, E: EdgeTrait>(
13    graph: &impl GraphViewTrait<N, E>,
14) -> Vec<Vec<u32>> {
15    // Panic if the graph does not have sequential keys
16    if !graph.has_sequential_keys() {
17        panic!("{}", INVALID_KEY_SEQUENCE);
18    }
19
20    // Here the algorithm starts
21    let mut circuits: Vec<Vec<u32>> = Vec::new();
22    let mut stack: Vec<u32> = Vec::new();
23
24    let n = match graph.get_nodes().count() {
25        0 => return circuits, // Return empty circuits if no nodes
26        len => (len - 1) as u32,
27    };
28
29    let mut s: u32 = n; // Start with the maximum node and decrease it, so that graph nodes are always {0, 1, ..., s}
30    let size = (n + 1) as usize;
31    let mut blocked_set: Vec<bool> = vec![false; size];
32    let mut blocked_map: Vec<SmallVec<[u32; 4]>> = vec![SmallVec::new(); size];
33
34    loop {
35        // Create the subgraph induced by {0, 1, ..., s}
36        let remaining_nodes: Vec<u32> = (0..=s).collect();
37        let subgraph = graph.filter(&remaining_nodes);
38
39        // Find the strongly connected components of the subgraph
40        let components = scc_iterative(&subgraph);
41
42        // Choose the component containing the max vertex
43        let component = components.iter().find(|c| c.contains(&s)).unwrap();
44
45        // Create the subgraph induced by the component containing the max vertex
46        let adj = subgraph.filter(component);
47
48        // Find the elementary circuits in the subgraph adj
49        if adj.get_nodes().next().is_some() {
50            for key in adj.get_node_keys() {
51                let k = key as usize;
52                blocked_set[k] = false;
53                blocked_map[k].clear();
54            }
55
56            find_circuit(
57                s,
58                s,
59                &mut circuits,
60                &mut stack,
61                &mut blocked_set,
62                &mut blocked_map,
63                &adj,
64            );
65            if s == 0 {
66                break;
67            }
68            s -= 1;
69        } else {
70            s = 0;
71        }
72    }
73
74    circuits
75}
76
77fn find_circuit<N: NodeTrait, E: EdgeTrait>(
78    s: u32,
79    v: u32,
80    circuits: &mut Vec<Vec<u32>>,
81    stack: &mut Vec<u32>,
82    blocked_set: &mut [bool],
83    blocked_map: &mut [SmallVec<[u32; 4]>],
84    adj: &impl GraphViewTrait<N, E>,
85) -> bool {
86    let mut f: bool = false;
87    let v_us = v as usize;
88
89    stack.push(v);
90    blocked_set[v_us] = true;
91
92    for w_key in adj.get_successors_keys(v) {
93        if w_key == s {
94            let mut circuit = Vec::with_capacity(stack.len() + 1);
95            circuit.extend_from_slice(stack);
96            circuit.push(s);
97            circuits.push(circuit);
98            f = true;
99        } else if !blocked_set[w_key as usize]
100            && find_circuit(s, w_key, circuits, stack, blocked_set, blocked_map, adj)
101        {
102            f = true;
103        }
104    }
105
106    if f {
107        unblock(v, blocked_set, blocked_map);
108    } else {
109        for w_key in adj.get_successors_keys(v) {
110            let list = &mut blocked_map[w_key as usize];
111            if !list.contains(&v) {
112                list.push(v);
113            }
114        }
115    }
116
117    stack.pop();
118    f
119}
120
121fn unblock(u: u32, blocked_set: &mut [bool], blocked_map: &mut [SmallVec<[u32; 4]>]) {
122    let u_us = u as usize;
123    blocked_set[u_us] = false;
124
125    while let Some(w) = blocked_map[u_us].pop() {
126        let w_us = w as usize;
127        if blocked_set[w_us] {
128            unblock(w, blocked_set, blocked_map);
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::utils::circuits_equal;
137    use crate::utils::number_circuits;
138    use ade_common::{self, assert_panics_with};
139    use ade_graph::utils::build::build_graph;
140    use ade_graph_generators::complete_graph_data;
141    use ade_graph_generators::generate_random_graph_data;
142    use graph_cycles::Cycles;
143    use petgraph::graph::Graph as PetGraph;
144    use rand::Rng;
145    use std::collections::HashSet;
146
147    #[test]
148    fn test_elementary_circuits_1() {
149        let graph = build_graph(vec![0, 1, 2], vec![(0, 1), (1, 2), (2, 1)]);
150        let circuits = elementary_circuits(&graph);
151        let expected = vec![vec![1, 2, 1]];
152
153        assert!(circuits_equal(&circuits, &expected));
154    }
155
156    #[test]
157    fn test_elementary_circuits_non_sequential_keys() {
158        let graph = build_graph(vec![1, 3, 5], vec![(1, 3), (3, 5), (5, 1)]);
159        assert_panics_with!(
160            elementary_circuits(&graph),
161            ade_common::INVALID_KEY_SEQUENCE
162        );
163    }
164
165    #[test]
166    fn test_elementary_circuits_3() {
167        let graph = build_graph(vec![0, 1, 2], vec![(0, 1), (1, 2)]);
168        let circuits = elementary_circuits(&graph);
169        let expected = vec![];
170
171        assert!(circuits_equal(&circuits, &expected));
172    }
173
174    #[test]
175    fn test_elementary_circuits_4() {
176        let graph = build_graph(vec![0, 1, 2], vec![]);
177        let circuits = elementary_circuits(&graph);
178        let expected = vec![];
179
180        assert!(circuits_equal(&circuits, &expected));
181    }
182
183    #[test]
184    fn test_elementary_circuits_5() {
185        let graph = build_graph(vec![], vec![]);
186        let circuits = elementary_circuits(&graph);
187        let expected = vec![];
188
189        assert!(circuits_equal(&circuits, &expected));
190    }
191
192    #[test]
193    fn test_elementary_circuits_6() {
194        let graph = build_graph(vec![0], vec![(0, 0)]);
195        let circuits = elementary_circuits(&graph);
196        let expected = vec![vec![0, 0]];
197
198        assert!(circuits_equal(&circuits, &expected));
199    }
200
201    #[test]
202    fn test_elementary_circuits_7() {
203        let graph = build_graph(vec![0, 1], vec![(0, 0), (1, 1), (0, 1)]);
204        let circuits = elementary_circuits(&graph);
205        let expected = vec![vec![0, 0], vec![1, 1]];
206
207        assert!(circuits_equal(&circuits, &expected));
208    }
209
210    #[test]
211    fn test_elementary_circuits_2() {
212        let graph = build_graph(
213            vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
214            vec![
215                (0, 1),
216                (0, 7),
217                (0, 4),
218                (1, 2),
219                (1, 6),
220                (1, 8),
221                (2, 1),
222                (2, 0),
223                (2, 3),
224                (2, 5),
225                (3, 4),
226                (4, 1),
227                (5, 3),
228                (7, 8),
229                (8, 7),
230            ],
231        );
232        let circuits = elementary_circuits(&graph);
233        let expected: Vec<Vec<u32>> = vec![
234            vec![0, 1, 2, 0],
235            vec![0, 4, 1, 2, 0],
236            vec![1, 2, 1],
237            vec![1, 2, 3, 4, 1],
238            vec![1, 2, 5, 3, 4, 1],
239            vec![7, 8, 7],
240        ];
241
242        assert!(circuits_equal(&circuits, &expected));
243    }
244
245    #[test]
246    fn test_elementary_circuits_complete_graph() {
247        let n: usize = 6;
248        let (nodes, edges) = complete_graph_data(n);
249        let graph = build_graph(nodes, edges);
250        let circuits = elementary_circuits(&graph);
251
252        assert_eq!(circuits.len(), number_circuits(n))
253    }
254
255    // #[test]
256    // fn test_elementary_circuits_random_graph() {
257    //     let (nodes, edges) = generate_random_graph_data(25, 125, 3);
258
259    //     // Remove duplicate edges
260    //     let mut seen = HashSet::new();
261    //     let mut unique_edges = Vec::new();
262    //     for edge in edges {
263    //         if seen.insert(edge) {
264    //             unique_edges.push(edge);
265    //         }
266    //     }
267
268    //     println!("Edges: {:?}", unique_edges.len());
269
270    //     let graph = build_graph(nodes.clone(), unique_edges.clone());
271    //     let circuits = elementary_circuits(&graph);
272
273    //     //let g = PetGraph::<(), ()>::from_edges(unique_edges.clone());
274    //     //let circuits = g.cycles();
275
276    //     println!("Circuits: {:?}", circuits.len());
277
278    //     //assert_eq!(circuits.len(), cycles.len());
279    // }
280
281    #[test]
282    fn test_elementary_circuits_multiple_random_graphs() {
283        let mut rng = rand::thread_rng();
284
285        for _ in 0..100 {
286            let node_count = rng.gen_range(5..20);
287            let edge_count = rng.gen_range(node_count * 2..node_count * 5);
288            let seed = rng.gen();
289
290            let (nodes, edges) = generate_random_graph_data(node_count, edge_count, seed);
291
292            // Remove duplicate edges
293            let mut seen = HashSet::new();
294            let mut unique_edges = Vec::new();
295            for edge in edges {
296                if seen.insert(edge) {
297                    unique_edges.push(edge);
298                }
299            }
300
301            let graph = build_graph(nodes.clone(), unique_edges.clone());
302            let circuits = elementary_circuits(&graph);
303
304            let g = PetGraph::<(), ()>::from_edges(unique_edges.clone());
305            let cycles = g.cycles();
306
307            assert_eq!(
308                circuits.len(),
309                cycles.len(),
310                "Mismatch con seed={:?}, nodi={}, edges={}",
311                seed,
312                node_count,
313                edge_count
314            );
315        }
316    }
317}