1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::graph::RuralPostman;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::topology::{Graph, SimpleGraph};
12use crate::types::WeightElement;
13
14#[derive(Debug, Clone)]
16pub struct ReductionRPToILP {
17 target: ILP<i64>,
18 num_edges: usize,
19}
20
21impl ReductionResult for ReductionRPToILP {
22 type Source = RuralPostman<SimpleGraph, 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 if self.target.num_vars() == 0 {
36 Ok(vec![0; self.num_edges])
37 } else {
38 crate::rules::ilp_helpers::decode_usize_values(&target_solution[..self.num_edges])
39 }
40 }
41}
42
43#[reduction(
44 transform = exact {
45 num_vars = "num_edges + num_vertices + num_edges + num_vertices + 2 * num_edges",
46 num_constraints = "2 * num_edges + num_required_edges + num_vertices + 2 * num_edges + num_vertices + 2 * num_edges + num_vertices + num_edges + num_edges + num_vertices",
47 },
48 unavailable = {
49 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
50 }
51)]
52impl ReduceTo<ILP<i64>> for RuralPostman<SimpleGraph, i64> {
53 type Result = ReductionRPToILP;
54
55 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
56 let m = self.num_edges();
57 let n = self.num_vertices();
58 let edges = self.graph().edges();
59
60 if self.required_edges().is_empty() {
62 return Ok(ReductionRPToILP {
63 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
64 .map_err(Self::target_construction)?,
65 num_edges: m,
66 });
67 }
68
69 let root = edges[self.required_edges()[0]].0;
71
72 let t_idx = |e: usize| e;
80 let q_idx = |v: usize| m + v;
81 let y_idx = |e: usize| m + n + e;
82 let z_idx = |v: usize| m + n + m + v;
83 let f_idx = |e: usize, dir: usize| m + n + m + n + 2 * e + dir;
84
85 let num_vars = m + n + m + n + 2 * m;
86 let mut constraints = Vec::new();
87
88 for e in 0..m {
90 constraints.push(LinearConstraint::le(vec![(y_idx(e), 1), (t_idx(e), -1)], 0));
91 constraints.push(LinearConstraint::le(vec![(t_idx(e), 1), (y_idx(e), -2)], 0));
92 }
93
94 for &req_idx in self.required_edges() {
96 constraints.push(LinearConstraint::ge(vec![(t_idx(req_idx), 1)], 1));
97 }
98
99 for v in 0..n {
101 let mut terms = Vec::new();
102 for (e, &(u, w)) in edges.iter().enumerate() {
103 if u == v || w == v {
104 terms.push((t_idx(e), 1));
105 }
106 }
107 terms.push((q_idx(v), -2));
108 constraints.push(LinearConstraint::eq(terms, 0));
109 }
110
111 for (e, &(u, v)) in edges.iter().enumerate() {
113 constraints.push(LinearConstraint::le(vec![(y_idx(e), 1), (z_idx(u), -1)], 0));
114 constraints.push(LinearConstraint::le(vec![(y_idx(e), 1), (z_idx(v), -1)], 0));
115 }
116
117 for v in 0..n {
119 let mut terms = vec![(z_idx(v), 1)];
120 for (e, &(u, w)) in edges.iter().enumerate() {
121 if u == v || w == v {
122 terms.push((y_idx(e), -1));
123 }
124 }
125 constraints.push(LinearConstraint::le(terms, 0));
126 }
127
128 let big_m = Self::exact_i64(n, "encoding the connectivity-flow bound")? - 1;
130 for e in 0..m {
131 constraints.push(LinearConstraint::le(
132 vec![(f_idx(e, 0), 1), (y_idx(e), -big_m)],
133 0,
134 ));
135 constraints.push(LinearConstraint::le(
136 vec![(f_idx(e, 1), 1), (y_idx(e), -big_m)],
137 0,
138 ));
139 }
140
141 {
147 let mut terms = Vec::new();
148 for (e, &(u, v)) in edges.iter().enumerate() {
149 if u == root {
150 terms.push((f_idx(e, 0), 1)); terms.push((f_idx(e, 1), -1)); }
153 if v == root {
154 terms.push((f_idx(e, 1), 1)); terms.push((f_idx(e, 0), -1)); }
157 }
158 for v in 0..n {
160 terms.push((z_idx(v), -1));
161 }
162 constraints.push(LinearConstraint::eq(terms, -1));
163 }
164
165 for v in 0..n {
169 if v == root {
170 continue;
171 }
172 let mut terms = Vec::new();
173 for (e, &(u, w)) in edges.iter().enumerate() {
174 if u == v {
175 terms.push((f_idx(e, 0), -1)); terms.push((f_idx(e, 1), 1)); }
179 if w == v {
180 terms.push((f_idx(e, 0), 1)); terms.push((f_idx(e, 1), -1)); }
184 }
185 terms.push((z_idx(v), -1));
186 constraints.push(LinearConstraint::eq(terms, 0));
187 }
188
189 for e in 0..m {
191 constraints.push(LinearConstraint::le(vec![(t_idx(e), 1)], 2));
192 }
193
194 for e in 0..m {
196 constraints.push(LinearConstraint::le(vec![(y_idx(e), 1)], 1));
197 }
198 for v in 0..n {
199 constraints.push(LinearConstraint::le(vec![(z_idx(v), 1)], 1));
200 }
201
202 let edge_lengths = self.edge_lengths();
204 let objective: Vec<(usize, i64)> = (0..m)
205 .map(|e| (t_idx(e), edge_lengths[e].to_sum()))
206 .collect();
207 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
208 .map_err(Self::target_construction)?;
209
210 Ok(ReductionRPToILP {
211 target,
212 num_edges: m,
213 })
214 }
215}
216
217#[cfg(feature = "example-db")]
218pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
219 vec![crate::example_db::specs::RuleExampleSpec {
220 id: "ruralpostman_to_ilp",
221 build: || {
222 let source = RuralPostman::new(
224 SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]),
225 vec![1, 1, 1],
226 vec![0],
227 );
228 crate::example_db::specs::rule_example_via_ilp::<_, i64>(source)
229 },
230 }]
231}
232
233#[cfg(test)]
234#[path = "../unit_tests/rules/ruralpostman_ilp.rs"]
235mod tests;