problemreductions/rules/
consecutiveonesmatrixaugmentation_ilp.rs1use crate::models::algebraic::{
4 Bounded, ConsecutiveOnesMatrixAugmentation, IntegerVariable, LinearConstraint, ObjectiveSense,
5 ILP,
6};
7use crate::reduction;
8use crate::rules::ilp_helpers::{one_hot_assignment_constraints, one_hot_decode};
9use crate::rules::traits::{ReduceTo, ReductionResult};
10
11#[derive(Debug, Clone)]
12pub struct ReductionCOMAToILP {
13 target: ILP<i64, i64, Bounded>,
14 num_cols: usize,
15}
16
17impl ReductionResult for ReductionCOMAToILP {
18 type Source = ConsecutiveOnesMatrixAugmentation;
19 type Target = ILP<i64, i64, Bounded>;
20
21 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
22 &self.target
23 }
24
25 fn extract_solution(
26 &self,
27 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
28 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
29 crate::rules::traits::validate_target_witness(
30 self.target_problem(),
31 target_solution,
32 |value| value.value.is_some(),
33 "target ILP assignment is infeasible",
34 )?;
35
36 one_hot_decode(target_solution, self.num_cols, self.num_cols, 0)
37 }
38}
39
40#[crate::aggregate_reduction(ilp_feasibility)]
41impl crate::rules::AggregateReductionResult for ReductionCOMAToILP {}
42
43#[reduction(transform = {
44 exact { num_vars = "num_cols^2 + 2 * num_rows", },
45 upper_bound {
46 num_constraints = "2 * num_cols + 2 * num_rows * num_cols + 1",
47 num_nonzeros = "2 * num_cols^2 + 2 * num_rows * num_cols^2 + 2 * num_rows",
48 max_constraint_magnitude_bits = "2 * num_rows * num_cols + num_cols + 1",
49 },
50})]
51impl ReduceTo<ILP<i64, i64, Bounded>> for ConsecutiveOnesMatrixAugmentation {
52 type Result = ReductionCOMAToILP;
53
54 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
55 let m = self.num_rows();
56 let n = self.num_cols();
57
58 let overflow = || {
59 crate::rules::ReductionError::integer_overflow::<Self, ILP<i64, i64, Bounded>>(
60 "counting matrix interval variables",
61 )
62 };
63 let square = n.checked_mul(n).ok_or_else(overflow)?;
64 let num_vars = m
65 .checked_mul(2)
66 .and_then(|count| square.checked_add(count))
67 .ok_or_else(overflow)?;
68 Self::exact_i64(square, "bounding permutation row arithmetic")?;
69 let cells = Self::exact_i64(
70 m.checked_mul(n).ok_or_else(overflow)?,
71 "counting matrix cells",
72 )?;
73 let last_position = Self::exact_i64(n.saturating_sub(1), "bounding interval endpoints")?;
74 let mut variables = vec![IntegerVariable::binary(); square];
75 let mut constraints = one_hot_assignment_constraints(n, n, 0);
76 let mut budget = Vec::new();
77 let mut total_ones = 0_i64;
78 let mut active_rows = 0_i64;
79 for (r, row) in self.matrix().iter().enumerate() {
80 let nonempty = row.iter().any(|&value| value);
81 let endpoint =
82 IntegerVariable::new(Some(0), Some(if nonempty { last_position } else { 0 }))
83 .map_err(Self::target_construction)?;
84 variables.extend([endpoint, endpoint]);
85 let left = square + 2 * r;
86 let right = left + 1;
87 for (c, &one) in row.iter().enumerate() {
88 if !one {
89 continue;
90 }
91 total_ones += 1;
92 let mut after_left = vec![(left, -1)];
93 let mut before_right = vec![(right, 1)];
94 for p in 1..n {
95 let coefficient = Self::exact_i64(p, "encoding a column position")?;
96 after_left.push((c * n + p, coefficient));
97 before_right.push((c * n + p, -coefficient));
98 }
99 constraints.push(LinearConstraint::ge(after_left, 0));
100 constraints.push(LinearConstraint::ge(before_right, 0));
101 }
102 if nonempty {
103 active_rows += 1;
104 budget.extend([(left, -1), (right, 1)]);
105 }
106 }
107 let rhs = self.bound().min(cells - total_ones) + total_ones - active_rows;
110 constraints.push(LinearConstraint::le(budget, rhs));
111 debug_assert_eq!(variables.len(), num_vars);
112 let target = ILP::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
113 .map_err(Self::target_construction)?;
114 Ok(ReductionCOMAToILP {
115 target,
116 num_cols: n,
117 })
118 }
119}
120
121#[cfg(feature = "example-db")]
122pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
123 vec![crate::example_db::specs::RuleExampleSpec {
124 id: "consecutiveonesmatrixaugmentation_to_ilp",
125 build: || {
126 crate::example_db::specs::rule_example_via_bounded_ilp(
127 ConsecutiveOnesMatrixAugmentation::new(
128 vec![vec![true, false, true], vec![false, true, true]],
129 1,
130 ),
131 )
132 },
133 }]
134}
135
136#[cfg(test)]
137#[path = "../unit_tests/rules/consecutiveonesmatrixaugmentation_ilp.rs"]
138mod tests;