use std::collections::{HashMap, HashSet, VecDeque};
use crate::graph::capability::{Bigraph, Directed, StableEdge, StableNode};
use crate::graph::Graph;
pub struct GreedyFeedbackArcSet<'r, G: ?Sized, E, N> {
graph: &'r G,
position: HashMap<N, usize>,
edges: E,
}
pub fn greedy_feedback_arc_set<'r, G>(
graph: &'r G,
) -> GreedyFeedbackArcSet<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices, G::NodeIx>
where
G: Graph + Directed<'r> + Bigraph + StableEdge + StableNode + ?Sized,
{
let nodes: Vec<G::NodeIx> = <_ as crate::graph::GraphOperation<'_>>::node_indices(graph).collect();
let position = if nodes.is_empty() {
HashMap::new()
} else {
let mut in_deg: HashMap<G::NodeIx, usize> = HashMap::new();
let mut out_deg: HashMap<G::NodeIx, usize> = HashMap::new();
let mut remaining: HashSet<G::NodeIx> = HashSet::new();
for &node in &nodes {
in_deg.insert(node, 0);
out_deg.insert(node, 0);
remaining.insert(node);
}
for eix in <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph) {
let tail = unsafe { graph.edge_tail_index_unchecked(eix) };
let head = unsafe { graph.edge_head_index_unchecked(eix) };
if tail != head {
*out_deg.get_mut(&tail).unwrap() += 1;
*in_deg.get_mut(&head).unwrap() += 1;
}
}
let mut left: VecDeque<G::NodeIx> = VecDeque::new();
let mut right: VecDeque<G::NodeIx> = VecDeque::new();
let mut cur_in: HashMap<G::NodeIx, usize> = in_deg.clone();
let mut cur_out: HashMap<G::NodeIx, usize> = out_deg.clone();
while !remaining.is_empty() {
let mut changed = true;
while changed {
changed = false;
let sinks: Vec<G::NodeIx> = remaining
.iter()
.filter(|&&n| cur_out[&n] == 0)
.copied()
.collect();
for sink in sinks {
remaining.remove(&sink);
right.push_front(sink);
for pred_eix in unsafe { Directed::edge_indices_to_unchecked(graph, sink) } {
let pred = unsafe { graph.edge_tail_index_unchecked(pred_eix) };
if remaining.contains(&pred) && pred != sink {
*cur_out.get_mut(&pred).unwrap() -= 1;
}
}
changed = true;
}
let sources: Vec<G::NodeIx> = remaining
.iter()
.filter(|&&n| cur_in[&n] == 0)
.copied()
.collect();
for source in sources {
remaining.remove(&source);
left.push_back(source);
for succ_eix in unsafe { <G as crate::graph::GraphOperation<'_>>::edge_indices_from_unchecked(graph, source) } {
let succ = unsafe { graph.edge_head_index_unchecked(succ_eix) };
if remaining.contains(&succ) && succ != source {
*cur_in.get_mut(&succ).unwrap() -= 1;
}
}
changed = true;
}
}
if remaining.is_empty() {
break;
}
let best = remaining
.iter()
.max_by_key(|&&n| cur_out[&n] as isize - cur_in[&n] as isize)
.copied()
.unwrap();
remaining.remove(&best);
left.push_back(best);
for succ_eix in unsafe { <G as crate::graph::GraphOperation<'_>>::edge_indices_from_unchecked(graph, best) } {
let succ = unsafe { graph.edge_head_index_unchecked(succ_eix) };
if remaining.contains(&succ) && succ != best {
*cur_in.get_mut(&succ).unwrap() -= 1;
}
}
for pred_eix in unsafe { Directed::edge_indices_to_unchecked(graph, best) } {
let pred = unsafe { graph.edge_tail_index_unchecked(pred_eix) };
if remaining.contains(&pred) && pred != best {
*cur_out.get_mut(&pred).unwrap() -= 1;
}
}
}
let mut ordering: Vec<G::NodeIx> = Vec::new();
ordering.extend(left);
ordering.extend(right);
ordering.iter().enumerate().map(|(i, &n)| (n, i)).collect()
};
let edges = <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph);
GreedyFeedbackArcSet {
graph,
position,
edges,
}
}
impl<'r, G> Iterator
for GreedyFeedbackArcSet<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices, G::NodeIx>
where
G: Graph + Directed<'r> + Bigraph + StableNode + ?Sized,
{
type Item = G::EdgeIx;
fn next(&mut self) -> Option<G::EdgeIx> {
loop {
let eix = self.edges.next()?;
let tail = unsafe { self.graph.edge_tail_index_unchecked(eix) };
let head = unsafe { self.graph.edge_head_index_unchecked(eix) };
if tail == head {
return Some(eix);
}
if self.position[&tail] >= self.position[&head] {
return Some(eix);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
use std::collections::HashSet;
#[test]
fn feedback_arc_set_acyclic() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
let fas: HashSet<_> = greedy_feedback_arc_set(&g).collect();
assert!(fas.is_empty());
}
#[test]
fn feedback_arc_set_simple_cycle() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->0", [2, 0]).unwrap();
let fas: HashSet<_> = greedy_feedback_arc_set(&g).collect();
assert_eq!(fas.len(), 1);
}
#[test]
fn feedback_arc_set_self_loop() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_edge("0->0", [0, 0]).unwrap();
let fas: HashSet<_> = greedy_feedback_arc_set(&g).collect();
assert_eq!(fas.len(), 1);
assert!(fas.contains(&"0->0"));
}
#[test]
fn feedback_arc_set_two_cycles() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->0", [1, 0]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g.insert_edge("3->2", [3, 2]).unwrap();
let fas: HashSet<_> = greedy_feedback_arc_set(&g).collect();
assert!(fas.len() >= 2);
}
#[test]
fn feedback_arc_set_removes_cycles() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->0", [2, 0]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g.insert_edge("3->1", [3, 1]).unwrap();
let fas: HashSet<_> = greedy_feedback_arc_set(&g).collect();
assert!(!fas.is_empty());
assert!(fas.len() <= 5);
}
}