20
20
def linprog_simplex (c , A_ub = np .empty ((0 , 0 )), b_ub = np .empty ((0 ,)),
21
21
A_eq = np .empty ((0 , 0 )), b_eq = np .empty ((0 ,)), max_iter = 10 ** 6 ,
22
22
tableau = None , basis = None , x = None , lambd = None ):
23
+ """
24
+ Solve a linear program in the following form by the simplex
25
+ algorithm (with the lexicographic pivoting rule):
26
+
27
+ maximize::
28
+
29
+ c @ x
30
+
31
+ subject to::
32
+
33
+ A_ub @ x <= b_ub
34
+ A_eq @ x == b_eq
35
+ x >= 0
36
+
37
+ Parameters
38
+ ----------
39
+ c : ndarray(float, ndim=1)
40
+ ndarray of shape (n,).
41
+
42
+ A_ub : ndarray(float, ndim=2), optional
43
+ ndarray of shape (m, n).
44
+
45
+ b_ub : ndarray(float, ndim=1), optional
46
+ ndarray of shape (m,).
47
+
48
+ A_eq : ndarray(float, ndim=2), optional
49
+ ndarray of shape (k, n).
50
+
51
+ b_eq : ndarray(float, ndim=1), optional
52
+ ndarray of shape (k,).
53
+
54
+ max_iter : int, optional(default=10**6)
55
+ Maximum number of iteration to perform.
56
+
57
+ tableau : ndarray(float, ndim=2), optional
58
+ Temporary ndarray of shape (L+1, n+m+L+1) to store the tableau,
59
+ where L=m+k. Modified in place.
60
+
61
+ basis : ndarray(int, ndim=1), optional
62
+ Temporary ndarray of shape (L,) to store the basic variables.
63
+ Modified in place.
64
+
65
+ x : ndarray(float, ndim=1), optional
66
+ Output ndarray of shape (n,) to store the primal solution.
67
+
68
+ lambd : ndarray(float, ndim=1), optional
69
+ Output ndarray of shape (L,) to store the dual solution.
70
+
71
+ Returns
72
+ -------
73
+ res : SimplexResult
74
+ namedtuple consisting of the fields:
75
+
76
+ x : ndarray(float, ndim=1)
77
+ ndarray of shape (n,) containing the primal solution.
78
+
79
+ lambd : ndarray(float, ndim=1)
80
+ ndarray of shape (L,) containing the dual solution.
81
+
82
+ fun : float
83
+ Value of the objective function.
84
+
85
+ success : bool
86
+ True if the algorithm succeeded in finding an optimal
87
+ solution.
88
+
89
+ status : int
90
+ An integer representing the exit status of the
91
+ optimization::
92
+
93
+ 0 : Optimization terminated successfully
94
+ 1 : Iteration limit reached
95
+ 2 : Problem appears to be infeasible
96
+ 3 : Problem apperas to be unbounded
97
+
98
+ num_iter : int
99
+ The number of iterations performed.
100
+
101
+ References
102
+ ----------
103
+ * K. C. Border, "The Gauss–Jordan and Simplex Algorithms," 2004.
104
+
105
+ """
23
106
n , m , k = c .shape [0 ], A_ub .shape [0 ], A_eq .shape [0 ]
24
107
L = m + k
25
108
@@ -69,6 +152,81 @@ def linprog_simplex(c, A_ub=np.empty((0, 0)), b_ub=np.empty((0,)),
69
152
70
153
@jit (nopython = True , cache = True )
71
154
def _initialize_tableau (A_ub , b_ub , A_eq , b_eq , tableau , basis ):
155
+ """
156
+ Initialize the `tableau` and `basis` arrays in place for Phase 1.
157
+
158
+ Suppose that the original linear program has the following form:
159
+
160
+ maximize::
161
+
162
+ c @ x
163
+
164
+ subject to::
165
+
166
+ A_ub @ x <= b_ub
167
+ A_eq @ x == b_eq
168
+ x >= 0
169
+
170
+ Let s be a vector of slack variables converting the inequality
171
+ constraint to an equality constraint so that the problem turns to be
172
+ the standard form:
173
+
174
+ maximize::
175
+
176
+ c @ x
177
+
178
+ subject to::
179
+
180
+ A_ub @ x + s == b_ub
181
+ A_eq @ x == b_eq
182
+ x, s >= 0
183
+
184
+ Then, let (z1, z2) be a vector of artificial variables for Phase 1:
185
+ we solve the following LP:
186
+
187
+ maximize::
188
+
189
+ -(1 @ z1 + 1 @ z2)
190
+
191
+ subject to::
192
+
193
+ A_ub @ x + s + z1 == b_ub
194
+ A_eq @ x + z2 == b_eq
195
+ x, s, z1, z2 >= 0
196
+
197
+ The tableau needs to be of shape (L+1, n+m+L+1), where L=m+k.
198
+
199
+ Parameters
200
+ ----------
201
+ A_ub : ndarray(float, ndim=2)
202
+ ndarray of shape (m, n).
203
+
204
+ b_ub : ndarray(float, ndim=1)
205
+ ndarray of shape (m,).
206
+
207
+ A_eq : ndarray(float, ndim=2)
208
+ ndarray of shape (k, n).
209
+
210
+ b_eq : ndarray(float, ndim=1)
211
+ ndarray of shape (k,).
212
+
213
+ tableau : ndarray(float, ndim=2)
214
+ Empty ndarray of shape (L+1, n+m+L+1) to store the tableau.
215
+ Modified in place.
216
+
217
+ basis : ndarray(int, ndim=1)
218
+ Empty ndarray of shape (L,) to store the basic variables.
219
+ Modified in place.
220
+
221
+ Returns
222
+ -------
223
+ tableau : ndarray(float, ndim=2)
224
+ View to `tableau`.
225
+
226
+ basis : ndarray(int, ndim=1)
227
+ View to `basis`.
228
+
229
+ """
72
230
m , k = A_ub .shape [0 ], A_eq .shape [0 ]
73
231
L = m + k
74
232
n = tableau .shape [1 ] - (m + L + 1 )
@@ -114,6 +272,27 @@ def _initialize_tableau(A_ub, b_ub, A_eq, b_eq, tableau, basis):
114
272
115
273
@jit (nopython = True , cache = True )
116
274
def _set_criterion_row (c , basis , tableau ):
275
+ """
276
+ Modify the criterion row of the tableau for Phase 2.
277
+
278
+ Parameters
279
+ ----------
280
+ c : ndarray(float, ndim=1)
281
+ ndarray of shape (n,).
282
+
283
+ basis : ndarray(int, ndim=1)
284
+ ndarray of shape (L,) containing the basis obtained by Phase 1.
285
+
286
+ tableau : ndarray(float, ndim=2)
287
+ ndarray of shape (L+1, n+m+L+1) containing the tableau obtained
288
+ by Phase 1. Modified in place.
289
+
290
+ Returns
291
+ -------
292
+ tableau : ndarray(float, ndim=2)
293
+ View to `tableau`.
294
+
295
+ """
117
296
n = c .shape [0 ]
118
297
L = basis .shape [0 ]
119
298
@@ -136,11 +315,15 @@ def solve_tableau(tableau, basis, max_iter=10**6, skip_aux=True):
136
315
137
316
Used to solve a linear program in the following form:
138
317
139
- maximize: c @ x
318
+ maximize::
140
319
141
- subject to: A_ub @ x <= b_ub
142
- A_eq @ x == b_eq
143
- x >= 0
320
+ c @ x
321
+
322
+ subject to::
323
+
324
+ A_ub @ x <= b_ub
325
+ A_eq @ x == b_eq
326
+ x >= 0
144
327
145
328
where A_ub is of shape (m, n) and A_eq is of shape (k, n). Thus,
146
329
`tableau` is of shape (L+1, n+m+L+1), where L=m+k, and
@@ -159,13 +342,24 @@ def solve_tableau(tableau, basis, max_iter=10**6, skip_aux=True):
159
342
ndarray of shape (L,) containing the basic variables. Modified
160
343
in place.
161
344
162
- max_iter : scalar( int) , optional(default=10**6)
345
+ max_iter : int, optional(default=10**6)
163
346
Maximum number of pivoting steps.
164
347
165
348
skip_aux : bool, optional(default=True)
166
349
Whether to skip the coefficients of the auxiliary (or
167
350
artificial) variables in pivot column selection.
168
351
352
+ Returns
353
+ -------
354
+ success : bool
355
+ True if the algorithm succeeded in finding an optimal solution.
356
+
357
+ status : int
358
+ An integer representing the exit status of the optimization.
359
+
360
+ num_iter : int
361
+ The number of iterations performed.
362
+
169
363
"""
170
364
L = tableau .shape [0 ] - 1
171
365
@@ -203,6 +397,33 @@ def solve_tableau(tableau, basis, max_iter=10**6, skip_aux=True):
203
397
204
398
@jit (nopython = True , cache = True )
205
399
def _pivot_col (tableau , skip_aux ):
400
+ """
401
+ Choose the column containing the pivot element: the column
402
+ containing the maximum positive element in the last row of the
403
+ tableau.
404
+
405
+ `skip_aux` should be True in phase 1, and False in phase 2.
406
+
407
+ Parameters
408
+ ----------
409
+ tableau : ndarray(float, ndim=2)
410
+ ndarray of shape (L+1, n+m+L+1) containing the tableau.
411
+
412
+ skip_aux : bool
413
+ Whether to skip the coefficients of the auxiliary (or
414
+ artificial) variables in pivot column selection.
415
+
416
+ Returns
417
+ -------
418
+ found : bool
419
+ True iff there is a positive element in the last row of the
420
+ tableau (and then pivotting should be conducted).
421
+
422
+ pivcol : int
423
+ The index of column containing the pivot element. (-1 if `found
424
+ == False`.)
425
+
426
+ """
206
427
L = tableau .shape [0 ] - 1
207
428
criterion_row_stop = tableau .shape [1 ] - 1
208
429
if skip_aux :
@@ -222,6 +443,37 @@ def _pivot_col(tableau, skip_aux):
222
443
223
444
@jit (nopython = True , cache = True )
224
445
def get_solution (tableau , basis , x , lambd , b_signs ):
446
+ """
447
+ Fetch the optimal solution and value from an optimal tableau.
448
+
449
+ Parameters
450
+ ----------
451
+ tableau : ndarray(float, ndim=2)
452
+ ndarray of shape (L+1, n+m+L+1) containing the optimal tableau,
453
+ where L=m+k.
454
+
455
+ basis : ndarray(int, ndim=1)
456
+ Empty ndarray of shape (L,) to store the basic variables.
457
+ Modified in place.
458
+
459
+ x : ndarray(float, ndim=1)
460
+ Empty ndarray of shape (n,) to store the primal solution.
461
+ Modified in place.
462
+
463
+ lambd : ndarray(float, ndim=1)
464
+ Empty ndarray of shape (L,) to store the dual solution. Modified
465
+ in place.
466
+
467
+ b_signs : ndarray(bool, ndim=1)
468
+ ndarray of shape (L,) whose i-th element is True iff the i-th
469
+ element of the vector (b_ub, b_eq) is positive.
470
+
471
+ Returns
472
+ -------
473
+ fun : float
474
+ The optimal value.
475
+
476
+ """
225
477
n , L = x .size , lambd .size
226
478
aux_start = tableau .shape [1 ] - L - 1
227
479
0 commit comments