Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Rehan-stack committed Dec 27, 2022
0 parents commit 5e0ffb7
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
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
56 changes: 56 additions & 0 deletions README.rst
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 added dist/op_research-0.0.1-py3-none-any.whl
Binary file not shown.
Binary file added dist/op_research-0.0.1.tar.gz
Binary file not shown.
3 changes: 3 additions & 0 deletions op_research/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = '0.0.1'

from oreaserch import *
256 changes: 256 additions & 0 deletions op_research/oreaserch.py
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
29 changes: 29 additions & 0 deletions pyproject.toml
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 added tests/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions tests/test.py
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'


0 comments on commit 5e0ffb7

Please sign in to comment.