mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Organize test fixtures; handle infeasibility in DropRedundant
This commit is contained in:
@@ -32,7 +32,7 @@ class DropRedundantInequalitiesStep(Component):
|
||||
classifier=CountingClassifier(),
|
||||
threshold=0.95,
|
||||
slack_tolerance=1e-5,
|
||||
check_feasibility=False,
|
||||
check_feasibility=True,
|
||||
violation_tolerance=1e-5,
|
||||
max_iterations=3,
|
||||
):
|
||||
@@ -208,6 +208,8 @@ class DropRedundantInequalitiesStep(Component):
|
||||
return False
|
||||
if self.current_iteration >= self.max_iterations:
|
||||
return False
|
||||
if solver.internal_solver.is_infeasible():
|
||||
return False
|
||||
self.current_iteration += 1
|
||||
logger.debug("Checking that dropped constraints are satisfied...")
|
||||
constraints_to_add = []
|
||||
|
||||
@@ -342,7 +342,7 @@ class GurobiSolver(InternalSolver):
|
||||
self.model.remove(constr)
|
||||
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
|
||||
if self.cb_where is not None:
|
||||
lhs_value = lhs.getConstant()
|
||||
@@ -378,6 +378,7 @@ class GurobiSolver(InternalSolver):
|
||||
|
||||
def relax(self) -> None:
|
||||
assert self.model is not None
|
||||
self.model.update()
|
||||
self.model = self.model.relax()
|
||||
self._update_vars()
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ class InternalSolver(ABC):
|
||||
pass
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
||||
@@ -308,7 +308,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
def extract_constraint(self, cid: str) -> Constraint:
|
||||
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")
|
||||
|
||||
def is_infeasible(self) -> bool:
|
||||
|
||||
@@ -6,13 +6,14 @@ from unittest.mock import Mock, call
|
||||
|
||||
import numpy as np
|
||||
|
||||
from miplearn import RelaxIntegralityStep, BasePyomoSolver
|
||||
from miplearn import RelaxIntegralityStep, GurobiSolver
|
||||
from miplearn.classifiers import Classifier
|
||||
from miplearn.components.steps.drop_redundant import DropRedundantInequalitiesStep
|
||||
from miplearn.instance import Instance
|
||||
from miplearn.solvers.internal import InternalSolver
|
||||
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():
|
||||
@@ -30,6 +31,7 @@ def _setup():
|
||||
)
|
||||
internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
|
||||
internal.is_constraint_satisfied = Mock(return_value=False)
|
||||
internal.is_infeasible = Mock(return_value=False)
|
||||
|
||||
instance = Mock(spec=Instance)
|
||||
instance.get_constraint_features = Mock(
|
||||
@@ -399,13 +401,18 @@ def test_x_multiple_solves():
|
||||
|
||||
|
||||
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=internal_solver,
|
||||
components=[
|
||||
RelaxIntegralityStep(),
|
||||
DropRedundantInequalitiesStep(),
|
||||
]
|
||||
],
|
||||
)
|
||||
instance = _get_knapsack_instance(BasePyomoSolver)
|
||||
# The following should not crash
|
||||
solver.solve(instance)
|
||||
solver.fit([instance])
|
||||
|
||||
3
tests/fixtures/__init__.py
vendored
Normal file
3
tests/fixtures/__init__.py
vendored
Normal file
@@ -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.
|
||||
40
tests/fixtures/infeasible.py
vendored
Normal file
40
tests/fixtures/infeasible.py
vendored
Normal file
@@ -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()
|
||||
39
tests/fixtures/redundant.py
vendored
Normal file
39
tests/fixtures/redundant.py
vendored
Normal file
@@ -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.
|
||||
|
||||
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.solvers.gurobi import GurobiSolver
|
||||
from miplearn.solvers.internal import InternalSolver
|
||||
@@ -16,27 +13,6 @@ from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
|
||||
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):
|
||||
return isinstance(obj, parent_class) or (
|
||||
isclass(obj) and issubclass(obj, parent_class)
|
||||
@@ -59,12 +35,5 @@ def _get_knapsack_instance(solver):
|
||||
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]]:
|
||||
return [GurobiPyomoSolver, GurobiSolver, XpressPyomoSolver]
|
||||
|
||||
@@ -14,8 +14,8 @@ from miplearn.solvers.pyomo.base import BasePyomoSolver
|
||||
from . import (
|
||||
_get_knapsack_instance,
|
||||
_get_internal_solvers,
|
||||
_get_infeasible_instance,
|
||||
)
|
||||
from ..fixtures.infeasible import get_infeasible_instance
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -186,7 +186,7 @@ def test_relax():
|
||||
|
||||
def test_infeasible_instance():
|
||||
for solver_class in _get_internal_solvers():
|
||||
instance = _get_infeasible_instance(solver_class)
|
||||
instance = get_infeasible_instance(solver_class)
|
||||
solver = solver_class()
|
||||
solver.set_instance(instance)
|
||||
stats = solver.solve()
|
||||
|
||||
Reference in New Issue
Block a user