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