Skip to main content

problemreductions/models/algebraic/
minimum_matrix_cover.rs

1//! Minimum Matrix Cover problem implementation.
2//!
3//! Given an n×n nonnegative integer matrix A, find a sign assignment
4//! f: {1,...,n} → {-1,+1} minimizing Σ a_ij · f(i) · f(j).
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: "MinimumMatrixCover",
14        display_name: "Minimum Matrix Cover",
15        aliases: &[],
16        dimensions: &[],
17        category: crate::registry::ProblemCategory::Algebraic,
18        module_path: module_path!(),
19        description: "Find sign assignment minimizing quadratic form over nonnegative integer matrix",
20        fields: &[
21            FieldInfo { name: "matrix", type_name: "Vec<Vec<i64>>", description: "n×n nonnegative integer matrix" },
22        ],
23    }
24}
25
26/// Minimum Matrix Cover.
27///
28/// Given an n×n nonnegative integer matrix A, find a function
29/// f: {1,...,n} → {-1,+1} that minimizes the quadratic form:
30///
31/// Σ_{i,j} a_ij · f(i) · f(j)
32///
33/// Each binary variable x_i ∈ {0,1} maps to a sign: f(i) = 2·x_i - 1
34/// (0 → -1, 1 → +1).
35///
36/// # Example
37///
38/// ```
39/// use problemreductions::models::algebraic::MinimumMatrixCover;
40/// use problemreductions::{Problem, BruteForce};
41///
42/// let problem = MinimumMatrixCover::new(vec![
43///     vec![0, 3, 1, 0],
44///     vec![3, 0, 0, 2],
45///     vec![1, 0, 0, 4],
46///     vec![0, 2, 4, 0],
47/// ]);
48///
49/// let solver = BruteForce::new();
50/// let witness = solver.solve(&problem).unwrap();
51/// assert!(witness.is_some());
52/// ```
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(try_from = "Data")]
55pub struct MinimumMatrixCover {
56    /// The n×n nonnegative integer matrix.
57    matrix: Vec<Vec<i64>>,
58}
59
60#[derive(Deserialize)]
61struct Data {
62    matrix: Vec<Vec<i64>>,
63}
64
65impl TryFrom<Data> for MinimumMatrixCover {
66    type Error = crate::registry::ConstructionError;
67
68    fn try_from(data: Data) -> Result<Self, Self::Error> {
69        Self::try_new(data.matrix)
70    }
71}
72
73impl MinimumMatrixCover {
74    /// Create a new MinimumMatrixCover instance.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the matrix is not square or contains a negative entry.
79    pub fn new(matrix: Vec<Vec<i64>>) -> Self {
80        Self::try_new(matrix).unwrap_or_else(|error| panic!("{error}"))
81    }
82
83    fn try_new(matrix: Vec<Vec<i64>>) -> Result<Self, crate::registry::ConstructionError> {
84        let n = matrix.len();
85        for (i, row) in matrix.iter().enumerate() {
86            if row.len() != n {
87                return Err(format!(
88                    "Matrix must be square: row {i} has {} columns, expected {n}",
89                    row.len()
90                )
91                .into());
92            }
93            if row.iter().any(|&entry| entry < 0) {
94                return Err(format!("matrix row {i} contains a negative entry").into());
95            }
96        }
97        Ok(Self { matrix })
98    }
99
100    /// Returns the number of rows (= columns) of the matrix.
101    pub fn num_rows(&self) -> usize {
102        self.matrix.len()
103    }
104
105    /// Returns a reference to the matrix.
106    pub fn matrix(&self) -> &[Vec<i64>] {
107        &self.matrix
108    }
109}
110
111impl Problem for MinimumMatrixCover {
112    const NAME: &'static str = "MinimumMatrixCover";
113    type Solution = Vec<bool>;
114    type Value = Min<i64>;
115
116    crate::problem_parameters![("num_rows", num_rows),];
117
118    fn variant() -> Vec<(&'static str, &'static str)> {
119        crate::variant_params![]
120    }
121
122    fn evaluate(
123        &self,
124        config: &Self::Solution,
125    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
126        Ok({
127            let n = self.num_rows();
128            if config.len() != n {
129                return Err(crate::traits::EvaluationError::InvalidConfiguration(
130                    "row-sign assignment length does not match the matrix".into(),
131                ));
132            }
133            // Map config to signs: 0 → -1, 1 → +1
134            let signs: Vec<i64> = config
135                .iter()
136                .map(|&value| if value { 1 } else { -1 })
137                .collect();
138
139            // Compute Σ_{i,j} a_ij * f(i) * f(j)
140            let mut value: i64 = 0;
141            for i in 0..n {
142                for j in 0..n {
143                    let term = self.matrix[i][j]
144                        .checked_mul(signs[i])
145                        .and_then(|term| term.checked_mul(signs[j]))
146                        .ok_or_else(|| {
147                            crate::traits::EvaluationError::IntegerOverflow(
148                                "multiplying matrix-cover objective term".into(),
149                            )
150                        })?;
151                    value = value.checked_add(term).ok_or_else(|| {
152                        crate::traits::EvaluationError::IntegerOverflow(
153                            "summing matrix-cover objective".into(),
154                        )
155                    })?;
156                }
157            }
158
159            Min(Some(value))
160        })
161    }
162}
163
164impl crate::solvers::BruteForceProblem for MinimumMatrixCover {
165    fn dimensions(&self) -> Vec<usize> {
166        vec![2; self.num_rows()]
167    }
168}
169
170crate::declare_variants! {
171    default MinimumMatrixCover => "2^num_rows",
172}
173
174crate::register_brute_force! {
175    MinimumMatrixCover decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
176}
177
178#[cfg(feature = "example-db")]
179pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
180    // 4×4 symmetric matrix with zero diagonal
181    // Config [0,1,1,0] → f=(-1,+1,+1,-1) → value = -20
182    vec![crate::example_db::specs::ModelExampleSpec {
183        id: "minimum_matrix_cover",
184        instance: Box::new(MinimumMatrixCover::new(vec![
185            vec![0, 3, 1, 0],
186            vec![3, 0, 0, 2],
187            vec![1, 0, 0, 4],
188            vec![0, 2, 4, 0],
189        ])),
190        optimal_config: serde_json::json!(vec![false, true, true, false]),
191        optimal_value: serde_json::json!(-20),
192    }]
193}
194
195#[cfg(test)]
196#[path = "../../unit_tests/models/algebraic/minimum_matrix_cover.rs"]
197mod tests;