InternalSolver: Better specify and test infeasibility

This commit is contained in:
2021-01-21 09:15:14 -06:00
parent 05497cab07
commit 6890840c6d
9 changed files with 134 additions and 65 deletions

View File

@@ -3,8 +3,11 @@
# Released under the modified BSD license. See COPYING.md for more details.
from inspect import isclass
from typing import List, Callable
from typing import List, Callable, Any
from pyomo import environ as pe
from miplearn.instance import Instance, PyomoInstance
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.internal import InternalSolver
@@ -13,28 +16,55 @@ from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
def _get_instance(solver):
def _is_subclass_or_instance(obj, parent_class):
return isinstance(obj, parent_class) or (
isclass(obj) and issubclass(obj, parent_class)
)
class InfeasiblePyomoInstance(PyomoInstance):
def to_model(self) -> pe.ConcreteModel:
model = pe.ConcreteModel()
model.x = pe.Var(domain=pe.Binary)
model.OBJ = pe.Objective(expr=model.x, sense=pe.maximize)
model.eq = pe.Constraint(expr=model.x >= 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)
)
def _get_knapsack_instance(solver):
if _is_subclass_or_instance(solver, BasePyomoSolver):
return KnapsackInstance(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
)
if _is_subclass_or_instance(solver, GurobiSolver):
return GurobiKnapsackInstance(
weights=[23.0, 26.0, 20.0, 18.0],
prices=[505.0, 352.0, 458.0, 220.0],
capacity=67.0,
)
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]

View File

@@ -11,7 +11,11 @@ import pyomo.environ as pe
from miplearn.solvers import RedirectOutput
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.pyomo.base import BasePyomoSolver
from miplearn.solvers.tests import _get_instance, _get_internal_solvers
from miplearn.solvers.tests import (
_get_knapsack_instance,
_get_internal_solvers,
_get_infeasible_instance,
)
logger = logging.getLogger(__name__)
@@ -30,7 +34,7 @@ def test_redirect_output():
def test_internal_solver_warm_starts():
for solver_class in _get_internal_solvers():
logger.info("Solver: %s" % solver_class)
instance = _get_instance(solver_class)
instance = _get_knapsack_instance(solver_class)
model = instance.to_model()
solver = solver_class()
solver.set_instance(instance, model)
@@ -82,7 +86,7 @@ def test_internal_solver():
for solver_class in _get_internal_solvers():
logger.info("Solver: %s" % solver_class)
instance = _get_instance(solver_class)
instance = _get_knapsack_instance(solver_class)
model = instance.to_model()
solver = solver_class()
solver.set_instance(instance, model)
@@ -158,10 +162,26 @@ def test_internal_solver():
assert round(stats["Lower bound"]) == 1179.0
def test_infeasible_instance():
for solver_class in _get_internal_solvers():
instance = _get_infeasible_instance(solver_class)
solver = solver_class()
solver.set_instance(instance)
stats = solver.solve()
assert solver.get_solution() is None
assert stats["Upper bound"] is None
assert stats["Lower bound"] is None
stats = solver.solve_lp()
assert solver.get_solution() is None
assert stats["Optimal value"] is None
def test_iteration_cb():
for solver_class in _get_internal_solvers():
logger.info("Solver: %s" % solver_class)
instance = _get_instance(solver_class)
instance = _get_knapsack_instance(solver_class)
solver = solver_class()
solver.set_instance(instance)
count = 0

View File

@@ -5,14 +5,14 @@
import logging
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.tests import _get_instance
from miplearn.solvers.tests import _get_knapsack_instance
logger = logging.getLogger(__name__)
def test_lazy_cb():
solver = GurobiSolver()
instance = _get_instance(solver)
instance = _get_knapsack_instance(solver)
model = instance.to_model()
def lazy_cb(cb_solver, cb_model):

View File

@@ -9,7 +9,7 @@ import os
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.learning import LearningSolver
from miplearn.solvers.tests import _get_instance, _get_internal_solvers
from miplearn.solvers.tests import _get_knapsack_instance, _get_internal_solvers
logger = logging.getLogger(__name__)
@@ -18,7 +18,7 @@ def test_learning_solver():
for mode in ["exact", "heuristic"]:
for internal_solver in _get_internal_solvers():
logger.info("Solver: %s" % internal_solver)
instance = _get_instance(internal_solver)
instance = _get_knapsack_instance(internal_solver)
solver = LearningSolver(
solver=internal_solver,
mode=mode,
@@ -50,7 +50,7 @@ def test_learning_solver():
def test_solve_without_lp():
for internal_solver in _get_internal_solvers():
logger.info("Solver: %s" % internal_solver)
instance = _get_instance(internal_solver)
instance = _get_knapsack_instance(internal_solver)
solver = LearningSolver(
solver=internal_solver,
solve_lp_first=False,
@@ -62,7 +62,7 @@ def test_solve_without_lp():
def test_parallel_solve():
for internal_solver in _get_internal_solvers():
instances = [_get_instance(internal_solver) for _ in range(10)]
instances = [_get_knapsack_instance(internal_solver) for _ in range(10)]
solver = LearningSolver(solver=internal_solver)
results = solver.parallel_solve(instances, n_jobs=3)
assert len(results) == 10
@@ -76,7 +76,7 @@ def test_solve_fit_from_disk():
# Create instances and pickle them
filenames = []
for k in range(3):
instance = _get_instance(internal_solver)
instance = _get_knapsack_instance(internal_solver)
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as file:
filenames += [file.name]
pickle.dump(instance, file)
@@ -114,7 +114,7 @@ def test_solve_fit_from_disk():
def test_simulate_perfect():
internal_solver = GurobiSolver
instance = _get_instance(internal_solver)
instance = _get_knapsack_instance(internal_solver)
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as tmp:
pickle.dump(instance, tmp)
tmp.flush()