_linprog_simplex.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. """
  2. Simplex method for solving linear programming problems
  3. """
  4. import numpy as np
  5. from warnings import warn
  6. from .optimize import OptimizeResult, OptimizeWarning, _check_unknown_options
  7. from ._linprog_util import _postsolve
  8. def _pivot_col(T, tol=1.0E-12, bland=False):
  9. """
  10. Given a linear programming simplex tableau, determine the column
  11. of the variable to enter the basis.
  12. Parameters
  13. ----------
  14. T : 2D array
  15. A 2D array representing the simplex tableau, T, corresponding to the
  16. linear programming problem. It should have the form:
  17. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  18. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  19. .
  20. .
  21. .
  22. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  23. [c[0], c[1], ..., c[n_total], 0]]
  24. for a Phase 2 problem, or the form:
  25. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  26. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  27. .
  28. .
  29. .
  30. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  31. [c[0], c[1], ..., c[n_total], 0],
  32. [c'[0], c'[1], ..., c'[n_total], 0]]
  33. for a Phase 1 problem (a problem in which a basic feasible solution is
  34. sought prior to maximizing the actual objective. ``T`` is modified in
  35. place by ``_solve_simplex``.
  36. tol : float
  37. Elements in the objective row larger than -tol will not be considered
  38. for pivoting. Nominally this value is zero, but numerical issues
  39. cause a tolerance about zero to be necessary.
  40. bland : bool
  41. If True, use Bland's rule for selection of the column (select the
  42. first column with a negative coefficient in the objective row,
  43. regardless of magnitude).
  44. Returns
  45. -------
  46. status: bool
  47. True if a suitable pivot column was found, otherwise False.
  48. A return of False indicates that the linear programming simplex
  49. algorithm is complete.
  50. col: int
  51. The index of the column of the pivot element.
  52. If status is False, col will be returned as nan.
  53. """
  54. ma = np.ma.masked_where(T[-1, :-1] >= -tol, T[-1, :-1], copy=False)
  55. if ma.count() == 0:
  56. return False, np.nan
  57. if bland:
  58. # ma.mask is sometimes 0d
  59. return True, np.nonzero(np.logical_not(np.atleast_1d(ma.mask)))[0][0]
  60. return True, np.ma.nonzero(ma == ma.min())[0][0]
  61. def _pivot_row(T, basis, pivcol, phase, tol=1.0E-12, bland=False):
  62. """
  63. Given a linear programming simplex tableau, determine the row for the
  64. pivot operation.
  65. Parameters
  66. ----------
  67. T : 2D array
  68. A 2D array representing the simplex tableau, T, corresponding to the
  69. linear programming problem. It should have the form:
  70. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  71. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  72. .
  73. .
  74. .
  75. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  76. [c[0], c[1], ..., c[n_total], 0]]
  77. for a Phase 2 problem, or the form:
  78. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  79. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  80. .
  81. .
  82. .
  83. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  84. [c[0], c[1], ..., c[n_total], 0],
  85. [c'[0], c'[1], ..., c'[n_total], 0]]
  86. for a Phase 1 problem (a Problem in which a basic feasible solution is
  87. sought prior to maximizing the actual objective. ``T`` is modified in
  88. place by ``_solve_simplex``.
  89. basis : array
  90. A list of the current basic variables.
  91. pivcol : int
  92. The index of the pivot column.
  93. phase : int
  94. The phase of the simplex algorithm (1 or 2).
  95. tol : float
  96. Elements in the pivot column smaller than tol will not be considered
  97. for pivoting. Nominally this value is zero, but numerical issues
  98. cause a tolerance about zero to be necessary.
  99. bland : bool
  100. If True, use Bland's rule for selection of the row (if more than one
  101. row can be used, choose the one with the lowest variable index).
  102. Returns
  103. -------
  104. status: bool
  105. True if a suitable pivot row was found, otherwise False. A return
  106. of False indicates that the linear programming problem is unbounded.
  107. row: int
  108. The index of the row of the pivot element. If status is False, row
  109. will be returned as nan.
  110. """
  111. if phase == 1:
  112. k = 2
  113. else:
  114. k = 1
  115. ma = np.ma.masked_where(T[:-k, pivcol] <= tol, T[:-k, pivcol], copy=False)
  116. if ma.count() == 0:
  117. return False, np.nan
  118. mb = np.ma.masked_where(T[:-k, pivcol] <= tol, T[:-k, -1], copy=False)
  119. q = mb / ma
  120. min_rows = np.ma.nonzero(q == q.min())[0]
  121. if bland:
  122. return True, min_rows[np.argmin(np.take(basis, min_rows))]
  123. return True, min_rows[0]
  124. def _apply_pivot(T, basis, pivrow, pivcol, tol=1e-12):
  125. """
  126. Pivot the simplex tableau inplace on the element given by (pivrow, pivol).
  127. The entering variable corresponds to the column given by pivcol forcing
  128. the variable basis[pivrow] to leave the basis.
  129. Parameters
  130. ----------
  131. T : 2D array
  132. A 2D array representing the simplex tableau, T, corresponding to the
  133. linear programming problem. It should have the form:
  134. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  135. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  136. .
  137. .
  138. .
  139. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  140. [c[0], c[1], ..., c[n_total], 0]]
  141. for a Phase 2 problem, or the form:
  142. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  143. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  144. .
  145. .
  146. .
  147. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  148. [c[0], c[1], ..., c[n_total], 0],
  149. [c'[0], c'[1], ..., c'[n_total], 0]]
  150. for a Phase 1 problem (a problem in which a basic feasible solution is
  151. sought prior to maximizing the actual objective. ``T`` is modified in
  152. place by ``_solve_simplex``.
  153. basis : 1D array
  154. An array of the indices of the basic variables, such that basis[i]
  155. contains the column corresponding to the basic variable for row i.
  156. Basis is modified in place by _apply_pivot.
  157. pivrow : int
  158. Row index of the pivot.
  159. pivcol : int
  160. Column index of the pivot.
  161. """
  162. basis[pivrow] = pivcol
  163. pivval = T[pivrow, pivcol]
  164. T[pivrow] = T[pivrow] / pivval
  165. for irow in range(T.shape[0]):
  166. if irow != pivrow:
  167. T[irow] = T[irow] - T[pivrow] * T[irow, pivcol]
  168. # The selected pivot should never lead to a pivot value less than the tol.
  169. if np.isclose(pivval, tol, atol=0, rtol=1e4):
  170. message = (
  171. "The pivot operation produces a pivot value of:{0: .1e}, "
  172. "which is only slightly greater than the specified "
  173. "tolerance{1: .1e}. This may lead to issues regarding the "
  174. "numerical stability of the simplex method. "
  175. "Removing redundant constraints, changing the pivot strategy "
  176. "via Bland's rule or increasing the tolerance may "
  177. "help reduce the issue.".format(pivval, tol))
  178. warn(message, OptimizeWarning)
  179. def _solve_simplex(T, n, basis, maxiter=1000, phase=2, status=0, message='',
  180. callback=None, tol=1.0E-12, nit0=0, bland=False, _T_o=None):
  181. """
  182. Solve a linear programming problem in "standard form" using the Simplex
  183. Method. Linear Programming is intended to solve the following problem form:
  184. Minimize::
  185. c @ x
  186. Subject to::
  187. A @ x == b
  188. x >= 0
  189. Parameters
  190. ----------
  191. T : 2D array
  192. A 2D array representing the simplex tableau, T, corresponding to the
  193. linear programming problem. It should have the form:
  194. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  195. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  196. .
  197. .
  198. .
  199. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  200. [c[0], c[1], ..., c[n_total], 0]]
  201. for a Phase 2 problem, or the form:
  202. [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]],
  203. [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]],
  204. .
  205. .
  206. .
  207. [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]],
  208. [c[0], c[1], ..., c[n_total], 0],
  209. [c'[0], c'[1], ..., c'[n_total], 0]]
  210. for a Phase 1 problem (a problem in which a basic feasible solution is
  211. sought prior to maximizing the actual objective. ``T`` is modified in
  212. place by ``_solve_simplex``.
  213. n : int
  214. The number of true variables in the problem.
  215. basis : 1D array
  216. An array of the indices of the basic variables, such that basis[i]
  217. contains the column corresponding to the basic variable for row i.
  218. Basis is modified in place by _solve_simplex
  219. maxiter : int
  220. The maximum number of iterations to perform before aborting the
  221. optimization.
  222. phase : int
  223. The phase of the optimization being executed. In phase 1 a basic
  224. feasible solution is sought and the T has an additional row
  225. representing an alternate objective function.
  226. callback : callable, optional (simplex only)
  227. If a callback function is provided, it will be called within each
  228. iteration of the simplex algorithm. The callback must require a
  229. `scipy.optimize.OptimizeResult` consisting of the following fields:
  230. x : 1D array
  231. The independent variable vector which optimizes the linear
  232. programming problem.
  233. fun : float
  234. Value of the objective function.
  235. success : bool
  236. True if the algorithm succeeded in finding an optimal solution.
  237. slack : 1D array
  238. The values of the slack variables. Each slack variable
  239. corresponds to an inequality constraint. If the slack is zero,
  240. the corresponding constraint is active.
  241. con : 1D array
  242. The (nominally zero) residuals of the equality constraints,
  243. that is, ``b - A_eq @ x``
  244. phase : int
  245. The phase of the optimization being executed. In phase 1 a basic
  246. feasible solution is sought and the T has an additional row
  247. representing an alternate objective function.
  248. status : int
  249. An integer representing the exit status of the optimization::
  250. 0 : Optimization terminated successfully
  251. 1 : Iteration limit reached
  252. 2 : Problem appears to be infeasible
  253. 3 : Problem appears to be unbounded
  254. 4 : Serious numerical difficulties encountered
  255. nit : int
  256. The number of iterations performed.
  257. message : str
  258. A string descriptor of the exit status of the optimization.
  259. tol : float
  260. The tolerance which determines when a solution is "close enough" to
  261. zero in Phase 1 to be considered a basic feasible solution or close
  262. enough to positive to serve as an optimal solution.
  263. nit0 : int
  264. The initial iteration number used to keep an accurate iteration total
  265. in a two-phase problem.
  266. bland : bool
  267. If True, choose pivots using Bland's rule [3]_. In problems which
  268. fail to converge due to cycling, using Bland's rule can provide
  269. convergence at the expense of a less optimal path about the simplex.
  270. Returns
  271. -------
  272. nit : int
  273. The number of iterations. Used to keep an accurate iteration total
  274. in the two-phase problem.
  275. status : int
  276. An integer representing the exit status of the optimization::
  277. 0 : Optimization terminated successfully
  278. 1 : Iteration limit reached
  279. 2 : Problem appears to be infeasible
  280. 3 : Problem appears to be unbounded
  281. 4 : Serious numerical difficulties encountered
  282. """
  283. nit = nit0
  284. complete = False
  285. if phase == 1:
  286. m = T.shape[0]-2
  287. elif phase == 2:
  288. m = T.shape[0]-1
  289. else:
  290. raise ValueError("Argument 'phase' to _solve_simplex must be 1 or 2")
  291. if phase == 2:
  292. # Check if any artificial variables are still in the basis.
  293. # If yes, check if any coefficients from this row and a column
  294. # corresponding to one of the non-artificial variable is non-zero.
  295. # If found, pivot at this term. If not, start phase 2.
  296. # Do this for all artificial variables in the basis.
  297. # Ref: "An Introduction to Linear Programming and Game Theory"
  298. # by Paul R. Thie, Gerard E. Keough, 3rd Ed,
  299. # Chapter 3.7 Redundant Systems (pag 102)
  300. for pivrow in [row for row in range(basis.size)
  301. if basis[row] > T.shape[1] - 2]:
  302. non_zero_row = [col for col in range(T.shape[1] - 1)
  303. if abs(T[pivrow, col]) > tol]
  304. if len(non_zero_row) > 0:
  305. pivcol = non_zero_row[0]
  306. _apply_pivot(T, basis, pivrow, pivcol)
  307. nit += 1
  308. if len(basis[:m]) == 0:
  309. solution = np.zeros(T.shape[1] - 1, dtype=np.float64)
  310. else:
  311. solution = np.zeros(max(T.shape[1] - 1, max(basis[:m]) + 1),
  312. dtype=np.float64)
  313. while not complete:
  314. # Find the pivot column
  315. pivcol_found, pivcol = _pivot_col(T, tol, bland)
  316. if not pivcol_found:
  317. pivcol = np.nan
  318. pivrow = np.nan
  319. status = 0
  320. complete = True
  321. else:
  322. # Find the pivot row
  323. pivrow_found, pivrow = _pivot_row(T, basis, pivcol, phase, tol, bland)
  324. if not pivrow_found:
  325. status = 3
  326. complete = True
  327. if callback is not None:
  328. solution[basis[:n]] = T[:n, -1]
  329. x = solution[:m]
  330. c, A_ub, b_ub, A_eq, b_eq, bounds, undo = _T_o
  331. x, fun, slack, con, _, _ = _postsolve(
  332. x, c, A_ub, b_ub, A_eq, b_eq, bounds, undo=undo, tol=tol
  333. )
  334. res = OptimizeResult({
  335. 'x': x,
  336. 'fun': fun,
  337. 'slack': slack,
  338. 'con': con,
  339. 'status': status,
  340. 'message': message,
  341. 'nit': nit,
  342. 'success': status == 0 and complete,
  343. 'phase': phase,
  344. 'complete': complete,
  345. })
  346. callback(res)
  347. if not complete:
  348. if nit >= maxiter:
  349. # Iteration limit exceeded
  350. status = 1
  351. complete = True
  352. else:
  353. _apply_pivot(T, basis, pivrow, pivcol)
  354. nit += 1
  355. return nit, status
  356. def _linprog_simplex(c, c0, A, b, maxiter=1000, disp=False, callback=None,
  357. tol=1.0E-12, bland=False, _T_o=None, **unknown_options):
  358. """
  359. Minimize a linear objective function subject to linear equality and
  360. non-negativity constraints using the two phase simplex method.
  361. Linear programming is intended to solve problems of the following form:
  362. Minimize::
  363. c @ x
  364. Subject to::
  365. A @ x == b
  366. x >= 0
  367. Parameters
  368. ----------
  369. c : 1D array
  370. Coefficients of the linear objective function to be minimized.
  371. c0 : float
  372. Constant term in objective function due to fixed (and eliminated)
  373. variables. (Purely for display.)
  374. A : 2D array
  375. 2D array such that ``A @ x``, gives the values of the equality
  376. constraints at ``x``.
  377. b : 1D array
  378. 1D array of values representing the right hand side of each equality
  379. constraint (row) in ``A``.
  380. callback : callable, optional (simplex only)
  381. If a callback function is provided, it will be called within each
  382. iteration of the simplex algorithm. The callback must require a
  383. `scipy.optimize.OptimizeResult` consisting of the following fields:
  384. x : 1D array
  385. The independent variable vector which optimizes the linear
  386. programming problem.
  387. fun : float
  388. Value of the objective function.
  389. success : bool
  390. True if the algorithm succeeded in finding an optimal solution.
  391. slack : 1D array
  392. The values of the slack variables. Each slack variable
  393. corresponds to an inequality constraint. If the slack is zero,
  394. the corresponding constraint is active.
  395. con : 1D array
  396. The (nominally zero) residuals of the equality constraints, that
  397. is, ``b - A_eq @ x``
  398. phase : int
  399. The phase of the optimization being executed. In phase 1 a basic
  400. feasible solution is sought and the T has an additional row
  401. representing an alternate objective function.
  402. status : int
  403. An integer representing the exit status of the optimization::
  404. 0 : Optimization terminated successfully
  405. 1 : Iteration limit reached
  406. 2 : Problem appears to be infeasible
  407. 3 : Problem appears to be unbounded
  408. 4 : Serious numerical difficulties encountered
  409. nit : int
  410. The number of iterations performed.
  411. message : str
  412. A string descriptor of the exit status of the optimization.
  413. Options
  414. -------
  415. maxiter : int
  416. The maximum number of iterations to perform.
  417. disp : bool
  418. If True, print exit status message to sys.stdout
  419. tol : float
  420. The tolerance which determines when a solution is "close enough" to
  421. zero in Phase 1 to be considered a basic feasible solution or close
  422. enough to positive to serve as an optimal solution.
  423. bland : bool
  424. If True, use Bland's anti-cycling rule [3]_ to choose pivots to
  425. prevent cycling. If False, choose pivots which should lead to a
  426. converged solution more quickly. The latter method is subject to
  427. cycling (non-convergence) in rare instances.
  428. Returns
  429. -------
  430. x : 1D array
  431. Solution vector.
  432. status : int
  433. An integer representing the exit status of the optimization::
  434. 0 : Optimization terminated successfully
  435. 1 : Iteration limit reached
  436. 2 : Problem appears to be infeasible
  437. 3 : Problem appears to be unbounded
  438. 4 : Serious numerical difficulties encountered
  439. message : str
  440. A string descriptor of the exit status of the optimization.
  441. iteration : int
  442. The number of iterations taken to solve the problem.
  443. References
  444. ----------
  445. .. [1] Dantzig, George B., Linear programming and extensions. Rand
  446. Corporation Research Study Princeton Univ. Press, Princeton, NJ,
  447. 1963
  448. .. [2] Hillier, S.H. and Lieberman, G.J. (1995), "Introduction to
  449. Mathematical Programming", McGraw-Hill, Chapter 4.
  450. .. [3] Bland, Robert G. New finite pivoting rules for the simplex method.
  451. Mathematics of Operations Research (2), 1977: pp. 103-107.
  452. Notes
  453. -----
  454. The expected problem formulation differs between the top level ``linprog``
  455. module and the method specific solvers. The method specific solvers expect a
  456. problem in standard form:
  457. Minimize::
  458. c @ x
  459. Subject to::
  460. A @ x == b
  461. x >= 0
  462. Whereas the top level ``linprog`` module expects a problem of form:
  463. Minimize::
  464. c @ x
  465. Subject to::
  466. A_ub @ x <= b_ub
  467. A_eq @ x == b_eq
  468. lb <= x <= ub
  469. where ``lb = 0`` and ``ub = None`` unless set in ``bounds``.
  470. The original problem contains equality, upper-bound and variable constraints
  471. whereas the method specific solver requires equality constraints and
  472. variable non-negativity.
  473. ``linprog`` module converts the original problem to standard form by
  474. converting the simple bounds to upper bound constraints, introducing
  475. non-negative slack variables for inequality constraints, and expressing
  476. unbounded variables as the difference between two non-negative variables.
  477. """
  478. _check_unknown_options(unknown_options)
  479. status = 0
  480. messages = {0: "Optimization terminated successfully.",
  481. 1: "Iteration limit reached.",
  482. 2: "Optimization failed. Unable to find a feasible"
  483. " starting point.",
  484. 3: "Optimization failed. The problem appears to be unbounded.",
  485. 4: "Optimization failed. Singular matrix encountered."}
  486. n, m = A.shape
  487. # All constraints must have b >= 0.
  488. is_negative_constraint = np.less(b, 0)
  489. A[is_negative_constraint] *= -1
  490. b[is_negative_constraint] *= -1
  491. # As all constraints are equality constraints the artificial variables
  492. # will also be basic variables.
  493. av = np.arange(n) + m
  494. basis = av.copy()
  495. # Format the phase one tableau by adding artificial variables and stacking
  496. # the constraints, the objective row and pseudo-objective row.
  497. row_constraints = np.hstack((A, np.eye(n), b[:, np.newaxis]))
  498. row_objective = np.hstack((c, np.zeros(n), c0))
  499. row_pseudo_objective = -row_constraints.sum(axis=0)
  500. row_pseudo_objective[av] = 0
  501. T = np.vstack((row_constraints, row_objective, row_pseudo_objective))
  502. nit1, status = _solve_simplex(T, n, basis, phase=1, callback=callback,
  503. maxiter=maxiter, tol=tol, bland=bland, _T_o=_T_o)
  504. # if pseudo objective is zero, remove the last row from the tableau and
  505. # proceed to phase 2
  506. if abs(T[-1, -1]) < tol:
  507. # Remove the pseudo-objective row from the tableau
  508. T = T[:-1, :]
  509. # Remove the artificial variable columns from the tableau
  510. T = np.delete(T, av, 1)
  511. else:
  512. # Failure to find a feasible starting point
  513. status = 2
  514. nit2 = nit1
  515. messages[status] = (
  516. "Phase 1 of the simplex method failed to find a feasible "
  517. "solution. The pseudo-objective function evaluates to {0:.1e} "
  518. "which exceeds the required tolerance of {1} for a solution to be "
  519. "considered 'close enough' to zero to be a basic solution. "
  520. "Consider increasing the tolerance to be greater than {0:.1e}. "
  521. "If this tolerance is unacceptably large the problem may be "
  522. "infeasible.".format(abs(T[-1, -1]), tol)
  523. )
  524. if status == 0:
  525. # Phase 2
  526. nit2, status = _solve_simplex(T, n, basis, maxiter=maxiter,
  527. phase=2, callback=callback, tol=tol,
  528. nit0=nit1, bland=bland, _T_o=_T_o)
  529. solution = np.zeros(n + m)
  530. solution[basis[:n]] = T[:n, -1]
  531. x = solution[:m]
  532. return x, status, messages[status], int(nit2)