|
|
@ -109,6 +109,9 @@ class InternalSolver:
|
|
|
|
for var in model.component_objects(Var):
|
|
|
|
for var in model.component_objects(Var):
|
|
|
|
self.var_name_to_var[var.name] = var
|
|
|
|
self.var_name_to_var[var.name] = var
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_instance(self, instance):
|
|
|
|
|
|
|
|
self.instance = instance
|
|
|
|
|
|
|
|
|
|
|
|
def fix(self, solution):
|
|
|
|
def fix(self, solution):
|
|
|
|
count_total, count_fixed = 0, 0
|
|
|
|
count_total, count_fixed = 0, 0
|
|
|
|
for var_name in solution:
|
|
|
|
for var_name in solution:
|
|
|
@ -123,9 +126,33 @@ class InternalSolver:
|
|
|
|
logger.info("Fixing values for %d variables (out of %d)" %
|
|
|
|
logger.info("Fixing values for %d variables (out of %d)" %
|
|
|
|
(count_fixed, count_total))
|
|
|
|
(count_fixed, count_total))
|
|
|
|
|
|
|
|
|
|
|
|
def add_constraint(self, cut):
|
|
|
|
def solve(self, tee=False):
|
|
|
|
|
|
|
|
total_wallclock_time = 0
|
|
|
|
|
|
|
|
self.instance.found_violations = []
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
|
|
logger.debug("Solving MIP...")
|
|
|
|
|
|
|
|
results = self.solver.solve(tee=tee)
|
|
|
|
|
|
|
|
total_wallclock_time += results["Solver"][0]["Wallclock time"]
|
|
|
|
|
|
|
|
if not hasattr(self.instance, "find_violations"):
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
logger.debug("Finding violated constraints...")
|
|
|
|
|
|
|
|
violations = self.instance.find_violations(self.model)
|
|
|
|
|
|
|
|
if len(violations) == 0:
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
self.instance.found_violations += violations
|
|
|
|
|
|
|
|
logger.debug(" %d violations found" % len(violations))
|
|
|
|
|
|
|
|
for v in violations:
|
|
|
|
|
|
|
|
cut = self.instance.build_lazy_constraint(self.model, v)
|
|
|
|
self.solver.add_constraint(cut)
|
|
|
|
self.solver.add_constraint(cut)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"Lower bound": results["Problem"][0]["Lower bound"],
|
|
|
|
|
|
|
|
"Upper bound": results["Problem"][0]["Upper bound"],
|
|
|
|
|
|
|
|
"Wallclock time": total_wallclock_time,
|
|
|
|
|
|
|
|
"Nodes": 1,
|
|
|
|
|
|
|
|
"Sense": self.sense,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GurobiSolver(InternalSolver):
|
|
|
|
class GurobiSolver(InternalSolver):
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
@ -143,7 +170,24 @@ class GurobiSolver(InternalSolver):
|
|
|
|
self.solver.options["MIPGap"] = gap_tolerance
|
|
|
|
self.solver.options["MIPGap"] = gap_tolerance
|
|
|
|
|
|
|
|
|
|
|
|
def solve(self, tee=False):
|
|
|
|
def solve(self, tee=False):
|
|
|
|
|
|
|
|
from gurobipy import GRB
|
|
|
|
|
|
|
|
def cb(cb_model, cb_opt, cb_where):
|
|
|
|
|
|
|
|
if cb_where == GRB.Callback.MIPSOL:
|
|
|
|
|
|
|
|
all_vars = [v[idx] for v in self.model.component_objects(Var) for idx in v]
|
|
|
|
|
|
|
|
cb_opt.cbGetSolution(all_vars)
|
|
|
|
|
|
|
|
logger.debug("Finding violated constraints...")
|
|
|
|
|
|
|
|
violations = self.instance.find_violations(cb_model)
|
|
|
|
|
|
|
|
self.instance.found_violations += violations
|
|
|
|
|
|
|
|
logger.debug(" %d violations found" % len(violations))
|
|
|
|
|
|
|
|
for v in violations:
|
|
|
|
|
|
|
|
cut = self.instance.build_lazy_constraint(cb_model, v)
|
|
|
|
|
|
|
|
cb_opt.cbLazy(cut)
|
|
|
|
|
|
|
|
if hasattr(self.instance, "find_violations"):
|
|
|
|
|
|
|
|
self.solver.options["LazyConstraints"] = 1
|
|
|
|
|
|
|
|
self.solver.set_callback(cb)
|
|
|
|
|
|
|
|
self.instance.found_violations = []
|
|
|
|
results = self.solver.solve(tee=tee, warmstart=self.is_warm_start_available)
|
|
|
|
results = self.solver.solve(tee=tee, warmstart=self.is_warm_start_available)
|
|
|
|
|
|
|
|
self.solver.set_callback(None)
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
"Lower bound": results["Problem"][0]["Lower bound"],
|
|
|
|
"Lower bound": results["Problem"][0]["Lower bound"],
|
|
|
|
"Upper bound": results["Problem"][0]["Upper bound"],
|
|
|
|
"Upper bound": results["Problem"][0]["Upper bound"],
|
|
|
@ -152,19 +196,6 @@ class GurobiSolver(InternalSolver):
|
|
|
|
"Sense": self.sense,
|
|
|
|
"Sense": self.sense,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _load_vars(self):
|
|
|
|
|
|
|
|
var_map = self._pyomo_var_to_solver_var_map
|
|
|
|
|
|
|
|
ref_vars = self._referenced_variables
|
|
|
|
|
|
|
|
vars_to_load = var_map.keys()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gurobi_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load]
|
|
|
|
|
|
|
|
vals = self._solver_model.getAttr("X", gurobi_vars_to_load)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for var, val in zip(vars_to_load, vals):
|
|
|
|
|
|
|
|
if ref_vars[var] > 0:
|
|
|
|
|
|
|
|
var.stale = False
|
|
|
|
|
|
|
|
var.value = val
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CPLEXSolver(InternalSolver):
|
|
|
|
class CPLEXSolver(InternalSolver):
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
@ -182,16 +213,6 @@ class CPLEXSolver(InternalSolver):
|
|
|
|
def set_gap_tolerance(self, gap_tolerance):
|
|
|
|
def set_gap_tolerance(self, gap_tolerance):
|
|
|
|
self.solver.options["mip_tolerances_mipgap"] = gap_tolerance
|
|
|
|
self.solver.options["mip_tolerances_mipgap"] = gap_tolerance
|
|
|
|
|
|
|
|
|
|
|
|
def solve(self, tee=False):
|
|
|
|
|
|
|
|
results = self.solver.solve(tee=tee, warmstart=self.is_warm_start_available)
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"Lower bound": results["Problem"][0]["Lower bound"],
|
|
|
|
|
|
|
|
"Upper bound": results["Problem"][0]["Upper bound"],
|
|
|
|
|
|
|
|
"Wallclock time": results["Solver"][0]["Wallclock time"],
|
|
|
|
|
|
|
|
"Nodes": 1,
|
|
|
|
|
|
|
|
"Sense": self.sense,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def solve_lp(self, tee=False):
|
|
|
|
def solve_lp(self, tee=False):
|
|
|
|
import cplex
|
|
|
|
import cplex
|
|
|
|
lp = self.solver._solver_model
|
|
|
|
lp = self.solver._solver_model
|
|
|
@ -245,6 +266,7 @@ class LearningSolver:
|
|
|
|
component.mode = self.mode
|
|
|
|
component.mode = self.mode
|
|
|
|
|
|
|
|
|
|
|
|
def _create_internal_solver(self):
|
|
|
|
def _create_internal_solver(self):
|
|
|
|
|
|
|
|
logger.debug("Initializing %s" % self.internal_solver_factory)
|
|
|
|
if self.internal_solver_factory == "cplex":
|
|
|
|
if self.internal_solver_factory == "cplex":
|
|
|
|
solver = CPLEXSolver()
|
|
|
|
solver = CPLEXSolver()
|
|
|
|
elif self.internal_solver_factory == "gurobi":
|
|
|
|
elif self.internal_solver_factory == "gurobi":
|
|
|
@ -270,6 +292,7 @@ class LearningSolver:
|
|
|
|
self.tee = tee
|
|
|
|
self.tee = tee
|
|
|
|
self.internal_solver = self._create_internal_solver()
|
|
|
|
self.internal_solver = self._create_internal_solver()
|
|
|
|
self.internal_solver.set_model(model)
|
|
|
|
self.internal_solver.set_model(model)
|
|
|
|
|
|
|
|
self.internal_solver.set_instance(instance)
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug("Solving LP relaxation...")
|
|
|
|
logger.debug("Solving LP relaxation...")
|
|
|
|
results = self.internal_solver.solve_lp(tee=tee)
|
|
|
|
results = self.internal_solver.solve_lp(tee=tee)
|
|
|
@ -283,25 +306,7 @@ class LearningSolver:
|
|
|
|
if relaxation_only:
|
|
|
|
if relaxation_only:
|
|
|
|
return results
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
total_wallclock_time = 0
|
|
|
|
|
|
|
|
instance.found_violations = []
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
|
|
logger.debug("Solving MIP...")
|
|
|
|
|
|
|
|
results = self.internal_solver.solve(tee=tee)
|
|
|
|
results = self.internal_solver.solve(tee=tee)
|
|
|
|
logger.debug(" %.2f s" % results["Wallclock time"])
|
|
|
|
|
|
|
|
total_wallclock_time += results["Wallclock time"]
|
|
|
|
|
|
|
|
if not hasattr(instance, "find_violations"):
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
logger.debug("Finding violated constraints...")
|
|
|
|
|
|
|
|
violations = instance.find_violations(model)
|
|
|
|
|
|
|
|
if len(violations) == 0:
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
instance.found_violations += violations
|
|
|
|
|
|
|
|
logger.debug(" %d violations found" % len(violations))
|
|
|
|
|
|
|
|
for v in violations:
|
|
|
|
|
|
|
|
cut = instance.build_lazy_constraint(model, v)
|
|
|
|
|
|
|
|
self.internal_solver.add_constraint(cut)
|
|
|
|
|
|
|
|
results["Wallclock time"] = total_wallclock_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Read MIP solution and bounds
|
|
|
|
# Read MIP solution and bounds
|
|
|
|
instance.lower_bound = results["Lower bound"]
|
|
|
|
instance.lower_bound = results["Lower bound"]
|
|
|
|