Skip to main content

problemreductions/models/algebraic/
minimum_matrix_domination.rs

1//! Minimum Matrix Domination problem implementation.
2//!
3//! Given an n×n binary matrix M, find a minimum subset C of 1-entries such that
4//! every 1-entry not in C shares a row or column with some entry in C.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12    ProblemSchemaEntry {
13        name: "MinimumMatrixDomination",
14        display_name: "Minimum Matrix Domination",
15        aliases: &[],
16        dimensions: &[],
17        module_path: module_path!(),
18        description: "Find minimum subset of 1-entries in a binary matrix that dominates all other 1-entries by shared row or column",
19        fields: &[
20            FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "n×n binary matrix M" },
21        ],
22    }
23}
24
25/// Minimum Matrix Domination.
26///
27/// Given an n×n binary matrix M, find a minimum-cardinality subset C of
28/// 1-entries such that every 1-entry not in C shares a row or column with
29/// some entry in C.
30///
31/// # Representation
32///
33/// Each 1-entry in the matrix is a binary variable: `x_k = 1` if the k-th
34/// 1-entry is selected into C. The 1-entries are enumerated in row-major
35/// order.
36///
37/// # Example
38///
39/// ```
40/// use problemreductions::models::algebraic::MinimumMatrixDomination;
41/// use problemreductions::{Problem, Solver, BruteForce};
42///
43/// // 3×3 identity matrix: 3 ones on the diagonal, no shared rows/cols
44/// let matrix = vec![
45///     vec![true, false, false],
46///     vec![false, true, false],
47///     vec![false, false, true],
48/// ];
49/// let problem = MinimumMatrixDomination::new(matrix);
50/// let solver = BruteForce::new();
51/// let witness = solver.find_witness(&problem);
52/// // All 3 diagonal entries must be selected (no domination possible)
53/// assert_eq!(witness, Some(vec![1, 1, 1]));
54/// ```
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MinimumMatrixDomination {
57    /// The binary matrix.
58    matrix: Vec<Vec<bool>>,
59    /// Positions of 1-entries in row-major order: (row, col).
60    ones: Vec<(usize, usize)>,
61}
62
63impl MinimumMatrixDomination {
64    /// Create a new MinimumMatrixDomination instance.
65    ///
66    /// # Panics
67    ///
68    /// Panics if the matrix rows have inconsistent lengths.
69    pub fn new(matrix: Vec<Vec<bool>>) -> Self {
70        let num_cols = matrix.first().map_or(0, Vec::len);
71        for row in &matrix {
72            assert_eq!(row.len(), num_cols, "All rows must have the same length");
73        }
74        let ones: Vec<(usize, usize)> = matrix
75            .iter()
76            .enumerate()
77            .flat_map(|(i, row)| {
78                row.iter()
79                    .enumerate()
80                    .filter(|(_, &v)| v)
81                    .map(move |(j, _)| (i, j))
82            })
83            .collect();
84        Self { matrix, ones }
85    }
86
87    /// Returns a reference to the binary matrix.
88    pub fn matrix(&self) -> &[Vec<bool>] {
89        &self.matrix
90    }
91
92    /// Returns the positions of 1-entries in row-major order.
93    pub fn ones(&self) -> &[(usize, usize)] {
94        &self.ones
95    }
96
97    /// Returns the number of rows in the matrix.
98    pub fn num_rows(&self) -> usize {
99        self.matrix.len()
100    }
101
102    /// Returns the number of columns in the matrix.
103    pub fn num_cols(&self) -> usize {
104        self.matrix.first().map_or(0, Vec::len)
105    }
106
107    /// Returns the number of 1-entries in the matrix.
108    pub fn num_ones(&self) -> usize {
109        self.ones.len()
110    }
111}
112
113impl Problem for MinimumMatrixDomination {
114    const NAME: &'static str = "MinimumMatrixDomination";
115    type Value = Min<usize>;
116
117    fn variant() -> Vec<(&'static str, &'static str)> {
118        crate::variant_params![]
119    }
120
121    fn dims(&self) -> Vec<usize> {
122        vec![2; self.num_ones()]
123    }
124
125    fn evaluate(&self, config: &[usize]) -> Min<usize> {
126        if config.len() != self.num_ones() {
127            return Min(None);
128        }
129        if config.iter().any(|&v| v >= 2) {
130            return Min(None);
131        }
132
133        // Collect the set of selected 1-entry indices
134        let selected: Vec<usize> = config
135            .iter()
136            .enumerate()
137            .filter(|(_, &v)| v == 1)
138            .map(|(i, _)| i)
139            .collect();
140
141        // Build sets of rows and columns covered by selected entries
142        let mut covered_rows = std::collections::HashSet::new();
143        let mut covered_cols = std::collections::HashSet::new();
144        for &idx in &selected {
145            let (r, c) = self.ones[idx];
146            covered_rows.insert(r);
147            covered_cols.insert(c);
148        }
149
150        // Check domination: every unselected 1-entry must share a row or
151        // column with some selected entry
152        for (k, &(r, c)) in self.ones.iter().enumerate() {
153            if config[k] == 1 {
154                continue; // selected entries don't need domination
155            }
156            if !covered_rows.contains(&r) && !covered_cols.contains(&c) {
157                return Min(None); // not dominated
158            }
159        }
160
161        Min(Some(selected.len()))
162    }
163}
164
165crate::declare_variants! {
166    default MinimumMatrixDomination => "2^num_ones",
167}
168
169#[cfg(feature = "example-db")]
170pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
171    // P6 adjacency matrix (6×6, 10 ones)
172    // 1-entries: (0,1),(1,0),(1,2),(2,1),(2,3),(3,2),(3,4),(4,3),(4,5),(5,4)
173    // Optimal: select indices 0,1,7,6 -> C = {(0,1),(1,0),(4,3),(3,4)}, value = 4
174    let matrix = vec![
175        vec![false, true, false, false, false, false],
176        vec![true, false, true, false, false, false],
177        vec![false, true, false, true, false, false],
178        vec![false, false, true, false, true, false],
179        vec![false, false, false, true, false, true],
180        vec![false, false, false, false, true, false],
181    ];
182    vec![crate::example_db::specs::ModelExampleSpec {
183        id: "minimum_matrix_domination",
184        instance: Box::new(MinimumMatrixDomination::new(matrix)),
185        optimal_config: vec![1, 1, 0, 0, 0, 0, 1, 1, 0, 0],
186        optimal_value: serde_json::json!(4),
187    }]
188}
189
190#[cfg(test)]
191#[path = "../../unit_tests/models/algebraic/minimum_matrix_domination.rs"]
192mod tests;