1use crate::models::algebraic::{Bounded, IntegerVariable, 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<i64, i64, Bounded>,
18 num_undirected_edges: usize,
19}
20
21impl ReductionResult for ReductionMCPToILP {
22 type Source = MixedChinesePostman<i64>;
23 type Target = ILP<i64, i64, Bounded>;
24
25 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
26 &self.target
27 }
28
29 fn extract_solution(
30 &self,
31 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
32 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
33 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
34
35 Ok({
36 target_solution[..self.num_undirected_edges]
38 .iter()
39 .map(|&value| value == 1)
40 .collect()
41 })
42 }
43}
44
45#[reduction(transform = upper_bound {
46 max_constraint_magnitude_bits = "(num_arcs + num_edges + 1) * (num_vertices + 1) + 1",
47 num_vars = "num_edges + 4 * (num_arcs + 2 * num_edges) + 3 * num_vertices + 1",
48 num_constraints = "num_edges + 8 * (num_arcs + 2 * num_edges) + 10 * num_vertices + 2",
49 num_nonzeros = "(num_edges + 4 * (num_arcs + 2 * num_edges) + 3 * num_vertices + 1) * (num_edges + 8 * (num_arcs + 2 * num_edges) + 10 * num_vertices + 2)",
50})]
51impl ReduceTo<ILP<i64, i64, Bounded>> for MixedChinesePostman<i64> {
52 type Result = ReductionMCPToILP;
53
54 #[allow(clippy::needless_range_loop)]
55 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
56 let n = self.num_vertices();
57 let m = self.num_arcs(); let q = self.num_edges(); let r_count = m + q; if r_count == 0 {
63 return Ok(ReductionMCPToILP {
64 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
65 .map_err(Self::target_construction)?,
66 num_undirected_edges: 0,
67 });
68 }
69
70 let original_arcs = self.graph().arcs();
74 let undirected_edges = self.graph().edges();
75
76 let l = m + 2 * q; let mut avail_arcs: Vec<(usize, usize)> = Vec::with_capacity(l);
80 let mut avail_lengths: Vec<i64> = Vec::with_capacity(l);
81
82 for (i, &(u, v)) in original_arcs.iter().enumerate() {
83 avail_arcs.push((u, v));
84 avail_lengths.push(self.arc_weights()[i].to_sum());
85 }
86 for (k, &(u, v)) in undirected_edges.iter().enumerate() {
87 let length = self.edge_weights()[k].to_sum();
88 avail_arcs.push((u, v)); avail_lengths.push(length);
90 avail_arcs.push((v, u)); avail_lengths.push(length);
92 }
93
94 let d_idx = |k: usize| k;
106 let g_idx = |j: usize| q + j;
107 let y_idx = |j: usize| q + l + j;
108 let z_idx = |v: usize| q + 2 * l + v;
109 let rho_idx = |v: usize| q + 2 * l + n + v;
110 let s_idx = q + 2 * l + 2 * n;
111 let b_idx = |v: usize| q + 2 * l + 2 * n + 1 + v;
112 let f_idx = |j: usize| q + 2 * l + 3 * n + 1 + j;
113 let h_idx = |j: usize| q + 3 * l + 3 * n + 1 + j;
114
115 let num_vars = q + 4 * l + 3 * n + 1;
116 let n_i64 = Self::exact_i64(n, "encoding the active-vertex count")?;
117 let r_count_i64 = Self::exact_i64(r_count, "encoding the required-arc count")?;
118 let big_g = r_count_i64.checked_mul(n_i64 - 1).ok_or_else(|| {
119 crate::rules::ReductionError::integer_overflow::<
120 MixedChinesePostman<i64>,
121 ILP<i64, i64, Bounded>,
122 >("computing the extra-traversal bound")
123 })?;
124 let m_use = big_g.checked_add(1).ok_or_else(|| {
125 crate::rules::ReductionError::integer_overflow::<
126 MixedChinesePostman<i64>,
127 ILP<i64, i64, Bounded>,
128 >("computing the arc-use bound")
129 })?;
130
131 let mut constraints = Vec::new();
132
133 for k in 0..q {
135 constraints.push(LinearConstraint::le(vec![(d_idx(k), 1)], 1));
136 }
137
138 for j in 0..l {
140 constraints.push(LinearConstraint::le(vec![(g_idx(j), 1)], big_g));
141 }
142
143 for j in 0..l {
145 constraints.push(LinearConstraint::le(vec![(y_idx(j), 1)], 1));
146 }
147 for v in 0..n {
148 constraints.push(LinearConstraint::le(vec![(z_idx(v), 1)], 1));
149 constraints.push(LinearConstraint::le(vec![(rho_idx(v), 1)], 1));
150 }
151
152 for v in 0..n {
160 let mut terms = Vec::new();
161 let mut constant = 0_i64; for j in 0..l {
164 let (tail, head) = avail_arcs[j];
165 let sign = if tail == v && head == v {
166 0 } else if tail == v {
168 1
169 } else if head == v {
170 -1
171 } else {
172 continue;
173 };
174 if sign == 0 {
175 continue;
176 }
177
178 terms.push((g_idx(j), sign));
180
181 if j < m {
183 constant += sign;
185 } else {
186 let k = (j - m) / 2;
187 if (j - m).is_multiple_of(2) {
188 constant += sign;
190 terms.push((d_idx(k), -sign));
191 } else {
192 terms.push((d_idx(k), sign));
194 }
195 }
196 }
197 constraints.push(LinearConstraint::eq(terms, -constant));
199 }
200
201 for j in 0..l {
203 if j < m {
204 constraints.push(LinearConstraint::le(
206 vec![(g_idx(j), 1), (y_idx(j), -m_use)],
207 -1,
208 ));
209 constraints.push(LinearConstraint::le(vec![(y_idx(j), 1), (g_idx(j), -1)], 1));
211 } else {
212 let k = (j - m) / 2;
213 if (j - m).is_multiple_of(2) {
214 constraints.push(LinearConstraint::le(
217 vec![(g_idx(j), 1), (d_idx(k), -1), (y_idx(j), -m_use)],
218 -1,
219 ));
220 constraints.push(LinearConstraint::le(
222 vec![(y_idx(j), 1), (d_idx(k), 1), (g_idx(j), -1)],
223 1,
224 ));
225 } else {
226 constraints.push(LinearConstraint::le(
229 vec![(d_idx(k), 1), (g_idx(j), 1), (y_idx(j), -m_use)],
230 0,
231 ));
232 constraints.push(LinearConstraint::le(
234 vec![(y_idx(j), 1), (d_idx(k), -1), (g_idx(j), -1)],
235 0,
236 ));
237 }
238 }
239 }
240
241 for j in 0..l {
243 let (tail, head) = avail_arcs[j];
244 constraints.push(LinearConstraint::le(
245 vec![(y_idx(j), 1), (z_idx(tail), -1)],
246 0,
247 ));
248 constraints.push(LinearConstraint::le(
249 vec![(y_idx(j), 1), (z_idx(head), -1)],
250 0,
251 ));
252 }
253
254 for v in 0..n {
256 let mut terms = vec![(z_idx(v), 1)];
257 for j in 0..l {
258 let (tail, head) = avail_arcs[j];
259 if tail == v || head == v {
260 terms.push((y_idx(j), -1));
261 }
262 }
263 constraints.push(LinearConstraint::le(terms, 0));
264 }
265
266 {
268 let mut terms = vec![(s_idx, -1)];
269 for v in 0..n {
270 terms.push((z_idx(v), 1));
271 }
272 constraints.push(LinearConstraint::eq(terms, 0));
273 }
274
275 {
277 let terms: Vec<(usize, i64)> = (0..n).map(|v| (rho_idx(v), 1)).collect();
278 constraints.push(LinearConstraint::eq(terms, 1));
279 }
280 for v in 0..n {
281 constraints.push(LinearConstraint::le(
282 vec![(rho_idx(v), 1), (z_idx(v), -1)],
283 0,
284 ));
285 }
286
287 for v in 0..n {
290 constraints.push(LinearConstraint::le(vec![(b_idx(v), 1), (s_idx, -1)], 0));
291 constraints.push(LinearConstraint::le(
292 vec![(b_idx(v), 1), (rho_idx(v), -n_i64)],
293 0,
294 ));
295 constraints.push(LinearConstraint::ge(
296 vec![(b_idx(v), 1), (s_idx, -1), (rho_idx(v), -n_i64)],
297 -n_i64,
298 ));
299 }
301
302 let flow_big_m = n_i64 - 1;
304 for j in 0..l {
305 constraints.push(LinearConstraint::le(
306 vec![(f_idx(j), 1), (y_idx(j), -flow_big_m)],
307 0,
308 ));
309 constraints.push(LinearConstraint::le(
310 vec![(h_idx(j), 1), (y_idx(j), -flow_big_m)],
311 0,
312 ));
313 }
314
315 for v in 0..n {
318 let mut terms = Vec::new();
319 for j in 0..l {
320 let (tail, head) = avail_arcs[j];
321 if tail == v {
322 terms.push((f_idx(j), 1));
323 }
324 if head == v {
325 terms.push((f_idx(j), -1));
326 }
327 }
328 terms.push((b_idx(v), -1));
329 terms.push((z_idx(v), 1));
330 constraints.push(LinearConstraint::eq(terms, 0));
331 }
332
333 for v in 0..n {
336 let mut terms = Vec::new();
337 for j in 0..l {
338 let (tail, head) = avail_arcs[j];
339 if head == v {
340 terms.push((h_idx(j), 1));
341 }
342 if tail == v {
343 terms.push((h_idx(j), -1));
344 }
345 }
346 terms.push((b_idx(v), -1));
347 terms.push((z_idx(v), 1));
348 constraints.push(LinearConstraint::eq(terms, 0));
349 }
350
351 let mut objective = Vec::new();
356 for j in 0..l {
357 let len_j = avail_lengths[j];
358 objective.push((g_idx(j), len_j));
360 if j >= m {
362 let k = (j - m) / 2;
363 if (j - m).is_multiple_of(2) {
364 objective.push((d_idx(k), -len_j));
366 } else {
367 objective.push((d_idx(k), len_j));
369 }
370 }
371 }
372
373 let mut variables = vec![IntegerVariable::binary(); num_vars];
374 variables[q..q + l]
375 .fill(IntegerVariable::new(Some(0), Some(big_g)).map_err(Self::target_construction)?);
376 variables[s_idx..q + 2 * l + 3 * n + 1]
377 .fill(IntegerVariable::new(Some(0), Some(n_i64)).map_err(Self::target_construction)?);
378 variables[q + 2 * l + 3 * n + 1..].fill(
379 IntegerVariable::new(Some(0), Some(flow_big_m)).map_err(Self::target_construction)?,
380 );
381
382 let target =
383 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
384 .map_err(Self::target_construction)?;
385
386 Ok(ReductionMCPToILP {
387 target,
388 num_undirected_edges: q,
389 })
390 }
391}
392
393#[cfg(feature = "example-db")]
394pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
395 use crate::topology::MixedGraph;
396
397 vec![crate::example_db::specs::RuleExampleSpec {
398 id: "mixedchinesepostman_to_ilp",
399 build: || {
400 let source = MixedChinesePostman::new(
402 MixedGraph::new(3, vec![(0, 1)], vec![(1, 2), (2, 0)]),
403 vec![1],
404 vec![1, 1],
405 );
406 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
407 },
408 }]
409}
410
411#[cfg(test)]
412#[path = "../unit_tests/rules/mixedchinesepostman_ilp.rs"]
413mod tests;