# Module `miplearn.instance`

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 gzip
    import json
    from abc import ABC, abstractmethod
    from typing import Any, List

    import numpy as np

    from miplearn.types import TrainingSample

    class Instance(ABC):
        """
        Abstract class holding all the data necessary to generate a concrete model of the
        problem.

        In the knapsack problem, for example, this class could hold the number of items,
        their weights and costs, as well as the size of the knapsack. Objects
        implementing this class are able to convert themselves into a concrete
        optimization model, which can be optimized by a solver, or into arrays of
        features, which can be provided as inputs to machine learning models.
        """

        def __init__(self):
            self.training_data: List[TrainingSample] = []

        @abstractmethod
        def to_model(self) -> Any:
            """
            Returns the optimization model corresponding to this instance.
            """
            pass

        def get_instance_features(self):
            """
            Returns a 1-dimensional Numpy array of (numerical) features describing the
            entire instance.

            The array is used by LearningSolver to determine how similar two instances
            are. It may also be used to predict, in combination with variable-specific
            features, the values of binary decision variables in the problem.

            There is not necessarily a one-to-one correspondence between models and
            instance features: the features may encode only part of the data necessary to
            generate the complete model. Features may also be statistics computed from
            the original data. For example, in the knapsack problem, an implementation
            may decide to provide as instance features only the average weights, average
            prices, number of items and the size of the knapsack.

            The returned array MUST have the same length for all relevant instances of
            the problem. If two instances map into arrays of different lengths,
            they cannot be solved by the same LearningSolver object.

            By default, returns [0].
            """
            return np.zeros(1)

        def get_variable_features(self, var, index):
            """
            Returns a 1-dimensional array of (numerical) features describing a particular
            decision variable.

            The argument `var` is a pyomo.core.Var object, which represents a collection
            of decision variables. The argument `index` specifies which variable in the
            collection is the relevant one.

            In combination with instance features, variable features are used by
            LearningSolver to predict, among other things, the optimal value of each
            decision variable before the optimization takes place. In the knapsack
            problem, for example, an implementation could provide as variable features
            the weight and the price of a specific item.

            Like instance features, the arrays returned by this method MUST have the same
            length for all variables within the same category, for all relevant instances
            of the problem.

            By default, returns [0].
            """
            return np.zeros(1)

        def get_variable_category(self, var, index):
            """
            Returns the category (a string, an integer or any hashable type) for each
            decision variable.

            If two variables have the same category, LearningSolver will use the same
            internal ML model to predict the values of both variables. If the returned
            category is None, ML models will ignore the variable.

            By default, returns "default".
            """
            return "default"

        def get_constraint_features(self, cid):
            return np.zeros(1)

        def get_constraint_category(self, cid):
            return cid

        def has_static_lazy_constraints(self):
            return False

        def has_dynamic_lazy_constraints(self):
            return False

        def is_constraint_lazy(self, cid):
            return False

        def find_violated_lazy_constraints(self, model):
            """
            Returns lazy constraint violations found for the current solution.

            After solving a model, LearningSolver will ask the instance to identify which
            lazy constraints are violated by the current solution. For each identified
            violation, LearningSolver will then call the build_lazy_constraint, add the
            generated Pyomo constraint to the model, then resolve the problem. The
            process repeats until no further lazy constraint violations are found.

            Each "violation" is simply a string, a tuple or any other hashable type which
            allows the instance to identify unambiguously which lazy constraint should be
            generated. In the Traveling Salesman Problem, for example, a subtour
            violation could be a frozen set containing the cities in the subtour.

            For a concrete example, see TravelingSalesmanInstance.
            """
            return []

        def build_lazy_constraint(self, model, violation):
            """
            Returns a Pyomo constraint which fixes a given violation.

            This method is typically called immediately after
            find_violated_lazy_constraints. The violation object provided to this method
            is exactly the same object returned earlier by
            find_violated_lazy_constraints. After some training, LearningSolver may
            decide to proactively build some lazy constraints at the beginning of the
            optimization process, before a solution is even available. In this case,
            build_lazy_constraints will be called without a corresponding call to
            find_violated_lazy_constraints.

            The implementation should not directly add the constraint to the model. The
            constraint will be added by LearningSolver after the method returns.

            For a concrete example, see TravelingSalesmanInstance.
            """
            pass

        def find_violated_user_cuts(self, model):
            return []

        def build_user_cut(self, model, violation):
            pass

        def load(self, filename):
            with gzip.GzipFile(filename, "r") as f:
                data = json.loads(f.read().decode("utf-8"))
            self.__dict__ = data

        def dump(self, filename):
            data = json.dumps(self.__dict__, indent=2).encode("utf-8")
            with gzip.GzipFile(filename, "w") as f:
                f.write(data)

## Classes

`class Instance`

  Abstract class holding all the data necessary to generate a concrete model of the problem.

  In the knapsack problem, for example, this class could hold the number of items, their weights and costs, as well as the size of the knapsack. Objects implementing this class are able to convert themselves into a concrete optimization model, which can be optimized by a solver, or into arrays of features, which can be provided as inputs to machine learning models.

  Expand source code

      class Instance(ABC):
          """
          Abstract class holding all the data necessary to generate a concrete model of the
          problem.

          In the knapsack problem, for example, this class could hold the number of items,
          their weights and costs, as well as the size of the knapsack. Objects
          implementing this class are able to convert themselves into a concrete
          optimization model, which can be optimized by a solver, or into arrays of
          features, which can be provided as inputs to machine learning models.
          """

          def __init__(self):
              self.training_data: List[TrainingSample] = []

          @abstractmethod
          def to_model(self) -> Any:
              """
              Returns the optimization model corresponding to this instance.
              """
              pass

          def get_instance_features(self):
              """
              Returns a 1-dimensional Numpy array of (numerical) features describing the
              entire instance.

              The array is used by LearningSolver to determine how similar two instances
              are. It may also be used to predict, in combination with variable-specific
              features, the values of binary decision variables in the problem.

              There is not necessarily a one-to-one correspondence between models and
              instance features: the features may encode only part of the data necessary to
              generate the complete model. Features may also be statistics computed from
              the original data. For example, in the knapsack problem, an implementation
              may decide to provide as instance features only the average weights, average
              prices, number of items and the size of the knapsack.

              The returned array MUST have the same length for all relevant instances of
              the problem. If two instances map into arrays of different lengths,
              they cannot be solved by the same LearningSolver object.

              By default, returns [0].
              """
              return np.zeros(1)

          def get_variable_features(self, var, index):
              """
              Returns a 1-dimensional array of (numerical) features describing a particular
              decision variable.

              The argument `var` is a pyomo.core.Var object, which represents a collection
              of decision variables. The argument `index` specifies which variable in the
              collection is the relevant one.

              In combination with instance features, variable features are used by
              LearningSolver to predict, among other things, the optimal value of each
              decision variable before the optimization takes place. In the knapsack
              problem, for example, an implementation could provide as variable features
              the weight and the price of a specific item.

              Like instance features, the arrays returned by this method MUST have the same
              length for all variables within the same category, for all relevant instances
              of the problem.

              By default, returns [0].
              """
              return np.zeros(1)

          def get_variable_category(self, var, index):
              """
              Returns the category (a string, an integer or any hashable type) for each
              decision variable.

              If two variables have the same category, LearningSolver will use the same
              internal ML model to predict the values of both variables. If the returned
              category is None, ML models will ignore the variable.

              By default, returns "default".
              """
              return "default"

          def get_constraint_features(self, cid):
              return np.zeros(1)

          def get_constraint_category(self, cid):
              return cid

          def has_static_lazy_constraints(self):
              return False

          def has_dynamic_lazy_constraints(self):
              return False

          def is_constraint_lazy(self, cid):
              return False

          def find_violated_lazy_constraints(self, model):
              """
              Returns lazy constraint violations found for the current solution.

              After solving a model, LearningSolver will ask the instance to identify which
              lazy constraints are violated by the current solution. For each identified
              violation, LearningSolver will then call the build_lazy_constraint, add the
              generated Pyomo constraint to the model, then resolve the problem. The
              process repeats until no further lazy constraint violations are found.

              Each "violation" is simply a string, a tuple or any other hashable type which
              allows the instance to identify unambiguously which lazy constraint should be
              generated. In the Traveling Salesman Problem, for example, a subtour
              violation could be a frozen set containing the cities in the subtour.

              For a concrete example, see TravelingSalesmanInstance.
              """
              return []

          def build_lazy_constraint(self, model, violation):
              """
              Returns a Pyomo constraint which fixes a given violation.

              This method is typically called immediately after
              find_violated_lazy_constraints. The violation object provided to this method
              is exactly the same object returned earlier by
              find_violated_lazy_constraints. After some training, LearningSolver may
              decide to proactively build some lazy constraints at the beginning of the
              optimization process, before a solution is even available. In this case,
              build_lazy_constraints will be called without a corresponding call to
              find_violated_lazy_constraints.

              The implementation should not directly add the constraint to the model. The
              constraint will be added by LearningSolver after the method returns.

              For a concrete example, see TravelingSalesmanInstance.
              """
              pass

          def find_violated_user_cuts(self, model):
              return []

          def build_user_cut(self, model, violation):
              pass

          def load(self, filename):
              with gzip.GzipFile(filename, "r") as f:
                  data = json.loads(f.read().decode("utf-8"))
              self.__dict__ = data

          def dump(self, filename):
              data = json.dumps(self.__dict__, indent=2).encode("utf-8")
              with gzip.GzipFile(filename, "w") as f:
                  f.write(data)

  ### Ancestors

  - abc.ABC

  ### Subclasses

  - SampleInstance
  - KnapsackInstance
  - MultiKnapsackInstance
  - MaxWeightStableSetInstance
  - TravelingSalesmanInstance
  - InfeasibleGurobiInstance
  - InfeasiblePyomoInstance

  ### Methods

  `def build_lazy_constraint(self, model, violation)`

    Returns a Pyomo constraint which fixes a given violation.

    This method is typically called immediately after find_violated_lazy_constraints. The violation object provided to this method is exactly the same object returned earlier by find_violated_lazy_constraints. After some training, LearningSolver may decide to proactively build some lazy constraints at the beginning of the optimization process, before a solution is even available. In this case, build_lazy_constraints will be called without a corresponding call to find_violated_lazy_constraints.

    The implementation should not directly add the constraint to the model. The constraint will be added by LearningSolver after the method returns.

    For a concrete example, see TravelingSalesmanInstance.

    Expand source code

        def build_lazy_constraint(self, model, violation):
            """
            Returns a Pyomo constraint which fixes a given violation.

            This method is typically called immediately after
            find_violated_lazy_constraints. The violation object provided to this method
            is exactly the same object returned earlier by
            find_violated_lazy_constraints. After some training, LearningSolver may
            decide to proactively build some lazy constraints at the beginning of the
            optimization process, before a solution is even available. In this case,
            build_lazy_constraints will be called without a corresponding call to
            find_violated_lazy_constraints.

            The implementation should not directly add the constraint to the model. The
            constraint will be added by LearningSolver after the method returns.

            For a concrete example, see TravelingSalesmanInstance.
            """
            pass

  `def build_user_cut(self, model, violation)`
    Expand source code

        def build_user_cut(self, model, violation):
            pass

  `def dump(self, filename)`
    Expand source code

        def dump(self, filename):
            data = json.dumps(self.__dict__, indent=2).encode("utf-8")
            with gzip.GzipFile(filename, "w") as f:
                f.write(data)

  `def find_violated_lazy_constraints(self, model)`

    Returns lazy constraint violations found for the current solution.

    After solving a model, LearningSolver will ask the instance to identify which lazy constraints are violated by the current solution. For each identified violation, LearningSolver will then call the build_lazy_constraint, add the generated Pyomo constraint to the model, then resolve the problem. The process repeats until no further lazy constraint violations are found.

    Each "violation" is simply a string, a tuple or any other hashable type which allows the instance to identify unambiguously which lazy constraint should be generated. In the Traveling Salesman Problem, for example, a subtour violation could be a frozen set containing the cities in the subtour.

    For a concrete example, see TravelingSalesmanInstance.

    Expand source code

        def find_violated_lazy_constraints(self, model):
            """
            Returns lazy constraint violations found for the current solution.

            After solving a model, LearningSolver will ask the instance to identify which
            lazy constraints are violated by the current solution. For each identified
            violation, LearningSolver will then call the build_lazy_constraint, add the
            generated Pyomo constraint to the model, then resolve the problem. The
            process repeats until no further lazy constraint violations are found.

            Each "violation" is simply a string, a tuple or any other hashable type which
            allows the instance to identify unambiguously which lazy constraint should be
            generated. In the Traveling Salesman Problem, for example, a subtour
            violation could be a frozen set containing the cities in the subtour.

            For a concrete example, see TravelingSalesmanInstance.
            """
            return []

  `def find_violated_user_cuts(self, model)`
    Expand source code

        def find_violated_user_cuts(self, model):
            return []

  `def get_constraint_category(self, cid)`
    Expand source code

        def get_constraint_category(self, cid):
            return cid

  `def get_constraint_features(self, cid)`
    Expand source code

        def get_constraint_features(self, cid):
            return np.zeros(1)

  `def get_instance_features(self)`

    Returns a 1-dimensional Numpy array of (numerical) features describing the entire instance.

    The array is used by LearningSolver to determine how similar two instances are. It may also be used to predict, in combination with variable-specific features, the values of binary decision variables in the problem.

    There is not necessarily a one-to-one correspondence between models and instance features: the features may encode only part of the data necessary to generate the complete model. Features may also be statistics computed from the original data. For example, in the knapsack problem, an implementation may decide to provide as instance features only the average weights, average prices, number of items and the size of the knapsack.

    The returned array MUST have the same length for all relevant instances of the problem. If two instances map into arrays of different lengths, they cannot be solved by the same LearningSolver object.

    By default, returns [0].

    Expand source code

        def get_instance_features(self):
            """
            Returns a 1-dimensional Numpy array of (numerical) features describing the
            entire instance.

            The array is used by LearningSolver to determine how similar two instances
            are. It may also be used to predict, in combination with variable-specific
            features, the values of binary decision variables in the problem.

            There is not necessarily a one-to-one correspondence between models and
            instance features: the features may encode only part of the data necessary to
            generate the complete model. Features may also be statistics computed from
            the original data. For example, in the knapsack problem, an implementation
            may decide to provide as instance features only the average weights, average
            prices, number of items and the size of the knapsack.

            The returned array MUST have the same length for all relevant instances of
            the problem. If two instances map into arrays of different lengths,
            they cannot be solved by the same LearningSolver object.

            By default, returns [0].
            """
            return np.zeros(1)

  `def get_variable_category(self, var, index)`

    Returns the category (a string, an integer or any hashable type) for each decision variable.

    If two variables have the same category, LearningSolver will use the same internal ML model to predict the values of both variables. If the returned category is None, ML models will ignore the variable.

    By default, returns "default".

    Expand source code

        def get_variable_category(self, var, index):
            """
            Returns the category (a string, an integer or any hashable type) for each
            decision variable.

            If two variables have the same category, LearningSolver will use the same
            internal ML model to predict the values of both variables. If the returned
            category is None, ML models will ignore the variable.

            By default, returns "default".
            """
            return "default"

  `def get_variable_features(self, var, index)`

    Returns a 1-dimensional array of (numerical) features describing a particular decision variable.

    The argument `var` is a pyomo.core.Var object, which represents a collection of decision variables. The argument `index` specifies which variable in the collection is the relevant one.

    In combination with instance features, variable features are used by LearningSolver to predict, among other things, the optimal value of each decision variable before the optimization takes place. In the knapsack problem, for example, an implementation could provide as variable features the weight and the price of a specific item.

    Like instance features, the arrays returned by this method MUST have the same length for all variables within the same category, for all relevant instances of the problem.

    By default, returns [0].

    Expand source code

        def get_variable_features(self, var, index):
            """
            Returns a 1-dimensional array of (numerical) features describing a particular
            decision variable.

            The argument `var` is a pyomo.core.Var object, which represents a collection
            of decision variables. The argument `index` specifies which variable in the
            collection is the relevant one.

            In combination with instance features, variable features are used by
            LearningSolver to predict, among other things, the optimal value of each
            decision variable before the optimization takes place. In the knapsack
            problem, for example, an implementation could provide as variable features
            the weight and the price of a specific item.

            Like instance features, the arrays returned by this method MUST have the same
            length for all variables within the same category, for all relevant instances
            of the problem.

            By default, returns [0].
            """
            return np.zeros(1)

  `def has_dynamic_lazy_constraints(self)`
    Expand source code

        def has_dynamic_lazy_constraints(self):
            return False

  `def has_static_lazy_constraints(self)`
    Expand source code

        def has_static_lazy_constraints(self):
            return False

  `def is_constraint_lazy(self, cid)`
    Expand source code

        def is_constraint_lazy(self, cid):
            return False

  `def load(self, filename)`
    Expand source code

        def load(self, filename):
            with gzip.GzipFile(filename, "r") as f:
                data = json.loads(f.read().decode("utf-8"))
            self.__dict__ = data

  `def to_model(self)`

    Returns the optimization model corresponding to this instance.

    Expand source code

        @abstractmethod
        def to_model(self) -> Any:
            """
            Returns the optimization model corresponding to this instance.
            """
            pass