Small fixes to lazy constraints

This commit is contained in:
2020-09-24 14:30:29 -05:00
parent 0fe6aab98f
commit ba96338d2d
7 changed files with 58 additions and 39 deletions

View File

@@ -15,10 +15,11 @@ logger = logging.getLogger(__name__)
class GurobiSolver(InternalSolver):
def __init__(self, params=None):
if params is None:
params = {
"LazyConstraints": 1,
"PreCrush": 1,
}
params = {}
# params = {
# "LazyConstraints": 1,
# "PreCrush": 1,
# }
from gurobipy import GRB
self.GRB = GRB
self.instance = None
@@ -83,6 +84,7 @@ class GurobiSolver(InternalSolver):
}
def solve(self, tee=False, iteration_cb=None):
self._apply_params()
total_wallclock_time = 0
total_nodes = 0
streams = [StringIO()]
@@ -122,7 +124,7 @@ class GurobiSolver(InternalSolver):
def get_variables(self):
variables = {}
for (varname, vardict) in self._all_vars.items():
variables[varname] = {}
variables[varname] = []
for (idx, var) in vardict.items():
variables[varname] += [idx]
return variables
@@ -161,7 +163,7 @@ class GurobiSolver(InternalSolver):
var.lb = value
var.ub = value
def get_constraints_ids(self):
def get_constraint_ids(self):
self.model.update()
return [c.ConstrName for c in self.model.getConstrs()]

View File

@@ -145,7 +145,7 @@ class InternalSolver(ABC):
pass
@abstractmethod
def get_constraints_ids(self):
def get_constraint_ids(self):
"""
Returns a list of ids, which uniquely identify each constraint in the model.
"""

View File

@@ -40,6 +40,14 @@ class LearningSolver:
Mixed-Integer Linear Programming (MIP) solver that extracts information
from previous runs, using Machine Learning methods, to accelerate the
solution of new (yet unseen) instances.
Parameters
----------
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.
"""
def __init__(self,
@@ -49,7 +57,8 @@ class LearningSolver:
solver="gurobi",
threads=None,
time_limit=None,
node_limit=None):
node_limit=None,
solve_lp_first=True):
self.components = {}
self.mode = mode
self.internal_solver = None
@@ -59,6 +68,7 @@ class LearningSolver:
self.gap_tolerance = gap_tolerance
self.tee = False
self.node_limit = node_limit
self.solve_lp_first = solve_lp_first
if components is not None:
for comp in components:
@@ -84,21 +94,23 @@ class LearningSolver:
else:
solver = self.internal_solver_factory
if self.threads is not None:
logger.info("Setting threads to %d" % self.threads)
solver.set_threads(self.threads)
if self.time_limit is not None:
logger.info("Setting time limit to %f" % self.time_limit)
solver.set_time_limit(self.time_limit)
if self.gap_tolerance is not None:
logger.info("Setting gap tolerance to %f" % self.gap_tolerance)
solver.set_gap_tolerance(self.gap_tolerance)
if self.node_limit is not None:
logger.info("Setting node limit to %d" % self.node_limit)
solver.set_node_limit(self.node_limit)
return solver
def solve(self,
instance,
model=None,
tee=False,
relaxation_only=False,
solve_lp_first=True):
tee=False):
"""
Solves the given instance. If trained machine-learning models are
available, they will be used to accelerate the solution process.
@@ -127,13 +139,6 @@ class LearningSolver:
The corresponding Pyomo model. If not provided, it will be created.
tee: bool
If true, prints solver log to screen.
relaxation_only: bool
If true, solve only the root LP relaxation.
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.
Returns
-------
@@ -155,7 +160,7 @@ class LearningSolver:
self.internal_solver = self._create_internal_solver()
self.internal_solver.set_instance(instance, model)
if solve_lp_first:
if self.solve_lp_first:
logger.info("Solving LP relaxation...")
results = self.internal_solver.solve_lp(tee=tee)
instance.lp_solution = self.internal_solver.get_solution()
@@ -168,9 +173,6 @@ class LearningSolver:
for component in self.components.values():
component.before_solve(self, instance, model)
if relaxation_only:
return results
def iteration_cb():
should_repeat = False
for component in self.components.values():

View File

@@ -203,7 +203,7 @@ class BasePyomoSolver(InternalSolver):
key = self._get_gap_tolerance_option_name()
self._pyomo_solver.options[key] = gap_tolerance
def get_constraints_ids(self):
def get_constraint_ids(self):
return list(self._cname_to_constr.keys())
def extract_constraint(self, cid):

View File

@@ -110,7 +110,7 @@ def test_internal_solver():
# New constraint should affect solution and should be listed in
# constraint ids
assert solver.get_constraints_ids() == ["eq_capacity", "cut"]
assert solver.get_constraint_ids() == ["eq_capacity", "cut"]
stats = solver.solve()
assert stats["Lower bound"] == 1030.0
@@ -120,7 +120,7 @@ def test_internal_solver():
# New constraint should no longer affect solution and should no longer
# be listed in constraint ids
assert solver.get_constraints_ids() == ["eq_capacity"]
assert solver.get_constraint_ids() == ["eq_capacity"]
stats = solver.solve()
assert stats["Lower bound"] == 1183.0
@@ -131,7 +131,7 @@ def test_internal_solver():
solver.add_constraint(cobj)
# Constraint should affect solution again
assert solver.get_constraints_ids() == ["eq_capacity", "cut"]
assert solver.get_constraint_ids() == ["eq_capacity", "cut"]
stats = solver.solve()
assert stats["Lower bound"] == 1030.0