1use crate::models::algebraic::{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>,
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>;
38
39 fn target_problem(&self) -> &ILP<i64> {
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(
61 transform = exact {
62 num_vars = "2 * num_edges + num_vertices",
63 num_constraints = "5 * num_edges + 4 * num_vertices + 2",
64 },
65 unavailable = {
66 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
67 }
68)]
69impl ReduceTo<ILP<i64>> for ShortestWeightConstrainedPath<SimpleGraph, i64> {
70 type Result = ReductionSWCPToILP;
71
72 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
73 let edges = self.graph().edges();
74 let num_vertices = self.num_vertices();
75 let num_edges = self.num_edges();
76 let num_vars = 2 * num_edges + num_vertices;
77 let source = self.source_vertex();
78 let target = self.target_vertex();
79 let big_m = Self::exact_i64(num_vertices, "encoding the vertex order")?;
80
81 let order_var = |vertex: usize| 2 * num_edges + vertex;
82
83 let mut outgoing: Vec<Vec<(usize, i64)>> = vec![Vec::new(); num_vertices];
86 let mut incoming: Vec<Vec<(usize, i64)>> = vec![Vec::new(); num_vertices];
87
88 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
89 let forward = ReductionSWCPToILP::arc_var(edge_idx, 0); let reverse = ReductionSWCPToILP::arc_var(edge_idx, 1); outgoing[u].push((forward, 1));
92 incoming[v].push((forward, 1));
93 outgoing[v].push((reverse, 1));
94 incoming[u].push((reverse, 1));
95 }
96
97 let mut constraints = Vec::new();
98
99 for edge_idx in 0..num_edges {
101 constraints.push(LinearConstraint::le(
102 vec![(ReductionSWCPToILP::arc_var(edge_idx, 0), 1)],
103 1,
104 ));
105 constraints.push(LinearConstraint::le(
106 vec![(ReductionSWCPToILP::arc_var(edge_idx, 1), 1)],
107 1,
108 ));
109 }
110
111 let max_order = if num_vertices == 0 { 0 } else { big_m - 1 };
113 for vertex in 0..num_vertices {
114 constraints.push(LinearConstraint::le(
115 vec![(order_var(vertex), 1)],
116 max_order,
117 ));
118 }
119
120 for vertex in 0..num_vertices {
122 let mut balance_terms = outgoing[vertex].clone();
124 for &(var, coef) in &incoming[vertex] {
125 balance_terms.push((var, -coef));
126 }
127
128 let rhs = if source != target {
129 if vertex == source {
130 1
131 } else if vertex == target {
132 -1
133 } else {
134 0
135 }
136 } else {
137 0
138 };
139 constraints.push(LinearConstraint::eq(balance_terms, rhs));
140 constraints.push(LinearConstraint::le(outgoing[vertex].clone(), 1));
141 constraints.push(LinearConstraint::le(incoming[vertex].clone(), 1));
142 }
143
144 for edge_idx in 0..num_edges {
146 constraints.push(LinearConstraint::le(
147 vec![
148 (ReductionSWCPToILP::arc_var(edge_idx, 0), 1),
149 (ReductionSWCPToILP::arc_var(edge_idx, 1), 1),
150 ],
151 1,
152 ));
153 }
154
155 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
157 constraints.push(LinearConstraint::ge(
159 vec![
160 (order_var(v), 1),
161 (order_var(u), -1),
162 (ReductionSWCPToILP::arc_var(edge_idx, 0), -big_m),
163 ],
164 1 - big_m,
165 ));
166 constraints.push(LinearConstraint::ge(
168 vec![
169 (order_var(u), 1),
170 (order_var(v), -1),
171 (ReductionSWCPToILP::arc_var(edge_idx, 1), -big_m),
172 ],
173 1 - big_m,
174 ));
175 }
176
177 constraints.push(LinearConstraint::eq(vec![(order_var(source), 1)], 0));
179
180 let edge_weights: Vec<i64> = self
182 .edge_weights()
183 .iter()
184 .map(WeightElement::to_sum)
185 .collect();
186 let weight_terms: Vec<(usize, i64)> = edges
187 .iter()
188 .enumerate()
189 .flat_map(|(edge_idx, _)| {
190 let coeff = edge_weights[edge_idx];
191 [
192 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
193 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
194 ]
195 })
196 .collect();
197 constraints.push(LinearConstraint::le(weight_terms, *self.weight_bound()));
198
199 let edge_lengths: Vec<i64> = self
201 .edge_lengths()
202 .iter()
203 .map(WeightElement::to_sum)
204 .collect();
205 let objective: Vec<(usize, i64)> = edges
206 .iter()
207 .enumerate()
208 .flat_map(|(edge_idx, _)| {
209 let coeff = edge_lengths[edge_idx];
210 [
211 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
212 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
213 ]
214 })
215 .collect();
216 let target_ilp = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
217 .map_err(Self::target_construction)?;
218
219 Ok(ReductionSWCPToILP {
220 target: target_ilp,
221 num_edges,
222 })
223 }
224}
225
226#[cfg(feature = "example-db")]
227pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
228 vec![crate::example_db::specs::RuleExampleSpec {
229 id: "shortestweightconstrainedpath_to_ilp",
230 build: || {
231 let source = ShortestWeightConstrainedPath::new(
236 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
237 vec![2, 3],
238 vec![1, 2],
239 0,
240 2,
241 4,
242 );
243 crate::example_db::specs::rule_example_via_ilp::<_, i64>(source)
244 },
245 }]
246}
247
248#[cfg(test)]
249#[path = "../unit_tests/rules/shortestweightconstrainedpath_ilp.rs"]
250mod tests;