#[cfg(any(test, feature = "test-utils"))]
pub mod utils;
use ade_common::INVALID_KEY_SEQUENCE;
use ade_strongly_connected_components::scc_iterative;
use ade_traits::{EdgeTrait, GraphViewTrait, NodeTrait};
use smallvec::SmallVec;
pub fn elementary_circuits<N: NodeTrait, E: EdgeTrait>(
graph: &impl GraphViewTrait<N, E>,
) -> Vec<Vec<u32>> {
if !graph.has_sequential_keys() {
panic!("{}", INVALID_KEY_SEQUENCE);
}
let mut circuits: Vec<Vec<u32>> = Vec::new();
let mut stack: Vec<u32> = Vec::new();
let n = match graph.get_nodes().count() {
0 => return circuits, len => (len - 1) as u32,
};
let mut s: u32 = n; let size = (n + 1) as usize;
let mut blocked_set: Vec<bool> = vec![false; size];
let mut blocked_map: Vec<SmallVec<[u32; 4]>> = vec![SmallVec::new(); size];
loop {
let remaining_nodes: Vec<u32> = (0..=s).collect();
let subgraph = graph.filter(&remaining_nodes);
let components = scc_iterative(&subgraph);
let component = components.iter().find(|c| c.contains(&s)).unwrap();
let adj = subgraph.filter(component);
if adj.get_nodes().next().is_some() {
for key in adj.get_node_keys() {
let k = key as usize;
blocked_set[k] = false;
blocked_map[k].clear();
}
find_circuit(
s,
s,
&mut circuits,
&mut stack,
&mut blocked_set,
&mut blocked_map,
&adj,
);
if s == 0 {
break;
}
s -= 1;
} else {
s = 0;
}
}
circuits
}
fn find_circuit<N: NodeTrait, E: EdgeTrait>(
s: u32,
v: u32,
circuits: &mut Vec<Vec<u32>>,
stack: &mut Vec<u32>,
blocked_set: &mut [bool],
blocked_map: &mut [SmallVec<[u32; 4]>],
adj: &impl GraphViewTrait<N, E>,
) -> bool {
let mut f: bool = false;
let v_us = v as usize;
stack.push(v);
blocked_set[v_us] = true;
for w_key in adj.get_successors_keys(v) {
if w_key == s {
let mut circuit = Vec::with_capacity(stack.len() + 1);
circuit.extend_from_slice(stack);
circuit.push(s);
circuits.push(circuit);
f = true;
} else if !blocked_set[w_key as usize]
&& find_circuit(s, w_key, circuits, stack, blocked_set, blocked_map, adj)
{
f = true;
}
}
if f {
unblock(v, blocked_set, blocked_map);
} else {
for w_key in adj.get_successors_keys(v) {
let list = &mut blocked_map[w_key as usize];
if !list.contains(&v) {
list.push(v);
}
}
}
stack.pop();
f
}
fn unblock(u: u32, blocked_set: &mut [bool], blocked_map: &mut [SmallVec<[u32; 4]>]) {
let u_us = u as usize;
blocked_set[u_us] = false;
while let Some(w) = blocked_map[u_us].pop() {
let w_us = w as usize;
if blocked_set[w_us] {
unblock(w, blocked_set, blocked_map);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::circuits_equal;
use crate::utils::number_circuits;
use ade_common::{self, assert_panics_with};
use ade_graph::utils::build::build_graph;
use ade_graph_generators::complete_graph_data;
use ade_graph_generators::generate_random_graph_data;
use graph_cycles::Cycles;
use petgraph::graph::Graph as PetGraph;
use rand::Rng;
use std::collections::HashSet;
#[test]
fn test_elementary_circuits_1() {
let graph = build_graph(vec![0, 1, 2], vec![(0, 1), (1, 2), (2, 1)]);
let circuits = elementary_circuits(&graph);
let expected = vec![vec![1, 2, 1]];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_non_sequential_keys() {
let graph = build_graph(vec![1, 3, 5], vec![(1, 3), (3, 5), (5, 1)]);
assert_panics_with!(
elementary_circuits(&graph),
ade_common::INVALID_KEY_SEQUENCE
);
}
#[test]
fn test_elementary_circuits_3() {
let graph = build_graph(vec![0, 1, 2], vec![(0, 1), (1, 2)]);
let circuits = elementary_circuits(&graph);
let expected = vec![];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_4() {
let graph = build_graph(vec![0, 1, 2], vec![]);
let circuits = elementary_circuits(&graph);
let expected = vec![];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_5() {
let graph = build_graph(vec![], vec![]);
let circuits = elementary_circuits(&graph);
let expected = vec![];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_6() {
let graph = build_graph(vec![0], vec![(0, 0)]);
let circuits = elementary_circuits(&graph);
let expected = vec![vec![0, 0]];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_7() {
let graph = build_graph(vec![0, 1], vec![(0, 0), (1, 1), (0, 1)]);
let circuits = elementary_circuits(&graph);
let expected = vec![vec![0, 0], vec![1, 1]];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_2() {
let graph = build_graph(
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
vec![
(0, 1),
(0, 7),
(0, 4),
(1, 2),
(1, 6),
(1, 8),
(2, 1),
(2, 0),
(2, 3),
(2, 5),
(3, 4),
(4, 1),
(5, 3),
(7, 8),
(8, 7),
],
);
let circuits = elementary_circuits(&graph);
let expected: Vec<Vec<u32>> = vec![
vec![0, 1, 2, 0],
vec![0, 4, 1, 2, 0],
vec![1, 2, 1],
vec![1, 2, 3, 4, 1],
vec![1, 2, 5, 3, 4, 1],
vec![7, 8, 7],
];
assert!(circuits_equal(&circuits, &expected));
}
#[test]
fn test_elementary_circuits_complete_graph() {
let n: usize = 6;
let (nodes, edges) = complete_graph_data(n);
let graph = build_graph(nodes, edges);
let circuits = elementary_circuits(&graph);
assert_eq!(circuits.len(), number_circuits(n))
}
#[test]
fn test_elementary_circuits_multiple_random_graphs() {
let mut rng = rand::thread_rng();
for _ in 0..100 {
let node_count = rng.gen_range(5..20);
let edge_count = rng.gen_range(node_count * 2..node_count * 5);
let seed = rng.gen();
let (nodes, edges) = generate_random_graph_data(node_count, edge_count, seed);
let mut seen = HashSet::new();
let mut unique_edges = Vec::new();
for edge in edges {
if seen.insert(edge) {
unique_edges.push(edge);
}
}
let graph = build_graph(nodes.clone(), unique_edges.clone());
let circuits = elementary_circuits(&graph);
let g = PetGraph::<(), ()>::from_edges(unique_edges.clone());
let cycles = g.cycles();
assert_eq!(
circuits.len(),
cycles.len(),
"Mismatch con seed={:?}, nodi={}, edges={}",
seed,
node_count,
edge_count
);
}
}
}