1use crate::models::algebraic::QUBO;
16use crate::models::graph::MinimumMultiwayCut;
17use crate::reduction;
18use crate::rules::traits::{ReduceTo, ReductionResult};
19use crate::topology::{Graph, SimpleGraph};
20
21#[derive(Debug, Clone)]
23pub struct ReductionMinimumMultiwayCutToQUBO {
24 target: QUBO<i64>,
25 num_vertices: usize,
26 num_terminals: usize,
27 edges: Vec<(usize, usize)>,
28 negative_edges: Vec<bool>,
29 terminals: Vec<usize>,
30}
31
32impl ReductionResult for ReductionMinimumMultiwayCutToQUBO {
33 type Source = MinimumMultiwayCut<SimpleGraph, i64>;
34 type Target = QUBO<i64>;
35
36 fn target_problem(&self) -> &Self::Target {
37 &self.target
38 }
39
40 fn extract_solution(
43 &self,
44 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
45 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
46 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
47
48 Ok({
49 let k = self.num_terminals;
50 let n = self.num_vertices;
51
52 let assignments: Vec<usize> = (0..n)
54 .map(|vertex| {
55 let mut selected =
56 (0..k).filter(|&terminal| target_solution[vertex * k + terminal]);
57 match (selected.next(), selected.next()) {
58 (Some(terminal), None) => Ok(terminal),
59 _ => Err(crate::rules::ExtractionError::invalid(format!(
60 "vertex {vertex} does not have exactly one terminal assignment"
61 ))),
62 }
63 })
64 .collect::<crate::rules::ExtractionResult<_>>()?;
65
66 if self
67 .terminals
68 .iter()
69 .enumerate()
70 .any(|(label, &vertex)| assignments[vertex] != label)
71 {
72 return Err(crate::rules::ExtractionError::invalid(
73 "target assignment does not pin each terminal to its own component",
74 ));
75 }
76
77 self.edges
79 .iter()
80 .zip(&self.negative_edges)
81 .map(|(&(u, v), &negative)| negative || assignments[u] != assignments[v])
82 .collect()
83 })
84 }
85}
86
87#[reduction(transform = {
88 exact {
89 num_vars = "num_terminals * num_vertices",
90 },
91 upper_bound {
92 num_quadratic_terms = "(num_terminals * num_vertices) * ((num_terminals * num_vertices) - 1) / 2",
93 },
94})]
95impl ReduceTo<QUBO<i64>> for MinimumMultiwayCut<SimpleGraph, i64> {
96 type Result = ReductionMinimumMultiwayCutToQUBO;
97
98 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
99 let n = self.num_vertices();
100 let k = self.num_terminals();
101 let edges = self.graph().edges();
102 let edge_weights = self.edge_weights();
103 let terminals = self.terminals();
104 let overflow = |operation| {
105 crate::rules::ReductionError::integer_overflow::<
106 MinimumMultiwayCut<SimpleGraph, i64>,
107 QUBO<i64>,
108 >(operation)
109 };
110 let nq = n
111 .checked_mul(k)
112 .ok_or_else(|| overflow("computing the number of QUBO variables"))?;
113
114 let alpha = edge_weights.iter().try_fold(0i64, |total, &weight| {
116 total
117 .checked_add(weight.max(0))
118 .ok_or_else(|| overflow("summing nonnegative cut weights"))
119 })?;
120 let alpha = alpha
121 .checked_add(1)
122 .ok_or_else(|| overflow("computing the partition penalty"))?;
123
124 let mut matrix = vec![vec![0i64; nq]; nq];
125
126 let mut add_upper = |i: usize, j: usize, val: i64| {
128 let (lo, hi) = if i <= j { (i, j) } else { (j, i) };
129 matrix[lo][hi] = matrix[lo][hi]
130 .checked_add(val)
131 .ok_or_else(|| overflow("adding a multiway-cut QUBO coefficient"))?;
132 Ok::<(), crate::rules::ReductionError>(())
133 };
134
135 for u in 0..n {
139 for s in 0..k {
141 add_upper(
142 u * k + s,
143 u * k + s,
144 alpha
145 .checked_neg()
146 .ok_or_else(|| overflow("negating the partition penalty"))?,
147 )?;
148 }
149 for s in 0..k {
151 for t in (s + 1)..k {
152 add_upper(
153 u * k + s,
154 u * k + t,
155 alpha
156 .checked_mul(2)
157 .ok_or_else(|| overflow("doubling the partition penalty"))?,
158 )?;
159 }
160 }
161 }
162
163 for (t_pos, &t_vertex) in terminals.iter().enumerate() {
166 for s in 0..k {
167 if s != t_pos {
168 add_upper(t_vertex * k + s, t_vertex * k + s, alpha)?;
169 }
170 }
171 }
172
173 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
177 let w = edge_weights[edge_idx].max(0);
178 for s in 0..k {
179 for t in 0..k {
180 if s != t {
181 add_upper(u * k + s, v * k + t, w)?;
182 }
183 }
184 }
185 }
186
187 Ok(ReductionMinimumMultiwayCutToQUBO {
188 target: QUBO::from_matrix(matrix).map_err(|message| {
189 crate::rules::ReductionError::construction::<
190 MinimumMultiwayCut<SimpleGraph, i64>,
191 QUBO<i64>,
192 >(message)
193 })?,
194 num_vertices: n,
195 num_terminals: k,
196 edges,
197 negative_edges: edge_weights.iter().map(|&weight| weight < 0).collect(),
198 terminals: terminals.to_vec(),
199 })
200 }
201}
202
203#[cfg(feature = "example-db")]
204pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
205 use crate::export::SolutionPair;
206
207 vec![crate::example_db::specs::RuleExampleSpec {
208 id: "minimummultiwaycut_to_qubo",
209 build: || {
210 use crate::models::algebraic::QUBO;
211 use crate::models::graph::MinimumMultiwayCut;
212 use crate::topology::SimpleGraph;
213 let graph = SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (0, 4), (1, 3)]);
214 let source = MinimumMultiwayCut::new(graph, vec![0, 2, 4], vec![2, 3, 1, 2, 4, 5]);
215 crate::example_db::specs::rule_example_with_witness::<_, QUBO<i64>>(
216 source,
217 SolutionPair {
218 source_config: serde_json::json!(vec![true, false, false, true, true, false]),
219 target_config: serde_json::json!(vec![
220 true, false, false, false, true, false, false, true, false, false, true,
221 false, false, false, true
222 ]),
223 },
224 )
225 },
226 }]
227}
228
229#[cfg(test)]
230#[path = "../unit_tests/rules/minimummultiwaycut_qubo.rs"]
231mod tests;