Solve linear programming problems using simplex algorithm.
Project description
🪄Sapro
Solve Linear Programming Problems using Simplex Algorithm.
‼️This is a demo implementation of the Simplex Algorithm, homework of the Operations Research class. Bugs are expected.‼️
Installation
Install via PyPI:
$ pip install sapro==0.4.0b1
CLI Usage
sapro exposes a command-line interface.
$ sapro solve --max x1+2*x2 \
-c"x1+x2<=4" \
-c"-2*x1+x2<=1" \
-c"x1<=3" \
--slack x3
z = 7 when x1 = 1, x2 = 3, x3 = 0, x4 = 0, x5 = 2
Run sapro solve -h for more info.
GUI Usage
sapro exposes a demo web GUI using wsgiref. To launch the WSGI server, use the following command:
$ sapro webui --open
Serving on 0.0.0.0:5678...
Press Ctrl+C to quit.
Open http://localhost:5678 in your browser to use the interface.
To use a custom host and port, specify the -H and -p arguments:
$ sapro webui -H 127.0.0.1 -p 8080
API Usage
To use sapro in your program, you can import the sapro module.
from sapro import *
Variables
Variables are defined using the Variable class:
x1 = Variable("x1")
I recommend using the same python variable name as the variable itself.
To conveniently define a series of variables e.g. $x_1$ ~ $x_4$ you can use the sequence method on Variable:
x1, x2, x3, x4 = Variable.sequence('x', 4)
You can also omit the last parameter to create a "variable generator":
x = Variable.sequence('x')
x1 = next(x)
x2 = next(x)
This is useful when you want to continue using the variable prefix for slack variables.
Expressions
Expressions are created in a manner as if you are defining them in python:
expr = -x1 + 2*x2
double_expr = 2 * expr
print(double_expr) # -2x1 + 4x2
Note that the multiplication sign (*) is omitted when printed, but is necessary in the expression definition.
Because this is a LP solver, only linear addition of the variables are allowed.
Constraints
Constraints are created in a manner as if you are comparing an expression to a constant in python:
cons = (-2*x1 + x2 <= 1)
print(cons) # -2x1 + x2 <= 1
cons.canonicalize(Variable("x3"))
print(cons) # -2x1 + x2 + x3 == 1
The right hand side of a constraint must be a single number. Only operators that permits equality are allowed (<=, == and >=).
Define LP Problem
To define a LP problem, use the Simplex class.
Here is a simple LP problem. You can probably answer it in your head:
$$ \begin{aligned} \max \space & x_1 + 2x_2 \ \text{s.t.} \space & x_1 + x_2 \le 4, \ & -2x_1 + x_2 \le 1, \ & x_1 \le 3, \ & x_i \ge 0, \space i = 1, 2, 3 \end{aligned} $$
Its corresponding definition in python:
x = Variable.sequence('x')
x1, x2 = next(x), next(x)
problem = Simplex(
# max
x1 + 2*x2,
# s.t.
x1 + x2 <= 4,
-2*x1 + x2 <= 1,
x1 >= 3,
maximize=True,
slack_var_generator=x
)
Pretty much the same, right? Here's what happened:
- The first positional argument is an expression of what the LP problem should optimize.
- The rest of the positional arguments are the constraints.
maximize=Truetells the algorithm to maximize instead of minimizing the target, which is the default behaviour.slack_var_generator=xtells the algorithm to continue using the generatorxto generate slack variables. In this case, sincex1andx2are consumed from the generator, slack variables will start their names fromx3. If you omit this parameter, slack variables will have the prefixs, unless you specify the parameterslack_var_prefix.
The intrinsic bounds $x_i \ge 0$ always take effect. You don't need to explicitly specify them.
Solving LP Problem
To solve a LP problem using simplex, the first thing to do is to determine the base variables. There are three ways to do this:
-
Select slack variables as base. This would be the most straightforward way for problems whose constraints are all inequations. After adding slack variables, their coefficient in the constraints are naturally 1 or -1, which composes a inversible matrix.
No code is needed to use this method, as this is the default approach used by the algorithm. But you need to make sure that there is enough slack variables for the base i.e. all constraints are inequations.
-
Use the Two-Phase algorithm. This approach is suitable for normal LP problems.
from sapro.error import LPError try: # the method returns a generator # we need to consume all elements # to complete the algorithm list(problem.two_phase()) print('feasible solution:') print(problem.result) except LPError as e: print('unsolvable problem:', e)
-
Manually specify the bases. Make sure the number of base variables matches the number of constraints.
problem.set_base_vars([x1, x2, x3]) # a list of variables
Now use the solve method to solve the problem:
from sapro.error import LPError
try:
# the method returns a generator
# we need to consume all elements
# to complete the algorithm
list(problem.solve())
print('best solution:')
print(problem.result)
except LPError as e:
print('unsolvable problem:', e)
Per-Step Simplex Tableau
You may notice that we haven't used the generator returned by Simplex.two_phase and Simplex.solve. They yield LPStep objects, which has 3 fields:
tableau: The simplex tableau after the current step.enter: The variable that became base.leave: The variable that left base.
Example of print the tableau:
for i, step in enumerate(problem.solve()):
print(f'Step {i+1}: ({step.enter} enters, {step.leave} leaves)')
print(step.tableau)
If you pass yield_initial_tableau=True to either of the two methods, then an table containing the initial data will be yielded, before any variable has entered / left the base.
For more detailed usage, refer to the in-code documentations.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sapro-0.4.0b1.tar.gz.
File metadata
- Download URL: sapro-0.4.0b1.tar.gz
- Upload date:
- Size: 24.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b5545ce3d7c4434383604de97a71343fd7b8e80d9b7ee62d53c7f853497ab70
|
|
| MD5 |
a8fa3ec789537637a2229f7a096f24ab
|
|
| BLAKE2b-256 |
17aa5f7b905b92720dcbea6afb60c0cff26839892f5686feded07668b823a5d8
|
File details
Details for the file sapro-0.4.0b1-py3-none-any.whl.
File metadata
- Download URL: sapro-0.4.0b1-py3-none-any.whl
- Upload date:
- Size: 28.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10c42ad6ab01ebbc5c09b017a9339b5bb75e9e9cb5ab70c4a5b26b724d662529
|
|
| MD5 |
ef10be4276aaac78c3bdee6202a96d4a
|
|
| BLAKE2b-256 |
584200ef40afb4917ef7bedf1283195c9ef5fa6f931f5eff5439b53a32c4c3e1
|