problemreductions/rules/
bmf_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, BMF, ILP};
4use crate::reduction;
5use crate::rules::traits::{ReduceTo, ReductionResult};
6
7#[derive(Debug, Clone)]
8pub struct ReductionBMFToILP {
9 target: ILP<bool>,
10 m: usize,
11 n: usize,
12 k: usize,
13}
14
15impl ReductionResult for ReductionBMFToILP {
16 type Source = BMF;
17 type Target = ILP<bool>;
18
19 fn target_problem(&self) -> &ILP<bool> {
20 &self.target
21 }
22
23 fn extract_solution(
24 &self,
25 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
26 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
27 crate::rules::traits::validate_target_witness(
28 self.target_problem(),
29 target_solution,
30 |v| v.value.is_some(),
31 "target ILP assignment does not reconstruct the Boolean matrix",
32 )?;
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(transform = {
54 exact { max_constraint_magnitude_bits = "1", },
55 upper_bound {
56 num_vars = "rows * rank + rank * cols + rows * rank * cols",
57 num_constraints = "(2 * rank + 1) * rows * cols + rank * (rows + cols)",
58 num_nonzeros = "5 * rows * rank * cols + rank * (rows + cols)",
59 },
60})]
61impl ReduceTo<ILP<bool>> for BMF {
62 type Result = ReductionBMFToILP;
63
64 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
65 let m = self.rows();
66 let n = self.cols();
67 let k = self.rank();
68 let overflow = || {
69 crate::rules::ReductionError::integer_overflow::<Self, ILP<bool>>(
70 "counting Boolean factorization variables",
71 )
72 };
73 let c_offset = m.checked_mul(k).ok_or_else(overflow)?;
74 let factor_count = k
75 .checked_mul(n)
76 .and_then(|v| v.checked_add(c_offset))
77 .ok_or_else(overflow)?;
78 <Self as ReduceTo<ILP<bool>>>::exact_i64(factor_count, "bounding Boolean factor size")?;
79 let matrix = self.matrix();
83 let row_degrees: Vec<_> = matrix
84 .iter()
85 .map(|row| row.iter().filter(|&&v| v).count())
86 .collect();
87 let col_degrees: Vec<_> = (0..n)
88 .map(|j| matrix.iter().filter(|row| row[j]).count())
89 .collect();
90 let mut edges: Vec<_> = matrix
91 .iter()
92 .enumerate()
93 .flat_map(|(i, row)| {
94 row.iter()
95 .enumerate()
96 .filter_map(move |(j, &v)| v.then_some((i, j)))
97 })
98 .collect();
99 edges.sort_by_key(|&(i, j)| (row_degrees[i], col_degrees[j]));
100 let mut anchors: Vec<(usize, usize)> = Vec::new();
101 for (i, j) in edges {
102 if anchors.len() == k {
103 break;
104 }
105 if anchors.iter().all(|&(u, v)| !matrix[i][v] || !matrix[u][j]) {
106 anchors.push((i, j));
107 }
108 }
109 let mut fixed = vec![None; factor_count];
110 for (r, &(u, v)) in anchors.iter().enumerate() {
111 for i in 0..m {
112 if !matrix[i][v] {
113 fixed[i * k + r] = Some(0);
114 }
115 }
116 for j in 0..n {
117 if !matrix[u][j] {
118 fixed[c_offset + r * n + j] = Some(0);
119 }
120 }
121 fixed[u * k + r] = Some(1);
122 fixed[c_offset + r * n + v] = Some(1);
123 }
124 let mut next = factor_count;
125 let mut constraints: Vec<_> = fixed
126 .iter()
127 .enumerate()
128 .filter_map(|(index, &value)| {
129 value.map(|value| LinearConstraint::eq(vec![(index, 1)], value))
130 })
131 .collect();
132 for (i, row) in self.matrix().iter().enumerate() {
133 for (j, &value) in row.iter().enumerate() {
134 if value {
135 if (0..k).any(|r| {
136 fixed[i * k + r] == Some(1) && fixed[c_offset + r * n + j] == Some(1)
137 }) {
138 continue;
139 }
140 let mut coverage = Vec::new();
141 for r in 0..k {
142 if fixed[i * k + r] == Some(0) || fixed[c_offset + r * n + j] == Some(0) {
143 continue;
144 }
145 let bit = next;
146 next = next.checked_add(1).ok_or_else(overflow)?;
147 constraints.push(LinearConstraint::le(vec![(bit, 1), (i * k + r, -1)], 0));
148 constraints.push(LinearConstraint::le(
149 vec![(bit, 1), (c_offset + r * n + j, -1)],
150 0,
151 ));
152 coverage.push((bit, 1));
153 }
154 constraints.push(LinearConstraint::ge(coverage, 1));
155 } else {
156 for r in 0..k {
157 if fixed[i * k + r] == Some(0) || fixed[c_offset + r * n + j] == Some(0) {
158 continue;
159 }
160 constraints.push(LinearConstraint::le(
161 vec![(i * k + r, 1), (c_offset + r * n + j, 1)],
162 1,
163 ));
164 }
165 }
166 }
167 }
168 let objective = (0..factor_count).map(|i| (i, 1)).collect();
169 let target = ILP::new(next, constraints, objective, ObjectiveSense::Minimize)
170 .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
171 Ok(ReductionBMFToILP { target, m, n, k })
172 }
173}
174
175#[cfg(feature = "example-db")]
176pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
177 vec![crate::example_db::specs::RuleExampleSpec {
178 id: "bmf_to_ilp",
179 build: || {
180 let source = BMF::new(vec![vec![true, false], vec![false, true]], 2);
182 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
183 },
184 }]
185}
186
187#[cfg(test)]
188#[path = "../unit_tests/rules/bmf_ilp.rs"]
189mod tests;