-
Notifications
You must be signed in to change notification settings - Fork 43
/
simplex.py
186 lines (137 loc) · 5.15 KB
/
simplex.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import heapq
'''
Return a rectangular identity matrix with the specified diagonal entiries, possibly
starting in the middle.
'''
def identity(numRows, numCols, val=1, rowStart=0):
return [[(val if i == j else 0) for j in range(numCols)]
for i in range(rowStart, numRows)]
'''
standardForm: [float], [[float]], [float], [[float]], [float], [[float]], [float] -> [float], [[float]], [float]
Convert a linear program in general form to the standard form for the
simplex algorithm. The inputs are assumed to have the correct dimensions: cost
is a length n list, greaterThans is an n-by-m matrix, gtThreshold is a vector
of length m, with the same pattern holding for the remaining inputs. No
dimension errors are caught, and we assume there are no unrestricted variables.
'''
def standardForm(cost, greaterThans=[], gtThreshold=[], lessThans=[], ltThreshold=[],
equalities=[], eqThreshold=[], maximization=True):
newVars = 0
numRows = 0
if gtThreshold != []:
newVars += len(gtThreshold)
numRows += len(gtThreshold)
if ltThreshold != []:
newVars += len(ltThreshold)
numRows += len(ltThreshold)
if eqThreshold != []:
numRows += len(eqThreshold)
if not maximization:
cost = [-x for x in cost]
if newVars == 0:
return cost, equalities, eqThreshold
newCost = list(cost) + [0] * newVars
constraints = []
threshold = []
oldConstraints = [(greaterThans, gtThreshold, -1), (lessThans, ltThreshold, 1),
(equalities, eqThreshold, 0)]
offset = 0
for constraintList, oldThreshold, coefficient in oldConstraints:
constraints += [c + r for c, r in zip(constraintList,
identity(numRows, newVars, coefficient, offset))]
threshold += oldThreshold
offset += len(oldThreshold)
return newCost, constraints, threshold
def dot(a,b):
return sum(x*y for x,y in zip(a,b))
def column(A, j):
return [row[j] for row in A]
def transpose(A):
return [column(A, j) for j in range(len(A[0]))]
def isPivotCol(col):
return (len([c for c in col if c == 0]) == len(col) - 1) and sum(col) == 1
def variableValueForPivotColumn(tableau, column):
pivotRow = [i for (i, x) in enumerate(column) if x == 1][0]
return tableau[pivotRow][-1]
# assume the last m columns of A are the slack variables; the initial basis is
# the set of slack variables
def initialTableau(c, A, b):
tableau = [row[:] + [x] for row, x in zip(A, b)]
tableau.append([ci for ci in c] + [0])
return tableau
def primalSolution(tableau):
# the pivot columns denote which variables are used
columns = transpose(tableau)
indices = [j for j, col in enumerate(columns[:-1]) if isPivotCol(col)]
return [(colIndex, variableValueForPivotColumn(tableau, columns[colIndex]))
for colIndex in indices]
def objectiveValue(tableau):
return -(tableau[-1][-1])
def canImprove(tableau):
lastRow = tableau[-1]
return any(x > 0 for x in lastRow[:-1])
# this can be slightly faster
def moreThanOneMin(L):
if len(L) <= 1:
return False
x,y = heapq.nsmallest(2, L, key=lambda x: x[1])
return x == y
def findPivotIndex(tableau):
# pick minimum positive index of the last row
column_choices = [(i,x) for (i,x) in enumerate(tableau[-1][:-1]) if x > 0]
column = min(column_choices, key=lambda a: a[1])[0]
# check if unbounded
if all(row[column] <= 0 for row in tableau):
raise Exception('Linear program is unbounded.')
# check for degeneracy: more than one minimizer of the quotient
quotients = [(i, r[-1] / r[column])
for i,r in enumerate(tableau[:-1]) if r[column] > 0]
if moreThanOneMin(quotients):
raise Exception('Linear program is degenerate.')
# pick row index minimizing the quotient
row = min(quotients, key=lambda x: x[1])[0]
return row, column
def pivotAbout(tableau, pivot):
i,j = pivot
pivotDenom = tableau[i][j]
tableau[i] = [x / pivotDenom for x in tableau[i]]
for k,row in enumerate(tableau):
if k != i:
pivotRowMultiple = [y * tableau[k][j] for y in tableau[i]]
tableau[k] = [x - y for x,y in zip(tableau[k], pivotRowMultiple)]
'''
simplex: [float], [[float]], [float] -> [float], float
Solve the given standard-form linear program:
max <c,x>
s.t. Ax = b
x >= 0
providing the optimal solution x* and the value of the objective function
'''
def simplex(c, A, b):
tableau = initialTableau(c, A, b)
print("Initial tableau:")
for row in tableau:
print(row)
print()
while canImprove(tableau):
pivot = findPivotIndex(tableau)
print("Next pivot index is=%d,%d \n" % pivot)
pivotAbout(tableau, pivot)
print("Tableau after pivot:")
for row in tableau:
print(row)
print()
return tableau, primalSolution(tableau), objectiveValue(tableau)
if __name__ == "__main__":
c = [300, 250, 450]
A = [[15, 20, 25], [35, 60, 60], [20, 30, 25], [0, 250, 0]]
b = [1200, 3000, 1500, 500]
# add slack variables by hand
A[0] += [1,0,0,0]
A[1] += [0,1,0,0]
A[2] += [0,0,1,0]
A[3] += [0,0,0,-1]
c += [0,0,0,0]
t, s, v = simplex(c, A, b)
print(s)
print(v)