mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
InternalSolver: Better specify and test infeasibility
This commit is contained in:
@@ -10,6 +10,7 @@ from typing import Any, List
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from miplearn.types import TrainingSample
|
from miplearn.types import TrainingSample
|
||||||
|
import pyomo.environ as pe
|
||||||
|
|
||||||
|
|
||||||
class Instance(ABC):
|
class Instance(ABC):
|
||||||
@@ -30,7 +31,7 @@ class Instance(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_model(self) -> Any:
|
def to_model(self) -> Any:
|
||||||
"""
|
"""
|
||||||
Returns a concrete Pyomo model corresponding to this instance.
|
Returns the optimization model corresponding to this instance.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -163,3 +164,12 @@ class Instance(ABC):
|
|||||||
data = json.dumps(self.__dict__, indent=2).encode("utf-8")
|
data = json.dumps(self.__dict__, indent=2).encode("utf-8")
|
||||||
with gzip.GzipFile(filename, "w") as f:
|
with gzip.GzipFile(filename, "w") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
class PyomoInstance(Instance, ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def to_model(self) -> pe.ConcreteModel:
|
||||||
|
"""
|
||||||
|
Returns the concrete Pyomo model corresponding to this instance.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
@@ -128,8 +128,11 @@ class GurobiSolver(InternalSolver):
|
|||||||
for (idx, var) in vardict.items():
|
for (idx, var) in vardict.items():
|
||||||
var.vtype = self.GRB.BINARY
|
var.vtype = self.GRB.BINARY
|
||||||
log = streams[0].getvalue()
|
log = streams[0].getvalue()
|
||||||
|
opt_value = None
|
||||||
|
if not self.is_infeasible():
|
||||||
|
opt_value = self.model.objVal
|
||||||
return {
|
return {
|
||||||
"Optimal value": self.model.objVal,
|
"Optimal value": opt_value,
|
||||||
"Log": log,
|
"Log": log,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +176,15 @@ class GurobiSolver(InternalSolver):
|
|||||||
if not should_repeat:
|
if not should_repeat:
|
||||||
break
|
break
|
||||||
log = streams[0].getvalue()
|
log = streams[0].getvalue()
|
||||||
if self.model.modelSense == 1:
|
ub, lb = None, None
|
||||||
sense = "min"
|
sense = "min" if self.model.modelSense == 1 else "max"
|
||||||
lb = self.model.objBound
|
if self.model.solCount > 0:
|
||||||
ub = self.model.objVal
|
if self.model.modelSense == 1:
|
||||||
else:
|
lb = self.model.objBound
|
||||||
sense = "max"
|
ub = self.model.objVal
|
||||||
lb = self.model.objVal
|
else:
|
||||||
ub = self.model.objBound
|
lb = self.model.objVal
|
||||||
|
ub = self.model.objBound
|
||||||
ws_value = self._extract_warm_start_value(log)
|
ws_value = self._extract_warm_start_value(log)
|
||||||
stats: MIPSolveStats = {
|
stats: MIPSolveStats = {
|
||||||
"Lower bound": lb,
|
"Lower bound": lb,
|
||||||
@@ -194,8 +198,10 @@ class GurobiSolver(InternalSolver):
|
|||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
def get_solution(self) -> Dict:
|
def get_solution(self) -> Optional[Dict]:
|
||||||
self._raise_if_callback()
|
self._raise_if_callback()
|
||||||
|
if self.model.solCount == 0:
|
||||||
|
return None
|
||||||
solution: Dict = {}
|
solution: Dict = {}
|
||||||
for (varname, vardict) in self._all_vars.items():
|
for (varname, vardict) in self._all_vars.items():
|
||||||
solution[varname] = {}
|
solution[varname] = {}
|
||||||
@@ -228,7 +234,7 @@ class GurobiSolver(InternalSolver):
|
|||||||
var = self._all_vars[var_name][index]
|
var = self._all_vars[var_name][index]
|
||||||
return self._get_value(var)
|
return self._get_value(var)
|
||||||
|
|
||||||
def is_infeasible(self):
|
def is_infeasible(self) -> bool:
|
||||||
return self.model.status in [self.GRB.INFEASIBLE, self.GRB.INF_OR_UNBD]
|
return self.model.status in [self.GRB.INFEASIBLE, self.GRB.INF_OR_UNBD]
|
||||||
|
|
||||||
def get_dual(self, cid):
|
def get_dual(self, cid):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from miplearn.instance import Instance
|
from miplearn.instance import Instance
|
||||||
from miplearn.types import (
|
from miplearn.types import (
|
||||||
@@ -39,15 +39,13 @@ class InternalSolver(ABC):
|
|||||||
Solves the LP relaxation of the currently loaded instance. After this
|
Solves the LP relaxation of the currently loaded instance. After this
|
||||||
method finishes, the solution can be retrieved by calling `get_solution`.
|
method finishes, the solution can be retrieved by calling `get_solution`.
|
||||||
|
|
||||||
|
This method should not permanently modify the problem. That is, subsequent
|
||||||
|
calls to `solve` should solve the original MIP, not the LP relaxation.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
tee: bool
|
tee
|
||||||
If true, prints the solver log to the screen.
|
If true, prints the solver log to the screen.
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict
|
|
||||||
A dictionary of solver statistics.
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -64,34 +62,27 @@ class InternalSolver(ABC):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
iteration_cb: () -> Bool
|
iteration_cb:
|
||||||
By default, InternalSolver makes a single call to the native `solve`
|
By default, InternalSolver makes a single call to the native `solve`
|
||||||
method and returns the result. If an iteration callback is provided
|
method and returns the result. If an iteration callback is provided
|
||||||
instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
|
instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
|
||||||
are called alternatively. To stop the loop, `iteration_cb` should
|
are called alternatively. To stop the loop, `iteration_cb` should return
|
||||||
return False. Any other result causes the solver to loop again.
|
False. Any other result causes the solver to loop again.
|
||||||
lazy_cb: (internal_solver, model) -> None
|
lazy_cb:
|
||||||
This function is called whenever the solver finds a new candidate
|
This function is called whenever the solver finds a new candidate
|
||||||
solution and can be used to add lazy constraints to the model. Only
|
solution and can be used to add lazy constraints to the model. Only the
|
||||||
the following operations within the callback are allowed:
|
following operations within the callback are allowed:
|
||||||
- Querying the value of a variable, through `get_value(var, idx)`
|
- Querying the value of a variable
|
||||||
- Querying if a constraint is satisfied, through `is_constraint_satisfied(cobj)`
|
- Querying if a constraint is satisfied
|
||||||
- Adding a new constraint to the problem, through `add_constraint`
|
- Adding a new constraint to the problem
|
||||||
Additional operations may be allowed by specific subclasses.
|
Additional operations may be allowed by specific subclasses.
|
||||||
tee: Bool
|
tee
|
||||||
If true, prints the solver log to the screen.
|
If true, prints the solver log to the screen.
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict
|
|
||||||
A dictionary of solver statistics containing the following keys:
|
|
||||||
"Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense",
|
|
||||||
"Log" and "Warm start value".
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_solution(self) -> Dict:
|
def get_solution(self) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Returns current solution found by the solver.
|
Returns current solution found by the solver.
|
||||||
|
|
||||||
@@ -201,7 +192,7 @@ class InternalSolver(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def set_constraint_rhs(self, cid: str, rhs: str) -> None:
|
def set_constraint_rhs(self, cid: str, rhs: float) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from typing import Any, List, Dict, Optional
|
|||||||
import pyomo
|
import pyomo
|
||||||
from pyomo import environ as pe
|
from pyomo import environ as pe
|
||||||
from pyomo.core import Var, Constraint
|
from pyomo.core import Var, Constraint
|
||||||
|
from pyomo.opt import TerminationCondition
|
||||||
|
|
||||||
from miplearn.instance import Instance
|
from miplearn.instance import Instance
|
||||||
from miplearn.solvers import RedirectOutput
|
from miplearn.solvers import RedirectOutput
|
||||||
@@ -44,6 +45,7 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
self._obj_sense = None
|
self._obj_sense = None
|
||||||
self._varname_to_var = {}
|
self._varname_to_var = {}
|
||||||
self._cname_to_constr = {}
|
self._cname_to_constr = {}
|
||||||
|
self._termination_condition = None
|
||||||
for (key, value) in params.items():
|
for (key, value) in params.items():
|
||||||
self._pyomo_solver.options[key] = value
|
self._pyomo_solver.options[key] = value
|
||||||
|
|
||||||
@@ -65,8 +67,11 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
for var in self._bin_vars:
|
for var in self._bin_vars:
|
||||||
var.domain = pyomo.core.base.set_types.Binary
|
var.domain = pyomo.core.base.set_types.Binary
|
||||||
self._pyomo_solver.update_var(var)
|
self._pyomo_solver.update_var(var)
|
||||||
|
opt_value = None
|
||||||
|
if not self.is_infeasible():
|
||||||
|
opt_value = results["Problem"][0]["Lower bound"]
|
||||||
return {
|
return {
|
||||||
"Optimal value": results["Problem"][0]["Lower bound"],
|
"Optimal value": opt_value,
|
||||||
"Log": streams[0].getvalue(),
|
"Log": streams[0].getvalue(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,9 +105,14 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
log = streams[0].getvalue()
|
log = streams[0].getvalue()
|
||||||
node_count = self._extract_node_count(log)
|
node_count = self._extract_node_count(log)
|
||||||
ws_value = self._extract_warm_start_value(log)
|
ws_value = self._extract_warm_start_value(log)
|
||||||
|
self._termination_condition = results["Solver"][0]["Termination condition"]
|
||||||
|
lb, ub = None, None
|
||||||
|
if not self.is_infeasible():
|
||||||
|
lb = results["Problem"][0]["Lower bound"]
|
||||||
|
ub = results["Problem"][0]["Upper bound"]
|
||||||
stats: MIPSolveStats = {
|
stats: MIPSolveStats = {
|
||||||
"Lower bound": results["Problem"][0]["Lower bound"],
|
"Lower bound": lb,
|
||||||
"Upper bound": results["Problem"][0]["Upper bound"],
|
"Upper bound": ub,
|
||||||
"Wallclock time": total_wallclock_time,
|
"Wallclock time": total_wallclock_time,
|
||||||
"Sense": self._obj_sense,
|
"Sense": self._obj_sense,
|
||||||
"Log": log,
|
"Log": log,
|
||||||
@@ -112,7 +122,9 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
def get_solution(self) -> Dict:
|
def get_solution(self) -> Optional[Dict]:
|
||||||
|
if self.is_infeasible():
|
||||||
|
return None
|
||||||
solution: Dict = {}
|
solution: Dict = {}
|
||||||
for var in self.model.component_objects(Var):
|
for var in self.model.component_objects(Var):
|
||||||
solution[str(var)] = {}
|
solution[str(var)] = {}
|
||||||
@@ -276,8 +288,8 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
def set_constraint_rhs(self, cid, rhs):
|
def set_constraint_rhs(self, cid, rhs):
|
||||||
raise Exception("Not implemented")
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
def is_infeasible(self):
|
def is_infeasible(self) -> bool:
|
||||||
raise Exception("Not implemented")
|
return self._termination_condition == TerminationCondition.infeasible
|
||||||
|
|
||||||
def get_dual(self, cid):
|
def get_dual(self, cid):
|
||||||
raise Exception("Not implemented")
|
raise Exception("Not implemented")
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
# 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
|
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.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
|
||||||
@@ -13,28 +16,55 @@ from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
|
|||||||
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
|
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
|
||||||
|
|
||||||
|
|
||||||
def _get_instance(solver):
|
class InfeasiblePyomoInstance(PyomoInstance):
|
||||||
def _is_subclass_or_instance(obj, parent_class):
|
def to_model(self) -> pe.ConcreteModel:
|
||||||
return isinstance(obj, parent_class) or (
|
model = pe.ConcreteModel()
|
||||||
isclass(obj) and issubclass(obj, parent_class)
|
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):
|
if _is_subclass_or_instance(solver, BasePyomoSolver):
|
||||||
return KnapsackInstance(
|
return KnapsackInstance(
|
||||||
weights=[23.0, 26.0, 20.0, 18.0],
|
weights=[23.0, 26.0, 20.0, 18.0],
|
||||||
prices=[505.0, 352.0, 458.0, 220.0],
|
prices=[505.0, 352.0, 458.0, 220.0],
|
||||||
capacity=67.0,
|
capacity=67.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _is_subclass_or_instance(solver, GurobiSolver):
|
if _is_subclass_or_instance(solver, GurobiSolver):
|
||||||
return GurobiKnapsackInstance(
|
return GurobiKnapsackInstance(
|
||||||
weights=[23.0, 26.0, 20.0, 18.0],
|
weights=[23.0, 26.0, 20.0, 18.0],
|
||||||
prices=[505.0, 352.0, 458.0, 220.0],
|
prices=[505.0, 352.0, 458.0, 220.0],
|
||||||
capacity=67.0,
|
capacity=67.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
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]
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import pyomo.environ as pe
|
|||||||
from miplearn.solvers import RedirectOutput
|
from miplearn.solvers import RedirectOutput
|
||||||
from miplearn.solvers.gurobi import GurobiSolver
|
from miplearn.solvers.gurobi import GurobiSolver
|
||||||
from miplearn.solvers.pyomo.base import BasePyomoSolver
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ 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_class in _get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver_class)
|
||||||
instance = _get_instance(solver_class)
|
instance = _get_knapsack_instance(solver_class)
|
||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
solver = solver_class()
|
solver = solver_class()
|
||||||
solver.set_instance(instance, model)
|
solver.set_instance(instance, model)
|
||||||
@@ -82,7 +86,7 @@ def test_internal_solver():
|
|||||||
for solver_class in _get_internal_solvers():
|
for solver_class in _get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver_class)
|
||||||
|
|
||||||
instance = _get_instance(solver_class)
|
instance = _get_knapsack_instance(solver_class)
|
||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
solver = solver_class()
|
solver = solver_class()
|
||||||
solver.set_instance(instance, model)
|
solver.set_instance(instance, model)
|
||||||
@@ -158,10 +162,26 @@ def test_internal_solver():
|
|||||||
assert round(stats["Lower bound"]) == 1179.0
|
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():
|
def test_iteration_cb():
|
||||||
for solver_class in _get_internal_solvers():
|
for solver_class in _get_internal_solvers():
|
||||||
logger.info("Solver: %s" % solver_class)
|
logger.info("Solver: %s" % solver_class)
|
||||||
instance = _get_instance(solver_class)
|
instance = _get_knapsack_instance(solver_class)
|
||||||
solver = solver_class()
|
solver = solver_class()
|
||||||
solver.set_instance(instance)
|
solver.set_instance(instance)
|
||||||
count = 0
|
count = 0
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from miplearn.solvers.gurobi import GurobiSolver
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def test_lazy_cb():
|
def test_lazy_cb():
|
||||||
solver = GurobiSolver()
|
solver = GurobiSolver()
|
||||||
instance = _get_instance(solver)
|
instance = _get_knapsack_instance(solver)
|
||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
|
|
||||||
def lazy_cb(cb_solver, cb_model):
|
def lazy_cb(cb_solver, cb_model):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import os
|
|||||||
|
|
||||||
from miplearn.solvers.gurobi import GurobiSolver
|
from miplearn.solvers.gurobi import GurobiSolver
|
||||||
from miplearn.solvers.learning import LearningSolver
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ def test_learning_solver():
|
|||||||
for mode in ["exact", "heuristic"]:
|
for mode in ["exact", "heuristic"]:
|
||||||
for internal_solver in _get_internal_solvers():
|
for internal_solver in _get_internal_solvers():
|
||||||
logger.info("Solver: %s" % internal_solver)
|
logger.info("Solver: %s" % internal_solver)
|
||||||
instance = _get_instance(internal_solver)
|
instance = _get_knapsack_instance(internal_solver)
|
||||||
solver = LearningSolver(
|
solver = LearningSolver(
|
||||||
solver=internal_solver,
|
solver=internal_solver,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
@@ -50,7 +50,7 @@ def test_learning_solver():
|
|||||||
def test_solve_without_lp():
|
def test_solve_without_lp():
|
||||||
for internal_solver in _get_internal_solvers():
|
for internal_solver in _get_internal_solvers():
|
||||||
logger.info("Solver: %s" % internal_solver)
|
logger.info("Solver: %s" % internal_solver)
|
||||||
instance = _get_instance(internal_solver)
|
instance = _get_knapsack_instance(internal_solver)
|
||||||
solver = LearningSolver(
|
solver = LearningSolver(
|
||||||
solver=internal_solver,
|
solver=internal_solver,
|
||||||
solve_lp_first=False,
|
solve_lp_first=False,
|
||||||
@@ -62,7 +62,7 @@ def test_solve_without_lp():
|
|||||||
|
|
||||||
def test_parallel_solve():
|
def test_parallel_solve():
|
||||||
for internal_solver in _get_internal_solvers():
|
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)
|
solver = LearningSolver(solver=internal_solver)
|
||||||
results = solver.parallel_solve(instances, n_jobs=3)
|
results = solver.parallel_solve(instances, n_jobs=3)
|
||||||
assert len(results) == 10
|
assert len(results) == 10
|
||||||
@@ -76,7 +76,7 @@ def test_solve_fit_from_disk():
|
|||||||
# Create instances and pickle them
|
# Create instances and pickle them
|
||||||
filenames = []
|
filenames = []
|
||||||
for k in range(3):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as file:
|
||||||
filenames += [file.name]
|
filenames += [file.name]
|
||||||
pickle.dump(instance, file)
|
pickle.dump(instance, file)
|
||||||
@@ -114,7 +114,7 @@ def test_solve_fit_from_disk():
|
|||||||
|
|
||||||
def test_simulate_perfect():
|
def test_simulate_perfect():
|
||||||
internal_solver = GurobiSolver
|
internal_solver = GurobiSolver
|
||||||
instance = _get_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:
|
||||||
pickle.dump(instance, tmp)
|
pickle.dump(instance, tmp)
|
||||||
tmp.flush()
|
tmp.flush()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ TrainingSample = TypedDict(
|
|||||||
LPSolveStats = TypedDict(
|
LPSolveStats = TypedDict(
|
||||||
"LPSolveStats",
|
"LPSolveStats",
|
||||||
{
|
{
|
||||||
"Optimal value": float,
|
"Optimal value": Optional[float],
|
||||||
"Log": str,
|
"Log": str,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user