mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Make InternalSolver clonable
This commit is contained in:
@@ -62,7 +62,7 @@ def train(args):
|
|||||||
PickleGzInstance(f) for f in glob.glob(f"{basepath}/train/*.gz")
|
PickleGzInstance(f) for f in glob.glob(f"{basepath}/train/*.gz")
|
||||||
]
|
]
|
||||||
solver = LearningSolver(
|
solver = LearningSolver(
|
||||||
solver=lambda: GurobiPyomoSolver(
|
solver=GurobiPyomoSolver(
|
||||||
params={
|
params={
|
||||||
"TimeLimit": int(args["--train-time-limit"]),
|
"TimeLimit": int(args["--train-time-limit"]),
|
||||||
"Threads": int(args["--solver-threads"]),
|
"Threads": int(args["--solver-threads"]),
|
||||||
@@ -83,7 +83,7 @@ def test_baseline(args):
|
|||||||
if not os.path.isfile(csv_filename):
|
if not os.path.isfile(csv_filename):
|
||||||
solvers = {
|
solvers = {
|
||||||
"baseline": LearningSolver(
|
"baseline": LearningSolver(
|
||||||
solver=lambda: GurobiPyomoSolver(
|
solver=GurobiPyomoSolver(
|
||||||
params={
|
params={
|
||||||
"TimeLimit": int(args["--test-time-limit"]),
|
"TimeLimit": int(args["--test-time-limit"]),
|
||||||
"Threads": int(args["--solver-threads"]),
|
"Threads": int(args["--solver-threads"]),
|
||||||
@@ -107,7 +107,7 @@ def test_ml(args):
|
|||||||
if not os.path.isfile(csv_filename):
|
if not os.path.isfile(csv_filename):
|
||||||
solvers = {
|
solvers = {
|
||||||
"ml-exact": LearningSolver(
|
"ml-exact": LearningSolver(
|
||||||
solver=lambda: GurobiPyomoSolver(
|
solver=GurobiPyomoSolver(
|
||||||
params={
|
params={
|
||||||
"TimeLimit": int(args["--test-time-limit"]),
|
"TimeLimit": int(args["--test-time-limit"]),
|
||||||
"Threads": int(args["--solver-threads"]),
|
"Threads": int(args["--solver-threads"]),
|
||||||
@@ -115,7 +115,7 @@ def test_ml(args):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
"ml-heuristic": LearningSolver(
|
"ml-heuristic": LearningSolver(
|
||||||
solver=lambda: GurobiPyomoSolver(
|
solver=GurobiPyomoSolver(
|
||||||
params={
|
params={
|
||||||
"TimeLimit": int(args["--test-time-limit"]),
|
"TimeLimit": int(args["--test-time-limit"]),
|
||||||
"Threads": int(args["--solver-threads"]),
|
"Threads": int(args["--solver-threads"]),
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ class GurobiSolver(InternalSolver):
|
|||||||
) -> None:
|
) -> None:
|
||||||
import gurobipy
|
import gurobipy
|
||||||
|
|
||||||
|
assert lazy_cb_frequency in [1, 2]
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
params["InfUnbdInfo"] = True
|
params["InfUnbdInfo"] = True
|
||||||
|
params["Seed"] = randint(0, 1_000_000)
|
||||||
|
|
||||||
self.gp = gurobipy
|
self.gp = gurobipy
|
||||||
self.instance: Optional[Instance] = None
|
self.instance: Optional[Instance] = None
|
||||||
@@ -62,9 +64,9 @@ class GurobiSolver(InternalSolver):
|
|||||||
self.varname_to_var: Dict[str, "gurobipy.Var"] = {}
|
self.varname_to_var: Dict[str, "gurobipy.Var"] = {}
|
||||||
self.bin_vars: List["gurobipy.Var"] = []
|
self.bin_vars: List["gurobipy.Var"] = []
|
||||||
self.cb_where: Optional[int] = None
|
self.cb_where: Optional[int] = None
|
||||||
|
self.lazy_cb_frequency = lazy_cb_frequency
|
||||||
|
|
||||||
assert lazy_cb_frequency in [1, 2]
|
if self.lazy_cb_frequency == 1:
|
||||||
if lazy_cb_frequency == 1:
|
|
||||||
self.lazy_cb_where = [self.gp.GRB.Callback.MIPSOL]
|
self.lazy_cb_where = [self.gp.GRB.Callback.MIPSOL]
|
||||||
else:
|
else:
|
||||||
self.lazy_cb_where = [
|
self.lazy_cb_where = [
|
||||||
@@ -113,8 +115,6 @@ class GurobiSolver(InternalSolver):
|
|||||||
with _RedirectOutput(streams):
|
with _RedirectOutput(streams):
|
||||||
for (name, value) in self.params.items():
|
for (name, value) in self.params.items():
|
||||||
self.model.setParam(name, value)
|
self.model.setParam(name, value)
|
||||||
if "seed" not in [k.lower() for k in self.params.keys()]:
|
|
||||||
self.model.setParam("Seed", randint(0, 1_000_000))
|
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def solve_lp(
|
def solve_lp(
|
||||||
@@ -428,3 +428,10 @@ class GurobiSolver(InternalSolver):
|
|||||||
self.instance = None
|
self.instance = None
|
||||||
self.model = None
|
self.model = None
|
||||||
self.cb_where = None
|
self.cb_where = None
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def clone(self) -> "GurobiSolver":
|
||||||
|
return GurobiSolver(
|
||||||
|
params=self.params,
|
||||||
|
lazy_cb_frequency=self.lazy_cb_frequency,
|
||||||
|
)
|
||||||
|
|||||||
@@ -284,3 +284,11 @@ class InternalSolver(ABC, EnforceOverrides):
|
|||||||
model before a solution is available.
|
model before a solution is available.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def clone(self) -> "InternalSolver":
|
||||||
|
"""
|
||||||
|
Returns a new copy of this solver with identical parameters, but otherwise
|
||||||
|
completely unitialized.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
@@ -91,20 +91,20 @@ class LearningSolver:
|
|||||||
self,
|
self,
|
||||||
components: List[Component] = None,
|
components: List[Component] = None,
|
||||||
mode: str = "exact",
|
mode: str = "exact",
|
||||||
solver: Callable[[], InternalSolver] = None,
|
solver: InternalSolver = None,
|
||||||
use_lazy_cb: bool = False,
|
use_lazy_cb: bool = False,
|
||||||
solve_lp: bool = True,
|
solve_lp: bool = True,
|
||||||
simulate_perfect: bool = False,
|
simulate_perfect: bool = False,
|
||||||
):
|
) -> None:
|
||||||
if solver is None:
|
if solver is None:
|
||||||
solver = GurobiPyomoSolver
|
solver = GurobiPyomoSolver()
|
||||||
assert callable(solver), f"Callable expected. Found {solver.__class__} instead."
|
assert isinstance(solver, InternalSolver)
|
||||||
self.components: Dict[str, Component] = {}
|
self.components: Dict[str, Component] = {}
|
||||||
self.internal_solver: Optional[InternalSolver] = None
|
self.internal_solver: Optional[InternalSolver] = None
|
||||||
|
self.internal_solver_prototype: InternalSolver = solver
|
||||||
self.mode: str = mode
|
self.mode: str = mode
|
||||||
self.simulate_perfect: bool = simulate_perfect
|
self.simulate_perfect: bool = simulate_perfect
|
||||||
self.solve_lp: bool = solve_lp
|
self.solve_lp: bool = solve_lp
|
||||||
self.solver_factory: Callable[[], InternalSolver] = solver
|
|
||||||
self.tee = False
|
self.tee = False
|
||||||
self.use_lazy_cb: bool = use_lazy_cb
|
self.use_lazy_cb: bool = use_lazy_cb
|
||||||
if components is not None:
|
if components is not None:
|
||||||
@@ -144,7 +144,7 @@ class LearningSolver:
|
|||||||
# Initialize internal solver
|
# Initialize internal solver
|
||||||
# -------------------------------------------------------
|
# -------------------------------------------------------
|
||||||
self.tee = tee
|
self.tee = tee
|
||||||
self.internal_solver = self.solver_factory()
|
self.internal_solver = self.internal_solver_prototype.clone()
|
||||||
assert self.internal_solver is not None
|
assert self.internal_solver is not None
|
||||||
assert isinstance(self.internal_solver, InternalSolver)
|
assert isinstance(self.internal_solver, InternalSolver)
|
||||||
self.internal_solver.set_instance(instance, model)
|
self.internal_solver.set_instance(instance, model)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.instance: Optional[Instance] = None
|
self.instance: Optional[Instance] = None
|
||||||
self.model: Optional[pe.ConcreteModel] = None
|
self.model: Optional[pe.ConcreteModel] = None
|
||||||
|
self.params = params
|
||||||
self._all_vars: List[pe.Var] = []
|
self._all_vars: List[pe.Var] = []
|
||||||
self._bin_vars: List[pe.Var] = []
|
self._bin_vars: List[pe.Var] = []
|
||||||
self._is_warm_start_available: bool = False
|
self._is_warm_start_available: bool = False
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class CplexPyomoSolver(BasePyomoSolver):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
if "randomseed" not in params.keys():
|
|
||||||
params["randomseed"] = randint(low=0, high=1000).rvs()
|
params["randomseed"] = randint(low=0, high=1000).rvs()
|
||||||
if "mip_display" not in params.keys():
|
if "mip_display" not in params.keys():
|
||||||
params["mip_display"] = 4
|
params["mip_display"] = 4
|
||||||
@@ -44,3 +43,7 @@ class CplexPyomoSolver(BasePyomoSolver):
|
|||||||
@overrides
|
@overrides
|
||||||
def _get_node_count_regexp(self):
|
def _get_node_count_regexp(self):
|
||||||
return "^[ *] *([0-9]+)"
|
return "^[ *] *([0-9]+)"
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def clone(self) -> "CplexPyomoSolver":
|
||||||
|
return CplexPyomoSolver(params=self.params)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class GurobiPyomoSolver(BasePyomoSolver):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
if "seed" not in params.keys():
|
|
||||||
params["seed"] = randint(low=0, high=1000).rvs()
|
params["seed"] = randint(low=0, high=1000).rvs()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
solver_factory=pe.SolverFactory("gurobi_persistent"),
|
solver_factory=pe.SolverFactory("gurobi_persistent"),
|
||||||
@@ -61,3 +60,7 @@ class GurobiPyomoSolver(BasePyomoSolver):
|
|||||||
var = self._varname_to_var[varname]
|
var = self._varname_to_var[varname]
|
||||||
gvar = self._pyomo_solver._pyomo_var_to_solver_var_map[var]
|
gvar = self._pyomo_solver._pyomo_var_to_solver_var_map[var]
|
||||||
gvar.setAttr(GRB.Attr.BranchPriority, int(round(priority)))
|
gvar.setAttr(GRB.Attr.BranchPriority, int(round(priority)))
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def clone(self) -> "GurobiPyomoSolver":
|
||||||
|
return GurobiPyomoSolver(params=self.params)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from overrides import overrides
|
||||||
from pyomo import environ as pe
|
from pyomo import environ as pe
|
||||||
from scipy.stats import randint
|
from scipy.stats import randint
|
||||||
|
|
||||||
@@ -27,9 +28,12 @@ class XpressPyomoSolver(BasePyomoSolver):
|
|||||||
def __init__(self, params: SolverParams = None) -> None:
|
def __init__(self, params: SolverParams = None) -> None:
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
if "randomseed" not in params.keys():
|
|
||||||
params["randomseed"] = randint(low=0, high=1000).rvs()
|
params["randomseed"] = randint(low=0, high=1000).rvs()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
solver_factory=pe.SolverFactory("xpress_persistent"),
|
solver_factory=pe.SolverFactory("xpress_persistent"),
|
||||||
params=params,
|
params=params,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def clone(self) -> "XpressPyomoSolver":
|
||||||
|
return XpressPyomoSolver(params=self.params)
|
||||||
|
|||||||
@@ -64,10 +64,8 @@ def stab_instance() -> Instance:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def solver() -> LearningSolver:
|
def solver() -> LearningSolver:
|
||||||
return LearningSolver(
|
return LearningSolver(
|
||||||
solver=lambda: GurobiSolver(),
|
solver=GurobiSolver(),
|
||||||
components=[
|
components=[UserCutsComponent()],
|
||||||
UserCutsComponent(),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,9 @@ def _get_knapsack_instance(solver):
|
|||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
def get_internal_solvers() -> List[Callable[[], InternalSolver]]:
|
def get_internal_solvers() -> List[InternalSolver]:
|
||||||
return [GurobiPyomoSolver, GurobiSolver, XpressPyomoSolver]
|
return [
|
||||||
|
GurobiPyomoSolver(),
|
||||||
|
GurobiSolver(),
|
||||||
|
XpressPyomoSolver(),
|
||||||
|
]
|
||||||
|
|||||||
@@ -32,18 +32,17 @@ def test_redirect_output():
|
|||||||
|
|
||||||
|
|
||||||
def test_internal_solver_warm_starts():
|
def test_internal_solver_warm_starts():
|
||||||
for solver_class in get_internal_solvers():
|
for solver in get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver)
|
||||||
instance = _get_knapsack_instance(solver_class)
|
instance = _get_knapsack_instance(solver)
|
||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
solver = solver_class()
|
|
||||||
solver.set_instance(instance, model)
|
solver.set_instance(instance, model)
|
||||||
solver.set_warm_start({"x[0]": 1.0, "x[1]": 0.0, "x[2]": 0.0, "x[3]": 1.0})
|
solver.set_warm_start({"x[0]": 1.0, "x[1]": 0.0, "x[2]": 0.0, "x[3]": 1.0})
|
||||||
stats = solver.solve(tee=True)
|
stats = solver.solve(tee=True)
|
||||||
if stats["Warm start value"] is not None:
|
if stats["Warm start value"] is not None:
|
||||||
assert stats["Warm start value"] == 725.0
|
assert stats["Warm start value"] == 725.0
|
||||||
else:
|
else:
|
||||||
warn(f"{solver_class.__name__} should set warm start value")
|
warn(f"{solver.__class__.__name__} should set warm start value")
|
||||||
|
|
||||||
solver.set_warm_start({"x[0]": 1.0, "x[1]": 1.0, "x[2]": 1.0, "x[3]": 1.0})
|
solver.set_warm_start({"x[0]": 1.0, "x[1]": 1.0, "x[2]": 1.0, "x[3]": 1.0})
|
||||||
stats = solver.solve(tee=True)
|
stats = solver.solve(tee=True)
|
||||||
@@ -56,12 +55,11 @@ def test_internal_solver_warm_starts():
|
|||||||
|
|
||||||
|
|
||||||
def test_internal_solver():
|
def test_internal_solver():
|
||||||
for solver_class in get_internal_solvers():
|
for solver in get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver)
|
||||||
|
|
||||||
instance = _get_knapsack_instance(solver_class)
|
instance = _get_knapsack_instance(solver)
|
||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
solver = solver_class()
|
|
||||||
solver.set_instance(instance, model)
|
solver.set_instance(instance, model)
|
||||||
|
|
||||||
assert solver.get_variable_names() == ["x[0]", "x[1]", "x[2]", "x[3]"]
|
assert solver.get_variable_names() == ["x[0]", "x[1]", "x[2]", "x[3]"]
|
||||||
@@ -150,9 +148,8 @@ def test_internal_solver():
|
|||||||
|
|
||||||
|
|
||||||
def test_relax():
|
def test_relax():
|
||||||
for solver_class in get_internal_solvers():
|
for solver in get_internal_solvers():
|
||||||
instance = _get_knapsack_instance(solver_class)
|
instance = _get_knapsack_instance(solver)
|
||||||
solver = solver_class()
|
|
||||||
solver.set_instance(instance)
|
solver.set_instance(instance)
|
||||||
solver.relax()
|
solver.relax()
|
||||||
stats = solver.solve()
|
stats = solver.solve()
|
||||||
@@ -160,9 +157,8 @@ def test_relax():
|
|||||||
|
|
||||||
|
|
||||||
def test_infeasible_instance():
|
def test_infeasible_instance():
|
||||||
for solver_class in get_internal_solvers():
|
for solver in get_internal_solvers():
|
||||||
instance = get_infeasible_instance(solver_class)
|
instance = get_infeasible_instance(solver)
|
||||||
solver = solver_class()
|
|
||||||
solver.set_instance(instance)
|
solver.set_instance(instance)
|
||||||
stats = solver.solve()
|
stats = solver.solve()
|
||||||
|
|
||||||
@@ -177,10 +173,9 @@ def test_infeasible_instance():
|
|||||||
|
|
||||||
|
|
||||||
def test_iteration_cb():
|
def test_iteration_cb():
|
||||||
for solver_class in get_internal_solvers():
|
for solver in get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver)
|
||||||
instance = _get_knapsack_instance(solver_class)
|
instance = _get_knapsack_instance(solver)
|
||||||
solver = solver_class()
|
|
||||||
solver.set_instance(instance)
|
solver.set_instance(instance)
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def test_solve_fit_from_disk():
|
|||||||
|
|
||||||
|
|
||||||
def test_simulate_perfect():
|
def test_simulate_perfect():
|
||||||
internal_solver = GurobiSolver
|
internal_solver = GurobiSolver()
|
||||||
instance = _get_knapsack_instance(internal_solver)
|
instance = _get_knapsack_instance(internal_solver)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as tmp:
|
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as tmp:
|
||||||
write_pickle_gz(instance, tmp.name)
|
write_pickle_gz(instance, tmp.name)
|
||||||
|
|||||||
Reference in New Issue
Block a user