uni_algo/algo/algorithms/
elementary_circuits.rs1use crate::algo::GraphProjection;
10use crate::algo::algorithms::{Algorithm, Scc, SccConfig};
11use std::collections::{HashMap, HashSet};
12use uni_common::core::id::Vid;
13
14pub struct ElementaryCircuits;
15
16#[derive(Debug, Clone)]
17pub struct ElementaryCircuitsConfig {
18 pub min_length: usize,
19 pub max_length: usize,
20 pub limit: usize,
21}
22
23impl Default for ElementaryCircuitsConfig {
24 fn default() -> Self {
25 Self {
26 min_length: 2, max_length: 10, limit: 1000,
29 }
30 }
31}
32
33pub struct ElementaryCircuitsResult {
34 pub cycles: Vec<Vec<Vid>>,
35}
36
37impl Algorithm for ElementaryCircuits {
38 type Config = ElementaryCircuitsConfig;
39 type Result = ElementaryCircuitsResult;
40
41 fn name() -> &'static str {
42 "elementary_circuits"
43 }
44
45 fn run(graph: &GraphProjection, config: Self::Config) -> Self::Result {
46 let scc_res = Scc::run(graph, SccConfig::default());
49
50 let mut scc_map: HashMap<u64, Vec<u32>> = HashMap::new();
51 for (vid, cid) in scc_res.components {
52 if let Some(slot) = graph.to_slot(vid) {
53 scc_map.entry(cid).or_default().push(slot);
54 }
55 }
56
57 let mut cycles = Vec::new();
58 let mut count = 0;
59
60 for nodes in scc_map.values() {
61 if nodes.len() < 2 {
62 if nodes.len() == 1 {
64 let u = nodes[0];
65 if graph.out_neighbors(u).contains(&u) && config.min_length <= 1 {
66 cycles.push(vec![graph.to_vid(u)]);
67 count += 1;
68 }
69 }
70 continue;
71 }
72
73 if count >= config.limit {
74 break;
75 }
76
77 let mut scc_nodes = nodes.clone();
84 scc_nodes.sort(); for (i, &start_node) in scc_nodes.iter().enumerate() {
87 if count >= config.limit {
88 break;
89 }
90
91 let sub_scc = &scc_nodes[i..];
93
94 let mut stack = Vec::new();
96 let mut blocked = HashSet::new();
97 let mut block_map: HashMap<u32, HashSet<u32>> = HashMap::new();
98
99 let mut ctx = CircuitCtx {
100 graph,
101 scc_nodes: sub_scc,
102 stack: &mut stack,
103 blocked: &mut blocked,
104 block_map: &mut block_map,
105 cycles: &mut cycles,
106 config: &config,
107 count: &mut count,
108 };
109
110 circuit(start_node, start_node, &mut ctx);
111 }
112 }
113
114 ElementaryCircuitsResult { cycles }
115 }
116}
117
118struct CircuitCtx<'a> {
119 graph: &'a GraphProjection,
120 scc_nodes: &'a [u32],
121 stack: &'a mut Vec<Vid>,
122 blocked: &'a mut HashSet<u32>,
123 block_map: &'a mut HashMap<u32, HashSet<u32>>,
124 cycles: &'a mut Vec<Vec<Vid>>,
125 config: &'a ElementaryCircuitsConfig,
126 count: &'a mut usize,
127}
128
129fn circuit(u: u32, start_node: u32, ctx: &mut CircuitCtx) -> bool {
130 if *ctx.count >= ctx.config.limit {
131 return false;
132 }
133
134 let vid = ctx.graph.to_vid(u);
135 ctx.stack.push(vid);
136 ctx.blocked.insert(u);
137 let mut found = false;
138
139 for &v in ctx.graph.out_neighbors(u) {
141 if !ctx.scc_nodes.contains(&v) {
142 continue;
143 }
144
145 if v == start_node {
146 if ctx.stack.len() >= ctx.config.min_length && ctx.stack.len() <= ctx.config.max_length
147 {
148 ctx.cycles.push(ctx.stack.clone());
149 *ctx.count += 1;
150 found = true;
151 }
152 } else if !ctx.blocked.contains(&v)
153 && ctx.stack.len() < ctx.config.max_length
154 && circuit(v, start_node, ctx)
155 {
156 found = true;
157 }
158 }
159
160 if found {
161 unblock(u, ctx.blocked, ctx.block_map);
162 } else {
163 for &v in ctx.graph.out_neighbors(u) {
164 if ctx.scc_nodes.contains(&v) {
165 ctx.block_map.entry(v).or_default().insert(u);
166 }
167 }
168 }
169
170 ctx.stack.pop();
171 found
172}
173
174fn unblock(u: u32, blocked: &mut HashSet<u32>, block_map: &mut HashMap<u32, HashSet<u32>>) {
175 blocked.remove(&u);
176 if let Some(list) = block_map.remove(&u) {
177 for w in list {
178 if blocked.contains(&w) {
179 unblock(w, blocked, block_map);
180 }
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::algo::test_utils::build_test_graph;
189
190 #[test]
191 fn test_elementary_circuits() {
192 let vids = vec![Vid::from(0), Vid::from(1), Vid::from(2)];
197 let edges = vec![
198 (Vid::from(0), Vid::from(1)),
199 (Vid::from(1), Vid::from(2)),
200 (Vid::from(2), Vid::from(0)),
201 (Vid::from(2), Vid::from(1)),
202 ];
203 let graph = build_test_graph(vids, edges);
204
205 let config = ElementaryCircuitsConfig {
206 min_length: 2,
207 ..Default::default()
208 };
209 let result = ElementaryCircuits::run(&graph, config);
210
211 assert_eq!(result.cycles.len(), 2);
212 }
214}