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)]
57#[serde(try_from = "BMFData")]
58pub struct BMF {
59 matrix: Vec<Vec<bool>>,
61 m: usize,
63 n: usize,
65 k: usize,
67}
68
69#[derive(Deserialize)]
70struct BMFData {
71 matrix: Vec<Vec<bool>>,
72 k: usize,
73}
74
75impl TryFrom<BMFData> for BMF {
76 type Error = crate::registry::ConstructionError;
77
78 fn try_from(data: BMFData) -> Result<Self, Self::Error> {
79 Self::try_new(data.matrix, data.k)
80 }
81}
82
83impl BMF {
84 pub fn new(matrix: Vec<Vec<bool>>, k: usize) -> Self {
90 Self::try_new(matrix, k).unwrap_or_else(|error| panic!("{error}"))
91 }
92
93 fn try_new(
94 matrix: Vec<Vec<bool>>,
95 k: usize,
96 ) -> Result<Self, crate::registry::ConstructionError> {
97 let m = matrix.len();
98 let n = matrix.first().map_or(0, Vec::len);
99 if matrix.iter().any(|row| row.len() != n) {
100 return Err("all matrix rows must have the same length".into());
101 }
102 Ok(Self { matrix, m, n, k })
103 }
104
105 pub fn rows(&self) -> usize {
107 self.m
108 }
109
110 pub fn cols(&self) -> usize {
112 self.n
113 }
114
115 pub fn rank(&self) -> usize {
117 self.k
118 }
119
120 pub fn m(&self) -> usize {
122 self.rows()
123 }
124
125 pub fn n(&self) -> usize {
127 self.cols()
128 }
129
130 pub fn matrix(&self) -> &[Vec<bool>] {
132 &self.matrix
133 }
134
135 pub fn extract_factors(
137 &self,
138 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
139 ) -> (Vec<Vec<bool>>, Vec<Vec<bool>>) {
140 solution.clone()
141 }
142
143 pub fn boolean_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
147 let m = b.len();
148 let n = if !c.is_empty() { c[0].len() } else { 0 };
149 let k = if !b.is_empty() { b[0].len() } else { 0 };
150
151 (0..m)
152 .map(|i| {
153 (0..n)
154 .map(|j| (0..k).any(|kk| b[i][kk] && c[kk][j]))
155 .collect()
156 })
157 .collect()
158 }
159
160 pub fn hamming_distance(
162 &self,
163 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
164 ) -> Result<i64, crate::traits::EvaluationError> {
165 let (b, c) = solution;
166
167 let distance = (0..self.m)
168 .map(|i| {
169 (0..self.n)
170 .filter(|&j| {
171 let product_entry = (0..self.k).any(|r| b[i][r] && c[r][j]);
172 self.matrix[i][j] != product_entry
173 })
174 .count()
175 })
176 .sum::<usize>();
177 i64::try_from(distance).map_err(|_| {
178 crate::traits::EvaluationError::IntegerOverflow(
179 "converting Boolean-matrix Hamming distance to i64".into(),
180 )
181 })
182 }
183
184 pub fn is_exact(
186 &self,
187 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
188 ) -> Result<bool, crate::traits::EvaluationError> {
189 Ok(self.hamming_distance(solution)? == 0)
190 }
191
192 pub fn total_factor_size(
194 &self,
195 solution: &(Vec<Vec<bool>>, Vec<Vec<bool>>),
196 ) -> Result<i64, crate::traits::EvaluationError> {
197 let (left, right) = solution;
198 let size = left
199 .iter()
200 .chain(right)
201 .flatten()
202 .filter(|&&value| value)
203 .count();
204 i64::try_from(size).map_err(|_| {
205 crate::traits::EvaluationError::IntegerOverflow(
206 "converting Boolean factor size to i64".into(),
207 )
208 })
209 }
210}
211
212#[cfg(test)]
214pub(crate) fn boolean_matrix_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
215 BMF::boolean_product(b, c)
216}
217
218#[cfg(test)]
220pub(crate) fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> usize {
221 a.iter()
222 .zip(b.iter())
223 .map(|(a_row, b_row)| {
224 a_row
225 .iter()
226 .zip(b_row.iter())
227 .filter(|(x, y)| x != y)
228 .count()
229 })
230 .sum()
231}
232
233impl Problem for BMF {
234 const NAME: &'static str = "BMF";
235 type Solution = (Vec<Vec<bool>>, Vec<Vec<bool>>);
236 type Value = Min<i64>;
237
238 crate::problem_parameters![("cols", cols), ("rank", rank), ("rows", rows),];
239
240 fn evaluate(
241 &self,
242 solution: &Self::Solution,
243 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
244 let (left, right) = solution;
245 if left.len() != self.m
246 || left.iter().any(|row| row.len() != self.k)
247 || right.len() != self.k
248 || right.iter().any(|row| row.len() != self.n)
249 {
250 return Err(crate::traits::EvaluationError::InvalidConfiguration(
251 "BMF factor dimensions do not match the instance".into(),
252 ));
253 }
254 Ok({
255 if self.hamming_distance(solution)? != 0 {
257 return Ok(Min(None));
258 }
259 Min(Some(self.total_factor_size(solution)?))
260 })
261 }
262
263 fn variant() -> Vec<(&'static str, &'static str)> {
264 crate::variant_params![]
265 }
266}
267
268impl crate::solvers::BruteForceProblem for BMF {
269 fn dimensions(&self) -> Vec<usize> {
270 vec![2; self.m * self.k + self.k * self.n]
272 }
273}
274
275crate::declare_variants! {
276 default BMF => "2^(rows * rank + rank * cols)",
277}
278
279crate::register_brute_force! {
280 BMF decode |problem: &BMF, indices: Vec<usize>| {
281 let split = problem.rows() * problem.rank();
282 (
283 indices[..split].chunks(problem.rank()).map(crate::config::config_to_bits).collect(),
284 indices[split..].chunks(problem.cols()).map(crate::config::config_to_bits).collect(),
285 )
286 },
287}
288
289#[cfg(feature = "example-db")]
290pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
291 vec![crate::example_db::specs::ModelExampleSpec {
292 id: "bmf",
293 instance: Box::new(BMF::new(
294 vec![
295 vec![true, true, false],
296 vec![true, true, true],
297 vec![false, true, true],
298 ],
299 2,
300 )),
301 optimal_config: serde_json::json!((
304 vec![vec![true, false], vec![true, true], vec![false, true]],
305 vec![vec![true, true, false], vec![false, true, true]]
306 )),
307 optimal_value: serde_json::json!(8),
308 }]
309}
310
311#[cfg(test)]
312#[path = "../../unit_tests/models/algebraic/bmf.rs"]
313mod tests;