Skip to main content

problemreductions/models/algebraic/
consecutive_ones_submatrix.rs

1//! Consecutive Ones Submatrix problem implementation.
2//!
3//! Given an m×n binary matrix A and an integer 0 ≤ K ≤ n, determine whether
4//! there exists a subset of K columns whose columns can be permuted so that in
5//! each row all 1's occur consecutively. The implementation treats K = 0 as the
6//! vacuous empty-submatrix case. NP-complete (Booth, 1975) via
7//! transformation from Hamiltonian Path.
8
9use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "ConsecutiveOnesSubmatrix",
16        display_name: "Consecutive Ones Submatrix",
17        aliases: &[],
18        dimensions: &[],
19        category: crate::registry::ProblemCategory::Algebraic,
20        module_path: module_path!(),
21        description: "Find K columns of a binary matrix that can be permuted to have the consecutive ones property",
22        fields: &[
23            FieldInfo { name: "matrix", type_name: "Vec<Vec<bool>>", description: "m×n binary matrix A" },
24            FieldInfo { name: "bound", type_name: "i64", description: "Required number of columns K" },
25        ],
26    }
27}
28
29/// The Consecutive Ones Submatrix problem.
30///
31/// Given an m×n binary matrix A and an integer 0 ≤ K ≤ n, determine
32/// whether there exists a subset of K columns that has the "consecutive ones
33/// property" — i.e., the columns can be permuted so that in each row all 1's
34/// occur consecutively. The implementation treats K = 0 as vacuously
35/// satisfiable.
36///
37/// # Representation
38///
39/// Each column has a binary variable: `x_j = 1` if column j is selected.
40/// The problem is satisfiable iff exactly K columns are selected and some
41/// permutation of those columns gives each row consecutive 1's. The current
42/// evaluator checks those permutations explicitly with Heap's algorithm.
43///
44/// # Example
45///
46/// ```
47/// use problemreductions::models::algebraic::ConsecutiveOnesSubmatrix;
48/// use problemreductions::{Problem, BruteForce};
49///
50/// // Tucker matrix (3×4) — full matrix does NOT have C1P, but K=3 does.
51/// let matrix = vec![
52///     vec![true, true, false, true],
53///     vec![true, false, true, true],
54///     vec![false, true, true, false],
55/// ];
56/// let problem = ConsecutiveOnesSubmatrix::new(matrix, 3);
57/// let solver = BruteForce::new();
58/// let solution = solver.solve(&problem).unwrap();
59/// assert!(solution.is_some());
60/// ```
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(try_from = "ConsecutiveOnesSubmatrixData")]
63pub struct ConsecutiveOnesSubmatrix {
64    matrix: Vec<Vec<bool>>,
65    bound: i64,
66}
67
68#[derive(Deserialize)]
69struct ConsecutiveOnesSubmatrixData {
70    matrix: Vec<Vec<bool>>,
71    bound: i64,
72}
73
74impl TryFrom<ConsecutiveOnesSubmatrixData> for ConsecutiveOnesSubmatrix {
75    type Error = crate::registry::ConstructionError;
76
77    fn try_from(data: ConsecutiveOnesSubmatrixData) -> Result<Self, Self::Error> {
78        Self::try_new(data.matrix, data.bound)
79    }
80}
81
82impl ConsecutiveOnesSubmatrix {
83    /// Create a new ConsecutiveOnesSubmatrix instance.
84    ///
85    /// # Panics
86    ///
87    /// Panics if `bound > n`, or if rows have inconsistent lengths.
88    pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
89        Self::try_new(matrix, bound).unwrap_or_else(|error| panic!("{error}"))
90    }
91
92    fn try_new(
93        matrix: Vec<Vec<bool>>,
94        bound: i64,
95    ) -> Result<Self, crate::registry::ConstructionError> {
96        let n = matrix.first().map_or(0, Vec::len);
97        if matrix.iter().any(|row| row.len() != n) {
98            return Err("all matrix rows must have the same length".into());
99        }
100        if !(bound < 0 || usize::try_from(bound).is_ok_and(|bound| bound <= n)) {
101            return Err(format!("bound ({bound}) must be <= number of columns ({n})").into());
102        }
103        Ok(Self { matrix, bound })
104    }
105
106    /// Returns the binary matrix.
107    pub fn matrix(&self) -> &[Vec<bool>] {
108        &self.matrix
109    }
110
111    /// Returns the bound (the required number of columns).
112    pub fn bound(&self) -> i64 {
113        self.bound
114    }
115
116    /// Returns the number of rows (m).
117    pub fn num_rows(&self) -> usize {
118        self.matrix.len()
119    }
120
121    /// Returns the number of columns (n).
122    pub fn num_cols(&self) -> usize {
123        if self.matrix.is_empty() {
124            0
125        } else {
126            self.matrix[0].len()
127        }
128    }
129
130    /// Check if a given column ordering has the consecutive ones property.
131    ///
132    /// `col_order` is a permutation of K column indices.
133    fn has_c1p(&self, col_order: &[usize]) -> bool {
134        for row in &self.matrix {
135            let mut first_one = None;
136            let mut last_one = None;
137            let mut count_ones = 0;
138            for (pos, &col_idx) in col_order.iter().enumerate() {
139                if row[col_idx] {
140                    if first_one.is_none() {
141                        first_one = Some(pos);
142                    }
143                    last_one = Some(pos);
144                    count_ones += 1;
145                }
146            }
147            // Ones are consecutive iff (last - first + 1) == count
148            if count_ones > 0 {
149                let span = last_one.unwrap() - first_one.unwrap() + 1;
150                if span != count_ones {
151                    return false;
152                }
153            }
154        }
155        true
156    }
157
158    /// Check if any permutation of the given columns has C1P.
159    fn any_permutation_has_c1p(&self, cols: &[usize]) -> bool {
160        let k = cols.len();
161        if k == 0 {
162            return true;
163        }
164        let mut perm: Vec<usize> = cols.to_vec();
165        // Generate all permutations using Heap's algorithm
166        let mut c = vec![0usize; k];
167        if self.has_c1p(&perm) {
168            return true;
169        }
170        let mut i = 0;
171        while i < k {
172            if c[i] < i {
173                if i % 2 == 0 {
174                    perm.swap(0, i);
175                } else {
176                    perm.swap(c[i], i);
177                }
178                if self.has_c1p(&perm) {
179                    return true;
180                }
181                c[i] += 1;
182                i = 0;
183            } else {
184                c[i] = 0;
185                i += 1;
186            }
187        }
188        false
189    }
190}
191
192impl Problem for ConsecutiveOnesSubmatrix {
193    const NAME: &'static str = "ConsecutiveOnesSubmatrix";
194    type Solution = Vec<bool>;
195    type Value = crate::types::Or;
196
197    crate::problem_parameters![
198        ("bound", bound),
199        ("num_cols", num_cols),
200        ("num_rows", num_rows),
201    ];
202
203    fn variant() -> Vec<(&'static str, &'static str)> {
204        crate::variant_params![]
205    }
206
207    fn evaluate(
208        &self,
209        config: &Self::Solution,
210    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
211        Ok({
212            crate::types::Or({
213                if config.len() != self.num_cols() {
214                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
215                        "column-selection length does not match the matrix".into(),
216                    ));
217                }
218                // Collect selected column indices
219                let selected: Vec<usize> = config
220                    .iter()
221                    .enumerate()
222                    .filter(|(_, &v)| v)
223                    .map(|(i, _)| i)
224                    .collect();
225                if usize::try_from(self.bound) != Ok(selected.len()) {
226                    return Ok(crate::types::Or(false));
227                }
228                self.any_permutation_has_c1p(&selected)
229            })
230        })
231    }
232}
233
234impl crate::solvers::BruteForceProblem for ConsecutiveOnesSubmatrix {
235    fn dimensions(&self) -> Vec<usize> {
236        vec![2; self.num_cols()]
237    }
238}
239
240crate::declare_variants! {
241    default ConsecutiveOnesSubmatrix => "2^(num_cols) * (num_rows + num_cols)",
242}
243
244crate::register_brute_force! {
245    ConsecutiveOnesSubmatrix decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
246}
247
248#[cfg(feature = "example-db")]
249pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
250    vec![crate::example_db::specs::ModelExampleSpec {
251        id: "consecutive_ones_submatrix",
252        // Tucker matrix (3×4): full matrix lacks C1P, but K=3 works
253        // Select columns {0,1,3} (config [1,1,0,1])
254        instance: Box::new(ConsecutiveOnesSubmatrix::new(
255            vec![
256                vec![true, true, false, true],
257                vec![true, false, true, true],
258                vec![false, true, true, false],
259            ],
260            3,
261        )),
262        optimal_config: serde_json::json!(vec![true, true, false, true]),
263        optimal_value: serde_json::json!(true),
264    }]
265}
266
267#[cfg(test)]
268#[path = "../../unit_tests/models/algebraic/consecutive_ones_submatrix.rs"]
269mod tests;