Minor fixes to docstrings; make some classes private

master
Alinson S. Xavier 5 years ago
parent 7dbbfdc418
commit c342a870d1

@ -55,11 +55,11 @@ class Component(ABC):
Parameters Parameters
---------- ----------
solver: "LearningSolver" solver: LearningSolver
The solver calling this method. The solver calling this method.
instance: Instance instance: Instance
The instance being solved. The instance being solved.
model: model: Any
The concrete optimization model being solved. The concrete optimization model being solved.
stats: dict stats: dict
A dictionary containing statistics about the solution process, such as A dictionary containing statistics about the solution process, such as
@ -101,11 +101,11 @@ class Component(ABC):
Parameters Parameters
---------- ----------
solver: "LearningSolver" solver: LearningSolver
The solver calling this method. The solver calling this method.
instance: Instance instance: Instance
The instance being solved. The instance being solved.
model: model: Any
The concrete optimization model being solved. The concrete optimization model being solved.
""" """
return False return False

@ -9,7 +9,7 @@ from typing import Any, List
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RedirectOutput: class _RedirectOutput:
def __init__(self, streams: List[Any]): def __init__(self, streams: List[Any]):
self.streams = streams self.streams = streams

@ -9,7 +9,7 @@ from random import randint
from typing import List, Any, Dict, Optional from typing import List, Any, Dict, Optional
from miplearn.instance import Instance from miplearn.instance import Instance
from miplearn.solvers import RedirectOutput from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import ( from miplearn.solvers.internal import (
InternalSolver, InternalSolver,
LPSolveStats, LPSolveStats,
@ -23,24 +23,25 @@ logger = logging.getLogger(__name__)
class GurobiSolver(InternalSolver): class GurobiSolver(InternalSolver):
def __init__(
self,
params: Optional[SolverParams] = None,
lazy_cb_frequency: int = 1,
) -> None:
""" """
An InternalSolver backed by Gurobi's Python API (without Pyomo). An InternalSolver backed by Gurobi's Python API (without Pyomo).
Parameters Parameters
---------- ----------
params params: Optional[SolverParams]
Parameters to pass to Gurobi. For example, params={"MIPGap": 1e-3} Parameters to pass to Gurobi. For example, `params={"MIPGap": 1e-3}`
sets the gap tolerance to 1e-3. sets the gap tolerance to 1e-3.
lazy_cb_frequency lazy_cb_frequency: int
If 1, calls lazy constraint callbacks whenever an integer solution If 1, calls lazy constraint callbacks whenever an integer solution
is found. If 2, calls it also at every node, after solving the is found. If 2, calls it also at every node, after solving the
LP relaxation of that node. LP relaxation of that node.
""" """
def __init__(
self,
params: Optional[SolverParams] = None,
lazy_cb_frequency: int = 1,
) -> None:
import gurobipy import gurobipy
if params is None: if params is None:
@ -108,7 +109,7 @@ class GurobiSolver(InternalSolver):
def _apply_params(self, streams: List[Any]) -> None: def _apply_params(self, streams: List[Any]) -> None:
assert self.model is not None assert self.model is not None
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()]: if "seed" not in [k.lower() for k in self.params.keys()]:
@ -130,7 +131,7 @@ class GurobiSolver(InternalSolver):
var.vtype = self.gp.GRB.CONTINUOUS var.vtype = self.gp.GRB.CONTINUOUS
var.lb = 0.0 var.lb = 0.0
var.ub = 1.0 var.ub = 1.0
with RedirectOutput(streams): with _RedirectOutput(streams):
self.model.optimize() self.model.optimize()
for (varname, vardict) in self._bin_vars.items(): for (varname, vardict) in self._bin_vars.items():
for (idx, var) in vardict.items(): for (idx, var) in vardict.items():
@ -174,7 +175,7 @@ class GurobiSolver(InternalSolver):
if iteration_cb is None: if iteration_cb is None:
iteration_cb = lambda: False iteration_cb = lambda: False
while True: while True:
with RedirectOutput(streams): with _RedirectOutput(streams):
if lazy_cb is None: if lazy_cb is None:
self.model.optimize() self.model.optimize()
else: else:
@ -362,11 +363,13 @@ class GurobiSolver(InternalSolver):
ineqs = [c for c in self.model.getConstrs() if c.sense != "="] ineqs = [c for c in self.model.getConstrs() if c.sense != "="]
return {c.ConstrName: c.Slack for c in ineqs} return {c.ConstrName: c.Slack for c in ineqs}
def set_constraint_sense(self, cid, sense): def set_constraint_sense(self, cid: str, sense: str) -> None:
assert self.model is not None
c = self.model.getConstrByName(cid) c = self.model.getConstrByName(cid)
c.Sense = sense c.Sense = sense
def get_constraint_sense(self, cid): def get_constraint_sense(self, cid: str) -> str:
assert self.model is not None
c = self.model.getConstrByName(cid) c = self.model.getConstrByName(cid)
return c.Sense return c.Sense

@ -15,15 +15,12 @@ from miplearn.types import (
VarIndex, VarIndex,
Solution, Solution,
BranchPriorities, BranchPriorities,
Constraint,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Constraint:
pass
class InternalSolver(ABC): class InternalSolver(ABC):
""" """
Abstract class representing the MIP solver used internally by LearningSolver. Abstract class representing the MIP solver used internally by LearningSolver.
@ -61,13 +58,13 @@ class InternalSolver(ABC):
Parameters Parameters
---------- ----------
iteration_cb: iteration_cb: IterationCallback
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 return are called alternatively. To stop the loop, `iteration_cb` should return
False. Any other result causes the solver to loop again. False. Any other result causes the solver to loop again.
lazy_cb: lazy_cb: LazyCallback
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 the solution and can be used to add lazy constraints to the model. Only the
following operations within the callback are allowed: following operations within the callback are allowed:
@ -75,7 +72,7 @@ class InternalSolver(ABC):
- Querying if a constraint is satisfied - Querying if a constraint is satisfied
- Adding a new constraint to the problem - Adding a new constraint to the problem
Additional operations may be allowed by specific subclasses. Additional operations may be allowed by specific subclasses.
tee tee: bool
If true, prints the solver log to the screen. If true, prints the solver log to the screen.
""" """
pass pass
@ -119,7 +116,7 @@ class InternalSolver(ABC):
---------- ----------
instance: Instance instance: Instance
The instance to be loaded. The instance to be loaded.
model: model: Any
The concrete optimization model corresponding to this instance The concrete optimization model corresponding to this instance
(e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided, (e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided,
it will be generated by calling `instance.to_model()`. it will be generated by calling `instance.to_model()`.
@ -184,10 +181,28 @@ class InternalSolver(ABC):
@abstractmethod @abstractmethod
def set_constraint_sense(self, cid: str, sense: str) -> None: def set_constraint_sense(self, cid: str, sense: str) -> None:
"""
Modifies the sense of a given constraint.
Parameters
----------
cid: str
The name of the constraint.
sense: str
The new sense (either "<", ">" or "=").
"""
pass pass
@abstractmethod @abstractmethod
def get_constraint_sense(self, cid: str) -> str: def get_constraint_sense(self, cid: str) -> str:
"""
Returns the sense of a given constraint (either "<", ">" or "=").
Parameters
----------
cid: str
The name of the constraint.
"""
pass pass
@abstractmethod @abstractmethod

@ -17,7 +17,7 @@ from miplearn.components.lazy_dynamic import DynamicLazyConstraintsComponent
from miplearn.components.objective import ObjectiveValueComponent from miplearn.components.objective import ObjectiveValueComponent
from miplearn.components.primal import PrimalSolutionComponent from miplearn.components.primal import PrimalSolutionComponent
from miplearn.instance import Instance from miplearn.instance import Instance
from miplearn.solvers import RedirectOutput from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import InternalSolver from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
from miplearn.types import MIPSolveStats, TrainingSample from miplearn.types import MIPSolveStats, TrainingSample
@ -25,7 +25,7 @@ from miplearn.types import MIPSolveStats, TrainingSample
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GlobalVariables: class _GlobalVariables:
def __init__(self) -> None: def __init__(self) -> None:
self.solver: Optional[LearningSolver] = None self.solver: Optional[LearningSolver] = None
self.instances: Optional[Union[List[str], List[Instance]]] = None self.instances: Optional[Union[List[str], List[Instance]]] = None
@ -36,14 +36,14 @@ class GlobalVariables:
# Global variables used for multiprocessing. Global variables are copied by the # Global variables used for multiprocessing. Global variables are copied by the
# operating system when the process forks. Local variables are copied through # operating system when the process forks. Local variables are copied through
# serialization, which is a much slower process. # serialization, which is a much slower process.
GLOBAL = [GlobalVariables()] _GLOBAL = [_GlobalVariables()]
def _parallel_solve(idx): def _parallel_solve(idx):
solver = GLOBAL[0].solver solver = _GLOBAL[0].solver
instances = GLOBAL[0].instances instances = _GLOBAL[0].instances
output_filenames = GLOBAL[0].output_filenames output_filenames = _GLOBAL[0].output_filenames
discard_outputs = GLOBAL[0].discard_outputs discard_outputs = _GLOBAL[0].discard_outputs
if output_filenames is None: if output_filenames is None:
output_filename = None output_filename = None
else: else:
@ -64,28 +64,26 @@ class LearningSolver:
Parameters Parameters
---------- ----------
components: [Component] components: List[Component]
Set of components in the solver. By default, includes: Set of components in the solver. By default, includes
- ObjectiveValueComponent `ObjectiveValueComponent`, `PrimalSolutionComponent`,
- PrimalSolutionComponent `DynamicLazyConstraintsComponent` and `UserCutsComponent`.
- DynamicLazyConstraintsComponent mode: str
- UserCutsComponent
mode:
If "exact", solves problem to optimality, keeping all optimality If "exact", solves problem to optimality, keeping all optimality
guarantees provided by the MIP solver. If "heuristic", uses machine guarantees provided by the MIP solver. If "heuristic", uses machine
learning more aggressively, and may return suboptimal solutions. learning more aggressively, and may return suboptimal solutions.
solver: solver: Callable[[], InternalSolver]
A callable that constructs the internal solver. If None is provided, A callable that constructs the internal solver. If None is provided,
use GurobiPyomoSolver. use GurobiPyomoSolver.
use_lazy_cb: use_lazy_cb: bool
If true, use native solver callbacks for enforcing lazy constraints, If true, use native solver callbacks for enforcing lazy constraints,
instead of a simple loop. May not be supported by all solvers. instead of a simple loop. May not be supported by all solvers.
solve_lp_first: solve_lp_first: bool
If true, solve LP relaxation first, then solve original MILP. This If true, solve LP relaxation first, then solve original MILP. This
option should be activated if the LP relaxation is not very option should be activated if the LP relaxation is not very
expensive to solve and if it provides good hints for the integer expensive to solve and if it provides good hints for the integer
solution. solution.
simulate_perfect: simulate_perfect: bool
If true, each call to solve actually performs three actions: solve If true, each call to solve actually performs three actions: solve
the original problem, train the ML models on the data that was just the original problem, train the ML models on the data that was just
collected, and solve the problem again. This is useful for evaluating collected, and solve the problem again. This is useful for evaluating
@ -150,7 +148,7 @@ class LearningSolver:
# Generate model # Generate model
if model is None: if model is None:
with RedirectOutput([]): with _RedirectOutput([]):
model = instance.to_model() model = instance.to_model()
# Initialize training sample # Initialize training sample
@ -261,23 +259,23 @@ class LearningSolver:
Parameters Parameters
---------- ----------
instance: instance: Union[Instance, str]
The instance to be solved, or a filename. The instance to be solved, or a filename.
model: model: Any
The corresponding Pyomo model. If not provided, it will be created. The corresponding Pyomo model. If not provided, it will be created.
output_filename: output_filename: Optional[str]
If instance is a filename and output_filename is provided, write the If instance is a filename and output_filename is provided, write the
modified instance to this file, instead of replacing the original one. If modified instance to this file, instead of replacing the original one. If
output_filename is None (the default), modified the original file in-place. output_filename is None (the default), modified the original file in-place.
discard_output: discard_output: bool
If True, do not write the modified instances anywhere; simply discard If True, do not write the modified instances anywhere; simply discard
them. Useful during benchmarking. them. Useful during benchmarking.
tee: tee: bool
If true, prints solver log to screen. If true, prints solver log to screen.
Returns Returns
------- -------
dict MIPSolveStats
A dictionary of solver statistics containing at least the following A dictionary of solver statistics containing at least the following
keys: "Lower bound", "Upper bound", "Wallclock time", "Nodes", keys: "Lower bound", "Upper bound", "Wallclock time", "Nodes",
"Sense", "Log", "Warm start value" and "LP value". "Sense", "Log", "Warm start value" and "LP value".
@ -324,34 +322,33 @@ class LearningSolver:
Parameters Parameters
---------- ----------
output_filenames: output_filenames: Optional[List[str]]
If instances are file names and output_filenames is provided, write the If instances are file names and output_filenames is provided, write the
modified instances to these files, instead of replacing the original modified instances to these files, instead of replacing the original
files. If output_filenames is None, modifies the instances in-place. files. If output_filenames is None, modifies the instances in-place.
discard_outputs: discard_outputs: bool
If True, do not write the modified instances anywhere; simply discard If True, do not write the modified instances anywhere; simply discard
them instead. Useful during benchmarking. them instead. Useful during benchmarking.
label: label: str
Label to show in the progress bar. Label to show in the progress bar.
instances: instances: Union[List[str], List[Instance]]
The instances to be solved The instances to be solved
n_jobs: n_jobs: int
Number of instances to solve in parallel at a time. Number of instances to solve in parallel at a time.
Returns Returns
------- -------
Returns a list of dictionaries, with one entry for each provided instance. List[MIPSolveStats]
This dictionary is the same you would obtain by calling: List of solver statistics, with one entry for each provided instance.
The list is the same you would obtain by calling
[solver.solve(p) for p in instances] `[solver.solve(p) for p in instances]`
""" """
self.internal_solver = None self.internal_solver = None
self._silence_miplearn_logger() self._silence_miplearn_logger()
GLOBAL[0].solver = self _GLOBAL[0].solver = self
GLOBAL[0].output_filenames = output_filenames _GLOBAL[0].output_filenames = output_filenames
GLOBAL[0].instances = instances _GLOBAL[0].instances = instances
GLOBAL[0].discard_outputs = discard_outputs _GLOBAL[0].discard_outputs = discard_outputs
results = p_map( results = p_map(
_parallel_solve, _parallel_solve,
list(range(len(instances))), list(range(len(instances))),

@ -15,7 +15,7 @@ from pyomo.opt import TerminationCondition
from pyomo.opt.base.solvers import SolverFactory from pyomo.opt.base.solvers import SolverFactory
from miplearn.instance import Instance from miplearn.instance import Instance
from miplearn.solvers import RedirectOutput from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import ( from miplearn.solvers.internal import (
InternalSolver, InternalSolver,
LPSolveStats, LPSolveStats,
@ -60,7 +60,7 @@ class BasePyomoSolver(InternalSolver):
streams: List[Any] = [StringIO()] streams: List[Any] = [StringIO()]
if tee: if tee:
streams += [sys.stdout] streams += [sys.stdout]
with RedirectOutput(streams): with _RedirectOutput(streams):
results = self._pyomo_solver.solve(tee=True) results = self._pyomo_solver.solve(tee=True)
self._restore_integrality() self._restore_integrality()
opt_value = None opt_value = None
@ -92,7 +92,7 @@ class BasePyomoSolver(InternalSolver):
iteration_cb = lambda: False iteration_cb = lambda: False
while True: while True:
logger.debug("Solving MIP...") logger.debug("Solving MIP...")
with RedirectOutput(streams): with _RedirectOutput(streams):
results = self._pyomo_solver.solve( results = self._pyomo_solver.solve(
tee=True, tee=True,
warmstart=self._is_warm_start_available, warmstart=self._is_warm_start_available,

@ -8,7 +8,7 @@ from warnings import warn
import pyomo.environ as pe 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 ( from miplearn.solvers.tests import (
@ -25,7 +25,7 @@ def test_redirect_output():
original_stdout = sys.stdout original_stdout = sys.stdout
io = StringIO() io = StringIO()
with RedirectOutput([io]): with _RedirectOutput([io]):
print("Hello world") print("Hello world")
assert sys.stdout == original_stdout assert sys.stdout == original_stdout
assert io.getvalue() == "Hello world\n" assert io.getvalue() == "Hello world\n"

@ -54,3 +54,7 @@ LazyCallback = Callable[[Any, Any], None]
SolverParams = Dict[str, Any] SolverParams = Dict[str, Any]
BranchPriorities = Solution BranchPriorities = Solution
class Constraint:
pass

Loading…
Cancel
Save