From dacb823be2d32d0803c6a17b8491b7280f4bcf98 Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Thu, 26 Jul 2018 14:19:28 +0800 Subject: [PATCH 1/6] Add contribution to THANKS.txt --- THANKS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/THANKS.txt b/THANKS.txt index defef3374499..af3e63f66187 100644 --- a/THANKS.txt +++ b/THANKS.txt @@ -195,6 +195,7 @@ Konrad Griessinger for the small sample Kendall test Tony Xiang for improvements in scipy.sparse Roy Zywina for contributions to scipy.fftpack. Christian H. Meyer for bug fixes in subspace_angles. +Kai Striega for improvments to the numerical stability of the simplex method. Institutions ------------ From ed0cb854d134d41de99be594d930822885ace10f Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Fri, 27 Jul 2018 14:22:31 +0800 Subject: [PATCH 2/6] Refactor pivot technique into new function. --- scipy/optimize/_linprog.py | 73 ++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index 8d639f47c5db..dc59a1c75d75 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -1,6 +1,6 @@ """ -A top-level linear programming interface. Currently this interface only -solves linear programming problems via the Simplex Method. +A top-level linear programming interface. Currently this interface solves +linear programming problems via the Simplex and Interior-Point methods. .. versionadded:: 0.15.0 @@ -218,6 +218,57 @@ def _pivot_row(T, basis, pivcol, phase, tol=1.0E-12, bland=False): return True, min_rows[0] +def _apply_pivot(T, basis, pivrow, pivcol): + """ + Pivot the simplex tableau inplace on the element given by (pivrow, pivol). + The entering variable corresponds to the column given by pivcol forcing + the variable basis[pivrow] to leave the basis. + + Parameters + ---------- + T : 2-D array + A 2-D array representing the simplex T corresponding to the + maximization problem. It should have the form: + + [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]], + [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]], + . + . + . + [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]], + [c[0], c[1], ..., c[n_total], 0]] + + for a Phase 2 problem, or the form: + + [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]], + [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]], + . + . + . + [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]], + [c[0], c[1], ..., c[n_total], 0], + [c'[0], c'[1], ..., c'[n_total], 0]] + + for a Phase 1 problem (a Problem in which a basic feasible solution is + sought prior to maximizing the actual objective. T is modified in + place by _apply_pivot. + basis : 1-D array + An array of the indices of the basic variables, such that basis[i] + contains the column corresponding to the basic variable for row i. + Basis is modified in place by _apply_pivot. + pivrow : int + Row index of the pivot. + pivcol : int + Column index of the pivot. + """ + basis[pivrow] = pivcol + pivval = T[pivrow, pivcol] + T[pivrow] = T[pivrow] / pivval + for irow in range(T.shape[0]): + if irow != pivrow: + T[irow] = T[irow] - T[pivrow] * T[irow, pivcol] + + def _solve_simplex(T, n, basis, maxiter=1000, phase=2, callback=None, tol=1.0E-12, nit0=0, bland=False): """ @@ -337,14 +388,7 @@ def _solve_simplex(T, n, basis, maxiter=1000, phase=2, callback=None, if abs(T[pivrow, col]) > tol] if len(non_zero_row) > 0: pivcol = non_zero_row[0] - # variable represented by pivcol enters - # variable in basis[pivrow] leaves - basis[pivrow] = pivcol - pivval = T[pivrow][pivcol] - T[pivrow, :] = T[pivrow, :] / pivval - for irow in range(T.shape[0]): - if irow != pivrow: - T[irow, :] = T[irow, :] - T[pivrow, :]*T[irow, pivcol] + _apply_pivot(T, basis, pivrow, pivcol) nit += 1 if len(basis[:m]) == 0: @@ -384,14 +428,7 @@ def _solve_simplex(T, n, basis, maxiter=1000, phase=2, callback=None, status = 1 complete = True else: - # variable represented by pivcol enters - # variable in basis[pivrow] leaves - basis[pivrow] = pivcol - pivval = T[pivrow][pivcol] - T[pivrow, :] = T[pivrow, :] / pivval - for irow in range(T.shape[0]): - if irow != pivrow: - T[irow, :] = T[irow, :] - T[pivrow, :]*T[irow, pivcol] + _apply_pivot(T, basis, pivrow, pivcol) nit += 1 return nit, status From 65559acb728fed33267581402a4ed4581fdbb32e Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Fri, 27 Jul 2018 17:56:43 +0800 Subject: [PATCH 3/6] Add warning if pivot value is close to the tolerance. The pivoting procedure may produce pivot values very close to zero. Dividing by these values can cause the simplex method "blow up". Adding the warning notifes the user if this is likely to occur and provides some hints to prevent this. --- scipy/optimize/_linprog.py | 43 +++++++++++++------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index dc59a1c75d75..6379a842d319 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -18,7 +18,8 @@ from __future__ import division, print_function, absolute_import import numpy as np -from .optimize import OptimizeResult, _check_unknown_options +from warnings import warn +from .optimize import OptimizeResult, OptimizeWarning, _check_unknown_options from ._linprog_ip import _linprog_ip __all__ = ['linprog', 'linprog_verbose_callback', 'linprog_terse_callback'] @@ -128,7 +129,6 @@ def linprog_terse_callback(xk, **kwargs): (and this is the final call to callback), otherwise False. """ nit = kwargs["nit"] - if nit == 0: print("Iter: X:") print("{0: <5d} ".format(nit), end="") @@ -218,7 +218,7 @@ def _pivot_row(T, basis, pivcol, phase, tol=1.0E-12, bland=False): return True, min_rows[0] -def _apply_pivot(T, basis, pivrow, pivcol): +def _apply_pivot(T, basis, pivrow, pivcol, tol=1e-12): """ Pivot the simplex tableau inplace on the element given by (pivrow, pivol). The entering variable corresponds to the column given by pivcol forcing @@ -227,31 +227,8 @@ def _apply_pivot(T, basis, pivrow, pivcol): Parameters ---------- T : 2-D array - A 2-D array representing the simplex T corresponding to the - maximization problem. It should have the form: - - [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]], - [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]], - . - . - . - [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]], - [c[0], c[1], ..., c[n_total], 0]] - - for a Phase 2 problem, or the form: - - [[A[0, 0], A[0, 1], ..., A[0, n_total], b[0]], - [A[1, 0], A[1, 1], ..., A[1, n_total], b[1]], - . - . - . - [A[m, 0], A[m, 1], ..., A[m, n_total], b[m]], - [c[0], c[1], ..., c[n_total], 0], - [c'[0], c'[1], ..., c'[n_total], 0]] - - for a Phase 1 problem (a Problem in which a basic feasible solution is - sought prior to maximizing the actual objective. T is modified in - place by _apply_pivot. + A 2-D array representing the simplex T to the corresponding + maximization problem. basis : 1-D array An array of the indices of the basic variables, such that basis[i] contains the column corresponding to the basic variable for row i. @@ -268,6 +245,16 @@ def _apply_pivot(T, basis, pivrow, pivcol): if irow != pivrow: T[irow] = T[irow] - T[pivrow] * T[irow, pivcol] + # The selected pivot should never lead to a pivot value less than the tol. + if np.isclose(pivval, tol, atol=0, rtol=1e4): + message = ("The pivot operation in requires a pivot value of:{0: .1e}." + " Being only slightly greater than the set tolerance{1: .1e}. This" + " may lead to issues regarding the numerical stability of the simplex" + " method. Increasing the tolerance, changing the pivot strategy via " + " Bland's rule or removing redundant constraints may help reduce" + " the issue.".format(pivval, tol)) + warn(message, OptimizeWarning) + def _solve_simplex(T, n, basis, maxiter=1000, phase=2, callback=None, tol=1.0E-12, nit0=0, bland=False): From 84a73f063d435c289b84bbc1ef9e99893ecf13f0 Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Fri, 27 Jul 2018 19:01:49 +0800 Subject: [PATCH 4/6] Update tests to reflect warnings. --- scipy/optimize/_linprog.py | 13 +-- scipy/optimize/tests/test_linprog.py | 152 +++++++++++++++++++++------ 2 files changed, 129 insertions(+), 36 deletions(-) diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index 6379a842d319..fb46fb946b9a 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -247,12 +247,13 @@ def _apply_pivot(T, basis, pivrow, pivcol, tol=1e-12): # The selected pivot should never lead to a pivot value less than the tol. if np.isclose(pivval, tol, atol=0, rtol=1e4): - message = ("The pivot operation in requires a pivot value of:{0: .1e}." - " Being only slightly greater than the set tolerance{1: .1e}. This" - " may lead to issues regarding the numerical stability of the simplex" - " method. Increasing the tolerance, changing the pivot strategy via " - " Bland's rule or removing redundant constraints may help reduce" - " the issue.".format(pivval, tol)) + message = ( + "The pivot operation produces a pivot value of:{0: .1e}. " + "Being only slightly greater than the set tolerance{1: .1e}. " + "This may lead to issues regarding the numerical stability of " + "the simplex method. Increasing the tolerance, changing the pivot " + "strategy via Bland's rule or removing redundant constraints may " + "help reduce the issue.".format(pivval, tol)) warn(message, OptimizeWarning) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index ebc38eb08d19..057bbc79ea0a 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -746,6 +746,62 @@ def test_bug_8663(self): desired_x=[0, 6./7], desired_fun=5*6./7) + def test_bug_5400(self): + # https://github.com/scipy/scipy/issues/5400 + bounds = [ + (0, None), + (0, 100), (0, 100), (0, 100), (0, 100), (0, 100), (0, 100), + (0, 900), (0, 900), (0, 900), (0, 900), (0, 900), (0, 900), + (0, None), (0, None), (0, None), (0, None), (0, None), (0, None)] + + f = 1 / 9 + g = -1e4 + h = -3.1 + A_ub = np.array([ + [1, -2.99, 0, 0, -3, 0, 0, 0, -1, -1, 0, -1, -1, 1, 1, 0, 0, 0, 0], + [1, 0, -2.9, h, 0, -3, 0, -1, 0, 0, -1, 0, -1, 0, 0, 1, 1, 0, 0], + [1, 0, 0, h, 0, 0, -3, -1, -1, 0, -1, -1, 0, 0, 0, 0, 0, 1, 1], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1], + [0, 1.99, -1, -1, 0, 0, 0, -1, f, f, 0, 0, 0, g, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 2, -1, -1, 0, 0, 0, -1, f, f, 0, g, 0, 0, 0, 0], + [0, -1, 1.9, 2.1, 0, 0, 0, f, -1, -1, 0, 0, 0, 0, 0, g, 0, 0, 0], + [0, 0, 0, 0, -1, 2, -1, 0, 0, 0, f, -1, f, 0, 0, 0, g, 0, 0], + [0, -1, -1, 2.1, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, 0, 0, g, 0], + [0, 0, 0, 0, -1, -1, 2, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, g]]) + + b_ub = np.array([ + 0.0, 0, 0, 100, 100, 100, 100, 100, 100, 900, 900, 900, 900, 900, + 900, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + c = np.array([-1.0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]) + + if self.method == 'simplex': + with pytest.warns(OptimizeWarning): + res = linprog(c, A_ub, b_ub, bounds=bounds, + method=self.method, options=self.options) + elif self.method == 'interior-point': + res = linprog(c, A_ub, b_ub, bounds=bounds, + method=self.method, options=self.options) + _assert_success(res, desired_fun=-106.63507541835018) + class TestLinprogSimplex(LinprogCommonTests): method = "simplex" @@ -822,6 +878,72 @@ def test_issue_6139(self): res2, desired_fun=14.95, desired_x=np.array([5, 4.95, 5]) ) + def test_issue_7237_passes_with_bland(self): + # https://github.com/scipy/scipy/issues/7237 + # The simplex method sometimes "explodes" if the pivot value is very + # close to zero. Bland's rule provides an alternative pivot selection + # and produces a valid result. + + c = np.array([-1., 0., 0., 0., 0., 0., 0., 0., 0.]) + A_ub = np.array([ + [ 1., -724., 911., -551., -555., -896., 478., -80., -293.], + [ 1., 566., 42., 937., 233., 883., 392., -909., 57.], + [ 1., -208., -894., 539., 321., 532., -924., 942., 55.], + [ 1., 857., -859., 83., 462., -265., -971., 826., 482.], + [ 1., 314., -424., 245., -424., 194., -443., -104., -429.], + [ 1., 540., 679., 361., 149., -827., 876., 633., 302.], + [ 0., -1., -0., -0., -0., -0., -0., -0., -0.], + [ 0., -0., -1., -0., -0., -0., -0., -0., -0.], + [ 0., -0., -0., -1., -0., -0., -0., -0., -0.], + [ 0., -0., -0., -0., -1., -0., -0., -0., -0.], + [ 0., -0., -0., -0., -0., -1., -0., -0., -0.], + [ 0., -0., -0., -0., -0., -0., -1., -0., -0.], + [ 0., -0., -0., -0., -0., -0., -0., -1., -0.], + [ 0., -0., -0., -0., -0., -0., -0., -0., -1.], + [ 0., 1., 0., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 1., 0., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 1., 0., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 1., 0., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 1., 0., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 1., 0., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 1., 0.], + [ 0., 0., 0., 0., 0., 0., 0., 0., 1.] + ]) + b_ub = np.array([ + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.]) + A_eq = np.array([[ 0., 1., 1., 1., 1., 1., 1., 1., 1.]]) + b_eq = np.array([[ 1.]]) + bounds = [(None, None)] * 9 + + # Should fail if Bland's rule is not used. + with pytest.warns(OptimizeWarning): + res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, + bounds=bounds, method=self.method, options=self.options) + + o = self.options.copy() + o['bland'] = True + res_bland = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, + bounds=bounds, method=self.method, options=o) + _assert_success(res_bland, desired_fun=108.568535) + + def test_issue_8174_warns_if_pivval_near_tol(self): + # https://github.com/scipy/scipy/issues/8174 + # The simplex method sometimes "explodes" if the pivot value is very + # close to zero. + A_ub = np.array([ + [ 22714., 1008., 13380., -2713.5, -1116. ], + [ -4986., -1092., -31220., 17386.5, 684. ], + [ -4986., 0., 0., -2713.5, 0. ], + [ 22714., 0., 0., 17386.5, 0. ]]) + b_ub = np.zeros(A_ub.shape[0]) + c = -np.ones(A_ub.shape[1]) + bounds = [(0,1)] * A_ub.shape[1] + + with pytest.warns(OptimizeWarning): + linprog(c=c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, + options=self.options, method=self.method) + class BaseTestLinprogIP(LinprogCommonTests): method = "interior-point" @@ -844,36 +966,6 @@ def test_bounds_equal_but_infeasible2(self): method=self.method, options=self.options) _assert_infeasible(res) - def test_bug_5400(self): - # https://github.com/scipy/scipy/issues/5400 - bounds = [ - (0, None), - (0, 100), (0, 100), (0, 100), (0, 100), (0, 100), (0, 100), - (0, 900), (0, 900), (0, 900), (0, 900), (0, 900), (0, 900), - (0, None), (0, None), (0, None), (0, None), (0, None), (0, None)] - - f = 1 / 9 - g = -1e4 - h = -3.1 - A_ub = np.array([ - [1, -2.99, 0, 0, -3, 0, 0, 0, -1, -1, 0, -1, -1, 1, 1, 0, 0, 0, 0], - [1, 0, -2.9, h, 0, -3, 0, -1, 0, 0, -1, 0, -1, 0, 0, 1, 1, 0, 0], - [1, 0, 0, h, 0, 0, -3, -1, -1, 0, -1, -1, 0, 0, 0, 0, 0, 1, 1], - [0, 1.99, -1, -1, 0, 0, 0, -1, f, f, 0, 0, 0, g, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 2, -1, -1, 0, 0, 0, -1, f, f, 0, g, 0, 0, 0, 0], - [0, -1, 1.9, 2.1, 0, 0, 0, f, -1, -1, 0, 0, 0, 0, 0, g, 0, 0, 0], - [0, 0, 0, 0, -1, 2, -1, 0, 0, 0, f, -1, f, 0, 0, 0, g, 0, 0], - [0, -1, -1, 2.1, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, 0, 0, g, 0], - [0, 0, 0, 0, -1, -1, 2, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, g]]) - - b_ub = np.array([0.0, 0, 0, 0, 0, 0, 0, 0, 0]) - c = np.array([-1.0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]) - - res = linprog(c, A_ub, b_ub, bounds=bounds, - method=self.method, options=self.options) - _assert_success(res, desired_fun=-106.63507541835018) - def test_empty_constraint_1(self): # detected in presolve? res = linprog([-1, 1, -1, 1], From f095ebbc8e3ad42220444a64b29db092f3ad5c15 Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Fri, 27 Jul 2018 20:35:44 +0800 Subject: [PATCH 5/6] Fix comment on expected behaviour of test. --- scipy/optimize/tests/test_linprog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 057bbc79ea0a..27598503d578 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -916,7 +916,7 @@ def test_issue_7237_passes_with_bland(self): b_eq = np.array([[ 1.]]) bounds = [(None, None)] * 9 - # Should fail if Bland's rule is not used. + # Should warn if Bland's rule is not used. with pytest.warns(OptimizeWarning): res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method=self.method, options=self.options) From b081e9e40750f085bcbc470111dc27eaa6a7545c Mon Sep 17 00:00:00 2001 From: Kai Striega Date: Sat, 28 Jul 2018 11:09:44 +0800 Subject: [PATCH 6/6] Slight changes to codestyle and warning message. Removes additional whitespace from tests in test_linprog. Changes the order of suggestions in the previously added warning message. The original message firstly suggested trying to increase the tolerance. This may work in theory, but shouldn't be the first choice. --- THANKS.txt | 2 +- scipy/optimize/_linprog.py | 11 ++--- scipy/optimize/tests/test_linprog.py | 62 ++++++++++++++-------------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/THANKS.txt b/THANKS.txt index af3e63f66187..3c1895223336 100644 --- a/THANKS.txt +++ b/THANKS.txt @@ -195,7 +195,7 @@ Konrad Griessinger for the small sample Kendall test Tony Xiang for improvements in scipy.sparse Roy Zywina for contributions to scipy.fftpack. Christian H. Meyer for bug fixes in subspace_angles. -Kai Striega for improvments to the numerical stability of the simplex method. +Kai Striega for improvements to the scipy.optimize.linprog simplex method. Institutions ------------ diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py index fb46fb946b9a..ce4ed9e8135f 100644 --- a/scipy/optimize/_linprog.py +++ b/scipy/optimize/_linprog.py @@ -248,11 +248,12 @@ def _apply_pivot(T, basis, pivrow, pivcol, tol=1e-12): # The selected pivot should never lead to a pivot value less than the tol. if np.isclose(pivval, tol, atol=0, rtol=1e4): message = ( - "The pivot operation produces a pivot value of:{0: .1e}. " - "Being only slightly greater than the set tolerance{1: .1e}. " - "This may lead to issues regarding the numerical stability of " - "the simplex method. Increasing the tolerance, changing the pivot " - "strategy via Bland's rule or removing redundant constraints may " + "The pivot operation produces a pivot value of:{0: .1e}, " + "which is only slightly greater than the specified " + "tolerance{1: .1e}. This may lead to issues regarding the " + "numerical stability of the simplex method. " + "Removing redundant constraints, changing the pivot strategy " + "via Bland's rule or increasing the tolerance may " "help reduce the issue.".format(pivval, tol)) warn(message, OptimizeWarning) diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py index 27598503d578..0cffc1ffd765 100644 --- a/scipy/optimize/tests/test_linprog.py +++ b/scipy/optimize/tests/test_linprog.py @@ -884,36 +884,36 @@ def test_issue_7237_passes_with_bland(self): # close to zero. Bland's rule provides an alternative pivot selection # and produces a valid result. - c = np.array([-1., 0., 0., 0., 0., 0., 0., 0., 0.]) + c = np.array([-1, 0, 0, 0, 0, 0, 0, 0, 0]) A_ub = np.array([ - [ 1., -724., 911., -551., -555., -896., 478., -80., -293.], - [ 1., 566., 42., 937., 233., 883., 392., -909., 57.], - [ 1., -208., -894., 539., 321., 532., -924., 942., 55.], - [ 1., 857., -859., 83., 462., -265., -971., 826., 482.], - [ 1., 314., -424., 245., -424., 194., -443., -104., -429.], - [ 1., 540., 679., 361., 149., -827., 876., 633., 302.], - [ 0., -1., -0., -0., -0., -0., -0., -0., -0.], - [ 0., -0., -1., -0., -0., -0., -0., -0., -0.], - [ 0., -0., -0., -1., -0., -0., -0., -0., -0.], - [ 0., -0., -0., -0., -1., -0., -0., -0., -0.], - [ 0., -0., -0., -0., -0., -1., -0., -0., -0.], - [ 0., -0., -0., -0., -0., -0., -1., -0., -0.], - [ 0., -0., -0., -0., -0., -0., -0., -1., -0.], - [ 0., -0., -0., -0., -0., -0., -0., -0., -1.], - [ 0., 1., 0., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 1., 0., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 1., 0., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 1., 0., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 1., 0., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 1., 0., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 1., 0.], - [ 0., 0., 0., 0., 0., 0., 0., 0., 1.] + [1., -724., 911., -551., -555., -896., 478., -80., -293.], + [1., 566., 42., 937.,233., 883., 392., -909., 57.], + [1., -208., -894., 539., 321., 532., -924., 942., 55.], + [1., 857., -859., 83., 462., -265., -971., 826., 482.], + [1., 314., -424., 245., -424., 194., -443., -104., -429.], + [1., 540., 679., 361., 149., -827., 876., 633., 302.], + [0., -1., -0., -0., -0., -0., -0., -0., -0.], + [0., -0., -1., -0., -0., -0., -0., -0., -0.], + [0., -0., -0., -1., -0., -0., -0., -0., -0.], + [0., -0., -0., -0., -1., -0., -0., -0., -0.], + [0., -0., -0., -0., -0., -1., -0., -0., -0.], + [0., -0., -0., -0., -0., -0., -1., -0., -0.], + [0., -0., -0., -0., -0., -0., -0., -1., -0.], + [0., -0., -0., -0., -0., -0., -0., -0., -1.], + [0., 1., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 1., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 1., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 1., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 1., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 1., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 1.] ]) b_ub = np.array([ - 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.]) - A_eq = np.array([[ 0., 1., 1., 1., 1., 1., 1., 1., 1.]]) - b_eq = np.array([[ 1.]]) + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.]) + A_eq = np.array([[0., 1., 1., 1., 1., 1., 1., 1., 1.]]) + b_eq = np.array([[1.]]) bounds = [(None, None)] * 9 # Should warn if Bland's rule is not used. @@ -932,10 +932,10 @@ def test_issue_8174_warns_if_pivval_near_tol(self): # The simplex method sometimes "explodes" if the pivot value is very # close to zero. A_ub = np.array([ - [ 22714., 1008., 13380., -2713.5, -1116. ], - [ -4986., -1092., -31220., 17386.5, 684. ], - [ -4986., 0., 0., -2713.5, 0. ], - [ 22714., 0., 0., 17386.5, 0. ]]) + [22714, 1008, 13380, -2713.5, -1116], + [-4986, -1092, -31220, 17386.5, 684], + [-4986, 0, 0, -2713.5, 0], + [22714, 0, 0, 17386.5, 0]]) b_ub = np.zeros(A_ub.shape[0]) c = -np.ones(A_ub.shape[1]) bounds = [(0,1)] * A_ub.shape[1]