problemreductions/models/algebraic/
feasible_basis_extension.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
11use crate::traits::Problem;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "FeasibleBasisExtension",
17 display_name: "Feasible Basis Extension",
18 aliases: &[],
19 dimensions: &[],
20 category: crate::registry::ProblemCategory::Algebraic,
21 module_path: module_path!(),
22 description: "Given matrix A, vector a_bar, and required columns S, find a feasible basis extending S",
23 fields: FeasibleBasisExtensionCreateSpec::FIELDS,
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct FeasibleBasisExtension {
61 matrix: Vec<Vec<i64>>,
62 rhs: Vec<i64>,
63 required_columns: Vec<usize>,
64}
65
66#[derive(Debug, Deserialize, crate::CreateSpec)]
67struct FeasibleBasisExtensionCreateSpec {
68 #[create(codec = "json")]
70 matrix: Vec<Vec<i64>>,
71 #[create(codec = "comma-separated")]
73 rhs: Vec<i64>,
74 #[create(codec = "comma-separated")]
76 required_columns: Vec<usize>,
77}
78
79impl TryFrom<FeasibleBasisExtensionCreateSpec> for FeasibleBasisExtension {
80 type Error = crate::registry::ConstructionError;
81 fn try_from(spec: FeasibleBasisExtensionCreateSpec) -> Result<Self, Self::Error> {
82 let m = spec.matrix.len();
83 let first = spec
84 .matrix
85 .first()
86 .ok_or("matrix must have at least one row")?;
87 let n = first.len();
88 if spec.matrix.iter().any(|row| row.len() != n) {
89 return Err("all matrix rows must have the same length".into());
90 }
91 if m >= n {
92 return Err("number of rows must be less than number of columns".into());
93 }
94 if spec.rhs.len() != m {
95 return Err("rhs length must equal number of rows".into());
96 }
97 if spec.required_columns.len() >= m {
98 return Err("required_columns length must be less than number of rows".into());
99 }
100 let mut seen = std::collections::HashSet::new();
101 for &column in &spec.required_columns {
102 if column >= n {
103 return Err(format!("required column {column} is out of bounds").into());
104 }
105 if !seen.insert(column) {
106 return Err(format!("duplicate required column {column}").into());
107 }
108 }
109 Ok(Self {
110 matrix: spec.matrix,
111 rhs: spec.rhs,
112 required_columns: spec.required_columns,
113 })
114 }
115}
116
117impl FeasibleBasisExtension {
118 pub fn new(matrix: Vec<Vec<i64>>, rhs: Vec<i64>, required_columns: Vec<usize>) -> Self {
130 let m = matrix.len();
131 assert!(m > 0, "Matrix must have at least one row");
132 let n = matrix[0].len();
133 for row in &matrix {
134 assert_eq!(row.len(), n, "All rows must have the same length");
135 }
136 assert!(
137 m < n,
138 "Number of rows ({m}) must be less than number of columns ({n})"
139 );
140 assert_eq!(
141 rhs.len(),
142 m,
143 "rhs length ({}) must equal number of rows ({m})",
144 rhs.len()
145 );
146 assert!(
147 required_columns.len() < m,
148 "|S| ({}) must be less than m ({m})",
149 required_columns.len()
150 );
151 for &col in &required_columns {
152 assert!(col < n, "Required column index {col} out of bounds (n={n})");
153 }
154 let mut sorted = required_columns.clone();
156 sorted.sort_unstable();
157 for i in 1..sorted.len() {
158 assert_ne!(
159 sorted[i - 1],
160 sorted[i],
161 "Duplicate required column index {}",
162 sorted[i]
163 );
164 }
165 Self {
166 matrix,
167 rhs,
168 required_columns,
169 }
170 }
171
172 pub fn matrix(&self) -> &[Vec<i64>] {
174 &self.matrix
175 }
176
177 pub fn rhs(&self) -> &[i64] {
179 &self.rhs
180 }
181
182 pub fn required_columns(&self) -> &[usize] {
184 &self.required_columns
185 }
186
187 pub fn num_rows(&self) -> usize {
189 self.matrix.len()
190 }
191
192 pub fn num_columns(&self) -> usize {
194 self.matrix[0].len()
195 }
196
197 pub fn num_required(&self) -> usize {
199 self.required_columns.len()
200 }
201
202 fn free_columns(&self) -> Vec<usize> {
204 let required_set: std::collections::HashSet<usize> =
205 self.required_columns.iter().copied().collect();
206 (0..self.num_columns())
207 .filter(|c| !required_set.contains(c))
208 .collect()
209 }
210
211 #[allow(clippy::needless_range_loop)]
216 fn check_feasible_basis(
217 &self,
218 basis_cols: &[usize],
219 ) -> Result<bool, crate::traits::EvaluationError> {
220 let m = self.num_rows();
221 assert_eq!(basis_cols.len(), m);
222
223 let mut augmented: Vec<Vec<i64>> = Vec::with_capacity(m);
225 for i in 0..m {
226 let mut row = Vec::with_capacity(m + 1);
227 for &col in basis_cols {
228 row.push(self.matrix[i][col]);
229 }
230 row.push(self.rhs[i]);
231 augmented.push(row);
232 }
233
234 let mut prev_pivot = 1_i64;
237
238 for k in 0..m {
239 let mut max_row = k;
241 let mut max_val = augmented[k][k].checked_abs().ok_or_else(|| {
242 crate::traits::EvaluationError::IntegerOverflow(
243 "taking an elimination pivot magnitude".into(),
244 )
245 })?;
246 for i in (k + 1)..m {
247 let candidate = augmented[i][k].checked_abs().ok_or_else(|| {
248 crate::traits::EvaluationError::IntegerOverflow(
249 "taking an elimination candidate magnitude".into(),
250 )
251 })?;
252 if candidate > max_val {
253 max_val = candidate;
254 max_row = i;
255 }
256 }
257 if max_val == 0 {
258 return Ok(false); }
260 if max_row != k {
261 augmented.swap(k, max_row);
262 }
263
264 for i in (k + 1)..m {
265 for j in (k + 1)..=m {
266 let left = augmented[k][k]
267 .checked_mul(augmented[i][j])
268 .ok_or_else(|| {
269 crate::traits::EvaluationError::IntegerOverflow(
270 "multiplying Bareiss pivot and row entry".into(),
271 )
272 })?;
273 let right = augmented[i][k]
274 .checked_mul(augmented[k][j])
275 .ok_or_else(|| {
276 crate::traits::EvaluationError::IntegerOverflow(
277 "multiplying Bareiss elimination entries".into(),
278 )
279 })?;
280 let numerator = left.checked_sub(right).ok_or_else(|| {
281 crate::traits::EvaluationError::IntegerOverflow(
282 "subtracting Bareiss products".into(),
283 )
284 })?;
285 augmented[i][j] = numerator.checked_div(prev_pivot).ok_or_else(|| {
286 crate::traits::EvaluationError::IntegerOverflow(
287 "dividing by the previous Bareiss pivot".into(),
288 )
289 })?;
290 }
291 augmented[i][k] = 0;
292 }
293 prev_pivot = augmented[k][k];
294 }
295
296 let mut x_nums = vec![0_i64; m];
305 let mut x_dens = vec![1_i64; m];
306
307 for i in (0..m).rev() {
308 let mut num = augmented[i][m];
310 let mut den = 1_i64;
311
312 for j in (i + 1)..m {
313 let left = num.checked_mul(x_dens[j]).ok_or_else(|| {
317 crate::traits::EvaluationError::IntegerOverflow(
318 "multiplying basis-solution numerator".into(),
319 )
320 })?;
321 let right = den
322 .checked_mul(augmented[i][j])
323 .and_then(|value| value.checked_mul(x_nums[j]))
324 .ok_or_else(|| {
325 crate::traits::EvaluationError::IntegerOverflow(
326 "multiplying basis-solution subtraction term".into(),
327 )
328 })?;
329 num = left.checked_sub(right).ok_or_else(|| {
330 crate::traits::EvaluationError::IntegerOverflow(
331 "subtracting basis-solution terms".into(),
332 )
333 })?;
334 den = den.checked_mul(x_dens[j]).ok_or_else(|| {
335 crate::traits::EvaluationError::IntegerOverflow(
336 "multiplying basis-solution denominators".into(),
337 )
338 })?;
339 let g = gcd_i64(
341 num.checked_abs().ok_or_else(|| {
342 crate::traits::EvaluationError::IntegerOverflow(
343 "taking basis-solution numerator magnitude".into(),
344 )
345 })?,
346 den.checked_abs().ok_or_else(|| {
347 crate::traits::EvaluationError::IntegerOverflow(
348 "taking basis-solution denominator magnitude".into(),
349 )
350 })?,
351 );
352 if g > 1 {
353 num /= g;
354 den /= g;
355 }
356 }
357 let diag = augmented[i][i];
359 x_nums[i] = num;
360 x_dens[i] = den.checked_mul(diag).ok_or_else(|| {
361 crate::traits::EvaluationError::IntegerOverflow(
362 "multiplying basis-solution denominator by diagonal".into(),
363 )
364 })?;
365 if x_dens[i] < 0 {
367 x_nums[i] = x_nums[i].checked_neg().ok_or_else(|| {
368 crate::traits::EvaluationError::IntegerOverflow(
369 "normalizing basis-solution numerator sign".into(),
370 )
371 })?;
372 x_dens[i] = x_dens[i].checked_neg().ok_or_else(|| {
373 crate::traits::EvaluationError::IntegerOverflow(
374 "normalizing basis-solution denominator sign".into(),
375 )
376 })?;
377 }
378 let g = gcd_i64(
379 x_nums[i].checked_abs().ok_or_else(|| {
380 crate::traits::EvaluationError::IntegerOverflow(
381 "taking normalized numerator magnitude".into(),
382 )
383 })?,
384 x_dens[i].checked_abs().ok_or_else(|| {
385 crate::traits::EvaluationError::IntegerOverflow(
386 "taking normalized denominator magnitude".into(),
387 )
388 })?,
389 );
390 if g > 1 {
391 x_nums[i] /= g;
392 x_dens[i] /= g;
393 }
394 }
395
396 Ok(x_nums.iter().take(m).all(|&num| num >= 0))
399 }
400}
401
402fn gcd_i64(mut a: i64, mut b: i64) -> i64 {
404 while b != 0 {
405 let t = b;
406 b = a % b;
407 a = t;
408 }
409 a
410}
411
412impl Problem for FeasibleBasisExtension {
413 const NAME: &'static str = "FeasibleBasisExtension";
414 type Solution = Vec<bool>;
415 type Value = crate::types::Or;
416
417 crate::problem_parameters![
418 ("num_rows", num_rows),
419 ("num_columns", num_columns),
420 ("num_required", num_required),
421 ];
422
423 fn variant() -> Vec<(&'static str, &'static str)> {
424 crate::variant_params![]
425 }
426
427 fn evaluate(
428 &self,
429 config: &Self::Solution,
430 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
431 Ok({
432 let free_cols = self.free_columns();
433 let num_free = free_cols.len();
434
435 if config.len() != num_free {
436 return Err(crate::traits::EvaluationError::InvalidConfiguration(
437 "free-column selection length does not match the matrix".into(),
438 ));
439 }
440 let m = self.num_rows();
441 let s = self.num_required();
442 let needed = m - s;
443
444 let selected_free: Vec<usize> = config
446 .iter()
447 .enumerate()
448 .filter(|(_, &v)| v)
449 .map(|(i, _)| free_cols[i])
450 .collect();
451
452 if selected_free.len() != needed {
453 return Ok(crate::types::Or(false));
454 }
455
456 let mut basis_cols: Vec<usize> = self.required_columns.clone();
458 basis_cols.extend_from_slice(&selected_free);
459
460 crate::types::Or(self.check_feasible_basis(&basis_cols)?)
461 })
462 }
463}
464
465impl crate::solvers::BruteForceProblem for FeasibleBasisExtension {
466 fn dimensions(&self) -> Vec<usize> {
467 vec![2; self.num_columns() - self.num_required()]
468 }
469}
470
471crate::declare_variants! {
472 default FeasibleBasisExtension => "2^num_columns * num_rows^3" create FeasibleBasisExtensionCreateSpec,
473}
474
475crate::register_brute_force! {
476 FeasibleBasisExtension decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
477}
478
479#[cfg(feature = "example-db")]
480pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
481 vec![crate::example_db::specs::ModelExampleSpec {
482 id: "feasible_basis_extension",
483 instance: Box::new(FeasibleBasisExtension::new(
485 vec![
486 vec![1, 0, 1, 2, -1, 0],
487 vec![0, 1, 0, 1, 1, 2],
488 vec![0, 0, 1, 1, 0, 1],
489 ],
490 vec![7, 5, 3],
491 vec![0, 1],
492 )),
493 optimal_config: serde_json::json!(vec![true, false, false, false]), optimal_value: serde_json::json!(true),
495 }]
496}
497
498#[cfg(test)]
499#[path = "../../unit_tests/models/algebraic/feasible_basis_extension.rs"]
500mod tests;