Skip to main content

simplex/
lib.rs

1use ndarray::*;
2
3#[cfg(test)]
4mod test;
5
6/// Constraints you can add to your LP problem
7///
8/// Equal is x + y + z = N
9/// LessThan is x + y + z <= N
10/// GreaterThan is x + y + z >= N
11pub enum SimplexConstraint {
12    Equal(Vec<f64>, f64),
13    LessThan(Vec<f64>, f64),
14    GreaterThan(Vec<f64>, f64),
15}
16
17impl SimplexConstraint {
18    fn get_vector(&self) -> &Vec<f64> {
19        match self {
20            SimplexConstraint::Equal(a, _b) => a,
21            SimplexConstraint::LessThan(a, _b) => a,
22            SimplexConstraint::GreaterThan(a, _b) => a,
23        }
24    }
25
26    fn get_b(&self) -> f64 {
27        match self {
28            SimplexConstraint::Equal(_a, b) => *b,
29            SimplexConstraint::LessThan(_a, b) => *b,
30            SimplexConstraint::GreaterThan(_a, b) => *b,
31        }
32    }
33}
34
35#[derive(Clone, Debug, PartialEq)]
36pub enum SimplexVar {
37    Real,
38    Slack(usize),
39    NegativeSlack(usize),
40    Artificial(usize),
41}
42
43impl SimplexVar {
44    fn is_artificial(&self) -> bool {
45        match self {
46            SimplexVar::Artificial(_) => true,
47            _ => false,
48        }
49    }
50
51    fn is_slack(&self) -> bool {
52        match self {
53            SimplexVar::Slack(_) => true,
54            _ => false,
55        }
56    }
57}
58/// The result of a Simplex calculation
59///
60/// UniqueOptimum means there's only one solution, and is an optimum
61/// MultipleOptimum means there's an optimum, but with different solutions. Run solve again to get another solution.
62/// InfiniteSolution means the problem is unbound, so the optimum is infinite
63/// NoSolution means the problem doesn't seem to be feasible
64#[derive(Debug, PartialEq)]
65pub enum SimplexOutput {
66    UniqueOptimum(f64),
67    MultipleOptimum(f64),
68    InfiniteSolution,
69    NoSolution,
70}
71
72pub struct SimplexTable {
73    objective: Vec<f64>,
74    table: Array2<f64>,
75    base: Vec<usize>,
76    vars: Vec<SimplexVar>,
77}
78
79impl SimplexTable {
80    fn get_entry_var(&self) -> Option<usize> {
81        let mut entry_var = None;
82        let mut max_entry = -1.0;
83        for (i, z) in self.table.row(0).iter().enumerate() {
84            if i == 0 || i == self.table.ncols() - 1 {
85                continue;
86            }
87            if max_entry < *z {
88                max_entry = *z;
89                entry_var = Some(i);
90            }
91        }
92        entry_var
93    }
94
95    fn get_exit_var(&self, entry_var: usize) -> Option<usize> {
96        let mut exit_var = None;
97        let mut min_entry = f64::MAX;
98        let b = self.table.column(self.table.ncols() - 1);
99        for (i, z) in self.table.column(entry_var).iter().enumerate() {
100            if i == 0 {
101                continue;
102            }
103            if *z <= 0.0 {
104                continue;
105            }
106            if min_entry > b[i] / z {
107                min_entry = b[i] / z;
108                exit_var = Some(self.base[i - 1]);
109            }
110        }
111        exit_var
112    }
113
114    fn step(&mut self, entry_var: usize, exit_var: usize) {
115        let exit_row = self.base.iter().position(|x| *x == exit_var).unwrap() + 1;
116        let pivot = self.table.row(exit_row)[entry_var];
117        {
118            let mut row = self.table.row_mut(exit_row);
119            row /= pivot;
120        }
121        for i in 0..self.table.nrows() {
122            if i == exit_row {
123                continue;
124            }
125            let mut exit_row = self.table.row(exit_row).to_owned();
126            let mut row = self.table.row_mut(i);
127            let factor = row[entry_var] / -1.0;
128            exit_row *= factor;
129            row += &exit_row;
130        }
131        self.base = self
132            .base
133            .iter_mut()
134            .map(|x| if *x == exit_var { entry_var } else { *x })
135            .collect();
136    }
137
138    /// Solve your LP problem
139    ///
140    /// Try to solve the LP problem. It uses the "standard" Simplex algorithm, with Big M method
141    /// There's no timeout, so it could run for a very long time if you're not careful.
142    /// It returns a SimplexOutput, which has a description of the solution and the optimum value (if exists).
143    /// ```rust
144    ///    let program = Simplex::minimize(&vec![-3.0, 1.0, -2.0])
145    ///    .with(vec![
146    ///        SimplexConstraint::LessThan(vec![2.0, -2.0, 3.0], 5.0),
147    ///        SimplexConstraint::LessThan(vec![1.0, 1.0, -1.0], 3.0),
148    ///        SimplexConstraint::LessThan(vec![1.0, -1.0, 1.0], 2.0),
149    ///    ]);
150    ///    let mut simplex = program.unwrap();
151    ///    assert_eq!(simplex.solve(), SimplexOutput::MultipleOptimum(-8.0));
152    ///    assert_eq!(simplex.get_var(1), Some(2.5));
153    ///    assert_eq!(simplex.get_var(2), Some(1.5));
154    ///    assert_eq!(simplex.get_var(3), Some(1.0));
155    /// ```
156    pub fn solve(&mut self) -> SimplexOutput {
157        loop {
158            if let Some(entry_var) = self.get_entry_var() {
159                if let Some(exit_var) = self.get_exit_var(entry_var) {
160                    self.step(entry_var, exit_var);
161                } else {
162                    return SimplexOutput::InfiniteSolution;
163                }
164            } else {
165                panic!("Can't continue");
166            }
167            let mut optimum = true;
168            let mut unique = true;
169            for (i, &z) in self.table.row(0).iter().skip(1).enumerate() {
170                optimum = optimum && z <= 0.0;
171                if !self.base.contains(&i) && i <= self.objective.len() {
172                    unique = unique && z - self.objective[i] < 0.0;
173                }
174            }
175            if optimum {
176                let optimum = self.table.row(0)[self.table.ncols() - 1];
177                for (i, var) in self.base.iter().enumerate() {
178                    if self.vars[*var - 1].is_artificial() {
179                        if self.table.row(i + 1)[self.table.ncols() - 1] > 0.0 {
180                            /* Artificial variable might have taken slack var value */
181                            if self.vars[*var - 2].is_slack() {
182                                if self.table.row(0)[*var - 1] == 0.0 {
183                                    continue;
184                                }
185                            }
186                            return SimplexOutput::NoSolution;
187                        }
188                    }
189                }
190                if unique {
191                    return SimplexOutput::UniqueOptimum(optimum);
192                } else {
193                    return SimplexOutput::MultipleOptimum(optimum);
194                }
195            }
196        }
197    }
198
199    /// Gets the value of the N var in a solved problem
200    ///
201    /// ```rust
202    ///    let program = Simplex::minimize(&vec![-3.0, 1.0, -2.0])
203    ///    .with(vec![
204    ///        SimplexConstraint::LessThan(vec![2.0, -2.0, 3.0], 5.0),
205    ///        SimplexConstraint::LessThan(vec![1.0, 1.0, -1.0], 3.0),
206    ///        SimplexConstraint::LessThan(vec![1.0, -1.0, 1.0], 2.0),
207    ///    ]);
208    ///    let mut simplex = program.unwrap();
209    ///    assert_eq!(simplex.solve(), SimplexOutput::MultipleOptimum(-8.0));
210    ///    assert_eq!(simplex.get_var(1), Some(2.5));
211    ///    assert_eq!(simplex.get_var(2), Some(1.5));
212    ///    assert_eq!(simplex.get_var(3), Some(1.0));
213    /// ```
214    pub fn get_var(&self, var: usize) -> Option<f64> {
215        if var > self.objective.len() {
216            return None;
217        }
218        for (i, v) in self.base.iter().enumerate() {
219            if *v == var {
220                return Some(self.table.row(i + 1)[self.table.ncols() - 1]);
221            }
222        }
223        return Some(0.0);
224    }
225}
226
227pub struct SimplexMinimizerBuilder {
228    objective: Vec<f64>,
229}
230
231impl SimplexMinimizerBuilder {
232    /// Add constraints to the problem
233    ///
234    /// Add constraints to your problem. All variables are already restricted to be equal or more than zero.
235    /// Constraints must be of type SimplexConstraint. It will return a Result. If the generated matrix is not valid (wrong dimensions,...), it will return an Err(String).
236    ///
237    /// ```rust
238    /// let mut simplex = Simplex::minimize(&vec![1.0, -2.0])
239    /// .with(vec![
240    ///     SimplexConstraint::GreaterThan(vec![1.0, 1.0], 2.0),
241    ///     SimplexConstraint::GreaterThan(vec![-1.0, 1.0], 1.0),
242    ///     SimplexConstraint::LessThan(vec![0.0, 1.0], 3.0),
243    /// ]);
244    /// ```
245    /// would be like:
246    /// ```
247    /// minimize z = x - 2y
248    /// with
249    ///      x + y >= 2
250    ///      -x +y >= 1
251    ///      y <= 3
252    /// ```
253    pub fn with(self, constraints: Vec<SimplexConstraint>) -> Result<SimplexTable, String> {
254        let mut table = Vec::new();
255        let mut vars = Vec::new();
256        table.push(1.0);
257        for var in self.objective.iter() {
258            table.push(var * -1.0);
259            vars.push(SimplexVar::Real);
260        }
261        for (i, constraint) in constraints.iter().enumerate() {
262            match constraint {
263                SimplexConstraint::LessThan(_, _) => {
264                    table.push(0.0);
265                    vars.push(SimplexVar::Slack(i));
266                }
267                SimplexConstraint::GreaterThan(_, _) => {
268                    table.push(0.0);
269                    vars.push(SimplexVar::NegativeSlack(i));
270                }
271                _ => {}
272            }
273            table.push(f64::MIN);
274            vars.push(SimplexVar::Artificial(i));
275        }
276        table.push(0.0);
277
278        for (i, constraint) in constraints.iter().enumerate() {
279            table.push(0.0);
280            for a in constraint.get_vector() {
281                table.push(*a);
282            }
283            for var in vars.iter() {
284                match var {
285                    SimplexVar::Slack(j) => {
286                        if *j == i {
287                            table.push(1.0);
288                        } else {
289                            table.push(0.0);
290                        }
291                    }
292                    SimplexVar::NegativeSlack(j) => {
293                        if *j == i {
294                            table.push(-1.0);
295                        } else {
296                            table.push(0.0);
297                        }
298                    }
299                    SimplexVar::Artificial(j) => {
300                        if *j == i {
301                            table.push(1.0);
302                        } else {
303                            table.push(0.0);
304                        }
305                    }
306                    _ => {}
307                }
308            }
309            table.push(constraint.get_b());
310        }
311        let base: Vec<usize> = vars
312            .iter()
313            .enumerate()
314            .filter_map(|(i, x)| if x.is_artificial() { Some(i + 1) } else { None })
315            .collect();
316        let table = Array2::from_shape_vec((base.len() + 1, vars.len() + 2), table);
317        match table {
318            Ok(table) => Ok(SimplexTable {
319                objective: self.objective,
320                table: table,
321                base: base,
322                vars: vars,
323            }),
324            Err(_) => Err(String::from("Invalid matrix")),
325        }
326    }
327}
328
329/// Initialize your Linnear Programming problem
330///
331/// Use it at the beginning, to define your problem.
332///
333/// ```rust
334/// let mut problem = Simplex::minimize(&vec![5.0, -6.0]);
335/// ```
336///
337pub struct Simplex;
338
339impl Simplex {
340    // Initialize a LP minimization problem
341    //
342    // Currently, only minimization is provided. Maximization can be achieved by changing the signs
343    //
344    // ```rust
345    // let mut problem = Simplex::minimize(&vec![5.0, -6.0]);
346    // ```
347    // It initializes Simplex with a minimization objective function z = 5x - 6y
348    pub fn minimize(objective: &Vec<f64>) -> SimplexMinimizerBuilder {
349        SimplexMinimizerBuilder {
350            objective: objective.clone(),
351        }
352    }
353}