1use crate::models::algebraic::{Bounded, IntegerVariable, 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, i64, Bounded>,
63 n: usize,
64}
65
66impl ReductionResult for ReductionRTSAToILP {
67 type Source = RootedTreeStorageAssignment;
68 type Target = ILP<i64, i64, Bounded>;
69
70 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
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_witness(
80 self.target_problem(),
81 target_solution,
82 |value| value.value.is_some(),
83 "target ILP assignment is infeasible",
84 )?;
85
86 one_hot_decode_rows(target_solution, self.n, self.n, 0)
87 }
88}
89
90#[crate::aggregate_reduction(ilp_feasibility)]
91impl crate::rules::AggregateReductionResult for ReductionRTSAToILP {}
92
93#[reduction(
98 transform = upper_bound {
99 max_constraint_magnitude_bits = "universe_size + num_subsets + 1",
100 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)",
101 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)",
102 num_nonzeros = "9 * universe_size^3 + 3 * universe_size^2 + num_subsets * (4 * universe_size^3 + 10 * universe_size^2 + 16 * universe_size + 9)",
103 },
104)]
105impl ReduceTo<ILP<i64, i64, Bounded>> for RootedTreeStorageAssignment {
106 type Result = ReductionRTSAToILP;
107
108 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
109 let n = self.universe_size();
110 let subsets = self.subsets();
111
112 let nontrivial: Vec<usize> = (0..subsets.len())
114 .filter(|&k| subsets[k].len() >= 2)
115 .collect();
116 let r = nontrivial.len();
117 let max_cost = Self::exact_i64(r, "encoding the subset count")?.saturating_mul(
120 Self::exact_i64(n.saturating_sub(1), "encoding the maximum extension cost")?,
121 );
122 let bound = self.bound().clamp(-1, max_cost);
123
124 if n == 0 {
125 return Ok(ReductionRTSAToILP {
126 target: ILP::new(
127 0,
128 vec![LinearConstraint::le(vec![], bound)],
129 vec![],
130 ObjectiveSense::Minimize,
131 )
132 .map_err(Self::target_construction)?,
133 n,
134 });
135 }
136
137 let nv = total_vars(n, r);
138 let big_m = Self::exact_i64(n, "representing the vertex count in ILP rows")?;
139 let big_m_depth =
140 Self::exact_i64(n - 1, "representing the maximum tree depth in ILP rows")?;
141
142 let mut constraints = Vec::new();
143
144 for v in 0..n {
148 let terms: Vec<(usize, i64)> = (0..n).map(|u| (idx_p(n, v, u), 1)).collect();
149 constraints.push(LinearConstraint::eq(terms, 1));
150 }
151
152 let root_terms: Vec<(usize, i64)> = (0..n).map(|v| (idx_p(n, v, v), 1)).collect();
154 constraints.push(LinearConstraint::eq(root_terms, 1));
155
156 for v in 0..n {
158 for u in 0..n {
159 constraints.push(LinearConstraint::le(vec![(idx_p(n, v, u), 1)], 1));
160 }
161 }
162
163 for v in 0..n {
165 constraints.push(LinearConstraint::le(
166 vec![(idx_d(n, v), 1), (idx_p(n, v, v), big_m_depth)],
167 big_m_depth,
168 ));
169 }
170
171 for v in 0..n {
173 constraints.push(LinearConstraint::ge(vec![(idx_d(n, v), 1)], 0));
174 }
175
176 for v in 0..n {
178 constraints.push(LinearConstraint::le(vec![(idx_d(n, v), 1)], big_m_depth));
179 }
180
181 for v in 0..n {
184 for u in 0..n {
185 if u != v {
186 constraints.push(LinearConstraint::ge(
188 vec![
189 (idx_d(n, v), 1),
190 (idx_d(n, u), -1),
191 (idx_p(n, v, u), -big_m),
192 ],
193 1 - big_m,
194 ));
195
196 constraints.push(LinearConstraint::le(
200 vec![(idx_d(n, v), 1), (idx_d(n, u), -1), (idx_p(n, v, u), big_m)],
201 1 + big_m,
202 ));
203 }
204 }
205 }
206
207 for v in 0..n {
211 constraints.push(LinearConstraint::eq(vec![(idx_a(n, v, v), 1)], 1));
212 }
213
214 for u in 0..n {
216 for v in 0..n {
217 constraints.push(LinearConstraint::eq(vec![(idx_h(n, u, v, v), 1)], 0));
218 }
219 }
220
221 for u in 0..n {
223 for v in 0..n {
224 if u != v {
225 let mut terms = vec![(idx_a(n, u, v), -1)];
226 for w in 0..n {
227 terms.push((idx_h(n, u, v, w), 1));
228 }
229 constraints.push(LinearConstraint::eq(terms, 0));
230 }
231 }
232 }
233
234 for u in 0..n {
238 for v in 0..n {
239 for w in 0..n {
240 if w != v {
241 constraints.extend(mccormick_product(
242 idx_h(n, u, v, w),
243 idx_p(n, v, w),
244 idx_a(n, u, w),
245 ));
246 }
247 }
248 }
249 }
250
251 for u in 0..n {
253 for v in 0..n {
254 constraints.push(LinearConstraint::le(vec![(idx_a(n, u, v), 1)], 1));
255 for w in 0..n {
256 constraints.push(LinearConstraint::le(vec![(idx_h(n, u, v, w), 1)], 1));
257 }
258 }
259 }
260
261 for (s, &orig_k) in nontrivial.iter().enumerate() {
263 let subset = &subsets[orig_k];
264 let subset_size = subset.len();
265
266 let top_terms: Vec<(usize, i64)> =
268 subset.iter().map(|&u| (idx_t(n, r, s, u), 1)).collect();
269 constraints.push(LinearConstraint::eq(top_terms, 1));
270 for u in 0..n {
271 if !subset.contains(&u) {
272 constraints.push(LinearConstraint::eq(vec![(idx_t(n, r, s, u), 1)], 0));
273 }
274 constraints.push(LinearConstraint::le(vec![(idx_t(n, r, s, u), 1)], 1));
276 }
277
278 let bot_terms: Vec<(usize, i64)> =
280 subset.iter().map(|&v| (idx_b(n, r, s, v), 1)).collect();
281 constraints.push(LinearConstraint::eq(bot_terms, 1));
282 for v in 0..n {
283 if !subset.contains(&v) {
284 constraints.push(LinearConstraint::eq(vec![(idx_b(n, r, s, v), 1)], 0));
285 }
286 constraints.push(LinearConstraint::le(vec![(idx_b(n, r, s, v), 1)], 1));
287 }
288
289 for u in 0..n {
291 for v in 0..n {
292 constraints.extend(mccormick_product(
293 idx_m(n, r, s, u, v),
294 idx_t(n, r, s, u),
295 idx_b(n, r, s, v),
296 ));
297 constraints.push(LinearConstraint::le(vec![(idx_m(n, r, s, u, v), 1)], 1));
298 }
299 }
300
301 for u in 0..n {
303 for v in 0..n {
304 constraints.push(LinearConstraint::le(
305 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, u, v), -1)],
306 0,
307 ));
308 }
309 }
310
311 for &w in subset {
314 for u in 0..n {
315 for v in 0..n {
316 constraints.push(LinearConstraint::le(
317 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, u, w), -1)],
318 0,
319 ));
320 constraints.push(LinearConstraint::le(
321 vec![(idx_m(n, r, s, u, v), 1), (idx_a(n, w, v), -1)],
322 0,
323 ));
324 }
325 }
326 }
327
328 for &u in subset {
331 constraints.push(LinearConstraint::le(
332 vec![
333 (idx_big_t(n, r, s), 1),
334 (idx_d(n, u), -1),
335 (idx_t(n, r, s, u), big_m_depth),
336 ],
337 big_m_depth,
338 ));
339 constraints.push(LinearConstraint::le(
340 vec![
341 (idx_d(n, u), 1),
342 (idx_big_t(n, r, s), -1),
343 (idx_t(n, r, s, u), big_m_depth),
344 ],
345 big_m_depth,
346 ));
347 }
348 for &v in subset {
350 constraints.push(LinearConstraint::le(
351 vec![
352 (idx_big_b(n, r, s), 1),
353 (idx_d(n, v), -1),
354 (idx_b(n, r, s, v), big_m_depth),
355 ],
356 big_m_depth,
357 ));
358 constraints.push(LinearConstraint::le(
359 vec![
360 (idx_d(n, v), 1),
361 (idx_big_b(n, r, s), -1),
362 (idx_b(n, r, s, v), big_m_depth),
363 ],
364 big_m_depth,
365 ));
366 }
367
368 constraints.push(LinearConstraint::ge(vec![(idx_big_t(n, r, s), 1)], 0));
370 constraints.push(LinearConstraint::le(
371 vec![(idx_big_t(n, r, s), 1)],
372 big_m_depth,
373 ));
374 constraints.push(LinearConstraint::ge(vec![(idx_big_b(n, r, s), 1)], 0));
375 constraints.push(LinearConstraint::le(
376 vec![(idx_big_b(n, r, s), 1)],
377 big_m_depth,
378 ));
379
380 constraints.push(LinearConstraint::eq(
383 vec![
384 (idx_c(n, r, s), 1),
385 (idx_big_b(n, r, s), -1),
386 (idx_big_t(n, r, s), 1),
387 ],
388 1 - Self::exact_i64(
389 subset_size,
390 "representing a subset cardinality in an ILP row",
391 )?,
392 ));
393
394 constraints.push(LinearConstraint::ge(vec![(idx_c(n, r, s), 1)], 0));
396 }
397
398 let cost_terms: Vec<(usize, i64)> = (0..r).map(|s| (idx_c(n, r, s), 1)).collect();
400 constraints.push(LinearConstraint::le(cost_terms, bound));
401
402 let mut variables = vec![IntegerVariable::binary(); nv];
403 let depth_domain =
404 IntegerVariable::new(Some(0), Some(big_m_depth)).map_err(Self::target_construction)?;
405 variables[n * n..n * n + n].fill(depth_domain);
406 variables[idx_big_t(n, r, 0)..].fill(depth_domain);
407
408 let target = ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
409 .map_err(Self::target_construction)?;
410 Ok(ReductionRTSAToILP { target, n })
411 }
412}
413
414#[cfg(feature = "example-db")]
415pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
416 use crate::export::SolutionPair;
417 vec![crate::example_db::specs::RuleExampleSpec {
418 id: "rootedtreestorageassignment_to_ilp",
419 build: || {
420 let source = RootedTreeStorageAssignment::new(3, vec![vec![0, 1], vec![1, 2]], 1);
421 let reduction: ReductionRTSAToILP =
422 ReduceTo::<ILP<i64, i64, Bounded>>::reduce_to(&source)
423 .expect("reduction should succeed");
424 let target_config = {
425 let ilp_solver = crate::solvers::ILPSolver::new();
426 ilp_solver
427 .solve(reduction.target_problem())
428 .expect("ILP should be solvable")
429 };
430 let source_config = reduction.extract_solution(&target_config).unwrap();
431 crate::example_db::specs::rule_example_with_witness::<_, ILP<i64, i64, Bounded>>(
432 source,
433 SolutionPair {
434 source_config: serde_json::to_value(source_config)
435 .expect("solution serialization must succeed"),
436 target_config: serde_json::to_value(target_config)
437 .expect("solution serialization must succeed"),
438 },
439 )
440 },
441 }]
442}
443
444#[cfg(test)]
445#[path = "../unit_tests/rules/rootedtreestorageassignment_ilp.rs"]
446mod tests;