Skip to main content

weavatrix_graph/algo/
feedback.rs

1use crate::IndexGraphView;
2use crate::Vec;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct FeedbackArcSet<Node, Edge> {
6    order: Vec<Node>,
7    edges: Vec<Edge>,
8}
9
10impl<Node, Edge> FeedbackArcSet<Node, Edge> {
11    #[must_use]
12    pub fn order(&self) -> &[Node] {
13        &self.order
14    }
15
16    #[must_use]
17    pub fn edges(&self) -> &[Edge] {
18        &self.edges
19    }
20}
21
22/// Approximates a directed feedback arc set with the Eades ordering heuristic.
23///
24/// Minimum feedback arc set is NP-hard. The returned edges are always a valid
25/// set whose removal makes the selected ordering acyclic, but not necessarily
26/// a minimum-cardinality set.
27pub fn feedback_arc_set_heuristic<G>(graph: &G) -> FeedbackArcSet<G::Node, G::Edge>
28where
29    G: IndexGraphView,
30{
31    let bound = graph.node_bound();
32    let mut nodes = vec![None; bound];
33    let mut outgoing = vec![Vec::new(); bound];
34    let mut incoming = vec![Vec::new(); bound];
35    for node in graph.node_indices() {
36        nodes[G::node_slot(node)] = Some(node);
37    }
38    for (_, endpoints) in graph.edge_references() {
39        let source = G::node_slot(endpoints.source());
40        let target = G::node_slot(endpoints.target());
41        outgoing[source].push(target);
42        incoming[target].push(source);
43    }
44    let active = nodes.iter().map(Option::is_some).collect::<Vec<_>>();
45    let mut state = OrderingState::new(active, outgoing, incoming);
46    let mut left = Vec::new();
47    let mut right = Vec::new();
48    while state.remaining > 0 {
49        let previous = state.remaining;
50        while let Some(node) = state.pop_sink() {
51            right.push(node);
52            state.remove(node);
53        }
54        while let Some(node) = state.pop_source() {
55            left.push(node);
56            state.remove(node);
57        }
58        if let Some(node) = state.pop_delta() {
59            left.push(node);
60            state.remove(node);
61        }
62        if state.remaining == previous {
63            break;
64        }
65    }
66    right.reverse();
67    left.extend(right);
68    let mut position = vec![usize::MAX; bound];
69    for (index, &node) in left.iter().enumerate() {
70        position[node] = index;
71    }
72    let mut edges = graph
73        .edge_references()
74        .filter_map(|(edge, endpoints)| {
75            (position[G::node_slot(endpoints.source())]
76                >= position[G::node_slot(endpoints.target())])
77            .then_some(edge)
78        })
79        .collect::<Vec<_>>();
80    edges.sort_unstable_by_key(|edge| G::edge_slot(*edge));
81    let order = left.into_iter().filter_map(|slot| nodes[slot]).collect();
82    FeedbackArcSet { order, edges }
83}
84
85struct OrderingState {
86    active: Vec<bool>,
87    outgoing: Vec<Vec<usize>>,
88    incoming: Vec<Vec<usize>>,
89    out_degree: Vec<usize>,
90    in_degree: Vec<usize>,
91    stamps: Vec<u64>,
92    sinks: Vec<(u64, usize)>,
93    sources: Vec<(u64, usize)>,
94    positive: Vec<Vec<(u64, usize)>>,
95    negative: Vec<Vec<(u64, usize)>>,
96    clock: u64,
97    remaining: usize,
98}
99
100impl OrderingState {
101    fn new(active: Vec<bool>, outgoing: Vec<Vec<usize>>, incoming: Vec<Vec<usize>>) -> Self {
102        let out_degree = outgoing.iter().map(Vec::len).collect();
103        let in_degree = incoming.iter().map(Vec::len).collect();
104        let remaining = active.iter().filter(|active| **active).count();
105        let bound = active.len();
106        let mut state = Self {
107            active,
108            outgoing,
109            incoming,
110            out_degree,
111            in_degree,
112            stamps: vec![0; bound],
113            sinks: Vec::new(),
114            sources: Vec::new(),
115            positive: Vec::new(),
116            negative: Vec::new(),
117            clock: 0,
118            remaining,
119        };
120        for node in 0..state.active.len() {
121            state.refresh(node);
122        }
123        state
124    }
125
126    fn pop_sink(&mut self) -> Option<usize> {
127        while let Some((stamp, node)) = self.sinks.pop() {
128            if self.active[node] && self.out_degree[node] == 0 && self.stamps[node] == stamp {
129                return Some(node);
130            }
131        }
132        None
133    }
134
135    fn pop_source(&mut self) -> Option<usize> {
136        while let Some((stamp, node)) = self.sources.pop() {
137            if self.active[node]
138                && self.in_degree[node] == 0
139                && self.out_degree[node] > 0
140                && self.stamps[node] == stamp
141            {
142                return Some(node);
143            }
144        }
145        None
146    }
147
148    fn pop_delta(&mut self) -> Option<usize> {
149        while !self.positive.is_empty() {
150            let index = self.positive.len() - 1;
151            let delta = isize::try_from(index).unwrap_or(isize::MAX);
152            let bucket = &mut self.positive[index];
153            while let Some((stamp, node)) = bucket.pop() {
154                if self.active[node]
155                    && self.in_degree[node] > 0
156                    && self.out_degree[node] > 0
157                    && signed(self.out_degree[node]) - signed(self.in_degree[node]) == delta
158                    && self.stamps[node] == stamp
159                {
160                    return Some(node);
161                }
162            }
163            self.positive.pop();
164        }
165        for index in 0..self.negative.len() {
166            while let Some((stamp, node)) = self.negative[index].pop() {
167                let delta = -isize::try_from(index + 1).unwrap_or(isize::MAX);
168                if self.active[node]
169                    && self.in_degree[node] > 0
170                    && self.out_degree[node] > 0
171                    && self.delta(node) == delta
172                    && self.stamps[node] == stamp
173                {
174                    return Some(node);
175                }
176            }
177        }
178        None
179    }
180
181    fn remove(&mut self, node: usize) {
182        self.active[node] = false;
183        self.remaining = self.remaining.saturating_sub(1);
184        for index in 0..self.outgoing[node].len() {
185            let target = self.outgoing[node][index];
186            if self.active[target] {
187                self.in_degree[target] = self.in_degree[target].saturating_sub(1);
188                self.refresh(target);
189            }
190        }
191        for index in 0..self.incoming[node].len() {
192            let source = self.incoming[node][index];
193            if self.active[source] {
194                self.out_degree[source] = self.out_degree[source].saturating_sub(1);
195                self.refresh(source);
196            }
197        }
198    }
199
200    fn refresh(&mut self, node: usize) {
201        if !self.active[node] {
202            return;
203        }
204        self.clock = self.clock.saturating_add(1);
205        self.stamps[node] = self.clock;
206        if self.out_degree[node] == 0 {
207            self.sinks.push((self.clock, node));
208        } else if self.in_degree[node] == 0 {
209            self.sources.push((self.clock, node));
210        } else {
211            let delta = self.delta(node);
212            if delta >= 0 {
213                let index = delta.unsigned_abs();
214                if self.positive.len() <= index {
215                    self.positive.resize_with(index + 1, Vec::new);
216                }
217                self.positive[index].push((self.clock, node));
218            } else {
219                let index = delta.unsigned_abs() - 1;
220                if self.negative.len() <= index {
221                    self.negative.resize_with(index + 1, Vec::new);
222                }
223                self.negative[index].push((self.clock, node));
224            }
225        }
226    }
227
228    fn delta(&self, node: usize) -> isize {
229        signed(self.out_degree[node]) - signed(self.in_degree[node])
230    }
231}
232
233fn signed(value: usize) -> isize {
234    isize::try_from(value).unwrap_or(isize::MAX)
235}