1use crate::graph_types::SimpleGraph;
8use crate::traits::Problem;
9use crate::types::{EnergyMode, ProblemSize, SolutionSize};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct BMF {
44 matrix: Vec<Vec<bool>>,
46 m: usize,
48 n: usize,
50 k: usize,
52}
53
54impl BMF {
55 pub fn new(matrix: Vec<Vec<bool>>, k: usize) -> Self {
61 let m = matrix.len();
62 let n = if m > 0 { matrix[0].len() } else { 0 };
63
64 for row in &matrix {
66 assert_eq!(row.len(), n, "All rows must have the same length");
67 }
68
69 Self { matrix, m, n, k }
70 }
71
72 pub fn rows(&self) -> usize {
74 self.m
75 }
76
77 pub fn cols(&self) -> usize {
79 self.n
80 }
81
82 pub fn rank(&self) -> usize {
84 self.k
85 }
86
87 pub fn matrix(&self) -> &[Vec<bool>] {
89 &self.matrix
90 }
91
92 pub fn extract_factors(&self, config: &[usize]) -> (Vec<Vec<bool>>, Vec<Vec<bool>>) {
96 let b_size = self.m * self.k;
97
98 let b: Vec<Vec<bool>> = (0..self.m)
100 .map(|i| {
101 (0..self.k)
102 .map(|j| config.get(i * self.k + j).copied().unwrap_or(0) == 1)
103 .collect()
104 })
105 .collect();
106
107 let c: Vec<Vec<bool>> = (0..self.k)
109 .map(|i| {
110 (0..self.n)
111 .map(|j| config.get(b_size + i * self.n + j).copied().unwrap_or(0) == 1)
112 .collect()
113 })
114 .collect();
115
116 (b, c)
117 }
118
119 pub fn boolean_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
123 let m = b.len();
124 let n = if !c.is_empty() { c[0].len() } else { 0 };
125 let k = if !b.is_empty() { b[0].len() } else { 0 };
126
127 (0..m)
128 .map(|i| {
129 (0..n)
130 .map(|j| (0..k).any(|kk| b[i][kk] && c[kk][j]))
131 .collect()
132 })
133 .collect()
134 }
135
136 pub fn hamming_distance(&self, config: &[usize]) -> usize {
138 let (b, c) = self.extract_factors(config);
139 let product = Self::boolean_product(&b, &c);
140
141 self.matrix
142 .iter()
143 .zip(product.iter())
144 .map(|(a_row, p_row)| {
145 a_row
146 .iter()
147 .zip(p_row.iter())
148 .filter(|(a, p)| a != p)
149 .count()
150 })
151 .sum()
152 }
153
154 pub fn is_exact(&self, config: &[usize]) -> bool {
156 self.hamming_distance(config) == 0
157 }
158}
159
160impl Problem for BMF {
161 const NAME: &'static str = "BMF";
162 type GraphType = SimpleGraph;
163 type Weight = i32;
164 type Size = i32;
165
166 fn num_variables(&self) -> usize {
167 self.m * self.k + self.k * self.n
169 }
170
171 fn num_flavors(&self) -> usize {
172 2 }
174
175 fn problem_size(&self) -> ProblemSize {
176 ProblemSize::new(vec![
177 ("rows", self.m),
178 ("cols", self.n),
179 ("rank", self.k),
180 ])
181 }
182
183 fn energy_mode(&self) -> EnergyMode {
184 EnergyMode::SmallerSizeIsBetter }
186
187 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
188 let distance = self.hamming_distance(config) as i32;
189 let is_valid = distance == 0; SolutionSize::new(distance, is_valid)
191 }
192}
193
194pub fn boolean_matrix_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
196 BMF::boolean_product(b, c)
197}
198
199pub fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> usize {
201 a.iter()
202 .zip(b.iter())
203 .map(|(a_row, b_row)| {
204 a_row
205 .iter()
206 .zip(b_row.iter())
207 .filter(|(x, y)| x != y)
208 .count()
209 })
210 .sum()
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::solvers::{BruteForce, Solver};
217
218 #[test]
219 fn test_bmf_creation() {
220 let matrix = vec![vec![true, false], vec![false, true]];
221 let problem = BMF::new(matrix, 2);
222 assert_eq!(problem.rows(), 2);
223 assert_eq!(problem.cols(), 2);
224 assert_eq!(problem.rank(), 2);
225 assert_eq!(problem.num_variables(), 8); }
227
228 #[test]
229 fn test_extract_factors() {
230 let matrix = vec![vec![true]];
231 let problem = BMF::new(matrix, 1);
232 let (b, c) = problem.extract_factors(&[1, 1]);
234 assert_eq!(b, vec![vec![true]]);
235 assert_eq!(c, vec![vec![true]]);
236 }
237
238 #[test]
239 fn test_extract_factors_larger() {
240 let matrix = vec![vec![true, true], vec![true, true]];
242 let problem = BMF::new(matrix, 1);
243 let (b, c) = problem.extract_factors(&[1, 1, 1, 1]);
246 assert_eq!(b, vec![vec![true], vec![true]]);
247 assert_eq!(c, vec![vec![true, true]]);
248 }
249
250 #[test]
251 fn test_boolean_product() {
252 let b = vec![vec![true], vec![true]];
255 let c = vec![vec![true, true]];
256 let product = BMF::boolean_product(&b, &c);
257 assert_eq!(
258 product,
259 vec![vec![true, true], vec![true, true]]
260 );
261 }
262
263 #[test]
264 fn test_boolean_product_rank2() {
265 let b = vec![vec![true, false], vec![false, true]];
268 let c = vec![vec![true, false], vec![false, true]];
269 let product = BMF::boolean_product(&b, &c);
270 assert_eq!(
271 product,
272 vec![vec![true, false], vec![false, true]]
273 );
274 }
275
276 #[test]
277 fn test_hamming_distance() {
278 let matrix = vec![vec![true, false], vec![false, true]];
280 let problem = BMF::new(matrix, 2);
281
282 let config = vec![1, 0, 0, 1, 1, 0, 0, 1];
285 assert_eq!(problem.hamming_distance(&config), 0);
286
287 let config = vec![0, 0, 0, 0, 0, 0, 0, 0];
289 assert_eq!(problem.hamming_distance(&config), 2);
290 }
291
292 #[test]
293 fn test_solution_size() {
294 let matrix = vec![vec![true, false], vec![false, true]];
295 let problem = BMF::new(matrix, 2);
296
297 let config = vec![1, 0, 0, 1, 1, 0, 0, 1];
299 let sol = problem.solution_size(&config);
300 assert!(sol.is_valid);
301 assert_eq!(sol.size, 0);
302
303 let config = vec![0, 0, 0, 0, 0, 0, 0, 0];
305 let sol = problem.solution_size(&config);
306 assert!(!sol.is_valid);
307 assert_eq!(sol.size, 2);
308 }
309
310 #[test]
311 fn test_brute_force_ones() {
312 let matrix = vec![vec![true, true], vec![true, true]];
314 let problem = BMF::new(matrix, 1);
315 let solver = BruteForce::new();
316
317 let solutions = solver.find_best(&problem);
318 for sol in &solutions {
319 let sol_size = problem.solution_size(sol);
320 assert_eq!(sol_size.size, 0);
321 assert!(sol_size.is_valid);
322 }
323 }
324
325 #[test]
326 fn test_brute_force_identity() {
327 let matrix = vec![vec![true, false], vec![false, true]];
329 let problem = BMF::new(matrix, 2);
330 let solver = BruteForce::new();
331
332 let solutions = solver.find_best(&problem);
333 for sol in &solutions {
335 assert!(problem.is_exact(sol));
336 }
337 }
338
339 #[test]
340 fn test_brute_force_insufficient_rank() {
341 let matrix = vec![vec![true, false], vec![false, true]];
343 let problem = BMF::new(matrix, 1);
344 let solver = BruteForce::new().valid_only(false);
345
346 let solutions = solver.find_best(&problem);
347 let best_distance = problem.hamming_distance(&solutions[0]);
349 assert!(best_distance >= 1);
351 }
352
353 #[test]
354 fn test_boolean_matrix_product_function() {
355 let b = vec![vec![true], vec![true]];
356 let c = vec![vec![true, true]];
357 let product = boolean_matrix_product(&b, &c);
358 assert_eq!(product, vec![vec![true, true], vec![true, true]]);
359 }
360
361 #[test]
362 fn test_matrix_hamming_distance_function() {
363 let a = vec![vec![true, false], vec![false, true]];
364 let b = vec![vec![true, true], vec![true, true]];
365 assert_eq!(matrix_hamming_distance(&a, &b), 2);
366
367 let c = vec![vec![true, false], vec![false, true]];
368 assert_eq!(matrix_hamming_distance(&a, &c), 0);
369 }
370
371 #[test]
372 fn test_energy_mode() {
373 let matrix = vec![vec![true]];
374 let problem = BMF::new(matrix, 1);
375 assert!(problem.energy_mode().is_minimization());
376 }
377
378 #[test]
379 fn test_problem_size() {
380 let matrix = vec![vec![true, false, true], vec![false, true, false]];
381 let problem = BMF::new(matrix, 2);
382 let size = problem.problem_size();
383 assert_eq!(size.get("rows"), Some(2));
384 assert_eq!(size.get("cols"), Some(3));
385 assert_eq!(size.get("rank"), Some(2));
386 }
387
388 #[test]
389 fn test_empty_matrix() {
390 let matrix: Vec<Vec<bool>> = vec![];
391 let problem = BMF::new(matrix, 1);
392 assert_eq!(problem.num_variables(), 0);
393 let sol = problem.solution_size(&[]);
394 assert!(sol.is_valid);
395 assert_eq!(sol.size, 0);
396 }
397
398 #[test]
399 fn test_is_exact() {
400 let matrix = vec![vec![true]];
401 let problem = BMF::new(matrix, 1);
402 assert!(problem.is_exact(&[1, 1]));
403 assert!(!problem.is_exact(&[0, 0]));
404 }
405}