Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch Cplex errors and raise optlang.SolverError #96

Merged
merged 6 commits into from
Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 56 additions & 31 deletions optlang/cplex_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,19 @@
import os
from six.moves import StringIO

log = logging.getLogger(__name__)

import sympy
from sympy.core.add import _unevaluated_Add
from sympy.core.mul import _unevaluated_Mul
from sympy.core.singleton import S
import cplex
from cplex.exceptions import CplexSolverError

from optlang import interface
from optlang.util import inheritdocstring, TemporaryFilename
from optlang.expression_parsing import parse_optimization_expression
from optlang.exceptions import SolverError

log = logging.getLogger(__name__)

Zero = S.Zero
One = S.One
Expand Down Expand Up @@ -190,17 +193,21 @@ def type(self, value):
super(Variable, Variable).type.fset(self, value)

def _get_primal(self):
primal_from_solver = self.problem.problem.solution.get_values(self.name)
return primal_from_solver
try:
return self.problem.problem.solution.get_values(self.name)
except CplexSolverError as err:
raise SolverError(str(err))

@property
def dual(self):
if self.problem is not None:
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
return self.problem.problem.solution.get_reduced_costs(self.name)
else:
if self.problem is None:
return None
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
try:
return self.problem.problem.solution.get_reduced_costs(self.name)
except CplexSolverError as err:
raise SolverError(str(err))

@interface.Variable.name.setter
def name(self, value):
Expand Down Expand Up @@ -254,21 +261,24 @@ def problem(self, value):

@property
def primal(self):
if self.problem is not None:
primal_from_solver = self.problem.problem.solution.get_activity_levels(self.name)
# return self._round_primal_to_bounds(primal_from_solver) # Test assertions fail
return primal_from_solver
else:
if self.problem is None:
return None
try:
# return self._round_primal_to_bounds(primal_from_solver) # Test assertions fail
return self.problem.problem.solution.get_activity_levels(self.name)
except CplexSolverError as err:
raise SolverError(str(err))

@property
def dual(self):
if self.problem is not None:
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
return self.problem.problem.solution.get_dual_values(self.name)
else:
if self.problem is None:
return None
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
try:
return self.problem.problem.solution.get_dual_values(self.name)
except CplexSolverError as err:
raise SolverError(str(err))

@interface.Constraint.name.setter
def name(self, value):
Expand Down Expand Up @@ -335,10 +345,12 @@ def __init__(self, expression, sloppy=False, **kwargs):

@property
def value(self):
if getattr(self, 'problem', None) is not None:
return self.problem.problem.solution.get_objective_value()
else:
if getattr(self, 'problem', None) is None:
return None
try:
return self.problem.problem.solution.get_objective_value()
except CplexSolverError as err:
raise SolverError(str(err))

@interface.Objective.direction.setter
def direction(self, value):
Expand Down Expand Up @@ -711,32 +723,42 @@ def _set_objective_direction(self, direction):

@property
def primal_values(self):
primal_values = collections.OrderedDict()
for variable, primal in zip(self.variables, self.problem.solution.get_values()):
primal_values[variable.name] = variable._round_primal_to_bounds(primal)
try:
primal_values = collections.OrderedDict(
(variable.name, variable._round_primal_to_bounds(primal))
for variable, primal in zip(self.variables, self.problem.solution.get_values())
)
except CplexSolverError as err:
raise SolverError(str(err))
return primal_values

@property
def reduced_costs(self):
if self.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
else:
try:
return collections.OrderedDict(
zip((variable.name for variable in self.variables), self.problem.solution.get_reduced_costs()))
except CplexSolverError as err:
raise SolverError(str(err))

@property
def constraint_values(self):
return collections.OrderedDict(
zip((constraint.name for constraint in self.constraints), self.problem.solution.get_activity_levels()))

try:
return collections.OrderedDict(
zip((constraint.name for constraint in self.constraints), self.problem.solution.get_activity_levels()))
except CplexSolverError as err:
raise SolverError(str(err))

@property
def shadow_prices(self):
if self.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
else:
try:
return collections.OrderedDict(
zip((constraint.name for constraint in self.constraints), self.problem.solution.get_dual_values()))
except CplexSolverError as err:
raise SolverError(str(err))

@property
def is_integer(self):
Expand All @@ -751,7 +773,10 @@ def to_lp(self):
return lp_form

def _optimize(self):
self.problem.solve()
try:
self.problem.solve()
except CplexSolverError as err:
raise SolverError(str(err))
cplex_status = self.problem.solution.get_status()
self._original_status = self.problem.solution.get_status_string()
status = _CPLEX_STATUS_TO_STATUS[cplex_status]
Expand Down
7 changes: 7 additions & 0 deletions optlang/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
# limitations under the License.


class SolverError(Exception):
"""Reraise solver specific errors with this unified optlang error instead."""

def __init__(self, message, **kwargs):
super(SolverError, self).__init__(message, **kwargs)


class ContainerAlreadyContains(Exception):
"""
This exception is raised when the name of an object being added to a Container is already
Expand Down
130 changes: 129 additions & 1 deletion optlang/tests/test_cplex_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def test_fail(self):

import nose
from optlang.tests import abstract_test_cases
from optlang.exceptions import SolverError
from optlang.interface import OPTIMAL, INFEASIBLE

from optlang.cplex_interface import Variable, Constraint, Model, Objective
from optlang import cplex_interface
Expand Down Expand Up @@ -443,7 +445,7 @@ def test_non_convex_obj(self):
obj = Objective(self.x1 * self.x2, direction="min")
model.objective = obj
model.configuration.solution_target = "convex"
self.assertRaises(CplexSolverError, model.optimize)
self.assertRaises(SolverError, model.optimize)
model.configuration.solution_target = "local"
model.configuration.qp_method = "barrier"
model.optimize()
Expand Down Expand Up @@ -497,6 +499,132 @@ def test_qp_non_convex(self):
model.optimize()
self.assertAlmostEqual(model.objective.value, 2441.999999971)

class InfeasibleMIPTestCase(unittest.TestCase):

interface = cplex_interface

def setUp(self):
model = self.interface.Model()
x = self.interface.Variable('x', lb=0, ub=10)
y = self.interface.Variable('y', lb=0, ub=10)
k = self.interface.Variable('k', type='binary')
i = self.interface.Variable('i', type='binary')
constr1 = self.interface.Constraint(1. * x + y, lb=3, name="constr1")
constr2 = self.interface.Constraint(x - k, ub=-10, name="constr2")
constr3 = self.interface.Constraint(y - i, ub=-10, name="constr3")
obj = self.interface.Objective(2 * x + y)
model.add(x)
model.add(y)
model.add(k)
model.add(i)
model.add(constr1)
model.add(constr2)
model.add(constr3)
model.objective = obj
model.optimize()
self.model = model
self.continuous_var = x
self.binary_var = k
self.constraint = constr1

def test_infeasible(self):
self.assertEqual(self.model.status, INFEASIBLE)

def test_objective_value(self):
with self.assertRaises(SolverError) as context:
self.model.objective.value
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_variable_primal(self):
with self.assertRaises(SolverError) as context:
self.continuous_var.primal
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_binary_variable_primal(self):
with self.assertRaises(SolverError) as context:
self.binary_var.primal
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_constraint_primal(self):
with self.assertRaises(SolverError) as context:
self.constraint.primal
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_primal_values(self):
with self.assertRaises(SolverError) as context:
self.model.primal_values
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_constraint_values(self):
with self.assertRaises(SolverError) as context:
self.model.constraint_values
self.assertIn("CPLEX Error 1217", str(context.exception))


class UnsolvedTestCase(unittest.TestCase):

interface = cplex_interface

def setUp(self):
model = self.interface.Model()
x = self.interface.Variable('x', lb=0, ub=10)
constr1 = self.interface.Constraint(1. * x, lb=3, name="constr1")
obj = self.interface.Objective(2 * x)
model.add(x)
model.add(constr1)
model.objective = obj
self.model = model
self.continuous_var = x
self.constraint = constr1

def test_status(self):
self.assertIs(self.model.status, None)

def test_objective_value(self):
with self.assertRaises(SolverError) as context:
self.model.objective.value
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_variable_primal(self):
with self.assertRaises(SolverError) as context:
self.continuous_var.primal
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_variable_dual(self):
with self.assertRaises(SolverError) as context:
self.continuous_var.dual
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_constraint_primal(self):
with self.assertRaises(SolverError) as context:
self.constraint.primal
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_constraint_dual(self):
with self.assertRaises(SolverError) as context:
self.constraint.dual
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_primal_values(self):
with self.assertRaises(SolverError) as context:
self.model.primal_values
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_constraint_values(self):
with self.assertRaises(SolverError) as context:
self.model.constraint_values
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_reduced_costs(self):
with self.assertRaises(SolverError) as context:
self.model.reduced_costs
self.assertIn("CPLEX Error 1217", str(context.exception))

def test_shadow_prices(self):
with self.assertRaises(SolverError) as context:
self.model.shadow_prices
self.assertIn("CPLEX Error 1217", str(context.exception))


if __name__ == '__main__':
nose.runmodule()