-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5e0ffb7
Showing
9 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# OP-Research | ||
|
||
A unified collection of algrithms for solving operational research problems in an effiecient way | ||
|
||
#### Purpose of the Package | ||
The purpose of the package is to provide a collection of tools to solve operation research problems and help researchers. | ||
### Features | ||
The current implementation uses two phase method and is able to identify case for Infeasible solution, Unbounded solution, Degeneracy and Alternate Solution. In case of Infeasible solution and Unbounded solution it raises an ValueError and in case of Degeneracy and Alternate Solution it gives a warning and returns a optimum solution. | ||
|
||
The constraints right hand side should be positive and all variables should hold non-negativity conditions. | ||
|
||
## Rules for constraint representation: | ||
|
||
Each variable should have coefficient if it is in constraint i.e x_1 is not allowd instead use 1x_1. Note that it is not necessary to represent each variable in a constraint, but if a variable is there then it should have a coefficient. | ||
Only single spaces should be used. | ||
For a variable x_i i should be an integer in [1, num_vars], where num_vars is number of variables | ||
Objective function should be a tuple with first element as objective ie to maximize or minimize and second element should value that is to be optimized. | ||
|
||
Simplex solution solver | ||
The package can be found on pypi hence you can install it using pip | ||
|
||
## Installation | ||
pip install op_research | ||
### Usage | ||
|
||
|
||
>>> from op_research import Simplex | ||
>>> objective = ('maximize', '7x_1 + 4x_2') | ||
>>> constraints = ['5x_1 + 2x_2 = 7', '1x_1 + 8x_2 >= 9', '3x_1 + 4x_2 <= 8'] | ||
>>> Lp_system = Simplex(num_vars=2, constraints=constraints, objective_function=objective) | ||
>>> print(Lp_system.solution) | ||
{'x_1': Fraction(6, 7), 'x_2': Fraction(19, 14)} | ||
|
||
|
||
|
||
## Contribution | ||
Contributions are welcome Notice a bug let us know. Thanks | ||
|
||
### Author | ||
Main Maintainer: Rehan Ahmed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
OP-Research | ||
=========== | ||
|
||
A unified collection of algrithms for solving operational research | ||
problems in an effiecient way | ||
|
||
Purpose of the Package | ||
---------------------- | ||
|
||
The purpose of the package is to provide a collection of tools to solve | ||
operation research problems and help researchers. ### Features The | ||
current implementation uses two phase method and is able to identify | ||
case for Infeasible solution, Unbounded solution, Degeneracy and | ||
Alternate Solution. In case of Infeasible solution and Unbounded | ||
solution it raises an ValueError and in case of Degeneracy and Alternate | ||
Solution it gives a warning and returns a optimum solution. | ||
|
||
The constraints right hand side should be positive and all variables | ||
should hold non-negativity conditions. | ||
|
||
Rules for constraint representation: | ||
------------------------------------ | ||
|
||
Each variable should have coefficient if it is in constraint i.e x_1 is | ||
not allowd instead use 1x_1. Note that it is not necessary to represent | ||
each variable in a constraint, but if a variable is there then it should | ||
have a coefficient. Only single spaces should be used. For a variable | ||
x_i i should be an integer in [1, num_vars], where num_vars is number of | ||
variables Objective function should be a tuple with first element as | ||
objective ie to maximize or minimize and second element should value | ||
that is to be optimized. | ||
|
||
Simplex solution solver The package can be found on pypi hence you can | ||
install it using pip | ||
|
||
Installation | ||
------------ | ||
|
||
pip install op_research ### Usage | ||
|
||
from op_research import Simplex objective = (‘maximize’, ‘7x_1 | ||
+ 4x_2’) constraints = [‘5x_1 + 2x_2 = 7’, ‘1x_1 + 8x_2 >= 9’, | ||
‘3x_1 + 4x_2 <= 8’] Lp_system = Simplex(num_vars=2, | ||
constraints=constraints, objective_function=objective) | ||
print(Lp_system.solution) {‘x_1’: Fraction(6, 7), ‘x_2’: | ||
Fraction(19, 14)} | ||
|
||
Contribution | ||
------------ | ||
|
||
Contributions are welcome Notice a bug let us know. Thanks | ||
|
||
Author | ||
~~~~~~ | ||
|
||
Main Maintainer: Rehan Ahmed |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__version__ = '0.0.1' | ||
|
||
from oreaserch import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
from fractions import Fraction | ||
from warnings import warn | ||
|
||
|
||
class Simplex(object): | ||
def __init__(self, num_vars, constraints, objective_function): | ||
|
||
self.num_vars = num_vars | ||
self.constraints = constraints | ||
self.objective = objective_function[0] | ||
self.objective_function = objective_function[1] | ||
self.coeff_matrix, self.r_rows, self.num_s_vars, self.num_r_vars = self.construct_matrix_from_constraints() | ||
del self.constraints | ||
self.basic_vars = [0 for i in range(len(self.coeff_matrix))] | ||
self.phase1() | ||
r_index = self.num_r_vars + self.num_s_vars | ||
|
||
for i in self.basic_vars: | ||
if i > r_index: | ||
raise ValueError("Infeasible solution") | ||
|
||
self.delete_r_vars() | ||
|
||
if 'min' in self.objective.lower(): | ||
self.solution = self.objective_minimize() | ||
|
||
else: | ||
self.solution = self.objective_maximize() | ||
self.optimize_val = self.coeff_matrix[0][-1] | ||
|
||
def construct_matrix_from_constraints(self): | ||
num_s_vars = 0 # number of slack and surplus variables | ||
num_r_vars = 0 # number of additional variables to balance equality and less than equal to | ||
for expression in self.constraints: | ||
if '>=' in expression: | ||
num_s_vars += 1 | ||
|
||
elif '<=' in expression: | ||
num_s_vars += 1 | ||
num_r_vars += 1 | ||
|
||
elif '=' in expression: | ||
num_r_vars += 1 | ||
total_vars = self.num_vars + num_s_vars + num_r_vars | ||
|
||
coeff_matrix = [[Fraction("0/1") for i in range(total_vars+1)] for j in range(len(self.constraints)+1)] | ||
s_index = self.num_vars | ||
r_index = self.num_vars + num_s_vars | ||
r_rows = [] # stores the non -zero index of r | ||
for i in range(1, len(self.constraints)+1): | ||
constraint = self.constraints[i-1].split(' ') | ||
|
||
for j in range(len(constraint)): | ||
|
||
if '_' in constraint[j]: | ||
coeff, index = constraint[j].split('_') | ||
if constraint[j-1] is '-': | ||
coeff_matrix[i][int(index)-1] = Fraction("-" + coeff[:-1] + "/1") | ||
else: | ||
coeff_matrix[i][int(index)-1] = Fraction(coeff[:-1] + "/1") | ||
|
||
elif constraint[j] == '<=': | ||
coeff_matrix[i][s_index] = Fraction("1/1") # add surplus variable | ||
s_index += 1 | ||
|
||
elif constraint[j] == '>=': | ||
coeff_matrix[i][s_index] = Fraction("-1/1") # slack variable | ||
coeff_matrix[i][r_index] = Fraction("1/1") # r variable | ||
s_index += 1 | ||
r_index += 1 | ||
r_rows.append(i) | ||
|
||
elif constraint[j] == '=': | ||
coeff_matrix[i][r_index] = Fraction("1/1") # r variable | ||
r_index += 1 | ||
r_rows.append(i) | ||
|
||
coeff_matrix[i][-1] = Fraction(constraint[-1] + "/1") | ||
|
||
return coeff_matrix, r_rows, num_s_vars, num_r_vars | ||
|
||
def phase1(self): | ||
# Objective function here is minimize r1+ r2 + r3 + ... + rn | ||
r_index = self.num_vars + self.num_s_vars | ||
for i in range(r_index, len(self.coeff_matrix[0])-1): | ||
self.coeff_matrix[0][i] = Fraction("-1/1") | ||
coeff_0 = 0 | ||
for i in self.r_rows: | ||
self.coeff_matrix[0] = add_row(self.coeff_matrix[0], self.coeff_matrix[i]) | ||
self.basic_vars[i] = r_index | ||
r_index += 1 | ||
s_index = self.num_vars | ||
for i in range(1, len(self.basic_vars)): | ||
if self.basic_vars[i] == 0: | ||
self.basic_vars[i] = s_index | ||
s_index += 1 | ||
|
||
# Run the simplex iterations | ||
key_column = max_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] > 0 | ||
|
||
while condition is True: | ||
|
||
key_row = self.find_key_row(key_column = key_column) | ||
self.basic_vars[key_row] = key_column | ||
pivot = self.coeff_matrix[key_row][key_column] | ||
self.normalize_to_pivot(key_row, pivot) | ||
self.make_key_column_zero(key_column, key_row) | ||
|
||
key_column = max_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] > 0 | ||
|
||
def find_key_row(self, key_column): | ||
min_val = float("inf") | ||
min_i = 0 | ||
for i in range(1, len(self.coeff_matrix)): | ||
if self.coeff_matrix[i][key_column] > 0: | ||
val = self.coeff_matrix[i][-1] / self.coeff_matrix[i][key_column] | ||
if val < min_val: | ||
min_val = val | ||
min_i = i | ||
if min_val == float("inf"): | ||
raise ValueError("Unbounded solution") | ||
if min_val == 0: | ||
warn("Dengeneracy") | ||
return min_i | ||
|
||
def normalize_to_pivot(self, key_row, pivot): | ||
for i in range(len(self.coeff_matrix[0])): | ||
self.coeff_matrix[key_row][i] /= pivot | ||
|
||
def make_key_column_zero(self, key_column, key_row): | ||
num_columns = len(self.coeff_matrix[0]) | ||
for i in range(len(self.coeff_matrix)): | ||
if i != key_row: | ||
factor = self.coeff_matrix[i][key_column] | ||
for j in range(num_columns): | ||
self.coeff_matrix[i][j] -= self.coeff_matrix[key_row][j] * factor | ||
|
||
def delete_r_vars(self): | ||
for i in range(len(self.coeff_matrix)): | ||
non_r_length = self.num_vars + self.num_s_vars + 1 | ||
length = len(self.coeff_matrix[i]) | ||
while length != non_r_length: | ||
del self.coeff_matrix[i][non_r_length-1] | ||
length -= 1 | ||
|
||
def update_objective_function(self): | ||
objective_function_coeffs = self.objective_function.split() | ||
for i in range(len(objective_function_coeffs)): | ||
if '_' in objective_function_coeffs[i]: | ||
coeff, index = objective_function_coeffs[i].split('_') | ||
if objective_function_coeffs[i-1] is '-': | ||
self.coeff_matrix[0][int(index)-1] = Fraction(coeff[:-1] + "/1") | ||
else: | ||
self.coeff_matrix[0][int(index)-1] = Fraction("-" + coeff[:-1] + "/1") | ||
|
||
def check_alternate_solution(self): | ||
for i in range(len(self.coeff_matrix[0])): | ||
if self.coeff_matrix[0][i] and i not in self.basic_vars[1:]: | ||
warn("Alternate Solution exists") | ||
break | ||
|
||
def objective_minimize(self): | ||
self.update_objective_function() | ||
|
||
for row, column in enumerate(self.basic_vars[1:]): | ||
if self.coeff_matrix[0][column] != 0: | ||
self.coeff_matrix[0] = add_row(self.coeff_matrix[0], multiply_const_row(-self.coeff_matrix[0][column], self.coeff_matrix[row+1])) | ||
|
||
key_column = max_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] > 0 | ||
|
||
while condition is True: | ||
|
||
key_row = self.find_key_row(key_column = key_column) | ||
self.basic_vars[key_row] = key_column | ||
pivot = self.coeff_matrix[key_row][key_column] | ||
self.normalize_to_pivot(key_row, pivot) | ||
self.make_key_column_zero(key_column, key_row) | ||
|
||
key_column = max_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] > 0 | ||
|
||
solution = {} | ||
for i, var in enumerate(self.basic_vars[1:]): | ||
if var < self.num_vars: | ||
solution['x_'+str(var+1)] = self.coeff_matrix[i+1][-1] | ||
|
||
for i in range(0, self.num_vars): | ||
if i not in self.basic_vars[1:]: | ||
solution['x_'+str(i+1)] = Fraction("0/1") | ||
self.check_alternate_solution() | ||
return solution | ||
|
||
def objective_maximize(self): | ||
self.update_objective_function() | ||
|
||
for row, column in enumerate(self.basic_vars[1:]): | ||
if self.coeff_matrix[0][column] != 0: | ||
self.coeff_matrix[0] = add_row(self.coeff_matrix[0], multiply_const_row(-self.coeff_matrix[0][column], self.coeff_matrix[row+1])) | ||
|
||
key_column = min_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] < 0 | ||
|
||
while condition is True: | ||
|
||
key_row = self.find_key_row(key_column = key_column) | ||
self.basic_vars[key_row] = key_column | ||
pivot = self.coeff_matrix[key_row][key_column] | ||
self.normalize_to_pivot(key_row, pivot) | ||
self.make_key_column_zero(key_column, key_row) | ||
|
||
key_column = min_index(self.coeff_matrix[0]) | ||
condition = self.coeff_matrix[0][key_column] < 0 | ||
|
||
solution = {} | ||
for i, var in enumerate(self.basic_vars[1:]): | ||
if var < self.num_vars: | ||
solution['x_'+str(var+1)] = self.coeff_matrix[i+1][-1] | ||
|
||
for i in range(0, self.num_vars): | ||
if i not in self.basic_vars[1:]: | ||
solution['x_'+str(i+1)] = Fraction("0/1") | ||
|
||
self.check_alternate_solution() | ||
|
||
return solution | ||
|
||
def add_row(row1, row2): | ||
row_sum = [0 for i in range(len(row1))] | ||
for i in range(len(row1)): | ||
row_sum[i] = row1[i] + row2[i] | ||
return row_sum | ||
|
||
def max_index(row): | ||
max_i = 0 | ||
for i in range(0, len(row)-1): | ||
if row[i] > row[max_i]: | ||
max_i = i | ||
|
||
return max_i | ||
|
||
def multiply_const_row(const, row): | ||
mul_row = [] | ||
for i in row: | ||
mul_row.append(const*i) | ||
return mul_row | ||
|
||
def min_index(row): | ||
min_i = 0 | ||
for i in range(0, len(row)): | ||
if row[min_i] > row[i]: | ||
min_i = i | ||
|
||
return min_i |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
[tool.poetry] | ||
name = "op_research" | ||
version = "0.0.1" | ||
description = "package for solving operational research problems" | ||
authors = ["rehan ahmed <[email protected]>"] | ||
license = "MIT" | ||
readme = "README.md" | ||
homepage = "https://github.com/Rehan-stack/op-research" | ||
repository = "https://github.com/Rehan-stack/op-research" | ||
keywords = ["op-research","operational-research","simplex-method","two-phase method","math"] | ||
|
||
|
||
|
||
classifiers=[ | ||
"License :: OSI Approved :: MIT License", | ||
"Programming Language :: Python :: 3", | ||
"Programming Language :: Python :: 3.6", | ||
"Programming Language :: Python :: 3.7", | ||
"Programming Language :: Python :: 3.8", | ||
"Programming Language :: Python :: 3.9", | ||
"Operating System :: OS Independent", | ||
] | ||
[tool.poetry.dependencies] | ||
python = "^3.10" | ||
|
||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from op_research.oreaserch import __version__ | ||
import unittest | ||
|
||
def test_version(): | ||
assert __version__ == '0.0.1' | ||
|
||
|