use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CycleDetectionResult {
pub cycles: Vec<Vec<NodeIndex>>,
pub has_cycles: bool,
pub cycle_details: Vec<CycleDetail>,
}
#[derive(Debug, Clone)]
pub struct CycleDetail {
pub nodes: Vec<NodeIndex>,
pub description: String,
}
#[derive(Debug, Clone)]
pub enum CycleResolution {
BreakEdges(Vec<(NodeIndex, NodeIndex)>),
PartialOrder(Vec<NodeIndex>),
MergeComponents(Vec<Vec<NodeIndex>>),
}
pub struct TarjanCycleDetector {
index: usize,
stack: Vec<NodeIndex>,
indices: HashMap<NodeIndex, usize>,
low_links: HashMap<NodeIndex, usize>,
on_stack: HashMap<NodeIndex, bool>,
sccs: Vec<Vec<NodeIndex>>,
}
impl TarjanCycleDetector {
pub fn new() -> Self {
Self {
index: 0,
stack: Vec::new(),
indices: HashMap::new(),
low_links: HashMap::new(),
on_stack: HashMap::new(),
sccs: Vec::new(),
}
}
pub fn detect_cycles<N, E>(&mut self, graph: &DiGraph<N, E>) -> CycleDetectionResult {
self.index = 0;
self.stack.clear();
self.indices.clear();
self.low_links.clear();
self.on_stack.clear();
self.sccs.clear();
for node in graph.node_indices() {
if !self.indices.contains_key(&node) {
self.strong_connect(graph, node);
}
}
let mut cycles = Vec::new();
for scc in &self.sccs {
if scc.len() > 1 {
cycles.push(scc.clone());
} else if scc.len() == 1 {
let node = scc[0];
if graph.find_edge(node, node).is_some() {
cycles.push(scc.clone());
}
}
}
let has_cycles = !cycles.is_empty();
let cycle_details = cycles
.iter()
.enumerate()
.map(|(i, cycle)| CycleDetail {
nodes: cycle.clone(),
description: format!("Cycle {}: {} nodes", i + 1, cycle.len()),
})
.collect();
CycleDetectionResult {
cycles,
has_cycles,
cycle_details,
}
}
pub fn find_strongly_connected_components<N, E>(
&mut self,
graph: &DiGraph<N, E>,
) -> Vec<Vec<NodeIndex>> {
let _result = self.detect_cycles(graph);
self.sccs.clone()
}
pub fn handle_cycles<N, E>(
&self,
graph: &DiGraph<N, E>,
cycles: Vec<Vec<NodeIndex>>,
) -> CycleResolution {
if cycles.is_empty() {
if let Ok(order) = petgraph::algo::toposort(graph, None) {
return CycleResolution::PartialOrder(order);
}
}
let mut nodes_in_cycles = std::collections::HashSet::new();
for cycle in &cycles {
for &node in cycle {
nodes_in_cycles.insert(node);
}
}
let mut visited = std::collections::HashSet::new();
let mut partial_order = Vec::new();
fn visit<N, E>(
node: NodeIndex,
graph: &DiGraph<N, E>,
visited: &mut std::collections::HashSet<NodeIndex>,
partial_order: &mut Vec<NodeIndex>,
nodes_in_cycles: &std::collections::HashSet<NodeIndex>,
) {
if visited.contains(&node) {
return;
}
visited.insert(node);
for neighbor in graph.neighbors(node) {
let skip = nodes_in_cycles.contains(&node) && nodes_in_cycles.contains(&neighbor);
if !skip {
visit(neighbor, graph, visited, partial_order, nodes_in_cycles);
}
}
partial_order.push(node);
}
for node in graph.node_indices() {
visit(
node,
graph,
&mut visited,
&mut partial_order,
&nodes_in_cycles,
);
}
partial_order.reverse();
CycleResolution::PartialOrder(partial_order)
}
fn strong_connect<N, E>(&mut self, graph: &DiGraph<N, E>, v: NodeIndex) {
self.indices.insert(v, self.index);
self.low_links.insert(v, self.index);
self.index += 1;
self.stack.push(v);
self.on_stack.insert(v, true);
for neighbor in graph.neighbors(v) {
if !self.indices.contains_key(&neighbor) {
self.strong_connect(graph, neighbor);
let v_low = *self.low_links.get(&v).unwrap();
let neighbor_low = *self.low_links.get(&neighbor).unwrap();
self.low_links.insert(v, v_low.min(neighbor_low));
} else if *self.on_stack.get(&neighbor).unwrap_or(&false) {
let v_low = *self.low_links.get(&v).unwrap();
let neighbor_index = *self.indices.get(&neighbor).unwrap();
self.low_links.insert(v, v_low.min(neighbor_index));
}
}
if self.low_links.get(&v) == self.indices.get(&v) {
let mut scc = Vec::new();
loop {
let w = self.stack.pop().unwrap();
self.on_stack.insert(w, false);
scc.push(w);
if w == v {
break;
}
}
self.sccs.push(scc);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::graph::DiGraph;
#[test]
fn test_simple_cycle_detection() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
graph.add_edge(a, b, ());
graph.add_edge(b, a, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
assert!(result.has_cycles);
assert_eq!(result.cycles.len(), 1);
assert_eq!(result.cycles[0].len(), 2);
}
#[test]
fn test_complex_cycle_detection() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ());
graph.add_edge(b, c, ());
graph.add_edge(c, a, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
assert!(result.has_cycles);
assert_eq!(result.cycles.len(), 1);
assert_eq!(result.cycles[0].len(), 3);
}
#[test]
fn test_multiple_cycles_detection() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
graph.add_edge(a, b, ());
graph.add_edge(b, a, ());
let c = graph.add_node("C");
let d = graph.add_node("D");
let e = graph.add_node("E");
graph.add_edge(c, d, ());
graph.add_edge(d, e, ());
graph.add_edge(e, c, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
assert!(result.has_cycles);
assert_eq!(result.cycles.len(), 2);
let cycle_sizes: Vec<usize> = result.cycles.iter().map(|c| c.len()).collect();
assert!(cycle_sizes.contains(&2));
assert!(cycle_sizes.contains(&3));
}
#[test]
fn test_no_cycle_detection() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ());
graph.add_edge(b, c, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
assert!(!result.has_cycles);
assert_eq!(result.cycles.len(), 0);
}
#[test]
fn test_self_cycle_detection() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
graph.add_edge(a, a, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
assert!(result.has_cycles);
assert_eq!(result.cycles.len(), 1);
assert_eq!(result.cycles[0].len(), 1);
assert_eq!(result.cycles[0][0], a);
}
#[test]
fn test_strongly_connected_components() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
graph.add_edge(a, b, ());
graph.add_edge(b, a, ());
let c = graph.add_node("C");
let d = graph.add_node("D");
let e = graph.add_node("E");
let f = graph.add_node("F");
graph.add_edge(d, e, ());
graph.add_edge(e, f, ());
graph.add_edge(f, d, ());
graph.add_edge(a, c, ());
graph.add_edge(c, d, ());
let mut detector = TarjanCycleDetector::new();
let sccs = detector.find_strongly_connected_components(&graph);
assert_eq!(sccs.len(), 3);
let scc_sizes: Vec<usize> = sccs.iter().map(|scc| scc.len()).collect();
assert!(scc_sizes.contains(&1)); assert!(scc_sizes.contains(&2)); assert!(scc_sizes.contains(&3)); }
#[test]
fn test_cycle_resolution() {
let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ());
graph.add_edge(b, c, ());
graph.add_edge(c, a, ());
graph.add_edge(c, d, ());
let mut detector = TarjanCycleDetector::new();
let result = detector.detect_cycles(&graph);
let resolution = detector.handle_cycles(&graph, result.cycles);
match resolution {
CycleResolution::PartialOrder(order) => {
assert_eq!(order.len(), 4); let d_index = order.iter().position(|&n| n == d).unwrap();
assert!(d_index > 0); }
_ => panic!("Expected PartialOrder resolution"),
}
}
}