problemreductions/models/algebraic/
minimum_weight_solution_to_linear_equations.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "MinimumWeightSolutionToLinearEquations",
14 display_name: "Minimum Weight Solution to Linear Equations",
15 aliases: &[],
16 dimensions: &[],
17 module_path: module_path!(),
18 description: "Find a rational solution to Ay=b minimizing the number of non-zero entries",
19 fields: &[
20 FieldInfo { name: "matrix", type_name: "Vec<Vec<i64>>", description: "n×m integer matrix A" },
21 FieldInfo { name: "rhs", type_name: "Vec<i64>", description: "right-hand side vector b of length n" },
22 ],
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MinimumWeightSolutionToLinearEquations {
57 matrix: Vec<Vec<i64>>,
59 rhs: Vec<i64>,
61}
62
63impl MinimumWeightSolutionToLinearEquations {
64 pub fn new(matrix: Vec<Vec<i64>>, rhs: Vec<i64>) -> Self {
71 assert!(!matrix.is_empty(), "Matrix must have at least one row");
72 let num_cols = matrix[0].len();
73 assert!(num_cols > 0, "Matrix must have at least one column");
74 for row in &matrix {
75 assert_eq!(row.len(), num_cols, "All rows must have the same length");
76 }
77 assert_eq!(
78 rhs.len(),
79 matrix.len(),
80 "RHS length must equal number of rows"
81 );
82 Self { matrix, rhs }
83 }
84
85 pub fn matrix(&self) -> &[Vec<i64>] {
87 &self.matrix
88 }
89
90 pub fn rhs(&self) -> &[i64] {
92 &self.rhs
93 }
94
95 pub fn num_equations(&self) -> usize {
97 self.matrix.len()
98 }
99
100 pub fn num_variables(&self) -> usize {
102 self.matrix[0].len()
103 }
104
105 fn is_consistent(&self, columns: &[usize]) -> bool {
109 let n = self.num_equations();
110 let k = columns.len();
111
112 let mut aug: Vec<Vec<i128>> = (0..n)
115 .map(|i| {
116 let mut row = Vec::with_capacity(k + 1);
117 for &j in columns {
118 row.push(self.matrix[i][j] as i128);
119 }
120 row.push(self.rhs[i] as i128);
121 row
122 })
123 .collect();
124
125 let mut pivot_row = 0;
126 for col in 0..k {
127 let Some(swap_row) = (pivot_row..n).find(|&r| aug[r][col] != 0) else {
129 continue;
130 };
131 aug.swap(pivot_row, swap_row);
132
133 let pivot_val = aug[pivot_row][col];
134 let pivot_row_snapshot = aug[pivot_row].clone();
135 for (r, row) in aug.iter_mut().enumerate() {
137 if r == pivot_row {
138 continue;
139 }
140 let factor = row[col];
141 if factor == 0 {
142 continue;
143 }
144 for (cell, &pv) in row.iter_mut().zip(pivot_row_snapshot.iter()) {
146 *cell = pivot_val * *cell - factor * pv;
147 }
148 }
149 pivot_row += 1;
150 }
151
152 for row in &aug[pivot_row..n] {
155 if row[k] != 0 {
156 return false;
157 }
158 }
159 true
160 }
161}
162
163impl Problem for MinimumWeightSolutionToLinearEquations {
164 const NAME: &'static str = "MinimumWeightSolutionToLinearEquations";
165 type Value = Min<usize>;
166
167 fn variant() -> Vec<(&'static str, &'static str)> {
168 crate::variant_params![]
169 }
170
171 fn dims(&self) -> Vec<usize> {
172 vec![2; self.num_variables()]
173 }
174
175 fn evaluate(&self, config: &[usize]) -> Min<usize> {
176 if config.len() != self.num_variables() {
177 return Min(None);
178 }
179 if config.iter().any(|&v| v >= 2) {
180 return Min(None);
181 }
182
183 let columns: Vec<usize> = config
184 .iter()
185 .enumerate()
186 .filter(|(_, &v)| v == 1)
187 .map(|(j, _)| j)
188 .collect();
189
190 if columns.is_empty() {
191 if self.rhs.iter().all(|&v| v == 0) {
193 return Min(Some(0));
194 } else {
195 return Min(None);
196 }
197 }
198
199 if self.is_consistent(&columns) {
200 Min(Some(columns.len()))
201 } else {
202 Min(None)
203 }
204 }
205}
206
207crate::declare_variants! {
208 default MinimumWeightSolutionToLinearEquations => "2^num_variables",
209}
210
211#[cfg(feature = "example-db")]
212pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
213 let matrix = vec![vec![1, 2, 3, 1], vec![2, 1, 1, 3]];
217 let rhs = vec![5, 4];
218 vec![crate::example_db::specs::ModelExampleSpec {
219 id: "minimum_weight_solution_to_linear_equations",
220 instance: Box::new(MinimumWeightSolutionToLinearEquations::new(matrix, rhs)),
221 optimal_config: vec![1, 1, 0, 0],
222 optimal_value: serde_json::json!(2),
223 }]
224}
225
226#[cfg(test)]
227#[path = "../../unit_tests/models/algebraic/minimum_weight_solution_to_linear_equations.rs"]
228mod tests;