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<i32>,
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, i32>;
37 type Target = ILP<i32>;
38
39 fn target_problem(&self) -> &ILP<i32> {
40 &self.target
41 }
42
43 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
44 (0..self.num_edges)
45 .map(|edge_idx| {
46 usize::from(
47 target_solution
48 .get(Self::arc_var(edge_idx, 0))
49 .copied()
50 .unwrap_or(0)
51 > 0
52 || target_solution
53 .get(Self::arc_var(edge_idx, 1))
54 .copied()
55 .unwrap_or(0)
56 > 0,
57 )
58 })
59 .collect()
60 }
61}
62
63#[reduction(overhead = {
64 num_vars = "2 * num_edges + num_vertices",
65 num_constraints = "5 * num_edges + 4 * num_vertices + 2",
66})]
67impl ReduceTo<ILP<i32>> for ShortestWeightConstrainedPath<SimpleGraph, i32> {
68 type Result = ReductionSWCPToILP;
69
70 fn reduce_to(&self) -> Self::Result {
71 let edges = self.graph().edges();
72 let num_vertices = self.num_vertices();
73 let num_edges = self.num_edges();
74 let num_vars = 2 * num_edges + num_vertices;
75 let source = self.source_vertex();
76 let target = self.target_vertex();
77 let big_m = num_vertices as f64;
78
79 let order_var = |vertex: usize| 2 * num_edges + vertex;
80
81 let mut outgoing: Vec<Vec<(usize, f64)>> = vec![Vec::new(); num_vertices];
84 let mut incoming: Vec<Vec<(usize, f64)>> = vec![Vec::new(); num_vertices];
85
86 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
87 let forward = ReductionSWCPToILP::arc_var(edge_idx, 0); let reverse = ReductionSWCPToILP::arc_var(edge_idx, 1); outgoing[u].push((forward, 1.0));
90 incoming[v].push((forward, 1.0));
91 outgoing[v].push((reverse, 1.0));
92 incoming[u].push((reverse, 1.0));
93 }
94
95 let mut constraints = Vec::new();
96
97 for edge_idx in 0..num_edges {
99 constraints.push(LinearConstraint::le(
100 vec![(ReductionSWCPToILP::arc_var(edge_idx, 0), 1.0)],
101 1.0,
102 ));
103 constraints.push(LinearConstraint::le(
104 vec![(ReductionSWCPToILP::arc_var(edge_idx, 1), 1.0)],
105 1.0,
106 ));
107 }
108
109 for vertex in 0..num_vertices {
111 constraints.push(LinearConstraint::le(
112 vec![(order_var(vertex), 1.0)],
113 num_vertices.saturating_sub(1) as f64,
114 ));
115 }
116
117 for vertex in 0..num_vertices {
119 let mut balance_terms = outgoing[vertex].clone();
121 for &(var, coef) in &incoming[vertex] {
122 balance_terms.push((var, -coef));
123 }
124
125 let rhs = if source != target {
126 if vertex == source {
127 1.0
128 } else if vertex == target {
129 -1.0
130 } else {
131 0.0
132 }
133 } else {
134 0.0
135 };
136 constraints.push(LinearConstraint::eq(balance_terms, rhs));
137 constraints.push(LinearConstraint::le(outgoing[vertex].clone(), 1.0));
138 constraints.push(LinearConstraint::le(incoming[vertex].clone(), 1.0));
139 }
140
141 for edge_idx in 0..num_edges {
143 constraints.push(LinearConstraint::le(
144 vec![
145 (ReductionSWCPToILP::arc_var(edge_idx, 0), 1.0),
146 (ReductionSWCPToILP::arc_var(edge_idx, 1), 1.0),
147 ],
148 1.0,
149 ));
150 }
151
152 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
154 constraints.push(LinearConstraint::ge(
156 vec![
157 (order_var(v), 1.0),
158 (order_var(u), -1.0),
159 (ReductionSWCPToILP::arc_var(edge_idx, 0), -big_m),
160 ],
161 1.0 - big_m,
162 ));
163 constraints.push(LinearConstraint::ge(
165 vec![
166 (order_var(u), 1.0),
167 (order_var(v), -1.0),
168 (ReductionSWCPToILP::arc_var(edge_idx, 1), -big_m),
169 ],
170 1.0 - big_m,
171 ));
172 }
173
174 constraints.push(LinearConstraint::eq(vec![(order_var(source), 1.0)], 0.0));
176
177 let weight_terms: Vec<(usize, f64)> = edges
179 .iter()
180 .enumerate()
181 .flat_map(|(edge_idx, _)| {
182 let coeff = self.edge_weights()[edge_idx].to_sum() as f64;
183 [
184 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
185 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
186 ]
187 })
188 .collect();
189 constraints.push(LinearConstraint::le(
190 weight_terms,
191 *self.weight_bound() as f64,
192 ));
193
194 let objective: Vec<(usize, f64)> = edges
196 .iter()
197 .enumerate()
198 .flat_map(|(edge_idx, _)| {
199 let coeff = self.edge_lengths()[edge_idx].to_sum() as f64;
200 [
201 (ReductionSWCPToILP::arc_var(edge_idx, 0), coeff),
202 (ReductionSWCPToILP::arc_var(edge_idx, 1), coeff),
203 ]
204 })
205 .collect();
206 let target_ilp = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
207
208 ReductionSWCPToILP {
209 target: target_ilp,
210 num_edges,
211 }
212 }
213}
214
215#[cfg(feature = "example-db")]
216pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
217 vec![crate::example_db::specs::RuleExampleSpec {
218 id: "shortestweightconstrainedpath_to_ilp",
219 build: || {
220 let source = ShortestWeightConstrainedPath::new(
225 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
226 vec![2, 3],
227 vec![1, 2],
228 0,
229 2,
230 4,
231 );
232 crate::example_db::specs::rule_example_via_ilp::<_, i32>(source)
233 },
234 }]
235}
236
237#[cfg(test)]
238#[path = "../unit_tests/rules/shortestweightconstrainedpath_ilp.rs"]
239mod tests;