mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
Implement lazy callbacks & two-phase gap
This commit is contained in:
@@ -24,3 +24,6 @@ class Component(ABC):
|
||||
|
||||
def after_iteration(self, solver, instance, model):
|
||||
return False
|
||||
|
||||
def on_lazy_callback(self, solver, instance, model):
|
||||
return
|
||||
|
||||
@@ -21,14 +21,29 @@ class LazyConstraint:
|
||||
class StaticLazyConstraintsComponent(Component):
|
||||
def __init__(self,
|
||||
classifier=CountingClassifier(),
|
||||
threshold=0.05):
|
||||
threshold=0.05,
|
||||
use_two_phase_gap=True,
|
||||
large_gap=1e-2,
|
||||
violation_tolerance=-0.5,
|
||||
):
|
||||
self.threshold = threshold
|
||||
self.classifier_prototype = classifier
|
||||
self.classifiers = {}
|
||||
self.pool = []
|
||||
self.original_gap = None
|
||||
self.large_gap = large_gap
|
||||
self.is_gap_large = False
|
||||
self.use_two_phase_gap = use_two_phase_gap
|
||||
self.violation_tolerance = violation_tolerance
|
||||
|
||||
def before_solve(self, solver, instance, model):
|
||||
self.pool = []
|
||||
if not solver.use_lazy_cb and self.use_two_phase_gap:
|
||||
logger.info("Increasing gap tolerance to %f", self.large_gap)
|
||||
self.original_gap = solver.gap_tolerance
|
||||
self.is_gap_large = True
|
||||
solver.internal_solver.set_gap_tolerance(self.large_gap)
|
||||
|
||||
instance.found_violated_lazy_constraints = []
|
||||
if instance.has_static_lazy_constraints():
|
||||
self._extract_and_predict_static(solver, instance)
|
||||
@@ -37,23 +52,41 @@ class StaticLazyConstraintsComponent(Component):
|
||||
pass
|
||||
|
||||
def after_iteration(self, solver, instance, model):
|
||||
logger.info("Finding violated lazy constraints...")
|
||||
if solver.use_lazy_cb:
|
||||
return False
|
||||
else:
|
||||
should_repeat = self._check_and_add(instance, solver)
|
||||
if should_repeat:
|
||||
return True
|
||||
else:
|
||||
if self.is_gap_large:
|
||||
logger.info("Restoring gap tolerance to %f", self.original_gap)
|
||||
solver.internal_solver.set_gap_tolerance(self.original_gap)
|
||||
self.is_gap_large = False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def on_lazy_callback(self, solver, instance, model):
|
||||
self._check_and_add(instance, solver)
|
||||
|
||||
def _check_and_add(self, instance, solver):
|
||||
logger.debug("Finding violated lazy constraints...")
|
||||
constraints_to_add = []
|
||||
for c in self.pool:
|
||||
if not solver.internal_solver.is_constraint_satisfied(c.obj):
|
||||
if not solver.internal_solver.is_constraint_satisfied(c.obj,
|
||||
tol=self.violation_tolerance):
|
||||
constraints_to_add.append(c)
|
||||
for c in constraints_to_add:
|
||||
self.pool.remove(c)
|
||||
solver.internal_solver.add_constraint(c.obj)
|
||||
instance.found_violated_lazy_constraints += [c.cid]
|
||||
if len(constraints_to_add) > 0:
|
||||
logger.info("Added %d lazy constraints back into the model" % len(constraints_to_add))
|
||||
logger.info("Lazy constraint pool has %d constraints" % len(self.pool))
|
||||
logger.info("%8d lazy constraints added %8d in the pool" % (len(constraints_to_add), len(self.pool)))
|
||||
return True
|
||||
else:
|
||||
logger.info("Found no violated lazy constraints")
|
||||
return False
|
||||
|
||||
|
||||
def fit(self, training_instances):
|
||||
training_instances = [t
|
||||
for t in training_instances
|
||||
@@ -92,7 +125,7 @@ class StaticLazyConstraintsComponent(Component):
|
||||
obj=solver.internal_solver.extract_constraint(cid))
|
||||
constraints[category] += [c]
|
||||
self.pool.append(c)
|
||||
logger.info("Extracted %d lazy constraints" % len(self.pool))
|
||||
logger.info("%8d lazy constraints extracted" % len(self.pool))
|
||||
logger.info("Predicting required lazy constraints...")
|
||||
n_added = 0
|
||||
for (category, x_values) in x.items():
|
||||
@@ -108,8 +141,7 @@ class StaticLazyConstraintsComponent(Component):
|
||||
self.pool.remove(c)
|
||||
solver.internal_solver.add_constraint(c.obj)
|
||||
instance.found_violated_lazy_constraints += [c.cid]
|
||||
logger.info("Added %d lazy constraints back into the model" % n_added)
|
||||
logger.info("Lazy constraint pool has %d constraints" % len(self.pool))
|
||||
logger.info("%8d lazy constraints added %8d in the pool" % (n_added, len(self.pool)))
|
||||
|
||||
def _collect_constraints(self, train_instances):
|
||||
constraints = {}
|
||||
|
||||
@@ -13,6 +13,9 @@ from miplearn.classifiers import Classifier
|
||||
|
||||
def test_usage_with_solver():
|
||||
solver = Mock(spec=LearningSolver)
|
||||
solver.use_lazy_cb = False
|
||||
solver.gap_tolerance = 1e-4
|
||||
|
||||
internal = solver.internal_solver = Mock(spec=InternalSolver)
|
||||
internal.get_constraint_ids = Mock(return_value=["c1", "c2", "c3", "c4"])
|
||||
internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
|
||||
@@ -37,7 +40,8 @@ def test_usage_with_solver():
|
||||
"c4": "type-b",
|
||||
}[cid])
|
||||
|
||||
component = StaticLazyConstraintsComponent(threshold=0.90)
|
||||
component = StaticLazyConstraintsComponent(threshold=0.90,
|
||||
use_two_phase_gap=False)
|
||||
component.classifiers = {
|
||||
"type-a": Mock(spec=Classifier),
|
||||
"type-b": Mock(spec=Classifier),
|
||||
|
||||
Reference in New Issue
Block a user