Skip to content

Commit

Permalink
fix: scipy time limit raises SolverError (#2080)
Browse files Browse the repository at this point in the history
* fix: scipy time limit raises SolverError

* fix: only allow optimal_inaccurate from scipy if solution is present

* chore: lint fix
  • Loading branch information
allenlawrence94 committed Mar 31, 2023
1 parent 6d7e27b commit 2ed1c48
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 2 deletions.
9 changes: 7 additions & 2 deletions cvxpy/reductions/solvers/conic_solvers/scipy_conif.py
Expand Up @@ -42,7 +42,7 @@ class SCIPY(ConicSolver):

# Map of SciPy linprog status
STATUS_MAP = {0: s.OPTIMAL, # Optimal
1: s.SOLVER_ERROR, # Iteration limit reached
1: s.OPTIMAL_INACCURATE, # Iteration/time limit reached
2: s.INFEASIBLE, # Infeasible
3: s.UNBOUNDED, # Unbounded
4: s.SOLVER_ERROR # Numerical difficulties encountered
Expand Down Expand Up @@ -217,7 +217,12 @@ def _log_scipy_method_warning(self, meth):
def invert(self, solution, inverse_data):
"""Returns the solution to the original problem given the inverse_data.
"""
status = self.STATUS_MAP[solution['status']]
status = self.STATUS_MAP[solution["status"]]

# Sometimes when the solver's time limit is reached, the solver doesn't return a solution.
# In these situations we correct the status from s.OPTIMAL_INACCURATE to s.SOLVER_ERROR
if (status == s.OPTIMAL_INACCURATE) and (solution.x is None):
status = s.SOLVER_ERROR

primal_vars = None
dual_vars = None
Expand Down
26 changes: 26 additions & 0 deletions cvxpy/tests/solver_test_helpers.py
Expand Up @@ -753,6 +753,32 @@ def mi_lp_5() -> SolverTestHelper:
return sth


def mi_lp_7() -> SolverTestHelper:
"""Problem that takes significant time to solve - for testing time/iteration limits"""
np.random.seed(0)
n = 24 * 8
c = cp.Variable((n,), pos=True)
d = cp.Variable((n,), pos=True)
c_or_d = cp.Variable((n,), boolean=True)
big = 1e3
s = cp.cumsum(c * 0.9 - d)
p = np.random.random(n)
objective = cp.Maximize(p @ (d - c))
constraints = [
d <= 1,
c <= 1,
s >= 0,
s <= 1,
c <= c_or_d * big,
d <= (1 - c_or_d) * big,
]
return SolverTestHelper(
(objective, None),
[(c, None,), (d, None), (c_or_d, None)],
[(con, None) for con in constraints]
)


def mi_socp_1() -> SolverTestHelper:
"""
Formulate the following mixed-integer SOCP with cvxpy
Expand Down
14 changes: 14 additions & 0 deletions cvxpy/tests/test_conic_solvers.py
Expand Up @@ -24,6 +24,7 @@

import cvxpy as cp
import cvxpy.tests.solver_test_helpers as sths
from cvxpy import SolverError
from cvxpy.reductions.solvers.defines import (
INSTALLED_MI_SOLVERS,
INSTALLED_SOLVERS,
Expand Down Expand Up @@ -1994,6 +1995,19 @@ def test_scipy_mi_lp_4(self) -> None:
def test_scipy_mi_lp_5(self) -> None:
StandardTestLPs.test_mi_lp_5(solver='SCIPY')

@unittest.skipUnless('SCIPY' in INSTALLED_MI_SOLVERS, 'SCIPY version cannot solve MILPs')
def test_scipy_mi_time_limit_reached(self) -> None:
sth = sths.mi_lp_7()

# run without enough time to find optimum
sth.solve(solver='SCIPY', scipy_options={"time_limit": 0.01})
assert sth.prob.status == cp.OPTIMAL_INACCURATE
assert sth.objective.value > 0

# run without enough time to do anything
with pytest.raises(SolverError):
sth.solve(solver='SCIPY', scipy_options={"time_limit": 0.})


@unittest.skipUnless('COPT' in INSTALLED_SOLVERS, 'COPT is not installed.')
class TestCOPT(unittest.TestCase):
Expand Down

0 comments on commit 2ed1c48

Please sign in to comment.