1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
10use crate::models::graph::ShortestWeightConstrainedPath;
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13use crate::topology::{Graph, SimpleGraph};
14use crate::types::WeightElement;
15
16#[derive(Debug, Clone)]
24pub struct ReductionSWCPToILP {
25 target: ILP<i64, i64, Bounded>,
26 num_edges: usize,
27}
28
29impl ReductionSWCPToILP {
30 fn arc_var(edge_idx: usize, dir: usize) -> usize {
31 2 * edge_idx + dir
32 }
33}
34
35impl ReductionResult for ReductionSWCPToILP {
36 type Source = ShortestWeightConstrainedPath<SimpleGraph, i64>;
37 type Target = ILP<i64, i64, Bounded>;
38
39 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
40 &self.target
41 }
42
43 fn extract_solution(
44 &self,
45 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
46 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
47 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
48
49 Ok({
50 (0..self.num_edges)
51 .map(|edge_idx| {
52 target_solution[Self::arc_var(edge_idx, 0)] > 0
53 || target_solution[Self::arc_var(edge_idx, 1)] > 0
54 })
55 .collect()
56 })
57 }
58}
59
60#[reduction(transform = {
61 exact {
62 num_vars = "2 * num_edges + num_vertices",
63 num_constraints = "5 * num_edges + 4 * num_vertices + 2",
64 },
65 upper_bound {
66 max_constraint_magnitude_bits = "max_weight_bits + num_vertices",
67 num_nonzeros = "(2 * num_edges + num_vertices) * (5 * num_edges + 4 * num_vertices + 2)",
68 },
69})]
70impl ReduceTo<ILP<i64, i64, Bounded>> for ShortestWeightConstrainedPath<SimpleGraph, i64> {
71 type Result = ReductionSWCPToILP;
72
73 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
74 let edges = self.graph().edges();
75 let num_vertices = self.num_vertices();
76 let num_edges = self.num_edges();
77 let num_vars = 2 * num_edges + num_vertices;
78 let source = self.source_vertex();
79 let target = self.target_vertex();
80 let big_m = Self::exact_i64(num_vertices, "encoding the vertex order")?;
81
82 let order_var = |vertex: usize| 2 * num_edges + vertex;
83
84 let mut outgoing: Vec<Vec<(usize, i64)>> = vec![Vec::new(); num_vertices];
87 let mut incoming: Vec<Vec<(usize, i64)>> = vec![Vec::new(); num_vertices];
88
89 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
90 let forward = ReductionSWCPToILP::arc_var(edge_idx, 0); let reverse = ReductionSWCPToILP::arc_var(edge_idx, 1); outgoing[u].push((forward, 1));
93 incoming[v].push((forward, 1));
94 outgoing[v].push((reverse, 1));
95 incoming[u].push((reverse, 1));
96 }
97
98 let mut constraints = Vec::new();
99
100 for edge_idx in 0..num_edges {
102 constraints.push(LinearConstraint::le(
103 vec![(ReductionSWCPToILP::arc_var(edge_idx, 0), 1)],
104 1,
105 ));
106 constraints.push(LinearConstraint::le(
107 vec![(ReductionSWCPToILP::arc_var(edge_idx, 1), 1)],
108 1,
109 ));
110 }
111
112 let max_order = if num_vertices == 0 { 0 } else { big_m - 1 };
114 for vertex in 0..num_vertices {
115 constraints.push(LinearConstraint::le(
116 vec![(order_var(vertex), 1)],
117 max_order,
118 ));
119 }
120
121 for vertex in 0..num_vertices {
123 let mut balance_terms = outgoing[vertex].clone();
125 for &(var, coef) in &incoming[vertex] {
126 balance_terms.push((var, -coef));
127 }
128
129 let rhs = if source != target {
130 if vertex == source {
131 1
132 } else if vertex == target {
133 -1
134 } else {
135 0
136 }
137 } else {
138 0
139 };
140 constraints.push(LinearConstraint::eq(balance_terms, rhs));
141 constraints.push(LinearConstraint::le(outgoing[vertex].clone(), 1));
142 constraints.push(LinearConstraint::le(incoming[vertex].clone(), 1));
143 }
144
145 for edge_idx in 0..num_edges {
147 constraints.push(LinearConstraint::le(
148 vec![
149 (ReductionSWCPToILP::arc_var(edge_idx, 0), 1),
150 (ReductionSWCPToILP::arc_var(edge_idx, 1), 1),
151 ],
152 1,
153 ));
154 }
155
156 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
158 constraints.push(LinearConstraint::ge(
160 vec![
161 (order_var(v), 1),
162 (order_var(u), -1),
163 (ReductionSWCPToILP::arc_var(edge_idx, 0), -big_m),
164 ],
165 1 - big_m,
166 ));
167 constraints.push(LinearConstraint::ge(
169 vec![
170 (order_var(u), 1),
171 (order_var(v), -1),
172 (ReductionSWCPToILP::arc_var(edge_idx, 1), -big_m),
173 ],
174 1 - big_m,
175 ));
176 }
177
178 constraints.push(LinearConstraint::eq(vec![(order_var(source), 1)], 0));
180
181 let edge_weights: Vec<i64> = self
183 .edge_weights()
184 .iter()
185 .map(WeightElement::to_sum)
186 .collect();
187 let weight_terms: Vec<(usize, i64)> = edges
188 .iter()
189 .enumerate()
190 .flat_map(|(edge_idx, _)| {
191 let coeff = edge_weights[edge_idx];
192 [
193 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
194 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
195 ]
196 })
197 .collect();
198 constraints.push(LinearConstraint::le(weight_terms, *self.weight_bound()));
199
200 let edge_lengths: Vec<i64> = self
202 .edge_lengths()
203 .iter()
204 .map(WeightElement::to_sum)
205 .collect();
206 let objective: Vec<(usize, i64)> = edges
207 .iter()
208 .enumerate()
209 .flat_map(|(edge_idx, _)| {
210 let coeff = edge_lengths[edge_idx];
211 [
212 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
213 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
214 ]
215 })
216 .collect();
217 let mut variables = vec![IntegerVariable::binary(); num_vars];
218 variables[2 * num_edges..].fill(
219 IntegerVariable::new(Some(0), Some(max_order)).map_err(Self::target_construction)?,
220 );
221
222 let target_ilp =
223 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
224 .map_err(Self::target_construction)?;
225
226 Ok(ReductionSWCPToILP {
227 target: target_ilp,
228 num_edges,
229 })
230 }
231}
232
233#[cfg(feature = "example-db")]
234pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
235 vec![crate::example_db::specs::RuleExampleSpec {
236 id: "shortestweightconstrainedpath_to_ilp",
237 build: || {
238 let source = ShortestWeightConstrainedPath::new(
243 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
244 vec![2, 3],
245 vec![1, 2],
246 0,
247 2,
248 4,
249 );
250 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
251 },
252 }]
253}
254
255#[cfg(test)]
256#[path = "../unit_tests/rules/shortestweightconstrainedpath_ilp.rs"]
257mod tests;