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 module_path: module_path!(),
21 description: "Boolean matrix factorization",
22 fields: &[
23 FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "Target boolean matrix A" },
24 FieldInfo { name: "k", type_name: "usize", description: "Factorization rank" },
25 ],
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct BMF {
57 matrix: Vec<Vec<bool>>,
59 m: usize,
61 n: usize,
63 k: usize,
65}
66
67impl BMF {
68 pub fn new(matrix: Vec<Vec<bool>>, k: usize) -> Self {
74 let m = matrix.len();
75 let n = if m > 0 { matrix[0].len() } else { 0 };
76
77 for row in &matrix {
79 assert_eq!(row.len(), n, "All rows must have the same length");
80 }
81
82 Self { matrix, m, n, k }
83 }
84
85 pub fn rows(&self) -> usize {
87 self.m
88 }
89
90 pub fn cols(&self) -> usize {
92 self.n
93 }
94
95 pub fn rank(&self) -> usize {
97 self.k
98 }
99
100 pub fn m(&self) -> usize {
102 self.rows()
103 }
104
105 pub fn n(&self) -> usize {
107 self.cols()
108 }
109
110 pub fn matrix(&self) -> &[Vec<bool>] {
112 &self.matrix
113 }
114
115 pub fn extract_factors(&self, config: &[usize]) -> (Vec<Vec<bool>>, Vec<Vec<bool>>) {
119 let b_size = self.m * self.k;
120
121 let b: Vec<Vec<bool>> = (0..self.m)
123 .map(|i| {
124 (0..self.k)
125 .map(|j| config.get(i * self.k + j).copied().unwrap_or(0) == 1)
126 .collect()
127 })
128 .collect();
129
130 let c: Vec<Vec<bool>> = (0..self.k)
132 .map(|i| {
133 (0..self.n)
134 .map(|j| config.get(b_size + i * self.n + j).copied().unwrap_or(0) == 1)
135 .collect()
136 })
137 .collect();
138
139 (b, c)
140 }
141
142 pub fn boolean_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
146 let m = b.len();
147 let n = if !c.is_empty() { c[0].len() } else { 0 };
148 let k = if !b.is_empty() { b[0].len() } else { 0 };
149
150 (0..m)
151 .map(|i| {
152 (0..n)
153 .map(|j| (0..k).any(|kk| b[i][kk] && c[kk][j]))
154 .collect()
155 })
156 .collect()
157 }
158
159 pub fn hamming_distance(&self, config: &[usize]) -> usize {
161 let (b, c) = self.extract_factors(config);
162
163 (0..self.m)
164 .map(|i| {
165 (0..self.n)
166 .filter(|&j| {
167 let product_entry = (0..self.k).any(|r| b[i][r] && c[r][j]);
168 self.matrix[i][j] != product_entry
169 })
170 .count()
171 })
172 .sum()
173 }
174
175 pub fn is_exact(&self, config: &[usize]) -> bool {
177 self.hamming_distance(config) == 0
178 }
179
180 pub fn total_factor_size(&self, config: &[usize]) -> usize {
182 config.iter().filter(|&&x| x == 1).count()
183 }
184}
185
186#[cfg(test)]
188pub(crate) fn boolean_matrix_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
189 BMF::boolean_product(b, c)
190}
191
192#[cfg(test)]
194pub(crate) fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> usize {
195 a.iter()
196 .zip(b.iter())
197 .map(|(a_row, b_row)| {
198 a_row
199 .iter()
200 .zip(b_row.iter())
201 .filter(|(x, y)| x != y)
202 .count()
203 })
204 .sum()
205}
206
207impl Problem for BMF {
208 const NAME: &'static str = "BMF";
209 type Value = Min<i32>;
210
211 fn dims(&self) -> Vec<usize> {
212 vec![2; self.m * self.k + self.k * self.n]
214 }
215
216 fn evaluate(&self, config: &[usize]) -> Min<i32> {
217 if self.hamming_distance(config) != 0 {
219 return Min(None);
220 }
221 Min(Some(self.total_factor_size(config) as i32))
222 }
223
224 fn variant() -> Vec<(&'static str, &'static str)> {
225 crate::variant_params![]
226 }
227}
228
229crate::declare_variants! {
230 default BMF => "2^(rows * rank + rank * cols)",
231}
232
233#[cfg(feature = "example-db")]
234pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
235 vec![crate::example_db::specs::ModelExampleSpec {
236 id: "bmf",
237 instance: Box::new(BMF::new(
238 vec![
239 vec![true, true, false],
240 vec![true, true, true],
241 vec![false, true, true],
242 ],
243 2,
244 )),
245 optimal_config: vec![1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1],
248 optimal_value: serde_json::json!(8),
249 }]
250}
251
252#[cfg(test)]
253#[path = "../../unit_tests/models/algebraic/bmf.rs"]
254mod tests;