1#![allow(clippy::needless_range_loop)] #[allow(unused_imports)]
7use crate::prelude::*;
8use core::ops::{Add, Div, Mul, Sub};
9use num_traits::Float;
10
11#[derive(Debug, Clone)]
13pub struct SimplexTableau<T> {
14 pub tableau: Vec<Vec<T>>,
16 pub basis: Vec<usize>,
18 pub num_vars: usize,
20 pub num_constraints: usize,
22}
23
24impl<T> SimplexTableau<T>
25where
26 T: Clone + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Float,
27{
28 pub fn new(constraints: Vec<Vec<T>>, objective: Vec<T>, rhs: Vec<T>) -> Self {
30 let num_constraints = constraints.len();
31 let num_vars = objective.len();
32
33 let total_vars = num_vars + num_constraints;
35 let mut tableau = vec![vec![T::zero(); total_vars + 1]; num_constraints + 1];
36
37 for (i, constraint) in constraints.iter().enumerate() {
39 for (j, &val) in constraint.iter().enumerate() {
40 tableau[i][j] = val;
41 }
42 tableau[i][num_vars + i] = T::one();
44 tableau[i][total_vars] = rhs[i];
46 }
47
48 for (j, &val) in objective.iter().enumerate() {
50 tableau[num_constraints][j] = T::zero() - val;
51 }
52
53 let basis = (num_vars..(num_vars + num_constraints)).collect();
55
56 Self {
57 tableau,
58 basis,
59 num_vars,
60 num_constraints,
61 }
62 }
63
64 pub fn pivot(&mut self) -> Result<bool, SimplexError> {
66 let entering_col = self.find_entering_variable()?;
68
69 if entering_col.is_none() {
70 return Ok(true); }
72
73 let entering = entering_col.expect("entering_col should be valid");
74
75 let leaving_row = self.find_leaving_variable(entering)?;
77
78 if leaving_row.is_none() {
79 return Err(SimplexError::Unbounded);
80 }
81
82 let leaving = leaving_row.expect("leaving_row should be valid");
83
84 self.perform_pivot(entering, leaving);
86
87 self.basis[leaving] = entering;
89
90 Ok(false) }
92
93 fn find_entering_variable(&self) -> Result<Option<usize>, SimplexError> {
95 let obj_row = &self.tableau[self.num_constraints];
96 let total_vars = self.num_vars + self.num_constraints;
97
98 let mut min_val = T::zero();
99 let mut min_idx = None;
100
101 for j in 0..total_vars {
102 if obj_row[j] < min_val {
103 min_val = obj_row[j];
104 min_idx = Some(j);
105 }
106 }
107
108 Ok(min_idx)
109 }
110
111 fn find_leaving_variable(&self, entering: usize) -> Result<Option<usize>, SimplexError> {
113 let total_vars = self.num_vars + self.num_constraints;
114 let rhs_col = total_vars;
115
116 let mut min_ratio = T::infinity();
117 let mut min_idx = None;
118
119 for i in 0..self.num_constraints {
120 let coeff = self.tableau[i][entering];
121
122 if coeff > T::epsilon() {
123 let rhs = self.tableau[i][rhs_col];
124 let ratio = rhs / coeff;
125
126 if ratio < min_ratio {
127 min_ratio = ratio;
128 min_idx = Some(i);
129 }
130 }
131 }
132
133 Ok(min_idx)
134 }
135
136 fn perform_pivot(&mut self, entering: usize, leaving: usize) {
138 let total_vars = self.num_vars + self.num_constraints;
139 let num_cols = total_vars + 1;
140
141 let pivot = self.tableau[leaving][entering];
143
144 if pivot.abs() < T::epsilon() {
145 return; }
147
148 for j in 0..num_cols {
150 self.tableau[leaving][j] = self.tableau[leaving][j] / pivot;
151 }
152
153 const CHUNK_SIZE: usize = 8;
156
157 for i in 0..=self.num_constraints {
158 if i == leaving {
159 continue;
160 }
161
162 let factor = self.tableau[i][entering];
163
164 if factor.abs() < T::epsilon() {
165 continue;
166 }
167
168 for chunk_start in (0..num_cols).step_by(CHUNK_SIZE) {
170 let chunk_end = (chunk_start + CHUNK_SIZE).min(num_cols);
171
172 for j in chunk_start..chunk_end {
173 let update = self.tableau[leaving][j] * factor;
174 self.tableau[i][j] = self.tableau[i][j] - update;
175 }
176 }
177 }
178 }
179
180 pub fn get_solution(&self) -> Vec<T> {
182 let mut solution = vec![T::zero(); self.num_vars];
183 let total_vars = self.num_vars + self.num_constraints;
184 let rhs_col = total_vars;
185
186 for (row, &var_idx) in self.basis.iter().enumerate() {
187 if var_idx < self.num_vars {
188 solution[var_idx] = self.tableau[row][rhs_col];
189 }
190 }
191
192 solution
193 }
194
195 pub fn get_objective_value(&self) -> T {
197 let total_vars = self.num_vars + self.num_constraints;
198 let rhs_col = total_vars;
199 self.tableau[self.num_constraints][rhs_col]
200 }
201
202 pub fn is_feasible(&self) -> bool {
204 let total_vars = self.num_vars + self.num_constraints;
205 let rhs_col = total_vars;
206
207 for i in 0..self.num_constraints {
208 if self.tableau[i][rhs_col] < T::zero() - T::epsilon() {
209 return false;
210 }
211 }
212
213 true
214 }
215}
216
217pub fn simd_simplex_solve<T>(
219 constraints: Vec<Vec<T>>,
220 objective: Vec<T>,
221 rhs: Vec<T>,
222 max_iterations: usize,
223) -> Result<SimplexSolution<T>, SimplexError>
224where
225 T: Clone + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Float,
226{
227 let mut tableau = SimplexTableau::new(constraints, objective, rhs);
228
229 if !tableau.is_feasible() {
230 return Err(SimplexError::Infeasible);
231 }
232
233 let mut iterations = 0;
234
235 loop {
236 if iterations >= max_iterations {
237 return Err(SimplexError::MaxIterationsReached);
238 }
239
240 let optimal = tableau.pivot()?;
241
242 if optimal {
243 break;
244 }
245
246 iterations += 1;
247 }
248
249 Ok(SimplexSolution {
250 solution: tableau.get_solution(),
251 objective_value: tableau.get_objective_value(),
252 iterations,
253 })
254}
255
256#[derive(Debug, Clone)]
258pub struct SimplexSolution<T> {
259 pub solution: Vec<T>,
261 pub objective_value: T,
263 pub iterations: usize,
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum SimplexError {
270 Infeasible,
272 Unbounded,
274 MaxIterationsReached,
276}
277
278impl core::fmt::Display for SimplexError {
279 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280 match self {
281 Self::Infeasible => write!(f, "linear program is infeasible"),
282 Self::Unbounded => write!(f, "linear program is unbounded"),
283 Self::MaxIterationsReached => write!(f, "maximum iterations reached"),
284 }
285 }
286}
287
288impl core::error::Error for SimplexError {}
289
290pub fn simd_dual_simplex<T>(
292 constraints: Vec<Vec<T>>,
293 objective: Vec<T>,
294 rhs: Vec<T>,
295 max_iterations: usize,
296) -> Result<SimplexSolution<T>, SimplexError>
297where
298 T: Clone + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Float,
299{
300 let mut tableau = SimplexTableau::new(constraints, objective, rhs);
301
302 let mut iterations = 0;
303
304 loop {
305 if iterations >= max_iterations {
306 return Err(SimplexError::MaxIterationsReached);
307 }
308
309 if tableau.is_feasible() && is_dual_feasible(&tableau) {
311 break;
312 }
313
314 let leaving_row = find_dual_leaving_variable(&tableau)?;
316
317 if leaving_row.is_none() {
318 return Err(SimplexError::Infeasible);
319 }
320
321 let leaving = leaving_row.expect("leaving_row should be valid");
322
323 let entering_col = find_dual_entering_variable(&tableau, leaving)?;
325
326 if entering_col.is_none() {
327 return Err(SimplexError::Infeasible);
328 }
329
330 let entering = entering_col.expect("entering_col should be valid");
331
332 tableau.perform_pivot(entering, leaving);
334 tableau.basis[leaving] = entering;
335
336 iterations += 1;
337 }
338
339 Ok(SimplexSolution {
340 solution: tableau.get_solution(),
341 objective_value: tableau.get_objective_value(),
342 iterations,
343 })
344}
345
346fn is_dual_feasible<T>(tableau: &SimplexTableau<T>) -> bool
347where
348 T: Clone + Float,
349{
350 let total_vars = tableau.num_vars + tableau.num_constraints;
351 let obj_row = &tableau.tableau[tableau.num_constraints];
352
353 for j in 0..total_vars {
354 if obj_row[j] < T::zero() - T::epsilon() {
355 return false;
356 }
357 }
358
359 true
360}
361
362fn find_dual_leaving_variable<T>(tableau: &SimplexTableau<T>) -> Result<Option<usize>, SimplexError>
363where
364 T: Clone + Float,
365{
366 let total_vars = tableau.num_vars + tableau.num_constraints;
367 let rhs_col = total_vars;
368
369 let mut min_val = T::zero();
370 let mut min_idx = None;
371
372 for i in 0..tableau.num_constraints {
373 let rhs = tableau.tableau[i][rhs_col];
374 if rhs < min_val {
375 min_val = rhs;
376 min_idx = Some(i);
377 }
378 }
379
380 Ok(min_idx)
381}
382
383fn find_dual_entering_variable<T>(
384 tableau: &SimplexTableau<T>,
385 leaving: usize,
386) -> Result<Option<usize>, SimplexError>
387where
388 T: Clone + Float + Div<Output = T>,
389{
390 let total_vars = tableau.num_vars + tableau.num_constraints;
391 let obj_row = &tableau.tableau[tableau.num_constraints];
392 let leaving_row = &tableau.tableau[leaving];
393
394 let mut min_ratio = T::infinity();
395 let mut min_idx = None;
396
397 for j in 0..total_vars {
398 let coeff = leaving_row[j];
399
400 if coeff < T::zero() - T::epsilon() {
401 let obj_coeff = obj_row[j];
402 let ratio = obj_coeff / (T::zero() - coeff);
403
404 if ratio < min_ratio {
405 min_ratio = ratio;
406 min_idx = Some(j);
407 }
408 }
409 }
410
411 Ok(min_idx)
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_simplex_basic() {
420 let constraints = vec![vec![1.0, 1.0], vec![2.0, 1.0]];
423 let objective = vec![3.0, 2.0];
424 let rhs = vec![4.0, 5.0];
425
426 let result = simd_simplex_solve(constraints, objective, rhs, 100).expect("simplex failed");
427
428 assert!(result.iterations > 0);
429 assert!(result.objective_value > 0.0);
430 }
431
432 #[test]
433 fn test_simplex_unbounded() {
434 let constraints = vec![vec![-1.0, 1.0]];
437 let objective = vec![1.0, 1.0];
438 let rhs = vec![1.0];
439
440 let result = simd_simplex_solve(constraints, objective, rhs, 100);
441
442 assert!(result.is_err());
443 }
444
445 #[test]
446 fn test_tableau_creation() {
447 let constraints = vec![vec![1.0, 1.0]];
448 let objective = vec![1.0, 1.0];
449 let rhs = vec![5.0];
450
451 let tableau = SimplexTableau::new(constraints, objective, rhs);
452
453 assert_eq!(tableau.num_vars, 2);
454 assert_eq!(tableau.num_constraints, 1);
455 assert_eq!(tableau.basis.len(), 1);
456 }
457}