1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::graph::MixedChinesePostman;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::types::WeightElement;
13
14#[derive(Debug, Clone)]
16pub struct ReductionMCPToILP {
17 target: ILP<i32>,
18 num_undirected_edges: usize,
19}
20
21impl ReductionResult for ReductionMCPToILP {
22 type Source = MixedChinesePostman<i32>;
23 type Target = ILP<i32>;
24
25 fn target_problem(&self) -> &ILP<i32> {
26 &self.target
27 }
28
29 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
30 target_solution[..self.num_undirected_edges].to_vec()
32 }
33}
34
35#[reduction(
36 overhead = {
37 num_vars = "num_edges + 4 * (num_arcs + 2 * num_edges) + 3 * num_vertices + 1",
38 num_constraints = "num_vertices + 2 * (num_arcs + 2 * num_edges) + 2 * (num_arcs + 2 * num_edges) + num_vertices + 1 + num_vertices + 4 * num_vertices + 2 * (num_arcs + 2 * num_edges) + 2 * num_vertices",
39 }
40)]
41impl ReduceTo<ILP<i32>> for MixedChinesePostman<i32> {
42 type Result = ReductionMCPToILP;
43
44 #[allow(clippy::needless_range_loop)]
45 fn reduce_to(&self) -> Self::Result {
46 let n = self.num_vertices();
47 let m = self.num_arcs(); let q = self.num_edges(); let r_count = m + q; if r_count == 0 {
53 return ReductionMCPToILP {
54 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize),
55 num_undirected_edges: 0,
56 };
57 }
58
59 let original_arcs = self.graph().arcs();
63 let undirected_edges = self.graph().edges();
64
65 let l = m + 2 * q; let mut avail_arcs: Vec<(usize, usize)> = Vec::with_capacity(l);
69 let mut avail_lengths: Vec<f64> = Vec::with_capacity(l);
70
71 for (i, &(u, v)) in original_arcs.iter().enumerate() {
72 avail_arcs.push((u, v));
73 avail_lengths.push(self.arc_weights()[i].to_sum() as f64);
74 }
75 for (k, &(u, v)) in undirected_edges.iter().enumerate() {
76 avail_arcs.push((u, v)); avail_lengths.push(self.edge_weights()[k].to_sum() as f64);
78 avail_arcs.push((v, u)); avail_lengths.push(self.edge_weights()[k].to_sum() as f64);
80 }
81
82 let d_idx = |k: usize| k;
94 let g_idx = |j: usize| q + j;
95 let y_idx = |j: usize| q + l + j;
96 let z_idx = |v: usize| q + 2 * l + v;
97 let rho_idx = |v: usize| q + 2 * l + n + v;
98 let s_idx = q + 2 * l + 2 * n;
99 let b_idx = |v: usize| q + 2 * l + 2 * n + 1 + v;
100 let f_idx = |j: usize| q + 2 * l + 3 * n + 1 + j;
101 let h_idx = |j: usize| q + 3 * l + 3 * n + 1 + j;
102
103 let num_vars = q + 4 * l + 3 * n + 1;
104 let big_g = (r_count * (n - 1)) as f64; let m_use = 1.0 + big_g; let n_f64 = n as f64;
107
108 let mut constraints = Vec::new();
109
110 for k in 0..q {
112 constraints.push(LinearConstraint::le(vec![(d_idx(k), 1.0)], 1.0));
113 }
114
115 for j in 0..l {
117 constraints.push(LinearConstraint::le(vec![(g_idx(j), 1.0)], big_g));
118 }
119
120 for j in 0..l {
122 constraints.push(LinearConstraint::le(vec![(y_idx(j), 1.0)], 1.0));
123 }
124 for v in 0..n {
125 constraints.push(LinearConstraint::le(vec![(z_idx(v), 1.0)], 1.0));
126 constraints.push(LinearConstraint::le(vec![(rho_idx(v), 1.0)], 1.0));
127 }
128
129 for v in 0..n {
137 let mut terms = Vec::new();
138 let mut constant = 0.0_f64; for j in 0..l {
141 let (tail, head) = avail_arcs[j];
142 let sign = if tail == v && head == v {
143 0.0 } else if tail == v {
145 1.0
146 } else if head == v {
147 -1.0
148 } else {
149 continue;
150 };
151 if sign == 0.0 {
152 continue;
153 }
154
155 terms.push((g_idx(j), sign));
157
158 if j < m {
160 constant += sign;
162 } else {
163 let k = (j - m) / 2;
164 if (j - m).is_multiple_of(2) {
165 constant += sign;
167 terms.push((d_idx(k), -sign));
168 } else {
169 terms.push((d_idx(k), sign));
171 }
172 }
173 }
174 constraints.push(LinearConstraint::eq(terms, -constant));
176 }
177
178 for j in 0..l {
180 if j < m {
181 constraints.push(LinearConstraint::le(
183 vec![(g_idx(j), 1.0), (y_idx(j), -m_use)],
184 -1.0,
185 ));
186 constraints.push(LinearConstraint::le(
188 vec![(y_idx(j), 1.0), (g_idx(j), -1.0)],
189 1.0,
190 ));
191 } else {
192 let k = (j - m) / 2;
193 if (j - m).is_multiple_of(2) {
194 constraints.push(LinearConstraint::le(
197 vec![(g_idx(j), 1.0), (d_idx(k), -1.0), (y_idx(j), -m_use)],
198 -1.0,
199 ));
200 constraints.push(LinearConstraint::le(
202 vec![(y_idx(j), 1.0), (d_idx(k), 1.0), (g_idx(j), -1.0)],
203 1.0,
204 ));
205 } else {
206 constraints.push(LinearConstraint::le(
209 vec![(d_idx(k), 1.0), (g_idx(j), 1.0), (y_idx(j), -m_use)],
210 0.0,
211 ));
212 constraints.push(LinearConstraint::le(
214 vec![(y_idx(j), 1.0), (d_idx(k), -1.0), (g_idx(j), -1.0)],
215 0.0,
216 ));
217 }
218 }
219 }
220
221 for j in 0..l {
223 let (tail, head) = avail_arcs[j];
224 constraints.push(LinearConstraint::le(
225 vec![(y_idx(j), 1.0), (z_idx(tail), -1.0)],
226 0.0,
227 ));
228 constraints.push(LinearConstraint::le(
229 vec![(y_idx(j), 1.0), (z_idx(head), -1.0)],
230 0.0,
231 ));
232 }
233
234 for v in 0..n {
236 let mut terms = vec![(z_idx(v), 1.0)];
237 for j in 0..l {
238 let (tail, head) = avail_arcs[j];
239 if tail == v || head == v {
240 terms.push((y_idx(j), -1.0));
241 }
242 }
243 constraints.push(LinearConstraint::le(terms, 0.0));
244 }
245
246 {
248 let mut terms = vec![(s_idx, -1.0)];
249 for v in 0..n {
250 terms.push((z_idx(v), 1.0));
251 }
252 constraints.push(LinearConstraint::eq(terms, 0.0));
253 }
254
255 {
257 let terms: Vec<(usize, f64)> = (0..n).map(|v| (rho_idx(v), 1.0)).collect();
258 constraints.push(LinearConstraint::eq(terms, 1.0));
259 }
260 for v in 0..n {
261 constraints.push(LinearConstraint::le(
262 vec![(rho_idx(v), 1.0), (z_idx(v), -1.0)],
263 0.0,
264 ));
265 }
266
267 for v in 0..n {
270 constraints.push(LinearConstraint::le(
271 vec![(b_idx(v), 1.0), (s_idx, -1.0)],
272 0.0,
273 ));
274 constraints.push(LinearConstraint::le(
275 vec![(b_idx(v), 1.0), (rho_idx(v), -n_f64)],
276 0.0,
277 ));
278 constraints.push(LinearConstraint::ge(
279 vec![(b_idx(v), 1.0), (s_idx, -1.0), (rho_idx(v), -n_f64)],
280 -n_f64,
281 ));
282 }
284
285 let flow_big_m = (n as f64) - 1.0;
287 for j in 0..l {
288 constraints.push(LinearConstraint::le(
289 vec![(f_idx(j), 1.0), (y_idx(j), -flow_big_m)],
290 0.0,
291 ));
292 constraints.push(LinearConstraint::le(
293 vec![(h_idx(j), 1.0), (y_idx(j), -flow_big_m)],
294 0.0,
295 ));
296 }
297
298 for v in 0..n {
301 let mut terms = Vec::new();
302 for j in 0..l {
303 let (tail, head) = avail_arcs[j];
304 if tail == v {
305 terms.push((f_idx(j), 1.0));
306 }
307 if head == v {
308 terms.push((f_idx(j), -1.0));
309 }
310 }
311 terms.push((b_idx(v), -1.0));
312 terms.push((z_idx(v), 1.0));
313 constraints.push(LinearConstraint::eq(terms, 0.0));
314 }
315
316 for v in 0..n {
319 let mut terms = Vec::new();
320 for j in 0..l {
321 let (tail, head) = avail_arcs[j];
322 if head == v {
323 terms.push((h_idx(j), 1.0));
324 }
325 if tail == v {
326 terms.push((h_idx(j), -1.0));
327 }
328 }
329 terms.push((b_idx(v), -1.0));
330 terms.push((z_idx(v), 1.0));
331 constraints.push(LinearConstraint::eq(terms, 0.0));
332 }
333
334 let mut objective = Vec::new();
339 for j in 0..l {
340 let len_j = avail_lengths[j];
341 objective.push((g_idx(j), len_j));
343 if j >= m {
345 let k = (j - m) / 2;
346 if (j - m).is_multiple_of(2) {
347 objective.push((d_idx(k), -len_j));
349 } else {
350 objective.push((d_idx(k), len_j));
352 }
353 }
354 }
355
356 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
357
358 ReductionMCPToILP {
359 target,
360 num_undirected_edges: q,
361 }
362 }
363}
364
365#[cfg(feature = "example-db")]
366pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
367 use crate::topology::MixedGraph;
368
369 vec![crate::example_db::specs::RuleExampleSpec {
370 id: "mixedchinesepostman_to_ilp",
371 build: || {
372 let source = MixedChinesePostman::new(
374 MixedGraph::new(3, vec![(0, 1)], vec![(1, 2), (2, 0)]),
375 vec![1],
376 vec![1, 1],
377 );
378 crate::example_db::specs::rule_example_via_ilp::<_, i32>(source)
379 },
380 }]
381}
382
383#[cfg(test)]
384#[path = "../unit_tests/rules/mixedchinesepostman_ilp.rs"]
385mod tests;