Organize test fixtures; handle infeasibility in DropRedundant

master
Alinson S. Xavier 5 years ago
parent 8153dfc825
commit 31ca45036a

@ -32,7 +32,7 @@ class DropRedundantInequalitiesStep(Component):
classifier=CountingClassifier(), classifier=CountingClassifier(),
threshold=0.95, threshold=0.95,
slack_tolerance=1e-5, slack_tolerance=1e-5,
check_feasibility=False, check_feasibility=True,
violation_tolerance=1e-5, violation_tolerance=1e-5,
max_iterations=3, max_iterations=3,
): ):
@ -208,6 +208,8 @@ class DropRedundantInequalitiesStep(Component):
return False return False
if self.current_iteration >= self.max_iterations: if self.current_iteration >= self.max_iterations:
return False return False
if solver.internal_solver.is_infeasible():
return False
self.current_iteration += 1 self.current_iteration += 1
logger.debug("Checking that dropped constraints are satisfied...") logger.debug("Checking that dropped constraints are satisfied...")
constraints_to_add = [] constraints_to_add = []

@ -342,7 +342,7 @@ class GurobiSolver(InternalSolver):
self.model.remove(constr) self.model.remove(constr)
return cobj return cobj
def is_constraint_satisfied(self, cobj, tol=1e-5): def is_constraint_satisfied(self, cobj, tol=1e-6):
lhs, sense, rhs, name = cobj lhs, sense, rhs, name = cobj
if self.cb_where is not None: if self.cb_where is not None:
lhs_value = lhs.getConstant() lhs_value = lhs.getConstant()
@ -378,6 +378,7 @@ class GurobiSolver(InternalSolver):
def relax(self) -> None: def relax(self) -> None:
assert self.model is not None assert self.model is not None
self.model.update()
self.model = self.model.relax() self.model = self.model.relax()
self._update_vars() self._update_vars()

@ -173,7 +173,7 @@ class InternalSolver(ABC):
pass pass
@abstractmethod @abstractmethod
def is_constraint_satisfied(self, cobj: Constraint) -> bool: def is_constraint_satisfied(self, cobj: Constraint, tol: float = 1e-6) -> bool:
""" """
Returns True if the current solution satisfies the given constraint. Returns True if the current solution satisfies the given constraint.
""" """

@ -308,7 +308,7 @@ class BasePyomoSolver(InternalSolver):
def extract_constraint(self, cid: str) -> Constraint: def extract_constraint(self, cid: str) -> Constraint:
raise Exception("Not implemented") raise Exception("Not implemented")
def is_constraint_satisfied(self, cobj: Constraint) -> bool: def is_constraint_satisfied(self, cobj: Constraint, tol: float = 1e-6) -> bool:
raise Exception("Not implemented") raise Exception("Not implemented")
def is_infeasible(self) -> bool: def is_infeasible(self) -> bool:

@ -6,13 +6,14 @@ from unittest.mock import Mock, call
import numpy as np import numpy as np
from miplearn import RelaxIntegralityStep, BasePyomoSolver from miplearn import RelaxIntegralityStep, GurobiSolver
from miplearn.classifiers import Classifier from miplearn.classifiers import Classifier
from miplearn.components.steps.drop_redundant import DropRedundantInequalitiesStep from miplearn.components.steps.drop_redundant import DropRedundantInequalitiesStep
from miplearn.instance import Instance from miplearn.instance import Instance
from miplearn.solvers.internal import InternalSolver from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.learning import LearningSolver from miplearn.solvers.learning import LearningSolver
from tests.solvers import _get_knapsack_instance from tests.fixtures.infeasible import get_infeasible_instance
from tests.fixtures.redundant import get_instance_with_redundancy
def _setup(): def _setup():
@ -30,6 +31,7 @@ def _setup():
) )
internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid) internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
internal.is_constraint_satisfied = Mock(return_value=False) internal.is_constraint_satisfied = Mock(return_value=False)
internal.is_infeasible = Mock(return_value=False)
instance = Mock(spec=Instance) instance = Mock(spec=Instance)
instance.get_constraint_features = Mock( instance.get_constraint_features = Mock(
@ -399,13 +401,18 @@ def test_x_multiple_solves():
def test_usage(): def test_usage():
for internal_solver in [GurobiSolver]:
for instance in [
get_instance_with_redundancy(internal_solver),
get_infeasible_instance(internal_solver),
]:
solver = LearningSolver( solver = LearningSolver(
solver=internal_solver,
components=[ components=[
RelaxIntegralityStep(), RelaxIntegralityStep(),
DropRedundantInequalitiesStep(), DropRedundantInequalitiesStep(),
] ],
) )
instance = _get_knapsack_instance(BasePyomoSolver)
# The following should not crash # The following should not crash
solver.solve(instance) solver.solve(instance)
solver.fit([instance]) solver.fit([instance])

@ -0,0 +1,3 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

@ -0,0 +1,40 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from typing import Any
from pyomo import environ as pe
from miplearn.instance import Instance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.pyomo.base import BasePyomoSolver
from tests.solvers import _is_subclass_or_instance
class InfeasiblePyomoInstance(Instance):
def to_model(self) -> pe.ConcreteModel:
model = pe.ConcreteModel()
model.x = pe.Var([0], domain=pe.Binary)
model.OBJ = pe.Objective(expr=model.x[0], sense=pe.maximize)
model.eq = pe.Constraint(expr=model.x[0] >= 2)
return model
class InfeasibleGurobiInstance(Instance):
def to_model(self) -> Any:
import gurobipy as gp
from gurobipy import GRB
model = gp.Model()
x = model.addVars(1, vtype=GRB.BINARY, name="x")
model.addConstr(x[0] >= 2)
model.setObjective(x[0])
return model
def get_infeasible_instance(solver):
if _is_subclass_or_instance(solver, BasePyomoSolver):
return InfeasiblePyomoInstance()
if _is_subclass_or_instance(solver, GurobiSolver):
return InfeasibleGurobiInstance()

@ -0,0 +1,39 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from typing import Any
from miplearn import Instance, BasePyomoSolver, GurobiSolver
import pyomo.environ as pe
from tests.solvers import _is_subclass_or_instance
class PyomoInstanceWithRedundancy(Instance):
def to_model(self) -> pe.ConcreteModel:
model = pe.ConcreteModel()
model.x = pe.Var([0, 1], domain=pe.Binary)
model.OBJ = pe.Objective(expr=model.x[0] + model.x[1], sense=pe.maximize)
model.eq1 = pe.Constraint(expr=model.x[0] + model.x[1] <= 1)
model.eq2 = pe.Constraint(expr=model.x[0] + model.x[1] <= 2)
return model
class GurobiInstanceWithRedundancy(Instance):
def to_model(self) -> Any:
import gurobipy as gp
from gurobipy import GRB
model = gp.Model()
x = model.addVars(2, vtype=GRB.BINARY, name="x")
model.addConstr(x[0] + x[1] <= 1)
model.addConstr(x[0] + x[1] <= 2)
model.setObjective(x[0] + x[1], GRB.MAXIMIZE)
return model
def get_instance_with_redundancy(solver):
if _is_subclass_or_instance(solver, BasePyomoSolver):
return PyomoInstanceWithRedundancy()
if _is_subclass_or_instance(solver, GurobiSolver):
return GurobiInstanceWithRedundancy()

@ -3,11 +3,8 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
from inspect import isclass from inspect import isclass
from typing import List, Callable, Any from typing import List, Callable
from pyomo import environ as pe
from miplearn.instance import Instance
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
from miplearn.solvers.gurobi import GurobiSolver from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.internal import InternalSolver from miplearn.solvers.internal import InternalSolver
@ -16,27 +13,6 @@ from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
class InfeasiblePyomoInstance(Instance):
def to_model(self) -> pe.ConcreteModel:
model = pe.ConcreteModel()
model.x = pe.Var([0], domain=pe.Binary)
model.OBJ = pe.Objective(expr=model.x[0], sense=pe.maximize)
model.eq = pe.Constraint(expr=model.x[0] >= 2)
return model
class InfeasibleGurobiInstance(Instance):
def to_model(self) -> Any:
import gurobipy as gp
from gurobipy import GRB
model = gp.Model()
x = model.addVars(1, vtype=GRB.BINARY, name="x")
model.addConstr(x[0] >= 2)
model.setObjective(x[0])
return model
def _is_subclass_or_instance(obj, parent_class): def _is_subclass_or_instance(obj, parent_class):
return isinstance(obj, parent_class) or ( return isinstance(obj, parent_class) or (
isclass(obj) and issubclass(obj, parent_class) isclass(obj) and issubclass(obj, parent_class)
@ -59,12 +35,5 @@ def _get_knapsack_instance(solver):
assert False assert False
def _get_infeasible_instance(solver):
if _is_subclass_or_instance(solver, BasePyomoSolver):
return InfeasiblePyomoInstance()
if _is_subclass_or_instance(solver, GurobiSolver):
return InfeasibleGurobiInstance()
def _get_internal_solvers() -> List[Callable[[], InternalSolver]]: def _get_internal_solvers() -> List[Callable[[], InternalSolver]]:
return [GurobiPyomoSolver, GurobiSolver, XpressPyomoSolver] return [GurobiPyomoSolver, GurobiSolver, XpressPyomoSolver]

@ -14,8 +14,8 @@ from miplearn.solvers.pyomo.base import BasePyomoSolver
from . import ( from . import (
_get_knapsack_instance, _get_knapsack_instance,
_get_internal_solvers, _get_internal_solvers,
_get_infeasible_instance,
) )
from ..fixtures.infeasible import get_infeasible_instance
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -186,7 +186,7 @@ def test_relax():
def test_infeasible_instance(): def test_infeasible_instance():
for solver_class in _get_internal_solvers(): for solver_class in _get_internal_solvers():
instance = _get_infeasible_instance(solver_class) instance = get_infeasible_instance(solver_class)
solver = solver_class() solver = solver_class()
solver.set_instance(instance) solver.set_instance(instance)
stats = solver.solve() stats = solver.solve()

Loading…
Cancel
Save