weavatrix_graph/algo/cuts/stoer_wagner/
mod.rs1mod candidate;
2mod queue;
3
4use self::candidate::{Candidate, better, canonical_candidate, map_nodes, nodes, validate_weight};
5use self::queue::MaxQueue;
6use crate::{GraphError, IndexUndirectedGraphView, Measure, Result, Vec};
7use alloc::collections::BTreeMap;
8use core::cmp::Ordering;
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct StoerWagnerCut<Node, Weight> {
13 weight: Weight,
14 partition: Vec<Node>,
15 complement: Vec<Node>,
16}
17
18impl<Node, Weight: Copy> StoerWagnerCut<Node, Weight> {
19 #[must_use]
20 pub const fn weight(&self) -> Weight {
21 self.weight
22 }
23
24 #[must_use]
25 pub fn partition(&self) -> &[Node] {
26 &self.partition
27 }
28
29 #[must_use]
30 pub fn complement(&self) -> &[Node] {
31 &self.complement
32 }
33
34 #[must_use]
35 pub fn into_parts(self) -> (Weight, Vec<Node>, Vec<Node>) {
36 (self.weight, self.partition, self.complement)
37 }
38}
39
40struct WorkingCut<M> {
41 adjacency: Vec<BTreeMap<usize, M>>,
42 active: Vec<bool>,
43 groups: Vec<Vec<usize>>,
44 active_count: usize,
45}
46
47pub fn stoer_wagner_min_cut<G, F, M>(
56 graph: &G,
57 edge_weight: F,
58) -> Result<Option<StoerWagnerCut<G::Node, M>>>
59where
60 G: IndexUndirectedGraphView,
61 F: FnMut(G::Edge) -> M,
62 M: Measure,
63{
64 stoer_wagner_min_cut_filtered(graph, |_| true, edge_weight)
65}
66
67pub fn stoer_wagner_min_cut_filtered<G, P, F, M>(
74 graph: &G,
75 mut allows_edge: P,
76 mut edge_weight: F,
77) -> Result<Option<StoerWagnerCut<G::Node, M>>>
78where
79 G: IndexUndirectedGraphView,
80 P: FnMut(G::Edge) -> bool,
81 F: FnMut(G::Edge) -> M,
82 M: Measure,
83{
84 if graph.node_count() < 2 {
85 return Ok(None);
86 }
87 let (nodes_by_slot, nodes) = nodes(graph);
88 let mut working = WorkingCut::new(graph.node_bound(), &nodes);
89 for edge in graph.edge_indices() {
90 if !allows_edge(edge) {
91 continue;
92 }
93 let weight = edge_weight(edge);
94 validate_weight(weight)?;
95 let Some(endpoints) = graph.edge_endpoints(edge) else {
96 continue;
97 };
98 let source = G::node_slot(endpoints.source());
99 let target = G::node_slot(endpoints.target());
100 if source != target {
101 working.add_edge(source, target, weight)?;
102 }
103 }
104 let candidate = working.solve(&nodes)?;
105 Ok(candidate.map(|cut| StoerWagnerCut {
106 weight: cut.weight,
107 partition: map_nodes(&cut.partition, &nodes_by_slot),
108 complement: map_nodes(&cut.complement, &nodes_by_slot),
109 }))
110}
111
112impl<M: Measure> WorkingCut<M> {
113 fn new(node_bound: usize, nodes: &[usize]) -> Self {
114 let mut active = vec![false; node_bound];
115 for &node in nodes {
116 active[node] = true;
117 }
118 let groups = (0..node_bound).map(|node| vec![node]).collect();
119 Self {
120 adjacency: vec![BTreeMap::new(); node_bound],
121 active,
122 groups,
123 active_count: nodes.len(),
124 }
125 }
126
127 fn add_edge(&mut self, source: usize, target: usize, weight: M) -> Result<()> {
128 let sum = self.adjacency[source]
129 .get(&target)
130 .copied()
131 .unwrap_or_else(M::zero)
132 .checked_add(weight)
133 .ok_or(GraphError::ArithmeticOverflow {
134 operation: "Stoer-Wagner edge aggregation",
135 })?;
136 self.adjacency[source].insert(target, sum);
137 self.adjacency[target].insert(source, sum);
138 Ok(())
139 }
140
141 fn solve(&mut self, nodes: &[usize]) -> Result<Option<Candidate<M>>> {
142 let mut best = None;
143 let mut queue = MaxQueue::with_capacity(nodes.len() * 2);
144 let mut weights = vec![M::zero(); self.active.len()];
145 let mut added = vec![false; self.active.len()];
146 while self.active_count > 1 {
147 let (source, target, weight) =
148 self.phase(nodes, &mut queue, &mut weights, &mut added)?;
149 let candidate = canonical_candidate(weight, &self.groups[target], nodes);
150 if better(&candidate, best.as_ref()) {
151 best = Some(candidate);
152 }
153 self.merge(source, target)?;
154 }
155 Ok(best)
156 }
157
158 fn phase(
159 &self,
160 nodes: &[usize],
161 queue: &mut MaxQueue<M>,
162 weights: &mut [M],
163 added: &mut [bool],
164 ) -> Result<(usize, usize, M)> {
165 weights.fill(M::zero());
166 added.fill(false);
167 queue.clear();
168 for &node in nodes {
169 if self.active[node] {
170 queue.push(node, M::zero());
171 }
172 }
173 let mut previous = None;
174 for index in 0..self.active_count {
175 let (node, weight) = pop_current(queue, &self.active, added, weights);
176 if index + 1 == self.active_count {
177 return Ok((previous.expect("phase has two nodes"), node, weight));
178 }
179 added[node] = true;
180 previous = Some(node);
181 for (&neighbor, &edge_weight) in &self.adjacency[node] {
182 if self.active[neighbor] && !added[neighbor] {
183 weights[neighbor] = weights[neighbor].checked_add(edge_weight).ok_or(
184 GraphError::ArithmeticOverflow {
185 operation: "Stoer-Wagner phase",
186 },
187 )?;
188 queue.push(neighbor, weights[neighbor]);
189 }
190 }
191 }
192 unreachable!("active phase always selects a target")
193 }
194
195 fn merge(&mut self, source: usize, target: usize) -> Result<()> {
196 let removed = core::mem::take(&mut self.adjacency[target]);
197 self.adjacency[source].remove(&target);
198 for (neighbor, weight) in removed {
199 self.adjacency[neighbor].remove(&target);
200 if neighbor == source || !self.active[neighbor] {
201 continue;
202 }
203 let sum = self.adjacency[source]
204 .get(&neighbor)
205 .copied()
206 .unwrap_or_else(M::zero)
207 .checked_add(weight)
208 .ok_or(GraphError::ArithmeticOverflow {
209 operation: "Stoer-Wagner contraction",
210 })?;
211 self.adjacency[source].insert(neighbor, sum);
212 self.adjacency[neighbor].insert(source, sum);
213 }
214 self.active[target] = false;
215 self.active_count -= 1;
216 let mut target_group = core::mem::take(&mut self.groups[target]);
217 self.groups[source].append(&mut target_group);
218 self.groups[source].sort_unstable();
219 Ok(())
220 }
221}
222
223fn pop_current<M: Measure>(
224 queue: &mut MaxQueue<M>,
225 active: &[bool],
226 added: &[bool],
227 weights: &[M],
228) -> (usize, M) {
229 while let Some((node, weight)) = queue.pop() {
230 if active[node] && !added[node] && weight.compare(weights[node]) == Some(Ordering::Equal) {
231 return (node, weight);
232 }
233 }
234 unreachable!("every active node is queued")
235}