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

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

@ -9,7 +9,7 @@ from random import randint
from typing import List, Any, Dict, Optional
from miplearn.instance import Instance
from miplearn.solvers import RedirectOutput
from miplearn.solvers import _RedirectOutput
from miplearn.solvers.internal import (
InternalSolver,
LPSolveStats,
@ -23,24 +23,25 @@ logger = logging.getLogger(__name__)
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).
Parameters
----------
params
Parameters to pass to Gurobi. For example, params={"MIPGap": 1e-3}
params: Optional[SolverParams]
Parameters to pass to Gurobi. For example, `params={"MIPGap": 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
is found. If 2, calls it also at every node, after solving the
LP relaxation of that node.
"""
def __init__(
self,
params: Optional[SolverParams] = None,
lazy_cb_frequency: int = 1,
) -> None:
import gurobipy
if params is None:
@ -108,7 +109,7 @@ class GurobiSolver(InternalSolver):
def _apply_params(self, streams: List[Any]) -> None:
assert self.model is not None
with RedirectOutput(streams):
with _RedirectOutput(streams):
for (name, value) in self.params.items():
self.model.setParam(name, value)
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.lb = 0.0
var.ub = 1.0
with RedirectOutput(streams):
with _RedirectOutput(streams):
self.model.optimize()
for (varname, vardict) in self._bin_vars.items():
for (idx, var) in vardict.items():
@ -174,7 +175,7 @@ class GurobiSolver(InternalSolver):
if iteration_cb is None:
iteration_cb = lambda: False
while True:
with RedirectOutput(streams):
with _RedirectOutput(streams):
if lazy_cb is None:
self.model.optimize()
else:
@ -362,11 +363,13 @@ class GurobiSolver(InternalSolver):
ineqs = [c for c in self.model.getConstrs() if c.sense != "="]
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.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)
return c.Sense

@ -15,15 +15,12 @@ from miplearn.types import (
VarIndex,
Solution,
BranchPriorities,
Constraint,
)
logger = logging.getLogger(__name__)
class Constraint:
pass
class InternalSolver(ABC):
"""
Abstract class representing the MIP solver used internally by LearningSolver.
@ -61,13 +58,13 @@ class InternalSolver(ABC):
Parameters
----------
iteration_cb:
iteration_cb: IterationCallback
By default, InternalSolver makes a single call to the native `solve`
method and returns the result. If an iteration callback is provided
instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
are called alternatively. To stop the loop, `iteration_cb` should return
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
solution and can be used to add lazy constraints to the model. Only the
following operations within the callback are allowed:
@ -75,7 +72,7 @@ class InternalSolver(ABC):
- Querying if a constraint is satisfied
- Adding a new constraint to the problem
Additional operations may be allowed by specific subclasses.
tee
tee: bool
If true, prints the solver log to the screen.
"""
pass
@ -119,7 +116,7 @@ class InternalSolver(ABC):
----------
instance: Instance
The instance to be loaded.
model:
model: Any
The concrete optimization model corresponding to this instance
(e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided,
it will be generated by calling `instance.to_model()`.
@ -184,10 +181,28 @@ class InternalSolver(ABC):
@abstractmethod
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
@abstractmethod
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
@abstractmethod

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

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

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

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

Loading…
Cancel
Save