problemreductions/rules/
factoring_ilp.rs1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
23use crate::models::misc::Factoring;
24use crate::reduction;
25use crate::rules::ilp_helpers::mccormick_product;
26use crate::rules::traits::{ReduceTo, ReductionResult};
27use std::cmp::min;
28
29#[derive(Debug, Clone)]
36pub struct ReductionFactoringToILP {
37 target: ILP<i64, i64, Bounded>,
38 m: usize, n: usize, }
41
42impl ReductionFactoringToILP {
43 fn p_var(&self, i: usize) -> usize {
45 i
46 }
47
48 fn q_var(&self, j: usize) -> usize {
50 self.m + j
51 }
52
53 #[cfg(test)]
55 fn z_var(&self, i: usize, j: usize) -> usize {
56 self.m + self.n + i * self.n + j
57 }
58
59 #[cfg(test)]
61 fn carry_var(&self, k: usize) -> usize {
62 self.m + self.n + self.m * self.n + k
63 }
64}
65
66impl ReductionResult for ReductionFactoringToILP {
67 type Source = Factoring;
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(
80 &self,
81 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
82 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
83 crate::rules::traits::validate_target_witness(
84 self.target_problem(),
85 target_solution,
86 |value| value.value.is_some(),
87 "target ILP assignment is infeasible",
88 )?;
89
90 Ok({
91 let p = (0..self.m)
93 .filter(|&i| target_solution[self.p_var(i)] == 1)
94 .fold(num_bigint::BigUint::from(0u8), |value, index| {
95 value + (num_bigint::BigUint::from(1u8) << index)
96 });
97
98 let q = (0..self.n)
100 .filter(|&j| target_solution[self.q_var(j)] == 1)
101 .fold(num_bigint::BigUint::from(0u8), |value, index| {
102 value + (num_bigint::BigUint::from(1u8) << index)
103 });
104 if p <= q {
105 (p, q)
106 } else {
107 (q, p)
108 }
109 })
110 }
111}
112
113#[crate::aggregate_reduction(ilp_feasibility)]
114impl crate::rules::AggregateReductionResult for ReductionFactoringToILP {}
115
116#[reduction(transform = upper_bound {
120 max_constraint_magnitude_bits = "num_bits_first + num_bits_second + 2",
121 num_vars = "num_bits_first * num_bits_second + 2 * num_bits_first + 2 * num_bits_second + target_bits",
122 num_constraints = "3 * num_bits_first * num_bits_second + 4 * num_bits_first + 4 * num_bits_second + 3 * target_bits + 1",
123 num_nonzeros = "8 * num_bits_first * num_bits_second + 5 * num_bits_first + 5 * num_bits_second + 4 * target_bits",
124})]
125impl ReduceTo<ILP<i64, i64, Bounded>> for Factoring {
126 type Result = ReductionFactoringToILP;
127
128 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
129 let m = self.m();
130 let n = self.n();
131 let target = self.target();
132
133 let target_bits = self.target_bits();
135
136 let num_bit_positions = std::cmp::max(m + n, target_bits);
141
142 let num_p = m;
144 let num_q = n;
145 let num_z = m * n;
146 let num_carries = num_bit_positions;
147 let num_vars = num_p + num_q + num_z + num_carries;
148
149 let p_var = |i: usize| -> usize { i };
151 let q_var = |j: usize| -> usize { m + j };
152 let z_var = |i: usize, j: usize| -> usize { m + n + i * n + j };
153 let carry_var = |k: usize| -> usize { m + n + m * n + k };
154
155 let mut constraints = Vec::new();
156
157 for i in 0..m {
163 for j in 0..n {
164 let z = z_var(i, j);
165 let p = p_var(i);
166 let q = q_var(j);
167
168 constraints.extend(mccormick_product(z, p, q));
169 }
170 }
171
172 for k in 0..num_bit_positions {
177 let mut terms: Vec<(usize, i64)> = Vec::new();
178
179 for i in 0..m {
181 if k >= i && k - i < n {
182 let j = k - i;
183 terms.push((z_var(i, j), 1));
184 }
185 }
186
187 if k > 0 {
189 terms.push((carry_var(k - 1), 1));
190 }
191
192 terms.push((carry_var(k), -2));
194
195 let n_k = i64::from(target.bit(u64::try_from(k).expect("bit index fits u64")));
197 constraints.push(LinearConstraint::eq(terms, n_k));
198 }
199
200 constraints.push(LinearConstraint::eq(
202 vec![(carry_var(num_bit_positions - 1), 1)],
203 0,
204 ));
205
206 for i in 0..m {
208 constraints.push(LinearConstraint::le(vec![(p_var(i), 1)], 1));
209 }
210 for j in 0..n {
211 constraints.push(LinearConstraint::le(vec![(q_var(j), 1)], 1));
212 }
213
214 let carry_upper = <Self as ReduceTo<ILP<i64, i64, Bounded>>>::exact_i64(
216 min(m, n),
217 "encoding a carry bound",
218 )?;
219 for k in 0..num_carries {
220 let cv = carry_var(k);
221 constraints.push(LinearConstraint::ge(vec![(cv, 1)], 0));
222 constraints.push(LinearConstraint::le(vec![(cv, 1)], carry_upper));
223 }
224
225 let objective: Vec<(usize, i64)> = vec![];
227
228 let mut variables = vec![IntegerVariable::binary(); num_vars];
229 variables[num_p + num_q + num_z..].fill(
230 IntegerVariable::new(Some(0), Some(carry_upper))
231 .map_err(<Self as ReduceTo<ILP<i64, i64, Bounded>>>::target_construction)?,
232 );
233
234 let ilp = ILP::<i64, i64, Bounded>::with_variables(
235 variables,
236 constraints,
237 objective,
238 ObjectiveSense::Minimize,
239 )
240 .map_err(<Self as ReduceTo<ILP<i64, i64, Bounded>>>::target_construction)?;
241
242 Ok(ReductionFactoringToILP { target: ilp, m, n })
243 }
244}
245
246#[cfg(feature = "example-db")]
247pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
248 vec![crate::example_db::specs::RuleExampleSpec {
249 id: "factoring_to_ilp",
250 build: || {
251 let source = Factoring::with_factor_bits(35, 3, 3);
252 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
253 },
254 }]
255}
256
257#[cfg(test)]
258#[path = "../unit_tests/rules/factoring_ilp.rs"]
259mod tests;