geographdb_core/algorithms/
scc.rs1use crate::algorithms::astar::CfgGraphNode;
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone)]
10pub struct SccResult {
11 pub components: Vec<Vec<u64>>,
13 pub node_to_component: HashMap<u64, usize>,
15 pub cycle_count: usize,
17}
18
19struct TarjanState {
20 index: u64,
21 stack: Vec<u64>,
22 on_stack: HashSet<u64>,
23 indices: HashMap<u64, u64>,
24 lowlinks: HashMap<u64, u64>,
25 components: Vec<Vec<u64>>,
26}
27
28impl TarjanState {
29 fn new() -> Self {
30 Self {
31 index: 0,
32 stack: Vec::new(),
33 on_stack: HashSet::new(),
34 indices: HashMap::new(),
35 lowlinks: HashMap::new(),
36 components: Vec::new(),
37 }
38 }
39}
40
41pub fn tarjan_scc(nodes: &[CfgGraphNode]) -> SccResult {
46 let mut state = TarjanState::new();
47 let node_map: HashMap<u64, &CfgGraphNode> = nodes.iter().map(|n| (n.id, n)).collect();
48
49 for node in nodes {
50 if !state.indices.contains_key(&node.id) {
51 strongconnect(node.id, &node_map, &mut state);
52 }
53 }
54
55 let mut node_to_component = HashMap::new();
56 for (i, component) in state.components.iter().enumerate() {
57 for &node_id in component {
58 node_to_component.insert(node_id, i);
59 }
60 }
61
62 let cycle_count = state.components.iter().filter(|c| c.len() > 1).count();
63
64 SccResult {
65 components: state.components,
66 node_to_component,
67 cycle_count,
68 }
69}
70
71fn strongconnect(node_id: u64, node_map: &HashMap<u64, &CfgGraphNode>, state: &mut TarjanState) {
72 state.indices.insert(node_id, state.index);
73 state.lowlinks.insert(node_id, state.index);
74 state.index += 1;
75 state.stack.push(node_id);
76 state.on_stack.insert(node_id);
77
78 if let Some(node) = node_map.get(&node_id) {
79 for &successor_id in &node.successors {
80 if !state.indices.contains_key(&successor_id) {
81 strongconnect(successor_id, node_map, state);
82 let successor_lowlink = *state.lowlinks.get(&successor_id).unwrap();
83 let node_lowlink = state.lowlinks.get_mut(&node_id).unwrap();
84 *node_lowlink = (*node_lowlink).min(successor_lowlink);
85 } else if state.on_stack.contains(&successor_id) {
86 let successor_index = *state.indices.get(&successor_id).unwrap();
87 let node_lowlink = state.lowlinks.get_mut(&node_id).unwrap();
88 *node_lowlink = (*node_lowlink).min(successor_index);
89 }
90 }
91 }
92
93 let node_lowlink = *state.lowlinks.get(&node_id).unwrap();
94 let node_index = *state.indices.get(&node_id).unwrap();
95
96 if node_lowlink == node_index {
97 let mut component = Vec::new();
98 loop {
99 let w = state.stack.pop().unwrap();
100 state.on_stack.remove(&w);
101 component.push(w);
102 if w == node_id {
103 break;
104 }
105 }
106 state.components.push(component);
107 }
108}
109
110pub fn find_cycles(nodes: &[CfgGraphNode]) -> Vec<Vec<u64>> {
112 let result = tarjan_scc(nodes);
113 result
114 .components
115 .into_iter()
116 .filter(|c| c.len() > 1)
117 .collect()
118}
119
120pub fn has_cycles(nodes: &[CfgGraphNode]) -> bool {
122 let result = tarjan_scc(nodes);
123 result.cycle_count > 0
124}
125
126pub fn condense_graph(nodes: &[CfgGraphNode]) -> Vec<Vec<usize>> {
128 let result = tarjan_scc(nodes);
129 let num_components = result.components.len();
130
131 let mut condensed = vec![HashSet::new(); num_components];
132
133 for node in nodes {
134 let from_component = result.node_to_component.get(&node.id).copied().unwrap_or(0);
135 for &successor_id in &node.successors {
136 let to_component = result
137 .node_to_component
138 .get(&successor_id)
139 .copied()
140 .unwrap_or(0);
141 if from_component != to_component {
142 condensed[from_component].insert(to_component);
143 }
144 }
145 }
146
147 condensed
148 .into_iter()
149 .map(|s| s.into_iter().collect())
150 .collect()
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_tarjan_scc_no_cycles() {
159 let nodes = vec![
160 CfgGraphNode {
161 id: 0,
162 x: 0.0,
163 y: 0.0,
164 z: 0.0,
165 successors: vec![1],
166 },
167 CfgGraphNode {
168 id: 1,
169 x: 1.0,
170 y: 0.0,
171 z: 0.0,
172 successors: vec![2],
173 },
174 CfgGraphNode {
175 id: 2,
176 x: 2.0,
177 y: 0.0,
178 z: 0.0,
179 successors: vec![],
180 },
181 ];
182
183 let result = tarjan_scc(&nodes);
184 assert_eq!(result.components.len(), 3);
185 assert_eq!(result.cycle_count, 0);
186 }
187
188 #[test]
189 fn test_tarjan_scc_simple_cycle() {
190 let nodes = vec![
191 CfgGraphNode {
192 id: 0,
193 x: 0.0,
194 y: 0.0,
195 z: 0.0,
196 successors: vec![1],
197 },
198 CfgGraphNode {
199 id: 1,
200 x: 1.0,
201 y: 0.0,
202 z: 0.0,
203 successors: vec![2],
204 },
205 CfgGraphNode {
206 id: 2,
207 x: 2.0,
208 y: 0.0,
209 z: 0.0,
210 successors: vec![0],
211 },
212 ];
213
214 let result = tarjan_scc(&nodes);
215 assert_eq!(result.components.len(), 1);
216 assert_eq!(result.cycle_count, 1);
217 }
218
219 #[test]
220 fn test_has_cycles() {
221 let nodes_no = vec![
222 CfgGraphNode {
223 id: 0,
224 x: 0.0,
225 y: 0.0,
226 z: 0.0,
227 successors: vec![1],
228 },
229 CfgGraphNode {
230 id: 1,
231 x: 1.0,
232 y: 0.0,
233 z: 0.0,
234 successors: vec![],
235 },
236 ];
237 assert!(!has_cycles(&nodes_no));
238
239 let nodes_yes = vec![
240 CfgGraphNode {
241 id: 0,
242 x: 0.0,
243 y: 0.0,
244 z: 0.0,
245 successors: vec![1],
246 },
247 CfgGraphNode {
248 id: 1,
249 x: 1.0,
250 y: 0.0,
251 z: 0.0,
252 successors: vec![0],
253 },
254 ];
255 assert!(has_cycles(&nodes_yes));
256 }
257
258 #[test]
259 fn test_condense_graph() {
260 let nodes = vec![
261 CfgGraphNode {
262 id: 0,
263 x: 0.0,
264 y: 0.0,
265 z: 0.0,
266 successors: vec![1, 2],
267 },
268 CfgGraphNode {
269 id: 1,
270 x: 1.0,
271 y: 0.0,
272 z: 0.0,
273 successors: vec![0],
274 },
275 CfgGraphNode {
276 id: 2,
277 x: 2.0,
278 y: 0.0,
279 z: 0.0,
280 successors: vec![],
281 },
282 ];
283
284 let condensed = condense_graph(&nodes);
285 assert_eq!(condensed.len(), 2);
286 }
287}