ringkernel_graph/algorithms/
scc.rs1use crate::models::{ComponentId, CsrMatrix, NodeId};
10use crate::Result;
11
12#[derive(Debug, Clone, Default)]
14pub struct SccConfig {
15 pub topological_order: bool,
17}
18
19impl SccConfig {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn with_topological_order(mut self) -> Self {
27 self.topological_order = true;
28 self
29 }
30}
31
32pub fn scc_tarjan(adj: &CsrMatrix) -> Result<Vec<ComponentId>> {
37 scc_tarjan_with_config(adj, &SccConfig::default())
38}
39
40pub fn scc_tarjan_with_config(adj: &CsrMatrix, _config: &SccConfig) -> Result<Vec<ComponentId>> {
42 if adj.num_rows == 0 {
43 return Ok(Vec::new());
44 }
45
46 let n = adj.num_rows;
47 let mut component = vec![ComponentId::UNASSIGNED; n];
48 let mut index = vec![u32::MAX; n]; let mut lowlink = vec![u32::MAX; n];
50 let mut on_stack = vec![false; n];
51 let mut stack = Vec::new();
52
53 let mut current_index = 0u32;
54 let mut current_component = 0u32;
55
56 for start in 0..n {
58 if index[start] != u32::MAX {
59 continue;
60 }
61
62 let mut dfs_stack: Vec<(usize, usize)> = vec![(start, 0)];
64
65 while let Some(&(v, neighbor_idx)) = dfs_stack.last() {
66 if neighbor_idx == 0 {
67 index[v] = current_index;
69 lowlink[v] = current_index;
70 current_index += 1;
71 stack.push(v);
72 on_stack[v] = true;
73 }
74
75 let neighbors = adj.neighbors(NodeId(v as u32));
76
77 if neighbor_idx < neighbors.len() {
78 let w = neighbors[neighbor_idx] as usize;
79 dfs_stack.last_mut().unwrap().1 += 1;
80
81 if index[w] == u32::MAX {
82 dfs_stack.push((w, 0));
84 } else if on_stack[w] {
85 lowlink[v] = lowlink[v].min(index[w]);
87 }
88 } else {
89 dfs_stack.pop();
91
92 if let Some(&(parent, _)) = dfs_stack.last() {
94 lowlink[parent] = lowlink[parent].min(lowlink[v]);
95 }
96
97 if lowlink[v] == index[v] {
99 loop {
101 let w = stack.pop().unwrap();
102 on_stack[w] = false;
103 component[w] = ComponentId::new(current_component);
104 if w == v {
105 break;
106 }
107 }
108 current_component += 1;
109 }
110 }
111 }
112 }
113
114 Ok(component)
115}
116
117pub fn scc_kosaraju(adj: &CsrMatrix) -> Result<Vec<ComponentId>> {
125 scc_kosaraju_with_config(adj, &SccConfig::default())
126}
127
128pub fn scc_kosaraju_with_config(adj: &CsrMatrix, _config: &SccConfig) -> Result<Vec<ComponentId>> {
130 if adj.num_rows == 0 {
131 return Ok(Vec::new());
132 }
133
134 let n = adj.num_rows;
135
136 let mut visited = vec![false; n];
138 let mut finish_order = Vec::with_capacity(n);
139
140 for start in 0..n {
141 if !visited[start] {
142 dfs_finish_order(adj, start, &mut visited, &mut finish_order);
143 }
144 }
145
146 let transposed = adj.transpose();
148
149 let mut component = vec![ComponentId::UNASSIGNED; n];
151 let mut current_component = 0u32;
152 visited.fill(false);
153
154 for &node in finish_order.iter().rev() {
155 if !visited[node] {
156 dfs_assign_component(
157 &transposed,
158 node,
159 &mut visited,
160 &mut component,
161 ComponentId::new(current_component),
162 );
163 current_component += 1;
164 }
165 }
166
167 Ok(component)
168}
169
170fn dfs_finish_order(
172 adj: &CsrMatrix,
173 start: usize,
174 visited: &mut [bool],
175 finish_order: &mut Vec<usize>,
176) {
177 let mut stack = vec![(start, false)]; while let Some((node, processed)) = stack.pop() {
180 if processed {
181 finish_order.push(node);
182 continue;
183 }
184
185 if visited[node] {
186 continue;
187 }
188 visited[node] = true;
189
190 stack.push((node, true));
192
193 for &neighbor in adj.neighbors(NodeId(node as u32)) {
194 let w = neighbor as usize;
195 if !visited[w] {
196 stack.push((w, false));
197 }
198 }
199 }
200}
201
202fn dfs_assign_component(
204 adj: &CsrMatrix,
205 start: usize,
206 visited: &mut [bool],
207 component: &mut [ComponentId],
208 comp_id: ComponentId,
209) {
210 let mut stack = vec![start];
211
212 while let Some(node) = stack.pop() {
213 if visited[node] {
214 continue;
215 }
216 visited[node] = true;
217 component[node] = comp_id;
218
219 for &neighbor in adj.neighbors(NodeId(node as u32)) {
220 let w = neighbor as usize;
221 if !visited[w] {
222 stack.push(w);
223 }
224 }
225 }
226}
227
228pub fn count_components(components: &[ComponentId]) -> usize {
230 if components.is_empty() {
231 return 0;
232 }
233 components
234 .iter()
235 .filter(|c| c.is_assigned())
236 .map(|c| c.0)
237 .max()
238 .map(|m| m as usize + 1)
239 .unwrap_or(0)
240}
241
242pub fn get_component_members(components: &[ComponentId]) -> Vec<Vec<NodeId>> {
244 let num_components = count_components(components);
245 let mut members = vec![Vec::new(); num_components];
246
247 for (node_id, &comp_id) in components.iter().enumerate() {
248 if comp_id.is_assigned() {
249 members[comp_id.0 as usize].push(NodeId(node_id as u32));
250 }
251 }
252
253 members
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_single_node() {
262 let adj = CsrMatrix::empty(1);
263 let components = scc_tarjan(&adj).unwrap();
264 assert_eq!(components.len(), 1);
265 assert!(components[0].is_assigned());
266 }
267
268 #[test]
269 fn test_line_graph_sccs() {
270 let edges = [(0, 1), (1, 2), (2, 3)];
273 let adj = CsrMatrix::from_edges(4, &edges);
274
275 let components = scc_tarjan(&adj).unwrap();
276 assert_eq!(count_components(&components), 4);
277 }
278
279 #[test]
280 fn test_cycle_scc() {
281 let edges = [(0, 1), (1, 2), (2, 0)];
284 let adj = CsrMatrix::from_edges(3, &edges);
285
286 let components = scc_tarjan(&adj).unwrap();
287 assert_eq!(count_components(&components), 1);
288 assert_eq!(components[0], components[1]);
289 assert_eq!(components[1], components[2]);
290 }
291
292 #[test]
293 fn test_two_sccs() {
294 let edges = [(0, 1), (1, 0), (1, 2), (2, 3), (3, 2)];
296 let adj = CsrMatrix::from_edges(4, &edges);
297
298 let components = scc_tarjan(&adj).unwrap();
299 assert_eq!(count_components(&components), 2);
300
301 assert_eq!(components[0], components[1]);
303 assert_eq!(components[2], components[3]);
305 assert_ne!(components[0], components[2]);
307 }
308
309 #[test]
310 fn test_kosaraju_same_as_tarjan() {
311 let edges = [
313 (0, 1),
314 (1, 2),
315 (2, 0), (2, 3),
317 (3, 4),
318 (4, 3), (5, 6),
320 (6, 5), ];
322 let adj = CsrMatrix::from_edges(7, &edges);
323
324 let tarjan = scc_tarjan(&adj).unwrap();
325 let kosaraju = scc_kosaraju(&adj).unwrap();
326
327 assert_eq!(count_components(&tarjan), count_components(&kosaraju));
329
330 let tarjan_members = get_component_members(&tarjan);
332 let kosaraju_members = get_component_members(&kosaraju);
333
334 assert_eq!(tarjan_members.len(), kosaraju_members.len());
335
336 let mut tarjan_sorted: Vec<_> = tarjan_members
338 .iter()
339 .map(|v| {
340 let mut v = v.clone();
341 v.sort();
342 v
343 })
344 .collect();
345 tarjan_sorted.sort();
346
347 let mut kosaraju_sorted: Vec<_> = kosaraju_members
348 .iter()
349 .map(|v| {
350 let mut v = v.clone();
351 v.sort();
352 v
353 })
354 .collect();
355 kosaraju_sorted.sort();
356
357 assert_eq!(tarjan_sorted, kosaraju_sorted);
358 }
359
360 #[test]
361 fn test_disconnected_graph() {
362 let adj = CsrMatrix::empty(2);
364
365 let components = scc_tarjan(&adj).unwrap();
366 assert_eq!(count_components(&components), 2);
367 assert_ne!(components[0], components[1]);
368 }
369
370 #[test]
371 fn test_get_component_members() {
372 let edges = [(0, 1), (1, 0), (2, 3), (3, 2)];
373 let adj = CsrMatrix::from_edges(4, &edges);
374
375 let components = scc_tarjan(&adj).unwrap();
376 let members = get_component_members(&components);
377
378 assert_eq!(members.len(), 2);
379
380 for comp in &members {
382 assert_eq!(comp.len(), 2);
383 }
384 }
385
386 #[test]
387 fn test_empty_graph() {
388 let adj = CsrMatrix::empty(0);
389 let components = scc_tarjan(&adj).unwrap();
390 assert!(components.is_empty());
391 }
392}