This project implements a Simplex solver basically follows from Dimitris Bertsimas's famous book Introduction to Linear Optimization. In order to solve a standard form LP problem, two phases are needed - the first phase checks if the problem is solvable and gives a feasible solution if so, the second phase checks if the problem is bounded and gives the optimal solution if so. Besides the final primal solution for the problem, this project can be set to provide step-by-step solutions as well as dual solutions.
If you just want to know how to use the script, you can SKIP this section and take a look at SECTION 3 instead.
Pivoting is explain first since it is the core of the whole algorithm.
Given an entering variable with index
Then we update the base
This section explains how does the algorithm choose the appropriate entering variable and leaving variable given coefficient matrix
To avoid degeneracy, the implementation follows the Bland's rule, i.e.
- For the entering variable
$q$ , choose the lowest-numbered non-basic column with a negative reduced cost$\boldsymbol{r}$ - For the leaving variable
$p$ , choose the row with the lowest ratio (i.e. "b/y ratio") between the right hand side$\boldsymbol{b}$ and the coefficient in the pivot tableau$\boldsymbol{A}$ where the coefficient is greater than zero. If the minimum ratio is shared by several rows, choose the row with the lowest-numbered column basic in it.
Now we take a look at how is the first phase implemented.
Given a LP problem in standard form, i.e.
First transform it to the corresponding auxiliary problem by adding manual variables
Multiply
The algorithm finds an entering variable
Before returning the feasible solution obtained from auxiliary problem, the algorithm makes sure that the solution doesn't contain any manual variable. If there were manual variables inside the solution, keep pivoting to replace them with non-manual variables or remove redundant constraints.
Finally, give out the feasible solution of the primal problem as will as the respective base.
The first phase has generated a BFS for the primal problem and we can start solving
The solving procedure in phase two is similar to the one described in section 2.3. However, this time, the algorithm cares about the boundness rather than feasibility. After selecting an entering variable
Notice that the objective value during pivoting is the opposite number of the true objective, hence the algorithm negates the obtained objective value as output at last.
The algorithm records the base at the start of the second phase and that base will be used to find the dual values. Notice that the dual values are the opposite numbers of corresponding reduced costs, so the algorithm can obtain those dual values easily.
The tested environment (with Anaconda under Windows 10) is listed below:
- Python 3.8.3
- NumPy 1.18.5
- pandas 1.0.5
You MUST provide the script with the path to the folder that contains input files. This script only accepts csv files for input, with names A.csv
, b.csv
, c.csv
for the same meaning in equation data\
.
Suppose the following is your file structure (Meaning that the script simplex.py
and the folder that contains input files are under the same directory. Note this repo contains a few more subfolders in data\
)
.
├── data
│ ├── A.csv
│ ├── b.csv
│ └── c.csv
└── simplex.py
Then you should pass the path of folder data\
to the script. For example,
python simplex.py data\
Note: Your input must at least contain A.csv
, b.csv
and c.csv
, missing any file will cause an exception, reminding you to check the path. The solution file is not required.
The script is capable of providing step-by-step solutions. If you want to use that functionality, simply add -s
or -step
after the basic command shown in section 3.2.1. For example, with the previous file structure, you can type the following command
python simplex.py data\ -s
or
python simplex.py data\ --step
The printed Simplex tableau can be represented as the table shown below, where
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ A ┃ b ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┃ ┃ ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━┫
┃ r ┃ z ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━┛
Note: You should turn this functionality off when you want to test run time, since printing requires lots of time.
The script is also capable of providing the final dual solution. If you want to use that functionality, simply add -d
or --dual
after the basic command shown in section 3.2.1. For example, with the previous file structure, you can type the following command
python simplex.py data\ -d
or
python simplex.py data\ --dual
You can also combine commands in section 3.2.2 with commands in section 3.2.3. For example,
python simplex.py data\ -s -d
or
python simplex.py data\ --step --dual
If you forget how to run the code, the help
functionality will help you
python simplex.py -h
or
python simplex.py --help
All testing examples in this section are provided under the folder data\
. In addition to the input files, each subfolder also contains optimal solutions to that problem, you can use them to check the algorithm.
Note: The first three sets of input test regular cases while the last three sets of input test some of the tricky cases, e.g. degenerate case, unbounded case and infeasible case.
The folder data\1\
contains the following inputs
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
-------------------------------------
Objective value: 3.0000
-------------------------------------
Value of each variable:
x_1 = 1.0000
x_2 = 1.6667
x_3 = 1.3333
x_4 = 0.0000
x_5 = 0.0000
x_6 = 0.0000
-------------------------------------
Value of dual variable:
d_1 = -0.0000
d_2 = -0.0000
d_3 = -0.0000
-------------------------------------
Total number of pivots: 3
-------------------------------------
Run time: 0.01399993896484375 second
The folder data\2\
contains the following inputs
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
-------------------------------------
Objective value: -10000.0000
-------------------------------------
Value of each variable:
x_1 = 0.0000
x_2 = 0.0000
x_3 = 10000.0000
x_4 = 1.0000
x_5 = 100.0000
x_6 = 0.0000
-------------------------------------
Value of dual variable:
d_1 = -100.0000
d_2 = -19.0000
d_3 = -0.0000
-------------------------------------
Total number of pivots: 5
-------------------------------------
Run time: 0.014025449752807617 second
The folder data\3\
contains a large input and it's omitted here.
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
-------------------------------------
Objective value: 196200.0000
-------------------------------------
Value of each variable:
x_1 = 0.0000
x_2 = 0.0000
x_3 = 0.0000
x_4 = 0.0000
x_5 = 0.0000
x_6 = 1100.0000
x_7 = 300.0000
x_8 = 0.0000
x_9 = 1200.0000
x_10 = 600.0000
x_11 = 400.0000
x_12 = 0.0000
x_13 = 0.0000
x_14 = 400.0000
x_15 = 900.0000
x_16 = 0.0000
x_17 = 0.0000
x_18 = 0.0000
x_19 = 1700.0000
x_20 = 0.0000
x_21 = 300.0000
-------------------------------------
Value of dual variable:
d_1 = -2.0000
d_2 = -4.0000
d_3 = -1.0000
d_4 = -0.0000
d_5 = -0.0000
d_6 = -0.0000
d_7 = -0.0000
d_8 = -1.0000
d_9 = -2.0000
-------------------------------------
Total number of pivots: 25
-------------------------------------
Run time: 0.024999380111694336 second
The folder data\4\
contains the following inputs, which tests the degenerate case
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
-------------------------------------
Objective value: -1.2500
-------------------------------------
Value of each variable:
x_1 = 0.7500
x_2 = 0.0000
x_3 = 0.0000
x_4 = 1.0000
x_5 = 0.0000
x_6 = 1.0000
x_7 = 0.0000
-------------------------------------
Value of dual variable:
d_1 = -0.0000
d_2 = -1.5000
d_3 = -1.2500
-------------------------------------
Total number of pivots: 9
-------------------------------------
Run time: 0.013999223709106445 second
The folder data\5\
contains the following inputs, which tests the unbounded case
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
Unbounded!
-------------------------------------
Run time: 0.000989675521850586 second
The folder data\6\
contains the following inputs, which tests the infeasible case
The output of objective value, primal solution, dual solution, number of pivots and run time are listed below
Infeasible!
-------------------------------------
Run time: 0.0009996891021728516 second
This project implements a prototype of Simplex solver, which solves LP problems in two phases. The implementation is capable of detecting unboundedness, infeasibility and avoiding degeneracy. Although the efficiency of this implementation might not be the best, this project should still be a good start for linear optimization.