problemreductions/models/algebraic/
feasible_basis_extension.rs1use crate::registry::{FieldInfo, 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 module_path: module_path!(),
21 description: "Given matrix A, vector a_bar, and required columns S, find a feasible basis extending S",
22 fields: &[
23 FieldInfo { name: "matrix", type_name: "Vec<Vec<i64>>", description: "m x n integer matrix A (row-major)" },
24 FieldInfo { name: "rhs", type_name: "Vec<i64>", description: "Column vector a_bar of length m" },
25 FieldInfo { name: "required_columns", type_name: "Vec<usize>", description: "Subset S of column indices that must be in the basis" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct FeasibleBasisExtension {
64 matrix: Vec<Vec<i64>>,
65 rhs: Vec<i64>,
66 required_columns: Vec<usize>,
67}
68
69impl FeasibleBasisExtension {
70 pub fn new(matrix: Vec<Vec<i64>>, rhs: Vec<i64>, required_columns: Vec<usize>) -> Self {
82 let m = matrix.len();
83 assert!(m > 0, "Matrix must have at least one row");
84 let n = matrix[0].len();
85 for row in &matrix {
86 assert_eq!(row.len(), n, "All rows must have the same length");
87 }
88 assert!(
89 m < n,
90 "Number of rows ({m}) must be less than number of columns ({n})"
91 );
92 assert_eq!(
93 rhs.len(),
94 m,
95 "rhs length ({}) must equal number of rows ({m})",
96 rhs.len()
97 );
98 assert!(
99 required_columns.len() < m,
100 "|S| ({}) must be less than m ({m})",
101 required_columns.len()
102 );
103 for &col in &required_columns {
104 assert!(col < n, "Required column index {col} out of bounds (n={n})");
105 }
106 let mut sorted = required_columns.clone();
108 sorted.sort_unstable();
109 for i in 1..sorted.len() {
110 assert_ne!(
111 sorted[i - 1],
112 sorted[i],
113 "Duplicate required column index {}",
114 sorted[i]
115 );
116 }
117 Self {
118 matrix,
119 rhs,
120 required_columns,
121 }
122 }
123
124 pub fn matrix(&self) -> &[Vec<i64>] {
126 &self.matrix
127 }
128
129 pub fn rhs(&self) -> &[i64] {
131 &self.rhs
132 }
133
134 pub fn required_columns(&self) -> &[usize] {
136 &self.required_columns
137 }
138
139 pub fn num_rows(&self) -> usize {
141 self.matrix.len()
142 }
143
144 pub fn num_columns(&self) -> usize {
146 self.matrix[0].len()
147 }
148
149 pub fn num_required(&self) -> usize {
151 self.required_columns.len()
152 }
153
154 fn free_columns(&self) -> Vec<usize> {
156 let required_set: std::collections::HashSet<usize> =
157 self.required_columns.iter().copied().collect();
158 (0..self.num_columns())
159 .filter(|c| !required_set.contains(c))
160 .collect()
161 }
162
163 #[allow(clippy::needless_range_loop)]
168 fn check_feasible_basis(&self, basis_cols: &[usize]) -> bool {
169 let m = self.num_rows();
170 assert_eq!(basis_cols.len(), m);
171
172 let mut aug128: Vec<Vec<i128>> = Vec::with_capacity(m);
175 for i in 0..m {
176 let mut row = Vec::with_capacity(m + 1);
177 for &col in basis_cols {
178 row.push(self.matrix[i][col] as i128);
179 }
180 row.push(self.rhs[i] as i128);
181 aug128.push(row);
182 }
183
184 let mut prev_pivot = 1i128;
187
188 for k in 0..m {
189 let mut max_row = k;
191 let mut max_val = aug128[k][k].abs();
192 for i in (k + 1)..m {
193 if aug128[i][k].abs() > max_val {
194 max_val = aug128[i][k].abs();
195 max_row = i;
196 }
197 }
198 if max_val == 0 {
199 return false; }
201 if max_row != k {
202 aug128.swap(k, max_row);
203 }
204
205 for i in (k + 1)..m {
206 for j in (k + 1)..=m {
207 aug128[i][j] =
208 (aug128[k][k] * aug128[i][j] - aug128[i][k] * aug128[k][j]) / prev_pivot;
209 }
210 aug128[i][k] = 0;
211 }
212 prev_pivot = aug128[k][k];
213 }
214
215 let mut x_nums = vec![0i128; m];
224 let mut x_dens = vec![1i128; m];
225
226 for i in (0..m).rev() {
227 let mut num = aug128[i][m];
229 let mut den = 1i128;
230
231 for j in (i + 1)..m {
232 let a = aug128[i][j];
236 num = num * x_dens[j] - den * a * x_nums[j];
237 den *= x_dens[j];
238 let g = gcd_i128(num.abs(), den.abs());
240 if g > 1 {
241 num /= g;
242 den /= g;
243 }
244 }
245 let diag = aug128[i][i];
247 x_nums[i] = num;
248 x_dens[i] = den * diag;
249 if x_dens[i] < 0 {
251 x_nums[i] = -x_nums[i];
252 x_dens[i] = -x_dens[i];
253 }
254 let g = gcd_i128(x_nums[i].abs(), x_dens[i].abs());
255 if g > 1 {
256 x_nums[i] /= g;
257 x_dens[i] /= g;
258 }
259 }
260
261 x_nums.iter().take(m).all(|&num| num >= 0)
264 }
265}
266
267fn gcd_i128(mut a: i128, mut b: i128) -> i128 {
269 while b != 0 {
270 let t = b;
271 b = a % b;
272 a = t;
273 }
274 a
275}
276
277impl Problem for FeasibleBasisExtension {
278 const NAME: &'static str = "FeasibleBasisExtension";
279 type Value = crate::types::Or;
280
281 fn variant() -> Vec<(&'static str, &'static str)> {
282 crate::variant_params![]
283 }
284
285 fn dims(&self) -> Vec<usize> {
286 vec![2; self.num_columns() - self.num_required()]
287 }
288
289 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
290 let free_cols = self.free_columns();
291 let num_free = free_cols.len();
292
293 if config.len() != num_free {
294 return crate::types::Or(false);
295 }
296 if config.iter().any(|&v| v >= 2) {
297 return crate::types::Or(false);
298 }
299
300 let m = self.num_rows();
301 let s = self.num_required();
302 let needed = m - s;
303
304 let selected_free: Vec<usize> = config
306 .iter()
307 .enumerate()
308 .filter(|(_, &v)| v == 1)
309 .map(|(i, _)| free_cols[i])
310 .collect();
311
312 if selected_free.len() != needed {
313 return crate::types::Or(false);
314 }
315
316 let mut basis_cols: Vec<usize> = self.required_columns.clone();
318 basis_cols.extend_from_slice(&selected_free);
319
320 crate::types::Or(self.check_feasible_basis(&basis_cols))
321 }
322}
323
324crate::declare_variants! {
325 default FeasibleBasisExtension => "2^num_columns * num_rows^3",
326}
327
328#[cfg(feature = "example-db")]
329pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
330 vec![crate::example_db::specs::ModelExampleSpec {
331 id: "feasible_basis_extension",
332 instance: Box::new(FeasibleBasisExtension::new(
334 vec![
335 vec![1, 0, 1, 2, -1, 0],
336 vec![0, 1, 0, 1, 1, 2],
337 vec![0, 0, 1, 1, 0, 1],
338 ],
339 vec![7, 5, 3],
340 vec![0, 1],
341 )),
342 optimal_config: vec![1, 0, 0, 0], optimal_value: serde_json::json!(true),
344 }]
345}
346
347#[cfg(test)]
348#[path = "../../unit_tests/models/algebraic/feasible_basis_extension.rs"]
349mod tests;