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