use super::{NodeId, OpenHypergraph};
use std::cmp::Reverse;
use std::collections::{BinaryHeap, VecDeque};
#[must_use]
pub fn cycle_breaking_nodes<O, A>(f: &OpenHypergraph<O, A>) -> Option<Vec<NodeId>> {
if !f.hypergraph.is_strict() {
return None;
}
assert_eq!(
f.hypergraph.edges.len(),
f.hypergraph.adjacency.len(),
"malformed hypergraph: edges and adjacency lengths differ"
);
let node_count = f.hypergraph.nodes.len();
let vertex_count = node_count + f.hypergraph.adjacency.len();
let mut outgoing = vec![Vec::new(); vertex_count];
let mut incoming = vec![Vec::new(); vertex_count];
for (operation, adjacency) in f.hypergraph.adjacency.iter().enumerate() {
let operation = node_count + operation;
for source in &adjacency.sources {
add_arc(source.0, operation, &mut outgoing, &mut incoming);
}
for target in &adjacency.targets {
add_arc(operation, target.0, &mut outgoing, &mut incoming);
}
}
let selected = greedy_feedback_nodes(node_count, &outgoing, &incoming);
Some(selected.into_iter().map(NodeId).collect())
}
fn add_arc(source: usize, target: usize, outgoing: &mut [Vec<usize>], incoming: &mut [Vec<usize>]) {
outgoing[source].push(target);
incoming[target].push(source);
}
fn greedy_feedback_nodes(
selectable_count: usize,
outgoing: &[Vec<usize>],
incoming: &[Vec<usize>],
) -> Vec<usize> {
debug_assert_eq!(outgoing.len(), incoming.len());
let vertex_count = outgoing.len();
let mut active = vec![true; vertex_count];
let mut active_count = vertex_count;
let mut indegree: Vec<usize> = incoming.iter().map(Vec::len).collect();
let mut outdegree: Vec<usize> = outgoing.iter().map(Vec::len).collect();
let mut peel = VecDeque::new();
let mut candidates = BinaryHeap::new();
let mut selected = Vec::new();
for vertex in 0..vertex_count {
if indegree[vertex] == 0 || outdegree[vertex] == 0 {
peel.push_back(vertex);
}
push_candidate(
vertex,
selectable_count,
&active,
&indegree,
&outdegree,
&mut candidates,
);
}
while active_count > 0 {
while let Some(vertex) = peel.pop_front() {
if !active[vertex] || (indegree[vertex] > 0 && outdegree[vertex] > 0) {
continue;
}
remove_vertex(
vertex,
selectable_count,
outgoing,
incoming,
&mut active,
&mut indegree,
&mut outdegree,
&mut peel,
&mut candidates,
);
active_count -= 1;
}
if active_count == 0 {
break;
}
let vertex = loop {
let (_, Reverse(vertex), candidate_indegree, candidate_outdegree) = candidates
.pop()
.expect("cyclic residual incidence graph must contain a selectable node");
if active[vertex]
&& indegree[vertex] == candidate_indegree
&& outdegree[vertex] == candidate_outdegree
{
break vertex;
}
};
selected.push(vertex);
remove_vertex(
vertex,
selectable_count,
outgoing,
incoming,
&mut active,
&mut indegree,
&mut outdegree,
&mut peel,
&mut candidates,
);
active_count -= 1;
}
selected.sort_unstable();
selected
}
fn score(indegree: usize, outdegree: usize) -> usize {
indegree.saturating_mul(outdegree)
}
type Candidate = (usize, Reverse<usize>, usize, usize);
fn push_candidate(
node: usize,
selectable_count: usize,
active: &[bool],
indegree: &[usize],
outdegree: &[usize],
candidates: &mut BinaryHeap<Candidate>,
) {
if node < selectable_count && active[node] && indegree[node] > 0 && outdegree[node] > 0 {
candidates.push((
score(indegree[node], outdegree[node]),
Reverse(node),
indegree[node],
outdegree[node],
));
}
}
#[allow(clippy::too_many_arguments)]
fn remove_vertex(
vertex: usize,
selectable_count: usize,
outgoing: &[Vec<usize>],
incoming: &[Vec<usize>],
active: &mut [bool],
indegree: &mut [usize],
outdegree: &mut [usize],
peel: &mut VecDeque<usize>,
candidates: &mut BinaryHeap<Candidate>,
) {
active[vertex] = false;
for &target in &outgoing[vertex] {
if active[target] {
indegree[target] -= 1;
if indegree[target] == 0 || outdegree[target] == 0 {
peel.push_back(target);
}
push_candidate(
target,
selectable_count,
active,
indegree,
outdegree,
candidates,
);
}
}
for &source in &incoming[vertex] {
if active[source] {
outdegree[source] -= 1;
if indegree[source] == 0 || outdegree[source] == 0 {
peel.push_back(source);
}
push_candidate(
source,
selectable_count,
active,
indegree,
outdegree,
candidates,
);
}
}
}