1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::set::RootedTreeStorageAssignment;
9use crate::reduction;
10use crate::rules::ilp_helpers::{mccormick_product, one_hot_decode_rows};
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13fn idx_p(n: usize, v: usize, u: usize) -> usize {
16 v * n + u
17}
18
19fn idx_d(n: usize, v: usize) -> usize {
20 n * n + v
21}
22
23fn idx_a(n: usize, u: usize, v: usize) -> usize {
24 n * n + n + u * n + v
25}
26
27fn idx_h(n: usize, u: usize, v: usize, w: usize) -> usize {
28 2 * n * n + n + (u * n + v) * n + w
29}
30
31fn idx_t(n: usize, r: usize, s: usize, u: usize) -> usize {
32 let _ = r;
33 n * n * n + 2 * n * n + n + s * n + u
34}
35
36fn idx_b(n: usize, r: usize, s: usize, v: usize) -> usize {
37 n * n * n + 2 * n * n + n + r * n + s * n + v
38}
39
40fn idx_m(n: usize, r: usize, s: usize, u: usize, v: usize) -> usize {
41 n * n * n + 2 * n * n + n + 2 * r * n + s * n * n + u * n + v
42}
43
44fn idx_big_t(n: usize, r: usize, s: usize) -> usize {
45 n * n * n + 2 * n * n + n + 2 * r * n + r * n * n + s
46}
47
48fn idx_big_b(n: usize, r: usize, s: usize) -> usize {
49 n * n * n + 2 * n * n + n + 2 * r * n + r * n * n + r + s
50}
51
52fn idx_c(n: usize, r: usize, s: usize) -> usize {
53 n * n * n + 2 * n * n + n + 2 * r * n + r * n * n + 2 * r + s
54}
55
56fn total_vars(n: usize, r: usize) -> usize {
57 n * n * n + 2 * n * n + n + r * (n * n + 2 * n + 3)
58}
59
60#[derive(Debug, Clone)]
61pub struct ReductionRTSAToILP {
62 target: ILP<i64>,
63 n: usize,
64}
65
66impl ReductionResult for ReductionRTSAToILP {
67 type Source = RootedTreeStorageAssignment;
68 type Target = ILP<i64>;
69
70 fn target_problem(&self) -> &ILP<i64> {
71 &self.target
72 }
73
74 fn extract_solution(
76 &self,
77 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
78 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
79 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
80
81 one_hot_decode_rows(target_solution, self.n, self.n, 0)
82 }
83}
84
85#[reduction(
86 transform = upper_bound {
87 num_vars = "universe_size * universe_size * universe_size + 2 * universe_size * universe_size + universe_size + num_subsets * (universe_size * universe_size + 2 * universe_size + 3)",
88 num_constraints = "4 * universe_size^3 + 6 * universe_size^2 + 5 * universe_size + 2 + num_subsets * (2 * universe_size^3 + 5 * universe_size^2 + 8 * universe_size + 8)",
89 },
90 unavailable = {
91 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
92 }
93)]
94impl ReduceTo<ILP<i64>> for RootedTreeStorageAssignment {
95 type Result = ReductionRTSAToILP;
96
97 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
98 let n = self.universe_size();
99 let subsets = self.subsets();
100 let bound = self.bound();
101
102 let nontrivial: Vec<usize> = (0..subsets.len())
104 .filter(|&k| subsets[k].len() >= 2)
105 .collect();
106 let r = nontrivial.len();
107
108 if n == 0 {
109 return Ok(ReductionRTSAToILP {
110 target: ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
111 .map_err(Self::target_construction)?,
112 n,
113 });
114 }
115
116 let nv = total_vars(n, r);
117 let big_m = Self::exact_i64(n, "representing the vertex count in ILP rows")?;
118 let big_m_depth =
119 Self::exact_i64(n - 1, "representing the maximum tree depth in ILP rows")?;
120
121 let mut constraints = Vec::new();
122
123 for v in 0..n {
127 let terms: Vec<(usize, i64)> = (0..n).map(|u| (idx_p(n, v, u), 1)).collect();
128 constraints.push(LinearConstraint::eq(terms, 1));
129 }
130
131 let root_terms: Vec<(usize, i64)> = (0..n).map(|v| (idx_p(n, v, v), 1)).collect();
133 constraints.push(LinearConstraint::eq(root_terms, 1));
134
135 for v in 0..n {
137 for u in 0..n {
138 constraints.push(LinearConstraint::le(vec![(idx_p(n, v, u), 1)], 1));
139 }
140 }
141
142 for v in 0..n {
144 constraints.push(LinearConstraint::le(
145 vec![(idx_d(n, v), 1), (idx_p(n, v, v), big_m_depth)],
146 big_m_depth,
147 ));
148 }
149
150 for v in 0..n {
152 constraints.push(LinearConstraint::ge(vec![(idx_d(n, v), 1)], 0));
153 }
154
155 for v in 0..n {
157 constraints.push(LinearConstraint::le(vec![(idx_d(n, v), 1)], big_m_depth));
158 }
159
160 for v in 0..n {
163 for u in 0..n {
164 if u != v {
165 constraints.push(LinearConstraint::ge(
172 vec![
173 (idx_d(n, v), 1),
174 (idx_d(n, u), -1),
175 (idx_p(n, v, u), -big_m),
176 ],
177 1 - big_m,
178 ));
179
180 constraints.push(LinearConstraint::le(
184 vec![(idx_d(n, v), 1), (idx_d(n, u), -1), (idx_p(n, v, u), big_m)],
185 1 + big_m,
186 ));
187 }
188 }
189 }
190
191 for v in 0..n {
195 constraints.push(LinearConstraint::eq(vec![(idx_a(n, v, v), 1)], 1));
196 }
197
198 for u in 0..n {
200 for v in 0..n {
201 constraints.push(LinearConstraint::eq(vec![(idx_h(n, u, v, v), 1)], 0));
202 }
203 }
204
205 for u in 0..n {
207 for v in 0..n {
208 if u != v {
209 let mut terms = vec![(idx_a(n, u, v), -1)];
210 for w in 0..n {
211 terms.push((idx_h(n, u, v, w), 1));
212 }
213 constraints.push(LinearConstraint::eq(terms, 0));
214 }
215 }
216 }
217
218 for u in 0..n {
222 for v in 0..n {
223 for w in 0..n {
224 if w != v {
225 constraints.extend(mccormick_product(
226 idx_h(n, u, v, w),
227 idx_p(n, v, w),
228 idx_a(n, u, w),
229 ));
230 }
231 }
232 }
233 }
234
235 for u in 0..n {
237 for v in 0..n {
238 constraints.push(LinearConstraint::le(vec![(idx_a(n, u, v), 1)], 1));
239 for w in 0..n {
240 constraints.push(LinearConstraint::le(vec![(idx_h(n, u, v, w), 1)], 1));
241 }
242 }
243 }
244
245 for (s, &orig_k) in nontrivial.iter().enumerate() {
247 let subset = &subsets[orig_k];
248 let subset_size = subset.len();
249
250 let top_terms: Vec<(usize, i64)> =
252 subset.iter().map(|&u| (idx_t(n, r, s, u), 1)).collect();
253 constraints.push(LinearConstraint::eq(top_terms, 1));
254 for u in 0..n {
255 if !subset.contains(&u) {
256 constraints.push(LinearConstraint::eq(vec![(idx_t(n, r, s, u), 1)], 0));
257 }
258 constraints.push(LinearConstraint::le(vec![(idx_t(n, r, s, u), 1)], 1));
260 }
261
262 let bot_terms: Vec<(usize, i64)> =
264 subset.iter().map(|&v| (idx_b(n, r, s, v), 1)).collect();
265 constraints.push(LinearConstraint::eq(bot_terms, 1));
266 for v in 0..n {
267 if !subset.contains(&v) {
268 constraints.push(LinearConstraint::eq(vec![(idx_b(n, r, s, v), 1)], 0));
269 }
270 constraints.push(LinearConstraint::le(vec![(idx_b(n, r, s, v), 1)], 1));
271 }
272
273 for u in 0..n {
275 for v in 0..n {
276 constraints.extend(mccormick_product(
277 idx_m(n, r, s, u, v),
278 idx_t(n, r, s, u),
279 idx_b(n, r, s, v),
280 ));
281 constraints.push(LinearConstraint::le(vec![(idx_m(n, r, s, u, v), 1)], 1));
282 }
283 }
284
285 for u in 0..n {
287 for v in 0..n {
288 constraints.push(LinearConstraint::le(
289 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, u, v), -1)],
290 0,
291 ));
292 }
293 }
294
295 for &w in subset {
298 for u in 0..n {
299 for v in 0..n {
300 constraints.push(LinearConstraint::le(
301 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, u, w), -1)],
302 0,
303 ));
304 constraints.push(LinearConstraint::le(
305 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, w, v), -1)],
306 0,
307 ));
308 }
309 }
310 }
311
312 for &u in subset {
315 constraints.push(LinearConstraint::le(
316 vec![
317 (idx_big_t(n, r, s), 1),
318 (idx_d(n, u), -1),
319 (idx_t(n, r, s, u), big_m_depth),
320 ],
321 big_m_depth,
322 ));
323 constraints.push(LinearConstraint::le(
324 vec![
325 (idx_d(n, u), 1),
326 (idx_big_t(n, r, s), -1),
327 (idx_t(n, r, s, u), big_m_depth),
328 ],
329 big_m_depth,
330 ));
331 }
332 for &v in subset {
334 constraints.push(LinearConstraint::le(
335 vec![
336 (idx_big_b(n, r, s), 1),
337 (idx_d(n, v), -1),
338 (idx_b(n, r, s, v), big_m_depth),
339 ],
340 big_m_depth,
341 ));
342 constraints.push(LinearConstraint::le(
343 vec![
344 (idx_d(n, v), 1),
345 (idx_big_b(n, r, s), -1),
346 (idx_b(n, r, s, v), big_m_depth),
347 ],
348 big_m_depth,
349 ));
350 }
351
352 constraints.push(LinearConstraint::ge(vec![(idx_big_t(n, r, s), 1)], 0));
354 constraints.push(LinearConstraint::le(
355 vec![(idx_big_t(n, r, s), 1)],
356 big_m_depth,
357 ));
358 constraints.push(LinearConstraint::ge(vec![(idx_big_b(n, r, s), 1)], 0));
359 constraints.push(LinearConstraint::le(
360 vec![(idx_big_b(n, r, s), 1)],
361 big_m_depth,
362 ));
363
364 constraints.push(LinearConstraint::eq(
367 vec![
368 (idx_c(n, r, s), 1),
369 (idx_big_b(n, r, s), -1),
370 (idx_big_t(n, r, s), 1),
371 ],
372 1 - Self::exact_i64(
373 subset_size,
374 "representing a subset cardinality in an ILP row",
375 )?,
376 ));
377
378 constraints.push(LinearConstraint::ge(vec![(idx_c(n, r, s), 1)], 0));
380 }
381
382 if r > 0 {
384 let cost_terms: Vec<(usize, i64)> = (0..r).map(|s| (idx_c(n, r, s), 1)).collect();
385 constraints.push(LinearConstraint::le(cost_terms, bound));
386 }
387
388 let target = ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize)
389 .map_err(Self::target_construction)?;
390 Ok(ReductionRTSAToILP { target, n })
391 }
392}
393
394#[cfg(feature = "example-db")]
395pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
396 use crate::export::SolutionPair;
397 vec![crate::example_db::specs::RuleExampleSpec {
398 id: "rootedtreestorageassignment_to_ilp",
399 build: || {
400 let source = RootedTreeStorageAssignment::new(3, vec![vec![0, 1], vec![1, 2]], 1);
401 let reduction: ReductionRTSAToILP =
402 ReduceTo::<ILP<i64>>::reduce_to(&source).expect("reduction should succeed");
403 let target_config = {
404 let ilp_solver = crate::solvers::ILPSolver::new();
405 ilp_solver
406 .solve(reduction.target_problem())
407 .expect("ILP should be solvable")
408 };
409 let source_config = reduction.extract_solution(&target_config).unwrap();
410 crate::example_db::specs::rule_example_with_witness::<_, ILP<i64>>(
411 source,
412 SolutionPair {
413 source_config: serde_json::to_value(source_config)
414 .expect("solution serialization must succeed"),
415 target_config: serde_json::to_value(target_config)
416 .expect("solution serialization must succeed"),
417 },
418 )
419 },
420 }]
421}
422
423#[cfg(test)]
424#[path = "../unit_tests/rules/rootedtreestorageassignment_ilp.rs"]
425mod tests;