problemreductions/rules/
bmf_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, BMF, ILP};
8use crate::reduction;
9use crate::rules::ilp_helpers::mccormick_product;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11
12#[derive(Debug, Clone)]
13pub struct ReductionBMFToILP {
14 target: ILP<bool>,
15 m: usize,
16 n: usize,
17 k: usize,
18}
19
20impl ReductionResult for ReductionBMFToILP {
21 type Source = BMF;
22 type Target = ILP<bool>;
23
24 fn target_problem(&self) -> &ILP<bool> {
25 &self.target
26 }
27
28 fn extract_solution(
29 &self,
30 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
31 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
32 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
33
34 let b = (0..self.m)
35 .map(|i| {
36 (0..self.k)
37 .map(|r| target_solution[i * self.k + r] == 1)
38 .collect()
39 })
40 .collect();
41 let c_offset = self.m * self.k;
42 let c = (0..self.k)
43 .map(|r| {
44 (0..self.n)
45 .map(|j| target_solution[c_offset + r * self.n + j] == 1)
46 .collect()
47 })
48 .collect();
49 Ok((b, c))
50 }
51}
52
53#[reduction(
54 transform = exact {
55 num_vars = "rows * rank + rank * cols + rows * rank * cols + rows * cols",
56 num_constraints = "3 * rows * rank * cols + rank * rows * cols + rows * cols + rows * cols",
57 },
58 unavailable = {
59 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
60 }
61)]
62impl ReduceTo<ILP<bool>> for BMF {
63 type Result = ReductionBMFToILP;
64
65 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
66 let m = self.rows();
67 let n = self.cols();
68 let k = self.rank();
69
70 let b_offset = 0;
76 let c_offset = m * k;
77 let p_offset = m * k + k * n;
78 let w_offset = p_offset + m * k * n;
79 let num_vars = w_offset + m * n;
80
81 let mut constraints = Vec::new();
82
83 for i in 0..m {
84 for j in 0..n {
85 for r in 0..k {
86 let p_idx = p_offset + i * k * n + r * n + j;
87 let b_idx = b_offset + i * k + r;
88 let c_idx = c_offset + r * n + j;
89
90 constraints.extend(mccormick_product(p_idx, b_idx, c_idx));
92 }
93
94 let w_idx = w_offset + i * n + j;
95
96 for r in 0..k {
98 let p_idx = p_offset + i * k * n + r * n + j;
99 constraints.push(LinearConstraint::ge(vec![(w_idx, 1), (p_idx, -1)], 0));
100 }
101
102 let mut w_upper_terms = vec![(w_idx, 1)];
104 for r in 0..k {
105 let p_idx = p_offset + i * k * n + r * n + j;
106 w_upper_terms.push((p_idx, -1));
107 }
108 constraints.push(LinearConstraint::le(w_upper_terms, 0));
109
110 let a_val = if self.matrix()[i][j] { 1 } else { 0 };
112 constraints.push(LinearConstraint::eq(vec![(w_idx, 1)], a_val));
113 }
114 }
115
116 let mut objective: Vec<(usize, i64)> = (0..m * k).map(|idx| (b_offset + idx, 1)).collect();
118 objective.extend((0..k * n).map(|idx| (c_offset + idx, 1)));
119
120 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
121 .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
122 Ok(ReductionBMFToILP { target, m, n, k })
123 }
124}
125
126#[cfg(feature = "example-db")]
127pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
128 vec![crate::example_db::specs::RuleExampleSpec {
129 id: "bmf_to_ilp",
130 build: || {
131 let source = BMF::new(vec![vec![true, false], vec![false, true]], 2);
133 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
134 },
135 }]
136}
137
138#[cfg(test)]
139#[path = "../unit_tests/rules/bmf_ilp.rs"]
140mod tests;