problemreductions/models/algebraic/
bmf.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use crate::types::Min;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "BMF",
17 display_name: "BMF",
18 aliases: &[],
19 dimensions: &[],
20 category: crate::registry::ProblemCategory::Algebraic,
21 module_path: module_path!(),
22 description: "Boolean matrix factorization",
23 fields: &[
24 FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "Target boolean matrix A" },
25 FieldInfo { name: "k", type_name: "usize", description: "Factorization rank" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct BMF {
58 matrix: Vec<Vec<bool>>,
60 m: usize,
62 n: usize,
64 k: usize,
66}
67
68impl BMF {
69 pub fn new(matrix: Vec<Vec<bool>>, k: usize) -> Self {
75 let m = matrix.len();
76 let n = if m > 0 { matrix[0].len() } else { 0 };
77
78 for row in &matrix {
80 assert_eq!(row.len(), n, "All rows must have the same length");
81 }
82
83 Self { matrix, m, n, k }
84 }
85
86 pub fn rows(&self) -> usize {
88 self.m
89 }
90
91 pub fn cols(&self) -> usize {
93 self.n
94 }
95
96 pub fn rank(&self) -> usize {
98 self.k
99 }
100
101 pub fn m(&self) -> usize {
103 self.rows()
104 }
105
106 pub fn n(&self) -> usize {
108 self.cols()
109 }
110
111 pub fn matrix(&self) -> &[Vec<bool>] {
113 &self.matrix
114 }
115
116 pub fn extract_factors(
118 &self,
119 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
120 ) -> (Vec<Vec<bool>>, Vec<Vec<bool>>) {
121 solution.clone()
122 }
123
124 pub fn boolean_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
128 let m = b.len();
129 let n = if !c.is_empty() { c[0].len() } else { 0 };
130 let k = if !b.is_empty() { b[0].len() } else { 0 };
131
132 (0..m)
133 .map(|i| {
134 (0..n)
135 .map(|j| (0..k).any(|kk| b[i][kk] && c[kk][j]))
136 .collect()
137 })
138 .collect()
139 }
140
141 pub fn hamming_distance(
143 &self,
144 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
145 ) -> Result<i64, crate::traits::EvaluationError> {
146 let (b, c) = solution;
147
148 let distance = (0..self.m)
149 .map(|i| {
150 (0..self.n)
151 .filter(|&j| {
152 let product_entry = (0..self.k).any(|r| b[i][r] && c[r][j]);
153 self.matrix[i][j] != product_entry
154 })
155 .count()
156 })
157 .sum::<usize>();
158 i64::try_from(distance).map_err(|_| {
159 crate::traits::EvaluationError::IntegerOverflow(
160 "converting Boolean-matrix Hamming distance to i64".into(),
161 )
162 })
163 }
164
165 pub fn is_exact(
167 &self,
168 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
169 ) -> Result<bool, crate::traits::EvaluationError> {
170 Ok(self.hamming_distance(solution)? == 0)
171 }
172
173 pub fn total_factor_size(
175 &self,
176 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
177 ) -> Result<i64, crate::traits::EvaluationError> {
178 let (left, right) = solution;
179 let size = left
180 .iter()
181 .chain(right)
182 .flatten()
183 .filter(|&&value| value)
184 .count();
185 i64::try_from(size).map_err(|_| {
186 crate::traits::EvaluationError::IntegerOverflow(
187 "converting Boolean factor size to i64".into(),
188 )
189 })
190 }
191}
192
193#[cfg(test)]
195pub(crate) fn boolean_matrix_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
196 BMF::boolean_product(b, c)
197}
198
199#[cfg(test)]
201pub(crate) fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> usize {
202 a.iter()
203 .zip(b.iter())
204 .map(|(a_row, b_row)| {
205 a_row
206 .iter()
207 .zip(b_row.iter())
208 .filter(|(x, y)| x != y)
209 .count()
210 })
211 .sum()
212}
213
214impl Problem for BMF {
215 const NAME: &'static str = "BMF";
216 type Solution = (Vec<Vec<bool>>, Vec<Vec<bool>>);
217 type Value = Min<i64>;
218
219 crate::problem_parameters![("cols", cols), ("rank", rank), ("rows", rows),];
220
221 fn evaluate(
222 &self,
223 solution: &Self::Solution,
224 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
225 let (left, right) = solution;
226 if left.len() != self.m
227 || left.iter().any(|row| row.len() != self.k)
228 || right.len() != self.k
229 || right.iter().any(|row| row.len() != self.n)
230 {
231 return Err(crate::traits::EvaluationError::InvalidConfiguration(
232 "BMF factor dimensions do not match the instance".into(),
233 ));
234 }
235 Ok({
236 if self.hamming_distance(solution)? != 0 {
238 return Ok(Min(None));
239 }
240 Min(Some(self.total_factor_size(solution)?))
241 })
242 }
243
244 fn variant() -> Vec<(&'static str, &'static str)> {
245 crate::variant_params![]
246 }
247}
248
249impl crate::solvers::BruteForceProblem for BMF {
250 fn dimensions(&self) -> Vec<usize> {
251 vec![2; self.m * self.k + self.k * self.n]
253 }
254}
255
256crate::declare_variants! {
257 default BMF => "2^(rows * rank + rank * cols)",
258}
259
260crate::register_brute_force! {
261 BMF decode |problem: &BMF, indices: Vec<usize>| {
262 let split = problem.rows() * problem.rank();
263 (
264 indices[..split].chunks(problem.rank()).map(crate::config::config_to_bits).collect(),
265 indices[split..].chunks(problem.cols()).map(crate::config::config_to_bits).collect(),
266 )
267 },
268}
269
270#[cfg(feature = "example-db")]
271pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
272 vec![crate::example_db::specs::ModelExampleSpec {
273 id: "bmf",
274 instance: Box::new(BMF::new(
275 vec![
276 vec![true, true, false],
277 vec![true, true, true],
278 vec![false, true, true],
279 ],
280 2,
281 )),
282 optimal_config: serde_json::json!((
285 vec![vec![true, false], vec![true, true], vec![false, true]],
286 vec![vec![true, true, false], vec![false, true, true]]
287 )),
288 optimal_value: serde_json::json!(8),
289 }]
290}
291
292#[cfg(test)]
293#[path = "../../unit_tests/models/algebraic/bmf.rs"]
294mod tests;