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)]
62pub struct ConsecutiveOnesSubmatrix {
63    matrix: Vec<Vec<bool>>,
64    bound: i64,
65}
66
67impl ConsecutiveOnesSubmatrix {
68    /// Create a new ConsecutiveOnesSubmatrix instance.
69    ///
70    /// # Panics
71    ///
72    /// Panics if `bound > n`, or if rows have inconsistent lengths.
73    pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
74        let n = if matrix.is_empty() {
75            0
76        } else {
77            matrix[0].len()
78        };
79        for row in &matrix {
80            assert_eq!(row.len(), n, "All rows must have the same length");
81        }
82        assert!(
83            bound < 0 || usize::try_from(bound).is_ok_and(|bound| bound <= n),
84            "bound ({bound}) must be <= number of columns ({n})"
85        );
86        Self { matrix, bound }
87    }
88
89    /// Returns the binary matrix.
90    pub fn matrix(&self) -> &[Vec<bool>] {
91        &self.matrix
92    }
93
94    /// Returns the bound (the required number of columns).
95    pub fn bound(&self) -> i64 {
96        self.bound
97    }
98
99    /// Returns the number of rows (m).
100    pub fn num_rows(&self) -> usize {
101        self.matrix.len()
102    }
103
104    /// Returns the number of columns (n).
105    pub fn num_cols(&self) -> usize {
106        if self.matrix.is_empty() {
107            0
108        } else {
109            self.matrix[0].len()
110        }
111    }
112
113    /// Check if a given column ordering has the consecutive ones property.
114    ///
115    /// `col_order` is a permutation of K column indices.
116    fn has_c1p(&self, col_order: &[usize]) -> bool {
117        for row in &self.matrix {
118            let mut first_one = None;
119            let mut last_one = None;
120            let mut count_ones = 0;
121            for (pos, &col_idx) in col_order.iter().enumerate() {
122                if row[col_idx] {
123                    if first_one.is_none() {
124                        first_one = Some(pos);
125                    }
126                    last_one = Some(pos);
127                    count_ones += 1;
128                }
129            }
130            // Ones are consecutive iff (last - first + 1) == count
131            if count_ones > 0 {
132                let span = last_one.unwrap() - first_one.unwrap() + 1;
133                if span != count_ones {
134                    return false;
135                }
136            }
137        }
138        true
139    }
140
141    /// Check if any permutation of the given columns has C1P.
142    fn any_permutation_has_c1p(&self, cols: &[usize]) -> bool {
143        let k = cols.len();
144        if k == 0 {
145            return true;
146        }
147        let mut perm: Vec<usize> = cols.to_vec();
148        // Generate all permutations using Heap's algorithm
149        let mut c = vec![0usize; k];
150        if self.has_c1p(&perm) {
151            return true;
152        }
153        let mut i = 0;
154        while i < k {
155            if c[i] < i {
156                if i % 2 == 0 {
157                    perm.swap(0, i);
158                } else {
159                    perm.swap(c[i], i);
160                }
161                if self.has_c1p(&perm) {
162                    return true;
163                }
164                c[i] += 1;
165                i = 0;
166            } else {
167                c[i] = 0;
168                i += 1;
169            }
170        }
171        false
172    }
173}
174
175impl Problem for ConsecutiveOnesSubmatrix {
176    const NAME: &'static str = "ConsecutiveOnesSubmatrix";
177    type Solution = Vec<bool>;
178    type Value = crate::types::Or;
179
180    crate::problem_parameters![
181        ("bound", bound),
182        ("num_cols", num_cols),
183        ("num_rows", num_rows),
184    ];
185
186    fn variant() -> Vec<(&'static str, &'static str)> {
187        crate::variant_params![]
188    }
189
190    fn evaluate(
191        &self,
192        config: &Self::Solution,
193    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
194        Ok({
195            crate::types::Or({
196                if config.len() != self.num_cols() {
197                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
198                        "column-selection length does not match the matrix".into(),
199                    ));
200                }
201                // Collect selected column indices
202                let selected: Vec<usize> = config
203                    .iter()
204                    .enumerate()
205                    .filter(|(_, &v)| v)
206                    .map(|(i, _)| i)
207                    .collect();
208                if usize::try_from(self.bound) != Ok(selected.len()) {
209                    return Ok(crate::types::Or(false));
210                }
211                self.any_permutation_has_c1p(&selected)
212            })
213        })
214    }
215}
216
217impl crate::solvers::BruteForceProblem for ConsecutiveOnesSubmatrix {
218    fn dimensions(&self) -> Vec<usize> {
219        vec![2; self.num_cols()]
220    }
221}
222
223crate::declare_variants! {
224    default ConsecutiveOnesSubmatrix => "2^(num_cols) * (num_rows + num_cols)",
225}
226
227crate::register_brute_force! {
228    ConsecutiveOnesSubmatrix decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
229}
230
231#[cfg(feature = "example-db")]
232pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
233    vec![crate::example_db::specs::ModelExampleSpec {
234        id: "consecutive_ones_submatrix",
235        // Tucker matrix (3×4): full matrix lacks C1P, but K=3 works
236        // Select columns {0,1,3} (config [1,1,0,1])
237        instance: Box::new(ConsecutiveOnesSubmatrix::new(
238            vec![
239                vec![true, true, false, true],
240                vec![true, false, true, true],
241                vec![false, true, true, false],
242            ],
243            3,
244        )),
245        optimal_config: serde_json::json!(vec![true, true, false, true]),
246        optimal_value: serde_json::json!(true),
247    }]
248}
249
250#[cfg(test)]
251#[path = "../../unit_tests/models/algebraic/consecutive_ones_submatrix.rs"]
252mod tests;