1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::misc::StackerCrane;
9use crate::reduction;
10use crate::rules::ilp_helpers::one_hot_decode;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13#[derive(Debug, Clone)]
21pub struct ReductionSCToILP {
22 target: ILP<bool>,
23 num_arcs: usize,
24}
25
26impl ReductionResult for ReductionSCToILP {
27 type Source = StackerCrane;
28 type Target = ILP<bool>;
29
30 fn target_problem(&self) -> &ILP<bool> {
31 &self.target
32 }
33
34 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
35 one_hot_decode(target_solution, self.num_arcs, self.num_arcs, 0)
37 }
38}
39
40#[reduction(
41 overhead = {
42 num_vars = "num_arcs * num_arcs + num_arcs * num_arcs * num_arcs",
43 num_constraints = "num_arcs + num_arcs + 3 * num_arcs * num_arcs * num_arcs",
44 }
45)]
46impl ReduceTo<ILP<bool>> for StackerCrane {
47 type Result = ReductionSCToILP;
48
49 fn reduce_to(&self) -> Self::Result {
50 let m = self.num_arcs();
51
52 if m == 0 {
53 return ReductionSCToILP {
54 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize),
55 num_arcs: 0,
56 };
57 }
58
59 let num_vars = m * m + m * m * m;
60 let x_idx = |i: usize, p: usize| i * m + p;
61 let z_idx = |i: usize, j: usize, p: usize| m * m + p * m * m + i * m + j;
62
63 let n = self.num_vertices();
65 let distances = all_pairs_shortest_paths(
66 n,
67 self.arcs(),
68 self.arc_lengths(),
69 self.edges(),
70 self.edge_lengths(),
71 );
72
73 let mut constraints = Vec::new();
74
75 for i in 0..m {
77 let terms: Vec<(usize, f64)> = (0..m).map(|p| (x_idx(i, p), 1.0)).collect();
78 constraints.push(LinearConstraint::eq(terms, 1.0));
79 }
80
81 for p in 0..m {
83 let terms: Vec<(usize, f64)> = (0..m).map(|i| (x_idx(i, p), 1.0)).collect();
84 constraints.push(LinearConstraint::eq(terms, 1.0));
85 }
86
87 for p in 0..m {
89 let next_p = (p + 1) % m;
90 for i in 0..m {
91 for j in 0..m {
92 let head_i = self.arcs()[i].1;
93 let tail_j = self.arcs()[j].0;
94
95 if distances[head_i][tail_j] == i64::MAX {
96 constraints.push(LinearConstraint::eq(vec![(z_idx(i, j, p), 1.0)], 0.0));
98 } else {
99 constraints.push(LinearConstraint::le(
101 vec![(z_idx(i, j, p), 1.0), (x_idx(i, p), -1.0)],
102 0.0,
103 ));
104 constraints.push(LinearConstraint::le(
106 vec![(z_idx(i, j, p), 1.0), (x_idx(j, next_p), -1.0)],
107 0.0,
108 ));
109 constraints.push(LinearConstraint::le(
111 vec![
112 (x_idx(i, p), 1.0),
113 (x_idx(j, next_p), 1.0),
114 (z_idx(i, j, p), -1.0),
115 ],
116 1.0,
117 ));
118 }
119 }
120 }
121 }
122
123 let mut objective = Vec::new();
126 for p in 0..m {
127 for i in 0..m {
128 for j in 0..m {
129 let head_i = self.arcs()[i].1;
130 let tail_j = self.arcs()[j].0;
131 let dist = distances[head_i][tail_j];
132 if dist < i64::MAX {
133 objective.push((z_idx(i, j, p), dist as f64));
134 }
135 }
136 }
137 }
138
139 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
140
141 ReductionSCToILP {
142 target,
143 num_arcs: m,
144 }
145 }
146}
147
148fn all_pairs_shortest_paths(
150 n: usize,
151 arcs: &[(usize, usize)],
152 arc_lengths: &[i32],
153 edges: &[(usize, usize)],
154 edge_lengths: &[i32],
155) -> Vec<Vec<i64>> {
156 let mut dist = vec![vec![i64::MAX; n]; n];
157 for (i, row) in dist.iter_mut().enumerate() {
158 row[i] = 0;
159 }
160
161 for (&(u, v), &length) in arcs.iter().zip(arc_lengths) {
163 let cost = i64::from(length);
164 if cost < dist[u][v] {
165 dist[u][v] = cost;
166 }
167 }
168
169 for (&(u, v), &length) in edges.iter().zip(edge_lengths) {
171 let cost = i64::from(length);
172 if cost < dist[u][v] {
173 dist[u][v] = cost;
174 }
175 if cost < dist[v][u] {
176 dist[v][u] = cost;
177 }
178 }
179
180 for via in 0..n {
182 for src in 0..n {
183 if dist[src][via] == i64::MAX {
184 continue;
185 }
186 for dst in 0..n {
187 if dist[via][dst] == i64::MAX {
188 continue;
189 }
190 let through = dist[src][via] + dist[via][dst];
191 if through < dist[src][dst] {
192 dist[src][dst] = through;
193 }
194 }
195 }
196 }
197
198 dist
199}
200
201#[cfg(feature = "example-db")]
202pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
203 vec![crate::example_db::specs::RuleExampleSpec {
204 id: "stackercrane_to_ilp",
205 build: || {
206 let source =
208 StackerCrane::new(3, vec![(0, 1), (2, 0)], vec![(1, 2)], vec![1, 1], vec![1]);
209 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
210 },
211 }]
212}
213
214#[cfg(test)]
215#[path = "../unit_tests/rules/stackercrane_ilp.rs"]
216mod tests;