# 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

  - `Component`:
    - `after_solve`
    - `before_solve`
    - `iteration_cb`