Sunday, January 30, 2011

Two-Phase Simplex Algorithm - Python Code

Today I post code that carries out the well known Two-Phase (or Artificial Variable) Simplex algorithm presented by Papadimitriou and Steiglitz. This method can be used to define an initial basic feasible solution (bfs) in a linear program where not all constraints are given by less-than linear inequalities but instead some linear equations are present. If all constraints were given by linear inequalities we could just introduce a slack variable per inequality, but if some linear equations are present we can use the Two-Phase Simplex algorithm.


-------------------------------------------------------------

def printTableu(tableu):
print '----------------------'
for row in tableu:
print row
print '----------------------'
return


def pivotOn(tableu, row, col):
j = 0
pivot = tableu[row][col]
for x in tableu[row]:
tableu[row][j] = tableu[row][j] / pivot
j += 1
i = 0
for xi in tableu:
if i != row:
ratio = xi[col]
j = 0
for xij in xi:
xij -= ratio * tableu[row][j]
tableu[i][j] = xij
j += 1
i += 1
return tableu


# assuming tablue in standard form with basis formed in last m columns
def phase_1_simplex(tableu):

THETA_INFINITE = -1
opt = False
unbounded = False
n = len(tableu[0])
m = len(tableu) - 2

while ((not opt) and (not unbounded)):
min = 0.0
pivotCol = j = 1
while(j < (n-m)):
cj = tableu[1][j]
if (cj < min):
min = cj
pivotCol = j
j += 1
if min == 0.0:
opt = True
continue
pivotRow = i = 0
minTheta = THETA_INFINITE
for xi in tableu:
if (i > 1):
xij = xi[pivotCol]
if xij > 0:
theta = (xi[0] / xij)
if (theta < minTheta) or (minTheta == THETA_INFINITE):
minTheta = theta
pivotRow = i
i += 1
if minTheta == THETA_INFINITE:
unbounded = True
continue
tableu = pivotOn(tableu, pivotRow, pivotCol)
return tableu

def simplex(tableu):

THETA_INFINITE = -1
opt = False
unbounded = False
n = len(tableu[0])
m = len(tableu) - 1

while ((not opt) and (not unbounded)):
min = 0.0
pivotCol = j = 0
while(j < (n-m)):
cj = tableu[0][j]
if (cj < min) and (j > 0):
min = cj
pivotCol = j
j += 1
if min == 0.0:
opt = True
continue
pivotRow = i = 0
minTheta = THETA_INFINITE
for xi in tableu:
if (i > 0):
xij = xi[pivotCol]
if xij > 0:
theta = (xi[0] / xij)
if (theta < minTheta) or (minTheta == THETA_INFINITE):
minTheta = theta
pivotRow = i
i += 1
if minTheta == THETA_INFINITE:
unbounded = True
continue
tableu = pivotOn(tableu, pivotRow, pivotCol)
return tableu

def drive_out_artificial_basis(tableu):
n = len(tableu[0])
j = n - 1
isbasis = True
while(j > 0):
found = False
i = -1
row = 0
for xi in tableu:
i += 1
if (xi[j] == 1):
if (found):
isbasis = False
continue
elif (i > 1):
row = i
found = True
elif (xi[0] != 0):
isbasis = False
continue
if (isbasis and found):
if (j >= n):
tableu = pivotOn(tableu, row, j)
else:
return tableu
j -= 1
return tableu

def two_phase_simpelx(tableu):
infeasible = False
tableu = phase_1_simplex(tableu)
sigma = tableu[1][0]
if (sigma > 0):
infeasible = True
print 'infeasible'
else:
#sigma is equals to zero
tableu = drive_out_artificial_basis(tableu)
m = len(tableu) - 2
n = len(tableu[0])
n -= m
tableu.pop(1)
i = 0
while (i < len(tableu)):
tableu[i] = tableu[i][:n]
i += 1
tableu = simplex(tableu)
return tableu

def getTableu(c, eqs, b):
#assume b >= 0 so if there is any b[i] negative make sure to enter
#it possitive by multiplying (-1 * eqs[i]) and (-1 * b[i]) for all i
tableu = []
m = len(eqs)
n = len(c)
c.insert(0, 0.0)
artificial = []
sigma = [0.0]
i = 0
while (i < n):
sigma.append(0.0)
i += 1
i = 0
while (i < m):
artificial.append(0.0)
sigma.append(1.0)
i += 1
c.extend(artificial)
tableu.append(c)
tableu.append(sigma)
i = 0
for eq in eqs:
eq.insert(0, b[i])
eq.extend(artificial)
eq[n+1+i] = 1.0
tableu.append(eq)
i += 1
i = 0
for xi in tableu:
if (i > 1):
j = 0
for xij in xi:
tableu[1][j] -= xij
j += 1
i += 1
return tableu

-------------------------------------------------------------
Given a Linear Program of the form:
Minimize:      c·x
subject to:    Axb


the function getTableu takes the cost vector c, the matrix A, and the vector b. You must make sure to do the necessary operations to ensure that b0.


So getTableu will return the tableu ready for the function two_phase_simpelx to do the work and return the final tableu from which we can get the information about the optimal bfs.


So I ran the following example:
Minimize: z = x1 + x2 + x3 + x4 + x5


Subject to: 3x1 + 2x2 + x3               = 1
                 5x1 +  x2 +  x3 + x4        = 3
                 2x1 + 5x2 + x3 +       x5  = 4
as:
----------------------------------------------------------

c = [ 1.0, 1.0, 1.0, 1.0, 1.0,]

eq1 = [ 3.0 ,  2.0 , 1.0 ,  0.0,  0.0]
eq2 = [ 5.0 ,  1.0 , 1.0 ,  1.0,  0.0]
eq3 = [ 2.0 ,  5.0 , 1.0 ,  0.0,  1.0]

b = [1.0 , 3.0 , 4.0]

eqs = []
eqs.append(eq1)
eqs.append(eq2)
eqs.append(eq3)

tableu = getTableu(c,eqs,b)
printTableu(tableu)
tableu = two_phase_simpelx(tableu)
printTableu(tableu)
print 'minimum cost is = {}'.format( -tableu[0][0])
------------------------------------------------------
Here I end up printing the original tableu used by the phase 1 of he algorithm, and then the final tableu produced by the simplex. I also print information telling us if we discovered an optimal solution, or the polytope is unbounded or infeasible.



MArio

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. So I don't really understand what the sigma variable is for, could you please explain that for me?

    ReplyDelete