Module miplearn.components.lazy_static
Expand source code
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
import logging
import sys
from copy import deepcopy
import numpy as np
from tqdm.auto import tqdm
from miplearn.classifiers.counting import CountingClassifier
from miplearn.components.component import Component
logger = logging.getLogger(__name__)
class LazyConstraint:
def __init__(self, cid, obj):
self.cid = cid
self.obj = obj
class StaticLazyConstraintsComponent(Component):
def __init__(
self,
classifier=CountingClassifier(),
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)
def after_solve(
self,
solver,
instance,
model,
stats,
training_data,
):
pass
def iteration_cb(self, solver, instance, model):
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 lazy_cb(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, 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(
"%8d lazy constraints added %8d in the pool"
% (len(constraints_to_add), len(self.pool))
)
return True
else:
return False
def fit(self, training_instances):
training_instances = [
t
for t in training_instances
if hasattr(t, "found_violated_lazy_constraints")
]
logger.debug("Extracting x and y...")
x = self.x(training_instances)
y = self.y(training_instances)
logger.debug("Fitting...")
for category in tqdm(
x.keys(), desc="Fit (lazy)", disable=not sys.stdout.isatty()
):
if category not in self.classifiers:
self.classifiers[category] = deepcopy(self.classifier_prototype)
self.classifiers[category].fit(x[category], y[category])
def predict(self, instance):
pass
def evaluate(self, instances):
pass
def _extract_and_predict_static(self, solver, instance):
x = {}
constraints = {}
logger.info("Extracting lazy constraints...")
for cid in solver.internal_solver.get_constraint_ids():
if instance.is_constraint_lazy(cid):
category = instance.get_constraint_category(cid)
if category not in x:
x[category] = []
constraints[category] = []
x[category] += [instance.get_constraint_features(cid)]
c = LazyConstraint(
cid=cid,
obj=solver.internal_solver.extract_constraint(cid),
)
constraints[category] += [c]
self.pool.append(c)
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():
if category not in self.classifiers:
continue
if isinstance(x_values[0], np.ndarray):
x[category] = np.array(x_values)
proba = self.classifiers[category].predict_proba(x[category])
for i in range(len(proba)):
if proba[i][1] > self.threshold:
n_added += 1
c = constraints[category][i]
self.pool.remove(c)
solver.internal_solver.add_constraint(c.obj)
instance.found_violated_lazy_constraints += [c.cid]
logger.info(
"%8d lazy constraints added %8d in the pool"
% (
n_added,
len(self.pool),
)
)
def _collect_constraints(self, train_instances):
constraints = {}
for instance in train_instances:
for cid in instance.found_violated_lazy_constraints:
category = instance.get_constraint_category(cid)
if category not in constraints:
constraints[category] = set()
constraints[category].add(cid)
for (category, cids) in constraints.items():
constraints[category] = sorted(list(cids))
return constraints
def x(self, train_instances):
result = {}
constraints = self._collect_constraints(train_instances)
for (category, cids) in constraints.items():
result[category] = []
for instance in train_instances:
for cid in cids:
result[category].append(instance.get_constraint_features(cid))
return result
def y(self, train_instances):
result = {}
constraints = self._collect_constraints(train_instances)
for (category, cids) in constraints.items():
result[category] = []
for instance in train_instances:
for cid in cids:
if cid in instance.found_violated_lazy_constraints:
result[category].append([0, 1])
else:
result[category].append([1, 0])
return result
Classes
class LazyConstraint (cid, obj)
-
Expand source code
class LazyConstraint: def __init__(self, cid, obj): self.cid = cid self.obj = obj
class StaticLazyConstraintsComponent (classifier=CountingClassifier(mean=None), threshold=0.05, use_two_phase_gap=True, large_gap=0.01, violation_tolerance=-0.5)
-
A Component is an object which adds functionality to a LearningSolver.
For better code maintainability, LearningSolver simply delegates most of its functionality to Components. Each Component is responsible for exactly one ML strategy.
Expand source code
class StaticLazyConstraintsComponent(Component): def __init__( self, classifier=CountingClassifier(), 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) def after_solve( self, solver, instance, model, stats, training_data, ): pass def iteration_cb(self, solver, instance, model): 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 lazy_cb(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, 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( "%8d lazy constraints added %8d in the pool" % (len(constraints_to_add), len(self.pool)) ) return True else: return False def fit(self, training_instances): training_instances = [ t for t in training_instances if hasattr(t, "found_violated_lazy_constraints") ] logger.debug("Extracting x and y...") x = self.x(training_instances) y = self.y(training_instances) logger.debug("Fitting...") for category in tqdm( x.keys(), desc="Fit (lazy)", disable=not sys.stdout.isatty() ): if category not in self.classifiers: self.classifiers[category] = deepcopy(self.classifier_prototype) self.classifiers[category].fit(x[category], y[category]) def predict(self, instance): pass def evaluate(self, instances): pass def _extract_and_predict_static(self, solver, instance): x = {} constraints = {} logger.info("Extracting lazy constraints...") for cid in solver.internal_solver.get_constraint_ids(): if instance.is_constraint_lazy(cid): category = instance.get_constraint_category(cid) if category not in x: x[category] = [] constraints[category] = [] x[category] += [instance.get_constraint_features(cid)] c = LazyConstraint( cid=cid, obj=solver.internal_solver.extract_constraint(cid), ) constraints[category] += [c] self.pool.append(c) 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(): if category not in self.classifiers: continue if isinstance(x_values[0], np.ndarray): x[category] = np.array(x_values) proba = self.classifiers[category].predict_proba(x[category]) for i in range(len(proba)): if proba[i][1] > self.threshold: n_added += 1 c = constraints[category][i] self.pool.remove(c) solver.internal_solver.add_constraint(c.obj) instance.found_violated_lazy_constraints += [c.cid] logger.info( "%8d lazy constraints added %8d in the pool" % ( n_added, len(self.pool), ) ) def _collect_constraints(self, train_instances): constraints = {} for instance in train_instances: for cid in instance.found_violated_lazy_constraints: category = instance.get_constraint_category(cid) if category not in constraints: constraints[category] = set() constraints[category].add(cid) for (category, cids) in constraints.items(): constraints[category] = sorted(list(cids)) return constraints def x(self, train_instances): result = {} constraints = self._collect_constraints(train_instances) for (category, cids) in constraints.items(): result[category] = [] for instance in train_instances: for cid in cids: result[category].append(instance.get_constraint_features(cid)) return result def y(self, train_instances): result = {} constraints = self._collect_constraints(train_instances) for (category, cids) in constraints.items(): result[category] = [] for instance in train_instances: for cid in cids: if cid in instance.found_violated_lazy_constraints: result[category].append([0, 1]) else: result[category].append([1, 0]) return result
Ancestors
- Component
- abc.ABC
Methods
def evaluate(self, instances)
-
Expand source code
def evaluate(self, instances): pass
def fit(self, training_instances)
-
Expand source code
def fit(self, training_instances): training_instances = [ t for t in training_instances if hasattr(t, "found_violated_lazy_constraints") ] logger.debug("Extracting x and y...") x = self.x(training_instances) y = self.y(training_instances) logger.debug("Fitting...") for category in tqdm( x.keys(), desc="Fit (lazy)", disable=not sys.stdout.isatty() ): if category not in self.classifiers: self.classifiers[category] = deepcopy(self.classifier_prototype) self.classifiers[category].fit(x[category], y[category])
def lazy_cb(self, solver, instance, model)
-
Expand source code
def lazy_cb(self, solver, instance, model): self._check_and_add(instance, solver)
def predict(self, instance)
-
Expand source code
def predict(self, instance): pass
def x(self, train_instances)
-
Expand source code
def x(self, train_instances): result = {} constraints = self._collect_constraints(train_instances) for (category, cids) in constraints.items(): result[category] = [] for instance in train_instances: for cid in cids: result[category].append(instance.get_constraint_features(cid)) return result
def y(self, train_instances)
-
Expand source code
def y(self, train_instances): result = {} constraints = self._collect_constraints(train_instances) for (category, cids) in constraints.items(): result[category] = [] for instance in train_instances: for cid in cids: if cid in instance.found_violated_lazy_constraints: result[category].append([0, 1]) else: result[category].append([1, 0]) return result
Inherited members