Skip to main content

problemreductions/models/algebraic/
consecutive_block_minimization.rs

1//! Consecutive Block Minimization (CBM) problem implementation.
2//!
3//! Given an m x n binary matrix A and a positive integer K,
4//! determine whether there exists a permutation of the columns of A
5//! such that the resulting matrix has at most K maximal blocks of
6//! consecutive 1-entries (summed over all rows).
7//!
8//! A "block" is a maximal contiguous run of 1-entries in a row.
9//! This is problem SR17 in Garey & Johnson.
10
11use crate::registry::{FieldInfo, ProblemSchemaEntry};
12use crate::traits::Problem;
13use serde::{Deserialize, Serialize};
14
15inventory::submit! {
16    ProblemSchemaEntry {
17        name: "ConsecutiveBlockMinimization",
18        display_name: "Consecutive Block Minimization",
19        aliases: &["CBM"],
20        dimensions: &[],
21        module_path: module_path!(),
22        description: "Permute columns of a binary matrix to have at most K consecutive blocks of 1s",
23        fields: &[
24            FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "Binary matrix A (m x n)" },
25            FieldInfo { name: "bound", type_name: "i64", description: "Upper bound K on total consecutive blocks" },
26        ],
27    }
28}
29
30/// Consecutive Block Minimization (CBM) problem.
31///
32/// Given an m x n binary matrix A and a positive integer K,
33/// determine whether there exists a permutation of the columns of A
34/// such that the resulting matrix has at most K maximal blocks of
35/// consecutive 1-entries (summed over all rows).
36///
37/// # Example
38///
39/// ```
40/// use problemreductions::models::algebraic::ConsecutiveBlockMinimization;
41/// use problemreductions::{Problem, Solver, BruteForce};
42///
43/// // 2x3 binary matrix
44/// let problem = ConsecutiveBlockMinimization::new(
45///     vec![
46///         vec![true, false, true],
47///         vec![false, true, true],
48///     ],
49///     2,
50/// );
51///
52/// let solver = BruteForce::new();
53/// let solutions = solver.find_all_witnesses(&problem);
54///
55/// // Verify solutions satisfy the block bound
56/// for sol in solutions {
57///     assert!(problem.evaluate(&sol));
58/// }
59/// ```
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(try_from = "ConsecutiveBlockMinimizationDef")]
62pub struct ConsecutiveBlockMinimization {
63    /// The binary matrix A (m x n).
64    matrix: Vec<Vec<bool>>,
65    /// Number of rows (m).
66    num_rows: usize,
67    /// Number of columns (n).
68    num_cols: usize,
69    /// Upper bound K on total consecutive blocks.
70    bound: i64,
71}
72
73impl ConsecutiveBlockMinimization {
74    /// Create a new ConsecutiveBlockMinimization problem.
75    ///
76    /// # Arguments
77    /// * `matrix` - The m x n binary matrix
78    /// * `bound` - Upper bound on total consecutive blocks
79    ///
80    /// # Panics
81    /// Panics if rows have inconsistent lengths.
82    pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
83        Self::try_new(matrix, bound).unwrap_or_else(|err| panic!("{err}"))
84    }
85
86    /// Create a new ConsecutiveBlockMinimization problem, returning an error
87    /// instead of panicking when the matrix is ragged.
88    pub fn try_new(matrix: Vec<Vec<bool>>, bound: i64) -> Result<Self, String> {
89        let (num_rows, num_cols) = validate_matrix_dimensions(&matrix)?;
90        Ok(Self {
91            matrix,
92            num_rows,
93            num_cols,
94            bound,
95        })
96    }
97
98    /// Get the binary matrix.
99    pub fn matrix(&self) -> &[Vec<bool>] {
100        &self.matrix
101    }
102
103    /// Get the number of rows.
104    pub fn num_rows(&self) -> usize {
105        self.num_rows
106    }
107
108    /// Get the number of columns.
109    pub fn num_cols(&self) -> usize {
110        self.num_cols
111    }
112
113    /// Get the upper bound K.
114    pub fn bound(&self) -> i64 {
115        self.bound
116    }
117
118    /// Count the total number of maximal consecutive blocks of 1s
119    /// when columns are permuted according to `config`.
120    ///
121    /// `config[position] = column_index` defines the column permutation.
122    /// Returns `Some(total_blocks)` if the config is a valid permutation,
123    /// or `None` if it is not (wrong length, duplicate columns, or out-of-range).
124    pub fn count_consecutive_blocks(&self, config: &[usize]) -> Option<usize> {
125        if config.len() != self.num_cols {
126            return None;
127        }
128
129        // Validate permutation: all values distinct and in 0..num_cols.
130        let mut seen = vec![false; self.num_cols];
131        for &col in config {
132            if col >= self.num_cols || seen[col] {
133                return None;
134            }
135            seen[col] = true;
136        }
137
138        let mut total_blocks = 0;
139        for row in &self.matrix {
140            let mut in_block = false;
141            for &pos in config {
142                if row[pos] {
143                    if !in_block {
144                        total_blocks += 1;
145                        in_block = true;
146                    }
147                } else {
148                    in_block = false;
149                }
150            }
151        }
152
153        Some(total_blocks)
154    }
155}
156
157impl Problem for ConsecutiveBlockMinimization {
158    const NAME: &'static str = "ConsecutiveBlockMinimization";
159    type Value = crate::types::Or;
160
161    fn dims(&self) -> Vec<usize> {
162        vec![self.num_cols; self.num_cols]
163    }
164
165    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
166        crate::types::Or({
167            match self.count_consecutive_blocks(config) {
168                Some(total) => (total as i64) <= self.bound,
169                None => false,
170            }
171        })
172    }
173
174    fn variant() -> Vec<(&'static str, &'static str)> {
175        crate::variant_params![]
176    }
177
178    fn num_variables(&self) -> usize {
179        self.num_cols
180    }
181}
182
183crate::declare_variants! {
184    default ConsecutiveBlockMinimization => "factorial(num_cols) * num_rows * num_cols",
185}
186
187#[derive(Debug, Clone, Deserialize)]
188struct ConsecutiveBlockMinimizationDef {
189    matrix: Vec<Vec<bool>>,
190    num_rows: usize,
191    num_cols: usize,
192    bound: i64,
193}
194
195impl TryFrom<ConsecutiveBlockMinimizationDef> for ConsecutiveBlockMinimization {
196    type Error = String;
197
198    fn try_from(value: ConsecutiveBlockMinimizationDef) -> Result<Self, Self::Error> {
199        let problem = Self::try_new(value.matrix, value.bound)?;
200        if value.num_rows != problem.num_rows {
201            return Err(format!(
202                "num_rows must match matrix row count ({})",
203                problem.num_rows
204            ));
205        }
206        if value.num_cols != problem.num_cols {
207            return Err(format!(
208                "num_cols must match matrix column count ({})",
209                problem.num_cols
210            ));
211        }
212        Ok(problem)
213    }
214}
215
216fn validate_matrix_dimensions(matrix: &[Vec<bool>]) -> Result<(usize, usize), String> {
217    let num_rows = matrix.len();
218    let num_cols = matrix.first().map_or(0, Vec::len);
219
220    if matrix.iter().any(|row| row.len() != num_cols) {
221        return Err("all matrix rows must have the same length".to_string());
222    }
223
224    Ok((num_rows, num_cols))
225}
226
227#[cfg(feature = "example-db")]
228pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
229    // Adjacency matrix of path graph P_6, K=6 (one block per row).
230    // Issue #420 Instance 2.
231    vec![crate::example_db::specs::ModelExampleSpec {
232        id: "consecutive_block_minimization",
233        instance: Box::new(ConsecutiveBlockMinimization::new(
234            vec![
235                vec![false, true, false, false, false, false],
236                vec![true, false, true, false, false, false],
237                vec![false, true, false, true, false, false],
238                vec![false, false, true, false, true, false],
239                vec![false, false, false, true, false, true],
240                vec![false, false, false, false, true, false],
241            ],
242            6,
243        )),
244        optimal_config: vec![0, 2, 4, 1, 3, 5],
245        optimal_value: serde_json::json!(true),
246    }]
247}
248
249#[cfg(test)]
250#[path = "../../unit_tests/models/algebraic/consecutive_block_minimization.rs"]
251mod tests;