problemreductions/models/algebraic/
consecutive_block_minimization.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(try_from = "ConsecutiveBlockMinimizationDef")]
62pub struct ConsecutiveBlockMinimization {
63 matrix: Vec<Vec<bool>>,
65 num_rows: usize,
67 num_cols: usize,
69 bound: i64,
71}
72
73impl ConsecutiveBlockMinimization {
74 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 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 pub fn matrix(&self) -> &[Vec<bool>] {
100 &self.matrix
101 }
102
103 pub fn num_rows(&self) -> usize {
105 self.num_rows
106 }
107
108 pub fn num_cols(&self) -> usize {
110 self.num_cols
111 }
112
113 pub fn bound(&self) -> i64 {
115 self.bound
116 }
117
118 pub fn count_consecutive_blocks(&self, config: &[usize]) -> Option<usize> {
125 if config.len() != self.num_cols {
126 return None;
127 }
128
129 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 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;