simplex_method/
lib.rs

1use log::debug;
2use ndarray::{Array1, Array2, ArrayView1};
3use std::fmt::Display;
4
5#[derive(Debug)]
6pub enum SimplexError {
7    UnableToCalculateError,
8    UnlimitedError,
9    NoSolutionsError,
10    InvalidDataError,
11}
12
13pub struct Table {
14    pub table: Array2<f64>,
15    pub base_var: Vec<String>,
16    pub supp_var: Vec<String>,
17}
18
19impl Display for Table {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        for i in self.base_var.iter() {
22            write!(f, "\t| {}\t", i)?;
23        }
24        write!(f, "\n")?;
25        for i in 0..self.table.nrows() {
26            write!(f, "{}", self.supp_var[i])?;
27            for j in self.table.row(i).iter() {
28                write!(f, "\t| {:.7}", j)?;
29            }
30            write!(f, "\n")?;
31        }
32        writeln!(f, "")?; // empty line
33        self.print_function(f)?;
34        Ok(())
35    }
36}
37
38impl Clone for Table {
39    fn clone(&self) -> Table {
40        Table {
41            table: self.table.clone(),
42            base_var: self.base_var.clone(),
43            supp_var: self.supp_var.clone(),
44        }
45    }
46}
47
48impl Table {
49    pub fn new(
50        mut constr_coeff: Array2<f64>,
51        constr_val: Array1<f64>,
52        mut func_coeff: Array1<f64>,
53        minimisation_task: bool,
54    ) -> Table {
55        if minimisation_task {
56            for i in func_coeff.iter_mut() {
57                *i *= -1f64;
58            }
59        }
60        constr_coeff.push_row(ArrayView1::from(&func_coeff));
61        let mut constr_val_vec = constr_val.to_vec();
62        while constr_val_vec.len() < constr_coeff.nrows() {
63            constr_val_vec.push(0f64);
64        }
65        let constr_val = Array1::from_vec(constr_val_vec);
66        constr_coeff
67            .push_column(ArrayView1::from(&constr_val))
68            .unwrap();
69        let mut base_var = Vec::<String>::with_capacity(constr_coeff.ncols() - 1);
70        for i in 1..constr_coeff.ncols() {
71            base_var.push(i.to_string());
72        }
73        base_var.push("S".to_string());
74        let mut supp_var = Vec::<String>::with_capacity(constr_coeff.nrows());
75        for i in 0..constr_coeff.nrows() - 1 {
76            supp_var.push((i + constr_coeff.ncols()).to_string());
77        }
78        supp_var.push("F".to_string());
79        Table {
80            table: constr_coeff,
81            base_var,
82            supp_var,
83        }
84    }
85    pub fn optimise(&mut self) -> Result<(), crate::SimplexError> {
86        debug!("Beginning table:\n{}", self);
87        loop {
88            let negative_row = self.find_in_free_column();
89            if negative_row.is_none() {
90                break;
91            }
92            let negative_row = negative_row.unwrap();
93            debug!("Found negative free coeff in row {}", negative_row);
94            self.make_acceptable(negative_row)?;
95            debug!("Making acceptable:\n{}", self);
96        }
97        // begin transformation
98        while !self.check_optimised() {
99            self.iterate()?;
100            debug!("Iteration:\n{}\n", self);
101        }
102        Ok(())
103    }
104
105    fn check_optimised(&self) -> bool {
106        for i in self.table.row(self.table.nrows() - 1).iter().enumerate() {
107            // don't check for the last column (with free coeffs)
108            if i.0 >= self.table.ncols() - 1 {
109                break;
110            }
111            if i.1.is_sign_positive() {
112                return false;
113            }
114        }
115        return true;
116    }
117
118    fn find_in_free_column(&self) -> Option<usize> {
119        for i in self.table.column(self.table.ncols() - 1).iter().enumerate() {
120            // don't check for the last column (with free coeffs)
121            if i.0 >= self.table.nrows() - 1 {
122                break;
123            }
124            if i.1.is_sign_negative() {
125                return Some(i.0);
126            }
127        }
128        return None;
129    }
130
131    fn make_acceptable(&mut self, negative: usize) -> Result<(), SimplexError> {
132        let j = {
133            let mut index = None;
134            for i in self.table.row(negative).iter().enumerate() {
135                // don't check for the last column (with free coeffs)
136                if i.0 >= self.table.ncols() - 1 {
137                    break;
138                }
139                if i.1.is_sign_negative() {
140                    index = Some(i.0);
141                    break;
142                }
143            }
144            if index.is_none() {
145                return Err(SimplexError::NoSolutionsError);
146            }
147            index.unwrap()
148        };
149        if let Some(i) = self.find_pivot_row(j) {
150            debug!("Transforming on pivot i: {}\tj: {}", i, j);
151            self.transform((i, j));
152            std::mem::swap(&mut self.base_var[j], &mut self.supp_var[i]);
153            return Ok(());
154        } else {
155            return Err(SimplexError::UnableToCalculateError);
156        }
157    }
158
159    fn iterate(&mut self) -> Result<(), SimplexError> {
160        let (i, j) = self.find_pivot()?;
161        debug!("Current pivot: i: {}\tj:{}", i, j);
162        self.transform((i, j));
163        std::mem::swap(&mut self.base_var[j], &mut self.supp_var[i]);
164        Ok(())
165    }
166
167    fn find_pivot(&self) -> Result<(usize, usize), SimplexError> {
168        let j = {
169            let mut index = None;
170            for j in self.table.row(self.table.nrows() - 1).iter().enumerate() {
171                if j.1.is_sign_positive() {
172                    index = Some(j.0);
173                    break;
174                }
175            }
176            if index.is_none() {
177                return Err(SimplexError::UnableToCalculateError);
178            }
179            index.unwrap()
180        };
181        let mut has_positive = false;
182        for i in self.table.column(j) {
183            if i.is_sign_positive() {
184                has_positive = true;
185                break;
186            }
187        }
188        if !has_positive {
189            return Err(SimplexError::UnlimitedError);
190        }
191        if let Some(i) = self.find_pivot_row(j) {
192            return Ok((i, j));
193        } else {
194            return Err(SimplexError::UnlimitedError);
195        }
196    }
197
198    fn find_pivot_row(&self, column: usize) -> Option<usize> {
199        let mut min = None;
200        let mut min_index = None;
201        for i in self.table.column(self.table.ncols() - 1).iter().enumerate() {
202            // don't check the function coeffs
203            if i.0 >= self.table.nrows() - 1 {
204                break;
205            }
206            let relation = i.1 / self.table[[i.0, column]];
207            if (min.is_none() || relation < min.unwrap()) && relation.is_sign_positive() && relation.is_finite() {
208                min = Some(relation);
209                min_index = Some(i.0);
210            }
211        }
212        min_index
213    }
214
215    fn transform(&mut self, pivot: (usize, usize)) {
216        let pivot_cpy = self.table[[pivot.0, pivot.1]];
217        for i in 0..self.table.nrows() {
218            for j in 0..self.table.ncols() {
219                if i == pivot.0 || j == pivot.1 {
220                    continue;
221                }
222                self.table[[i, j]] -=
223                    self.table[[pivot.0, j]] * self.table[[i, pivot.1]] / pivot_cpy;
224            }
225        }
226        for i in self.table.row_mut(pivot.0) {
227            *i /= pivot_cpy;
228        }
229        for i in self.table.column_mut(pivot.1) {
230            *i /= -pivot_cpy;
231        }
232        self.table[[pivot.0, pivot.1]] = 1f64 / pivot_cpy;
233    }
234    pub fn print_function(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        for i in self.table.column(self.table.ncols() - 1).iter().enumerate() {
236            if i.0 >= self.table.nrows() - 1 {
237                break;
238            }
239            writeln!(f, "X_{} = {}", self.supp_var[i.0], i.1)?;
240        }
241        write!(f, "F = {}", self.table.column(self.table.ncols() - 1).last().unwrap())?;
242        Ok(())
243    }
244}