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}