1use crate::models::algebraic::{ConsecutiveOnesSubmatrix, LinearConstraint, ObjectiveSense, ILP};
8use crate::reduction;
9use crate::rules::traits::{ReduceTo, ReductionResult};
10
11#[derive(Debug, Clone)]
12pub struct ReductionCOSToILP {
13 target: ILP<bool>,
14 num_cols: usize,
15}
16
17impl ReductionResult for ReductionCOSToILP {
18 type Source = ConsecutiveOnesSubmatrix;
19 type Target = ILP<bool>;
20
21 fn target_problem(&self) -> &ILP<bool> {
22 &self.target
23 }
24
25 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
26 target_solution[..self.num_cols].to_vec()
28 }
29}
30
31#[reduction(
32 overhead = {
33 num_vars = "num_cols + num_cols * bound + 5 * num_rows * bound",
34 num_constraints = "1 + num_cols + bound + num_rows * bound + 2 * num_rows + num_rows + 3 * num_rows * bound + 4 * num_rows * bound",
35 }
36)]
37impl ReduceTo<ILP<bool>> for ConsecutiveOnesSubmatrix {
38 type Result = ReductionCOSToILP;
39
40 fn reduce_to(&self) -> Self::Result {
41 let m = self.num_rows();
42 let n = self.num_cols();
43 let k = self.bound() as usize;
44
45 let s_off = 0;
54 let x_off = n;
55 let a_off = n + n * k;
56 let l_off = a_off + m * k;
57 let u_off = l_off + m * k;
58 let h_off = u_off + m * k;
59 let f_off = h_off + m * k;
60 let num_vars = f_off + m * k;
61
62 let mut constraints = Vec::new();
63
64 let s_terms: Vec<(usize, f64)> = (0..n).map(|c| (s_off + c, 1.0)).collect();
66 constraints.push(LinearConstraint::eq(s_terms, k as f64));
67
68 for c in 0..n {
70 let mut terms: Vec<(usize, f64)> = (0..k).map(|p| (x_off + c * k + p, 1.0)).collect();
71 terms.push((s_off + c, -1.0));
72 constraints.push(LinearConstraint::eq(terms, 0.0));
73 }
74
75 for p in 0..k {
77 let terms: Vec<(usize, f64)> = (0..n).map(|c| (x_off + c * k + p, 1.0)).collect();
78 constraints.push(LinearConstraint::eq(terms, 1.0));
79 }
80
81 for r in 0..m {
83 for p in 0..k {
84 let a_idx = a_off + r * k + p;
85 let mut terms = vec![(a_idx, 1.0)];
86 for c in 0..n {
87 if self.matrix()[r][c] {
88 terms.push((x_off + c * k + p, -1.0));
89 }
90 }
91 constraints.push(LinearConstraint::eq(terms, 0.0));
92 }
93 }
94
95 for r in 0..m {
97 let beta_r: f64 = if self.matrix()[r].iter().any(|&v| v) {
101 1.0
102 } else {
103 0.0
104 };
105
106 let l_terms: Vec<(usize, f64)> = (0..k).map(|p| (l_off + r * k + p, 1.0)).collect();
108 constraints.push(LinearConstraint::eq(l_terms, beta_r));
109
110 let u_terms: Vec<(usize, f64)> = (0..k).map(|p| (u_off + r * k + p, 1.0)).collect();
112 constraints.push(LinearConstraint::eq(u_terms, beta_r));
113
114 if k > 0 {
116 let mut order_terms = Vec::new();
117 for p in 0..k {
118 order_terms.push((l_off + r * k + p, p as f64));
119 order_terms.push((u_off + r * k + p, -(p as f64)));
120 }
121 constraints.push(LinearConstraint::le(
122 order_terms,
123 (k as f64 - 1.0).max(0.0) * (1.0 - beta_r),
124 ));
125 }
126
127 for p in 0..k {
128 let h_idx = h_off + r * k + p;
129 let a_idx = a_off + r * k + p;
130 let f_idx = f_off + r * k + p;
131
132 let mut h_le_l = vec![(h_idx, 1.0)];
134 for q in 0..=p {
135 h_le_l.push((l_off + r * k + q, -1.0));
136 }
137 constraints.push(LinearConstraint::le(h_le_l, 0.0));
138
139 let mut h_le_u = vec![(h_idx, 1.0)];
141 for q in p..k {
142 h_le_u.push((u_off + r * k + q, -1.0));
143 }
144 constraints.push(LinearConstraint::le(h_le_u, 0.0));
145
146 let mut h_ge_terms = vec![(h_idx, 1.0)];
148 for q in 0..=p {
149 h_ge_terms.push((l_off + r * k + q, -1.0));
150 }
151 for q in p..k {
152 h_ge_terms.push((u_off + r * k + q, -1.0));
153 }
154 constraints.push(LinearConstraint::ge(h_ge_terms, -1.0));
155
156 constraints.push(LinearConstraint::le(vec![(a_idx, 1.0), (h_idx, -1.0)], 0.0));
158
159 constraints.push(LinearConstraint::le(
162 vec![(h_idx, 1.0), (a_idx, -1.0), (f_idx, -1.0)],
163 0.0,
164 ));
165
166 constraints.push(LinearConstraint::le(vec![(f_idx, 1.0), (h_idx, -1.0)], 0.0));
168
169 constraints.push(LinearConstraint::le(vec![(f_idx, 1.0), (a_idx, 1.0)], 1.0));
171 }
172 }
173
174 let mut flip_terms = Vec::new();
177 for r in 0..m {
178 for p in 0..k {
179 flip_terms.push((f_off + r * k + p, 1.0));
180 }
181 }
182 if !flip_terms.is_empty() {
183 constraints.push(LinearConstraint::eq(flip_terms, 0.0));
184 }
185
186 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize);
187 ReductionCOSToILP {
188 target,
189 num_cols: n,
190 }
191 }
192}
193
194#[cfg(feature = "example-db")]
195pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
196 use crate::export::SolutionPair;
197 vec![crate::example_db::specs::RuleExampleSpec {
198 id: "consecutiveonessubmatrix_to_ilp",
199 build: || {
200 let source = ConsecutiveOnesSubmatrix::new(
202 vec![
203 vec![true, true, false, true],
204 vec![true, false, true, true],
205 vec![false, true, true, false],
206 ],
207 3,
208 );
209 let reduction: ReductionCOSToILP = ReduceTo::<ILP<bool>>::reduce_to(&source);
210 let ilp_solver = crate::solvers::ILPSolver::new();
211 let target_config = ilp_solver
212 .solve(reduction.target_problem())
213 .expect("ILP should be solvable");
214 let extracted = reduction.extract_solution(&target_config);
215 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
216 source,
217 SolutionPair {
218 source_config: extracted,
219 target_config,
220 },
221 )
222 },
223 }]
224}
225
226#[cfg(test)]
227#[path = "../unit_tests/rules/consecutiveonessubmatrix_ilp.rs"]
228mod tests;