problemreductions/rules/
ilp_i32_ilp_bool.rs1use crate::models::algebraic::{Comparison, LinearConstraint, ILP};
8use crate::reduction;
9use crate::rules::traits::{ReduceTo, ReductionResult};
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum FbbtError {
14 Unbounded,
16 Infeasible,
18}
19
20#[derive(Debug, Clone)]
22struct VarEncoding {
23 start: usize,
25 weights: Vec<i64>,
27}
28
29fn fbbt(num_vars: usize, constraints: &[LinearConstraint]) -> Result<Vec<i64>, FbbtError> {
34 const INF: i64 = i64::MAX / 2; let mut lower = vec![0i64; num_vars];
37 let mut upper = vec![INF; num_vars];
38
39 let max_iters = num_vars + 1;
40
41 for _ in 0..max_iters {
42 let mut changed = false;
43
44 for c in constraints {
45 let mut act_min: i64 = 0;
47 let mut act_max: i64 = 0;
48 let mut act_min_finite = true;
49 let mut act_max_finite = true;
50
51 for &(var, coef) in &c.terms {
52 let coef_i = coef as i64; if coef_i > 0 {
54 act_min = act_min.saturating_add(coef_i.saturating_mul(lower[var]));
55 if upper[var] >= INF {
56 act_max_finite = false;
57 } else {
58 act_max = act_max.saturating_add(coef_i.saturating_mul(upper[var]));
59 }
60 } else if coef_i < 0 {
61 if upper[var] >= INF {
62 act_min_finite = false;
63 } else {
64 act_min = act_min.saturating_add(coef_i.saturating_mul(upper[var]));
65 }
66 act_max = act_max.saturating_add(coef_i.saturating_mul(lower[var]));
67 }
68 }
69
70 let rhs = c.rhs as i64;
71
72 if matches!(c.cmp, Comparison::Le | Comparison::Eq) && act_min_finite && act_min > rhs {
74 return Err(FbbtError::Infeasible);
75 }
76 if matches!(c.cmp, Comparison::Ge | Comparison::Eq) && act_max_finite && act_max < rhs {
77 return Err(FbbtError::Infeasible);
78 }
79
80 for &(var, coef) in &c.terms {
82 let coef_i = coef as i64;
83 if coef_i == 0 {
84 continue;
85 }
86
87 if matches!(c.cmp, Comparison::Le | Comparison::Eq) {
89 let my_min = if coef_i > 0 {
91 coef_i.saturating_mul(lower[var])
92 } else {
93 if upper[var] >= INF {
94 continue; }
96 coef_i.saturating_mul(upper[var])
97 };
98 if !(act_min_finite || coef_i < 0 && upper[var] >= INF) {
99 continue;
101 }
102 let res_min = if act_min_finite {
103 act_min - my_min
104 } else {
105 continue;
109 };
110
111 if coef_i > 0 {
112 let new_u = floor_div(rhs - res_min, coef_i);
114 if new_u < upper[var] {
115 upper[var] = new_u;
116 changed = true;
117 }
118 } else {
119 let new_l = ceil_div(rhs - res_min, coef_i);
121 if new_l > lower[var] {
122 lower[var] = new_l;
123 changed = true;
124 }
125 }
126 }
127
128 if matches!(c.cmp, Comparison::Ge | Comparison::Eq) {
130 let my_max = if coef_i > 0 {
131 if upper[var] >= INF {
132 continue;
133 }
134 coef_i.saturating_mul(upper[var])
135 } else {
136 coef_i.saturating_mul(lower[var])
137 };
138 if !(act_max_finite || coef_i > 0 && upper[var] >= INF) {
139 continue;
140 }
141 let res_max = if act_max_finite {
142 act_max - my_max
143 } else {
144 continue;
145 };
146
147 if coef_i > 0 {
148 let new_l = ceil_div(rhs - res_max, coef_i);
150 if new_l > lower[var] {
151 lower[var] = new_l;
152 changed = true;
153 }
154 } else {
155 let new_u = floor_div(rhs - res_max, coef_i);
157 if new_u < upper[var] {
158 upper[var] = new_u;
159 changed = true;
160 }
161 }
162 }
163
164 if lower[var] > upper[var] {
165 return Err(FbbtError::Infeasible);
166 }
167 }
168 }
169
170 if !changed {
171 break;
172 }
173 }
174
175 for &u in &upper {
177 if u >= INF {
178 return Err(FbbtError::Unbounded);
179 }
180 }
181
182 Ok(upper)
183}
184
185fn floor_div(a: i64, b: i64) -> i64 {
187 let d = a / b;
188 let r = a % b;
189 if (r != 0) && ((r ^ b) < 0) {
190 d - 1
191 } else {
192 d
193 }
194}
195
196fn ceil_div(a: i64, b: i64) -> i64 {
198 let d = a / b;
199 let r = a % b;
200 if (r != 0) && ((r ^ b) >= 0) {
201 d + 1
202 } else {
203 d
204 }
205}
206
207fn binary_weights(upper_bound: i64) -> Vec<i64> {
211 if upper_bound == 0 {
212 return vec![]; }
214 let k = num_bits(upper_bound);
215 let mut weights = Vec::with_capacity(k);
216 for j in 0..(k - 1) {
217 weights.push(1i64 << j);
218 }
219 let last = upper_bound - ((1i64 << (k - 1)) - 1);
221 weights.push(last);
222 weights
223}
224
225fn num_bits(upper_bound: i64) -> usize {
227 if upper_bound <= 0 {
228 return 0;
229 }
230 64 - (upper_bound as u64).leading_zeros() as usize
232}
233
234#[derive(Debug, Clone)]
236pub struct ReductionIntILPToBinaryILP {
237 target: ILP<bool>,
238 encodings: Vec<VarEncoding>,
240}
241
242impl ReductionResult for ReductionIntILPToBinaryILP {
243 type Source = ILP<i32>;
244 type Target = ILP<bool>;
245
246 fn target_problem(&self) -> &ILP<bool> {
247 &self.target
248 }
249
250 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
251 self.encodings
252 .iter()
253 .map(|enc| {
254 let val: i64 = enc
255 .weights
256 .iter()
257 .enumerate()
258 .map(|(j, &w)| w * target_solution[enc.start + j] as i64)
259 .sum();
260 val as usize
261 })
262 .collect()
263 }
264}
265
266#[reduction(overhead = {
267 num_vars = "31 * num_variables",
268 num_constraints = "num_constraints",
269})]
270impl ReduceTo<ILP<bool>> for ILP<i32> {
271 type Result = ReductionIntILPToBinaryILP;
272
273 fn reduce_to(&self) -> Self::Result {
274 if self.num_vars == 0 {
275 return ReductionIntILPToBinaryILP {
276 target: ILP::<bool>::new(0, vec![], vec![], self.sense),
277 encodings: vec![],
278 };
279 }
280
281 let upper_bounds = match fbbt(self.num_vars, &self.constraints) {
283 Ok(bounds) => bounds,
284 Err(FbbtError::Infeasible) => {
285 return ReductionIntILPToBinaryILP {
287 target: ILP::<bool>::new(
288 1,
289 vec![
290 LinearConstraint::ge(vec![(0, 1.0)], 1.0),
291 LinearConstraint::le(vec![(0, 1.0)], 0.0),
292 ],
293 vec![],
294 self.sense,
295 ),
296 encodings: (0..self.num_vars)
297 .map(|_| VarEncoding {
298 start: 0,
299 weights: vec![],
300 })
301 .collect(),
302 };
303 }
304 Err(FbbtError::Unbounded) => {
305 vec![(1i64 << 31) - 1; self.num_vars]
307 }
308 };
309
310 let mut encodings = Vec::with_capacity(self.num_vars);
312 let mut total_bool_vars = 0;
313 for &u in &upper_bounds {
314 let weights = binary_weights(u);
315 encodings.push(VarEncoding {
316 start: total_bool_vars,
317 weights: weights.clone(),
318 });
319 total_bool_vars += weights.len();
320 }
321
322 let constraints = self
324 .constraints
325 .iter()
326 .map(|c| {
327 let mut new_terms = Vec::new();
328 for &(var, coef) in &c.terms {
329 let enc = &encodings[var];
330 for (j, &w) in enc.weights.iter().enumerate() {
331 new_terms.push((enc.start + j, coef * w as f64));
332 }
333 }
334 LinearConstraint::new(new_terms, c.cmp, c.rhs)
335 })
336 .collect();
337
338 let mut new_objective = Vec::new();
340 for &(var, coef) in &self.objective {
341 let enc = &encodings[var];
342 for (j, &w) in enc.weights.iter().enumerate() {
343 new_objective.push((enc.start + j, coef * w as f64));
344 }
345 }
346
347 ReductionIntILPToBinaryILP {
348 target: ILP::<bool>::new(total_bool_vars, constraints, new_objective, self.sense),
349 encodings,
350 }
351 }
352}
353
354#[cfg(test)]
355#[path = "../unit_tests/rules/ilp_i32_ilp_bool.rs"]
356mod tests;