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<i64>,
18 num_undirected_edges: usize,
19}
20
21impl ReductionResult for ReductionMCPToILP {
22 type Source = MixedChinesePostman<i64>;
23 type Target = ILP<i64>;
24
25 fn target_problem(&self) -> &ILP<i64> {
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(
46 transform = upper_bound {
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 },
50 unavailable = {
51 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
52 }
53)]
54impl ReduceTo<ILP<i64>> for MixedChinesePostman<i64> {
55 type Result = ReductionMCPToILP;
56
57 #[allow(clippy::needless_range_loop)]
58 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
59 let n = self.num_vertices();
60 let m = self.num_arcs(); let q = self.num_edges(); let r_count = m + q; if r_count == 0 {
66 return Ok(ReductionMCPToILP {
67 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
68 .map_err(Self::target_construction)?,
69 num_undirected_edges: 0,
70 });
71 }
72
73 let original_arcs = self.graph().arcs();
77 let undirected_edges = self.graph().edges();
78
79 let l = m + 2 * q; let mut avail_arcs: Vec<(usize, usize)> = Vec::with_capacity(l);
83 let mut avail_lengths: Vec<i64> = Vec::with_capacity(l);
84
85 for (i, &(u, v)) in original_arcs.iter().enumerate() {
86 avail_arcs.push((u, v));
87 avail_lengths.push(self.arc_weights()[i].to_sum());
88 }
89 for (k, &(u, v)) in undirected_edges.iter().enumerate() {
90 let length = self.edge_weights()[k].to_sum();
91 avail_arcs.push((u, v)); avail_lengths.push(length);
93 avail_arcs.push((v, u)); avail_lengths.push(length);
95 }
96
97 let d_idx = |k: usize| k;
109 let g_idx = |j: usize| q + j;
110 let y_idx = |j: usize| q + l + j;
111 let z_idx = |v: usize| q + 2 * l + v;
112 let rho_idx = |v: usize| q + 2 * l + n + v;
113 let s_idx = q + 2 * l + 2 * n;
114 let b_idx = |v: usize| q + 2 * l + 2 * n + 1 + v;
115 let f_idx = |j: usize| q + 2 * l + 3 * n + 1 + j;
116 let h_idx = |j: usize| q + 3 * l + 3 * n + 1 + j;
117
118 let num_vars = q + 4 * l + 3 * n + 1;
119 let n_i64 = Self::exact_i64(n, "encoding the active-vertex count")?;
120 let r_count_i64 = Self::exact_i64(r_count, "encoding the required-arc count")?;
121 let big_g = r_count_i64.checked_mul(n_i64 - 1).ok_or_else(|| {
122 crate::rules::ReductionError::integer_overflow::<MixedChinesePostman<i64>, ILP<i64>>(
123 "computing the extra-traversal bound",
124 )
125 })?;
126 let m_use = big_g.checked_add(1).ok_or_else(|| {
127 crate::rules::ReductionError::integer_overflow::<MixedChinesePostman<i64>, ILP<i64>>(
128 "computing the arc-use bound",
129 )
130 })?;
131
132 let mut constraints = Vec::new();
133
134 for k in 0..q {
136 constraints.push(LinearConstraint::le(vec![(d_idx(k), 1)], 1));
137 }
138
139 for j in 0..l {
141 constraints.push(LinearConstraint::le(vec![(g_idx(j), 1)], big_g));
142 }
143
144 for j in 0..l {
146 constraints.push(LinearConstraint::le(vec![(y_idx(j), 1)], 1));
147 }
148 for v in 0..n {
149 constraints.push(LinearConstraint::le(vec![(z_idx(v), 1)], 1));
150 constraints.push(LinearConstraint::le(vec![(rho_idx(v), 1)], 1));
151 }
152
153 for v in 0..n {
161 let mut terms = Vec::new();
162 let mut constant = 0_i64; for j in 0..l {
165 let (tail, head) = avail_arcs[j];
166 let sign = if tail == v && head == v {
167 0 } else if tail == v {
169 1
170 } else if head == v {
171 -1
172 } else {
173 continue;
174 };
175 if sign == 0 {
176 continue;
177 }
178
179 terms.push((g_idx(j), sign));
181
182 if j < m {
184 constant += sign;
186 } else {
187 let k = (j - m) / 2;
188 if (j - m).is_multiple_of(2) {
189 constant += sign;
191 terms.push((d_idx(k), -sign));
192 } else {
193 terms.push((d_idx(k), sign));
195 }
196 }
197 }
198 constraints.push(LinearConstraint::eq(terms, -constant));
200 }
201
202 for j in 0..l {
204 if j < m {
205 constraints.push(LinearConstraint::le(
207 vec![(g_idx(j), 1), (y_idx(j), -m_use)],
208 -1,
209 ));
210 constraints.push(LinearConstraint::le(vec![(y_idx(j), 1), (g_idx(j), -1)], 1));
212 } else {
213 let k = (j - m) / 2;
214 if (j - m).is_multiple_of(2) {
215 constraints.push(LinearConstraint::le(
218 vec![(g_idx(j), 1), (d_idx(k), -1), (y_idx(j), -m_use)],
219 -1,
220 ));
221 constraints.push(LinearConstraint::le(
223 vec![(y_idx(j), 1), (d_idx(k), 1), (g_idx(j), -1)],
224 1,
225 ));
226 } else {
227 constraints.push(LinearConstraint::le(
230 vec![(d_idx(k), 1), (g_idx(j), 1), (y_idx(j), -m_use)],
231 0,
232 ));
233 constraints.push(LinearConstraint::le(
235 vec![(y_idx(j), 1), (d_idx(k), -1), (g_idx(j), -1)],
236 0,
237 ));
238 }
239 }
240 }
241
242 for j in 0..l {
244 let (tail, head) = avail_arcs[j];
245 constraints.push(LinearConstraint::le(
246 vec![(y_idx(j), 1), (z_idx(tail), -1)],
247 0,
248 ));
249 constraints.push(LinearConstraint::le(
250 vec![(y_idx(j), 1), (z_idx(head), -1)],
251 0,
252 ));
253 }
254
255 for v in 0..n {
257 let mut terms = vec![(z_idx(v), 1)];
258 for j in 0..l {
259 let (tail, head) = avail_arcs[j];
260 if tail == v || head == v {
261 terms.push((y_idx(j), -1));
262 }
263 }
264 constraints.push(LinearConstraint::le(terms, 0));
265 }
266
267 {
269 let mut terms = vec![(s_idx, -1)];
270 for v in 0..n {
271 terms.push((z_idx(v), 1));
272 }
273 constraints.push(LinearConstraint::eq(terms, 0));
274 }
275
276 {
278 let terms: Vec<(usize, i64)> = (0..n).map(|v| (rho_idx(v), 1)).collect();
279 constraints.push(LinearConstraint::eq(terms, 1));
280 }
281 for v in 0..n {
282 constraints.push(LinearConstraint::le(
283 vec![(rho_idx(v), 1), (z_idx(v), -1)],
284 0,
285 ));
286 }
287
288 for v in 0..n {
291 constraints.push(LinearConstraint::le(vec![(b_idx(v), 1), (s_idx, -1)], 0));
292 constraints.push(LinearConstraint::le(
293 vec![(b_idx(v), 1), (rho_idx(v), -n_i64)],
294 0,
295 ));
296 constraints.push(LinearConstraint::ge(
297 vec![(b_idx(v), 1), (s_idx, -1), (rho_idx(v), -n_i64)],
298 -n_i64,
299 ));
300 }
302
303 let flow_big_m = n_i64 - 1;
305 for j in 0..l {
306 constraints.push(LinearConstraint::le(
307 vec![(f_idx(j), 1), (y_idx(j), -flow_big_m)],
308 0,
309 ));
310 constraints.push(LinearConstraint::le(
311 vec![(h_idx(j), 1), (y_idx(j), -flow_big_m)],
312 0,
313 ));
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 tail == v {
323 terms.push((f_idx(j), 1));
324 }
325 if head == v {
326 terms.push((f_idx(j), -1));
327 }
328 }
329 terms.push((b_idx(v), -1));
330 terms.push((z_idx(v), 1));
331 constraints.push(LinearConstraint::eq(terms, 0));
332 }
333
334 for v in 0..n {
337 let mut terms = Vec::new();
338 for j in 0..l {
339 let (tail, head) = avail_arcs[j];
340 if head == v {
341 terms.push((h_idx(j), 1));
342 }
343 if tail == v {
344 terms.push((h_idx(j), -1));
345 }
346 }
347 terms.push((b_idx(v), -1));
348 terms.push((z_idx(v), 1));
349 constraints.push(LinearConstraint::eq(terms, 0));
350 }
351
352 let mut objective = Vec::new();
357 for j in 0..l {
358 let len_j = avail_lengths[j];
359 objective.push((g_idx(j), len_j));
361 if j >= m {
363 let k = (j - m) / 2;
364 if (j - m).is_multiple_of(2) {
365 objective.push((d_idx(k), -len_j));
367 } else {
368 objective.push((d_idx(k), len_j));
370 }
371 }
372 }
373
374 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
375 .map_err(Self::target_construction)?;
376
377 Ok(ReductionMCPToILP {
378 target,
379 num_undirected_edges: q,
380 })
381 }
382}
383
384#[cfg(feature = "example-db")]
385pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
386 use crate::topology::MixedGraph;
387
388 vec![crate::example_db::specs::RuleExampleSpec {
389 id: "mixedchinesepostman_to_ilp",
390 build: || {
391 let source = MixedChinesePostman::new(
393 MixedGraph::new(3, vec![(0, 1)], vec![(1, 2), (2, 0)]),
394 vec![1],
395 vec![1, 1],
396 );
397 crate::example_db::specs::rule_example_via_ilp::<_, i64>(source)
398 },
399 }]
400}
401
402#[cfg(test)]
403#[path = "../unit_tests/rules/mixedchinesepostman_ilp.rs"]
404mod tests;