problemreductions/models/algebraic/
consecutive_block_minimization.rs1use crate::registry::{CreateSpec, 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 category: crate::registry::ProblemCategory::Algebraic,
22 module_path: module_path!(),
23 description: "Permute columns of a binary matrix to have at most K consecutive blocks of 1s",
24 fields: ConsecutiveBlockMinimizationCreateSpec::FIELDS,
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(
60 try_from = "ConsecutiveBlockMinimizationDef",
61 into = "ConsecutiveBlockMinimizationDef"
62)]
63pub struct ConsecutiveBlockMinimization {
64 matrix: Vec<Vec<bool>>,
66 num_rows: usize,
68 num_cols: usize,
70 bound: i64,
72}
73
74#[derive(Debug, Deserialize, crate::CreateSpec)]
75struct ConsecutiveBlockMinimizationCreateSpec {
76 matrix: Vec<Vec<bool>>,
78 bound_k: i64,
80}
81
82impl TryFrom<ConsecutiveBlockMinimizationCreateSpec> for ConsecutiveBlockMinimization {
83 type Error = crate::registry::ConstructionError;
84
85 fn try_from(spec: ConsecutiveBlockMinimizationCreateSpec) -> Result<Self, Self::Error> {
86 Self::try_new(spec.matrix, spec.bound_k)
87 }
88}
89
90impl ConsecutiveBlockMinimization {
91 pub fn new(matrix: Vec<Vec<bool>>, bound: i64) -> Self {
100 Self::try_new(matrix, bound).unwrap_or_else(|err| panic!("{err}"))
101 }
102
103 pub fn try_new(
106 matrix: Vec<Vec<bool>>,
107 bound: i64,
108 ) -> Result<Self, crate::registry::ConstructionError> {
109 let (num_rows, num_cols) = validate_matrix_dimensions(&matrix)?;
110 Ok(Self {
111 matrix,
112 num_rows,
113 num_cols,
114 bound,
115 })
116 }
117
118 pub fn matrix(&self) -> &[Vec<bool>] {
120 &self.matrix
121 }
122
123 pub fn num_rows(&self) -> usize {
125 self.num_rows
126 }
127
128 pub fn num_cols(&self) -> usize {
130 self.num_cols
131 }
132
133 pub fn bound(&self) -> i64 {
135 self.bound
136 }
137
138 pub fn count_consecutive_blocks(
145 &self,
146 config: &[usize],
147 ) -> Result<Option<i64>, crate::traits::EvaluationError> {
148 if config.len() != self.num_cols {
149 return Ok(None);
150 }
151
152 let mut seen = vec![false; self.num_cols];
154 for &col in config {
155 if col >= self.num_cols || seen[col] {
156 return Ok(None);
157 }
158 seen[col] = true;
159 }
160
161 let mut total_blocks = 0usize;
162 for row in &self.matrix {
163 let mut in_block = false;
164 for &pos in config {
165 if row[pos] {
166 if !in_block {
167 total_blocks = total_blocks.checked_add(1).ok_or_else(|| {
168 crate::traits::EvaluationError::IntegerOverflow(
169 "counting consecutive blocks".into(),
170 )
171 })?;
172 in_block = true;
173 }
174 } else {
175 in_block = false;
176 }
177 }
178 }
179
180 Ok(Some(i64::try_from(total_blocks).map_err(|_| {
181 crate::traits::EvaluationError::IntegerOverflow(
182 "converting consecutive-block count to i64".into(),
183 )
184 })?))
185 }
186}
187
188impl Problem for ConsecutiveBlockMinimization {
189 const NAME: &'static str = "ConsecutiveBlockMinimization";
190 type Solution = Vec<usize>;
191 type Value = crate::types::Or;
192
193 crate::problem_parameters![("num_cols", num_cols), ("num_rows", num_rows),];
194
195 fn evaluate(
196 &self,
197 config: &Self::Solution,
198 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
199 if config.len() != self.num_cols {
200 return Err(crate::traits::EvaluationError::InvalidConfiguration(
201 "column ordering length does not match the matrix".into(),
202 ));
203 }
204 if config.iter().any(|&column| column >= self.num_cols) {
205 return Err(crate::traits::EvaluationError::InvalidConfiguration(
206 "column ordering contains an out-of-range column".into(),
207 ));
208 }
209 Ok({
210 crate::types::Or({
211 match self.count_consecutive_blocks(config)? {
212 Some(total) => total <= self.bound,
213 None => false,
214 }
215 })
216 })
217 }
218
219 fn variant() -> Vec<(&'static str, &'static str)> {
220 crate::variant_params![]
221 }
222}
223
224impl crate::solvers::BruteForceProblem for ConsecutiveBlockMinimization {
225 fn dimensions(&self) -> Vec<usize> {
226 vec![self.num_cols; self.num_cols]
227 }
228}
229
230crate::declare_variants! {
231 default ConsecutiveBlockMinimization => "factorial(num_cols) * num_rows * num_cols" create ConsecutiveBlockMinimizationCreateSpec,
232}
233
234crate::register_brute_force! {
235 ConsecutiveBlockMinimization,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239struct ConsecutiveBlockMinimizationDef {
240 matrix: Vec<Vec<bool>>,
241 bound: i64,
242}
243
244impl TryFrom<ConsecutiveBlockMinimizationDef> for ConsecutiveBlockMinimization {
245 type Error = crate::registry::ConstructionError;
246
247 fn try_from(value: ConsecutiveBlockMinimizationDef) -> Result<Self, Self::Error> {
248 Self::try_new(value.matrix, value.bound)
249 }
250}
251
252impl From<ConsecutiveBlockMinimization> for ConsecutiveBlockMinimizationDef {
253 fn from(value: ConsecutiveBlockMinimization) -> Self {
254 Self {
255 matrix: value.matrix,
256 bound: value.bound,
257 }
258 }
259}
260
261fn validate_matrix_dimensions(
262 matrix: &[Vec<bool>],
263) -> Result<(usize, usize), crate::registry::ConstructionError> {
264 let num_rows = matrix.len();
265 let num_cols = matrix.first().map_or(0, Vec::len);
266
267 if matrix.iter().any(|row| row.len() != num_cols) {
268 return Err("all matrix rows must have the same length"
269 .to_string()
270 .into());
271 }
272
273 Ok((num_rows, num_cols))
274}
275
276#[cfg(feature = "example-db")]
277pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
278 vec![crate::example_db::specs::ModelExampleSpec {
281 id: "consecutive_block_minimization",
282 instance: Box::new(ConsecutiveBlockMinimization::new(
283 vec![
284 vec![false, true, false, false, false, false],
285 vec![true, false, true, false, false, false],
286 vec![false, true, false, true, false, false],
287 vec![false, false, true, false, true, false],
288 vec![false, false, false, true, false, true],
289 vec![false, false, false, false, true, false],
290 ],
291 6,
292 )),
293 optimal_config: serde_json::json!(vec![0, 2, 4, 1, 3, 5]),
294 optimal_value: serde_json::json!(true),
295 }]
296}
297
298#[cfg(test)]
299#[path = "../../unit_tests/models/algebraic/consecutive_block_minimization.rs"]
300mod tests;