# Module `miplearn.solvers.internal`

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

    from miplearn.instance import Instance
    from miplearn.types import (
        LPSolveStats,
        IterationCallback,
        LazyCallback,
        MIPSolveStats,
        VarIndex,
        Solution,
        BranchPriorities,
        Constraint,
    )

    logger = logging.getLogger(__name__)

    class InternalSolver(ABC):
        """
        Abstract class representing the MIP solver used internally by LearningSolver.
        """

        @abstractmethod
        def solve_lp(
            self,
            tee: bool = False,
        ) -> LPSolveStats:
            """
            Solves the LP relaxation of the currently loaded instance. After this
            method finishes, the solution can be retrieved by calling `get_solution`.

            This method should not permanently modify the problem. That is, subsequent
            calls to `solve` should solve the original MIP, not the LP relaxation.

            Parameters
            ----------
            tee
                If true, prints the solver log to the screen.
            """
            pass

        @abstractmethod
        def solve(
            self,
            tee: bool = False,
            iteration_cb: IterationCallback = None,
            lazy_cb: LazyCallback = None,
        ) -> MIPSolveStats:
            """
            Solves the currently loaded instance. After this method finishes,
            the best solution found can be retrieved by calling `get_solution`.

            Parameters
            ----------
            iteration_cb: IterationCallback
                By default, InternalSolver makes a single call to the native `solve`
                method and returns the result. If an iteration callback is provided
                instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
                are called alternatively. To stop the loop, `iteration_cb` should return
                False. Any other result causes the solver to loop again.
            lazy_cb: LazyCallback
                This function is called whenever the solver finds a new candidate
                solution and can be used to add lazy constraints to the model. Only the
                following operations within the callback are allowed:
                    - Querying the value of a variable
                    - Querying if a constraint is satisfied
                    - Adding a new constraint to the problem
                Additional operations may be allowed by specific subclasses.
            tee: bool
                If true, prints the solver log to the screen.
            """
            pass

        @abstractmethod
        def get_solution(self) -> Optional[Solution]:
            """
            Returns current solution found by the solver.

            If called after `solve`, returns the best primal solution found during
            the search. If called after `solve_lp`, returns the optimal solution
            to the LP relaxation. If no primal solution is available, return None.

            The solution is a dictionary `sol`, where the optimal value of `var[idx]`
            is given by `sol[var][idx]`.
            """
            pass

        @abstractmethod
        def set_warm_start(self, solution: Solution) -> None:
            """
            Sets the warm start to be used by the solver.

            The solution should be a dictionary following the same format as the
            one produced by `get_solution`. Only one warm start is supported.
            Calling this function when a warm start already exists will
            remove the previous warm start.
            """
            pass

        @abstractmethod
        def set_instance(
            self,
            instance: Instance,
            model: Any = None,
        ) -> None:
            """
            Loads the given instance into the solver.

            Parameters
            ----------
            instance: Instance
                The instance to be loaded.
            model: Any
                The concrete optimization model corresponding to this instance
                (e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided,
                it will be generated by calling `instance.to_model()`.
            """
            pass

        @abstractmethod
        def fix(self, solution: Solution) -> None:
            """
            Fixes the values of a subset of decision variables.

            The values should be provided in the dictionary format generated by
            `get_solution`. Missing values in the solution indicate variables
            that should be left free.
            """
            pass

        def set_branching_priorities(self, priorities: BranchPriorities) -> None:
            """
            Sets the branching priorities for the given decision variables.

            When the MIP solver needs to decide on which variable to branch, variables
            with higher priority are picked first, given that they are fractional.
            Ties are solved arbitrarily. By default, all variables have priority zero.

            The priorities should be provided in the dictionary format generated by
            `get_solution`. Missing values indicate variables whose priorities
            should not be modified.
            """
            raise Exception("Not implemented")

        @abstractmethod
        def get_constraint_ids(self) -> List[str]:
            """
            Returns a list of ids which uniquely identify each constraint in the model.
            """
            pass

        @abstractmethod
        def add_constraint(self, cobj: Constraint) -> None:
            """
            Adds a single constraint to the model.
            """
            pass

        @abstractmethod
        def extract_constraint(self, cid: str) -> Constraint:
            """
            Removes a given constraint from the model and returns an object `cobj` which
            can be used to verify if the removed constraint is still satisfied by
            the current solution, using `is_constraint_satisfied(cobj)`, and can potentially
            be re-added to the model using `add_constraint(cobj)`.
            """
            pass

        @abstractmethod
        def is_constraint_satisfied(self, cobj: Constraint) -> bool:
            """
            Returns True if the current solution satisfies the given constraint.
            """
            pass

        @abstractmethod
        def set_constraint_sense(self, cid: str, sense: str) -> None:
            """
            Modifies the sense of a given constraint.

            Parameters
            ----------
            cid: str
                The name of the constraint.
            sense: str
                The new sense (either "<", ">" or "=").
            """
            pass

        @abstractmethod
        def get_constraint_sense(self, cid: str) -> str:
            """
            Returns the sense of a given constraint (either "<", ">" or "=").

            Parameters
            ----------
            cid: str
                The name of the constraint.
            """
            pass

        @abstractmethod
        def get_value(self, var_name: str, index: VarIndex) -> Optional[float]:
            """
            Returns the value of a given variable in the current solution. If no
            solution is available, returns None.
            """
            pass

        @abstractmethod
        def relax(self) -> None:
            """
            Drops all integrality constraints from the model.
            """
            pass

        @abstractmethod
        def get_inequality_slacks(self) -> Dict[str, float]:
            """
            Returns a dictionary mapping constraint name to the constraint slack
            in the current solution.
            """
            pass

        @abstractmethod
        def is_infeasible(self) -> bool:
            """
            Returns True if the model has been proved to be infeasible.
            Must be called after solve.
            """
            pass

        @abstractmethod
        def get_dual(self, cid: str) -> float:
            """
            If the model is feasible and has been solved to optimality, returns the
            optimal value of the dual variable associated with this constraint. If the
            model is infeasible, returns a portion of the infeasibility certificate
            corresponding to the given constraint.

            Only available for relaxed problems. Must be called after solve.
            """
            pass

        @abstractmethod
        def get_sense(self) -> str:
            """
            Returns the sense of the problem (either "min" or "max").
            """
            pass

        @abstractmethod
        def get_empty_solution(self) -> Dict:
            """
            Returns a dictionary with the same shape as the one produced by
            `get_solution`, but with all values set to None. This method is
            used by the ML components to query what variables are there in
            the model before a solution is available.
            """
            pass

## Classes

`class InternalSolver`

  Abstract class representing the MIP solver used internally by LearningSolver.

  Expand source code

      class InternalSolver(ABC):
          """
          Abstract class representing the MIP solver used internally by LearningSolver.
          """

          @abstractmethod
          def solve_lp(
              self,
              tee: bool = False,
          ) -> LPSolveStats:
              """
              Solves the LP relaxation of the currently loaded instance. After this
              method finishes, the solution can be retrieved by calling `get_solution`.

              This method should not permanently modify the problem. That is, subsequent
              calls to `solve` should solve the original MIP, not the LP relaxation.

              Parameters
              ----------
              tee
                  If true, prints the solver log to the screen.
              """
              pass

          @abstractmethod
          def solve(
              self,
              tee: bool = False,
              iteration_cb: IterationCallback = None,
              lazy_cb: LazyCallback = None,
          ) -> MIPSolveStats:
              """
              Solves the currently loaded instance. After this method finishes,
              the best solution found can be retrieved by calling `get_solution`.

              Parameters
              ----------
              iteration_cb: IterationCallback
                  By default, InternalSolver makes a single call to the native `solve`
                  method and returns the result. If an iteration callback is provided
                  instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
                  are called alternatively. To stop the loop, `iteration_cb` should return
                  False. Any other result causes the solver to loop again.
              lazy_cb: LazyCallback
                  This function is called whenever the solver finds a new candidate
                  solution and can be used to add lazy constraints to the model. Only the
                  following operations within the callback are allowed:
                      - Querying the value of a variable
                      - Querying if a constraint is satisfied
                      - Adding a new constraint to the problem
                  Additional operations may be allowed by specific subclasses.
              tee: bool
                  If true, prints the solver log to the screen.
              """
              pass

          @abstractmethod
          def get_solution(self) -> Optional[Solution]:
              """
              Returns current solution found by the solver.

              If called after `solve`, returns the best primal solution found during
              the search. If called after `solve_lp`, returns the optimal solution
              to the LP relaxation. If no primal solution is available, return None.

              The solution is a dictionary `sol`, where the optimal value of `var[idx]`
              is given by `sol[var][idx]`.
              """
              pass

          @abstractmethod
          def set_warm_start(self, solution: Solution) -> None:
              """
              Sets the warm start to be used by the solver.

              The solution should be a dictionary following the same format as the
              one produced by `get_solution`. Only one warm start is supported.
              Calling this function when a warm start already exists will
              remove the previous warm start.
              """
              pass

          @abstractmethod
          def set_instance(
              self,
              instance: Instance,
              model: Any = None,
          ) -> None:
              """
              Loads the given instance into the solver.

              Parameters
              ----------
              instance: Instance
                  The instance to be loaded.
              model: Any
                  The concrete optimization model corresponding to this instance
                  (e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided,
                  it will be generated by calling `instance.to_model()`.
              """
              pass

          @abstractmethod
          def fix(self, solution: Solution) -> None:
              """
              Fixes the values of a subset of decision variables.

              The values should be provided in the dictionary format generated by
              `get_solution`. Missing values in the solution indicate variables
              that should be left free.
              """
              pass

          def set_branching_priorities(self, priorities: BranchPriorities) -> None:
              """
              Sets the branching priorities for the given decision variables.

              When the MIP solver needs to decide on which variable to branch, variables
              with higher priority are picked first, given that they are fractional.
              Ties are solved arbitrarily. By default, all variables have priority zero.

              The priorities should be provided in the dictionary format generated by
              `get_solution`. Missing values indicate variables whose priorities
              should not be modified.
              """
              raise Exception("Not implemented")

          @abstractmethod
          def get_constraint_ids(self) -> List[str]:
              """
              Returns a list of ids which uniquely identify each constraint in the model.
              """
              pass

          @abstractmethod
          def add_constraint(self, cobj: Constraint) -> None:
              """
              Adds a single constraint to the model.
              """
              pass

          @abstractmethod
          def extract_constraint(self, cid: str) -> Constraint:
              """
              Removes a given constraint from the model and returns an object `cobj` which
              can be used to verify if the removed constraint is still satisfied by
              the current solution, using `is_constraint_satisfied(cobj)`, and can potentially
              be re-added to the model using `add_constraint(cobj)`.
              """
              pass

          @abstractmethod
          def is_constraint_satisfied(self, cobj: Constraint) -> bool:
              """
              Returns True if the current solution satisfies the given constraint.
              """
              pass

          @abstractmethod
          def set_constraint_sense(self, cid: str, sense: str) -> None:
              """
              Modifies the sense of a given constraint.

              Parameters
              ----------
              cid: str
                  The name of the constraint.
              sense: str
                  The new sense (either "<", ">" or "=").
              """
              pass

          @abstractmethod
          def get_constraint_sense(self, cid: str) -> str:
              """
              Returns the sense of a given constraint (either "<", ">" or "=").

              Parameters
              ----------
              cid: str
                  The name of the constraint.
              """
              pass

          @abstractmethod
          def get_value(self, var_name: str, index: VarIndex) -> Optional[float]:
              """
              Returns the value of a given variable in the current solution. If no
              solution is available, returns None.
              """
              pass

          @abstractmethod
          def relax(self) -> None:
              """
              Drops all integrality constraints from the model.
              """
              pass

          @abstractmethod
          def get_inequality_slacks(self) -> Dict[str, float]:
              """
              Returns a dictionary mapping constraint name to the constraint slack
              in the current solution.
              """
              pass

          @abstractmethod
          def is_infeasible(self) -> bool:
              """
              Returns True if the model has been proved to be infeasible.
              Must be called after solve.
              """
              pass

          @abstractmethod
          def get_dual(self, cid: str) -> float:
              """
              If the model is feasible and has been solved to optimality, returns the
              optimal value of the dual variable associated with this constraint. If the
              model is infeasible, returns a portion of the infeasibility certificate
              corresponding to the given constraint.

              Only available for relaxed problems. Must be called after solve.
              """
              pass

          @abstractmethod
          def get_sense(self) -> str:
              """
              Returns the sense of the problem (either "min" or "max").
              """
              pass

          @abstractmethod
          def get_empty_solution(self) -> Dict:
              """
              Returns a dictionary with the same shape as the one produced by
              `get_solution`, but with all values set to None. This method is
              used by the ML components to query what variables are there in
              the model before a solution is available.
              """
              pass

  ### Ancestors

  - abc.ABC

  ### Subclasses

  - GurobiSolver
  - BasePyomoSolver

  ### Methods

  `def add_constraint(self, cobj)`

    Adds a single constraint to the model.

    Expand source code

        @abstractmethod
        def add_constraint(self, cobj: Constraint) -> None:
            """
            Adds a single constraint to the model.
            """
            pass

  `def extract_constraint(self, cid)`

    Removes a given constraint from the model and returns an object `cobj` which can be used to verify if the removed constraint is still satisfied by the current solution, using `is_constraint_satisfied(cobj)`, and can potentially be re-added to the model using `add_constraint(cobj)`.

    Expand source code

        @abstractmethod
        def extract_constraint(self, cid: str) -> Constraint:
            """
            Removes a given constraint from the model and returns an object `cobj` which
            can be used to verify if the removed constraint is still satisfied by
            the current solution, using `is_constraint_satisfied(cobj)`, and can potentially
            be re-added to the model using `add_constraint(cobj)`.
            """
            pass

  `def fix(self, solution)`

    Fixes the values of a subset of decision variables.

    The values should be provided in the dictionary format generated by `get_solution`. Missing values in the solution indicate variables that should be left free.

    Expand source code

        @abstractmethod
        def fix(self, solution: Solution) -> None:
            """
            Fixes the values of a subset of decision variables.

            The values should be provided in the dictionary format generated by
            `get_solution`. Missing values in the solution indicate variables
            that should be left free.
            """
            pass

  `def get_constraint_ids(self)`

    Returns a list of ids which uniquely identify each constraint in the model.

    Expand source code

        @abstractmethod
        def get_constraint_ids(self) -> List[str]:
            """
            Returns a list of ids which uniquely identify each constraint in the model.
            """
            pass

  `def get_constraint_sense(self, cid)`

    Returns the sense of a given constraint (either "<", ">" or "=").

    ## Parameters

    `cid` :`str`
      The name of the constraint.
    Expand source code

        @abstractmethod
        def get_constraint_sense(self, cid: str) -> str:
            """
            Returns the sense of a given constraint (either "<", ">" or "=").

            Parameters
            ----------
            cid: str
                The name of the constraint.
            """
            pass

  `def get_dual(self, cid)`

    If the model is feasible and has been solved to optimality, returns the optimal value of the dual variable associated with this constraint. If the model is infeasible, returns a portion of the infeasibility certificate corresponding to the given constraint.

    Only available for relaxed problems. Must be called after solve.

    Expand source code

        @abstractmethod
        def get_dual(self, cid: str) -> float:
            """
            If the model is feasible and has been solved to optimality, returns the
            optimal value of the dual variable associated with this constraint. If the
            model is infeasible, returns a portion of the infeasibility certificate
            corresponding to the given constraint.

            Only available for relaxed problems. Must be called after solve.
            """
            pass

  `def get_empty_solution(self)`

    Returns a dictionary with the same shape as the one produced by `get_solution`, but with all values set to None. This method is used by the ML components to query what variables are there in the model before a solution is available.

    Expand source code

        @abstractmethod
        def get_empty_solution(self) -> Dict:
            """
            Returns a dictionary with the same shape as the one produced by
            `get_solution`, but with all values set to None. This method is
            used by the ML components to query what variables are there in
            the model before a solution is available.
            """
            pass

  `def get_inequality_slacks(self)`

    Returns a dictionary mapping constraint name to the constraint slack in the current solution.

    Expand source code

        @abstractmethod
        def get_inequality_slacks(self) -> Dict[str, float]:
            """
            Returns a dictionary mapping constraint name to the constraint slack
            in the current solution.
            """
            pass

  `def get_sense(self)`

    Returns the sense of the problem (either "min" or "max").

    Expand source code

        @abstractmethod
        def get_sense(self) -> str:
            """
            Returns the sense of the problem (either "min" or "max").
            """
            pass

  `def get_solution(self)`

    Returns current solution found by the solver.

    If called after `solve`, returns the best primal solution found during the search. If called after `solve_lp`, returns the optimal solution to the LP relaxation. If no primal solution is available, return None.

    The solution is a dictionary `sol`, where the optimal value of `var[idx]` is given by `sol[var][idx]`.

    Expand source code

        @abstractmethod
        def get_solution(self) -> Optional[Solution]:
            """
            Returns current solution found by the solver.

            If called after `solve`, returns the best primal solution found during
            the search. If called after `solve_lp`, returns the optimal solution
            to the LP relaxation. If no primal solution is available, return None.

            The solution is a dictionary `sol`, where the optimal value of `var[idx]`
            is given by `sol[var][idx]`.
            """
            pass

  `def get_value(self, var_name, index)`

    Returns the value of a given variable in the current solution. If no solution is available, returns None.

    Expand source code

        @abstractmethod
        def get_value(self, var_name: str, index: VarIndex) -> Optional[float]:
            """
            Returns the value of a given variable in the current solution. If no
            solution is available, returns None.
            """
            pass

  `def is_constraint_satisfied(self, cobj)`

    Returns True if the current solution satisfies the given constraint.

    Expand source code

        @abstractmethod
        def is_constraint_satisfied(self, cobj: Constraint) -> bool:
            """
            Returns True if the current solution satisfies the given constraint.
            """
            pass

  `def is_infeasible(self)`

    Returns True if the model has been proved to be infeasible. Must be called after solve.

    Expand source code

        @abstractmethod
        def is_infeasible(self) -> bool:
            """
            Returns True if the model has been proved to be infeasible.
            Must be called after solve.
            """
            pass

  `def relax(self)`

    Drops all integrality constraints from the model.

    Expand source code

        @abstractmethod
        def relax(self) -> None:
            """
            Drops all integrality constraints from the model.
            """
            pass

  `def set_branching_priorities(self, priorities)`

    Sets the branching priorities for the given decision variables.

    When the MIP solver needs to decide on which variable to branch, variables with higher priority are picked first, given that they are fractional. Ties are solved arbitrarily. By default, all variables have priority zero.

    The priorities should be provided in the dictionary format generated by `get_solution`. Missing values indicate variables whose priorities should not be modified.

    Expand source code

        def set_branching_priorities(self, priorities: BranchPriorities) -> None:
            """
            Sets the branching priorities for the given decision variables.

            When the MIP solver needs to decide on which variable to branch, variables
            with higher priority are picked first, given that they are fractional.
            Ties are solved arbitrarily. By default, all variables have priority zero.

            The priorities should be provided in the dictionary format generated by
            `get_solution`. Missing values indicate variables whose priorities
            should not be modified.
            """
            raise Exception("Not implemented")

  `def set_constraint_sense(self, cid, sense)`

    Modifies the sense of a given constraint.

    ## Parameters

    `cid` :`str`
      The name of the constraint.
    `sense` :`str`
      The new sense (either "<", ">" or "=").
    Expand source code

        @abstractmethod
        def set_constraint_sense(self, cid: str, sense: str) -> None:
            """
            Modifies the sense of a given constraint.

            Parameters
            ----------
            cid: str
                The name of the constraint.
            sense: str
                The new sense (either "<", ">" or "=").
            """
            pass

  `def set_instance(self, instance, model=None)`

    Loads the given instance into the solver.

    ## Parameters

    `instance` :`Instance`
      The instance to be loaded.
    `model` :`Any`
      The concrete optimization model corresponding to this instance (e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided, it will be generated by calling `instance.to_model()`.
    Expand source code

        @abstractmethod
        def set_instance(
            self,
            instance: Instance,
            model: Any = None,
        ) -> None:
            """
            Loads the given instance into the solver.

            Parameters
            ----------
            instance: Instance
                The instance to be loaded.
            model: Any
                The concrete optimization model corresponding to this instance
                (e.g. JuMP.Model or pyomo.core.ConcreteModel). If not provided,
                it will be generated by calling `instance.to_model()`.
            """
            pass

  `def set_warm_start(self, solution)`

    Sets the warm start to be used by the solver.

    The solution should be a dictionary following the same format as the one produced by `get_solution`. Only one warm start is supported. Calling this function when a warm start already exists will remove the previous warm start.

    Expand source code

        @abstractmethod
        def set_warm_start(self, solution: Solution) -> None:
            """
            Sets the warm start to be used by the solver.

            The solution should be a dictionary following the same format as the
            one produced by `get_solution`. Only one warm start is supported.
            Calling this function when a warm start already exists will
            remove the previous warm start.
            """
            pass

  `def solve(self, tee=False, iteration_cb=None, lazy_cb=None)`

    Solves the currently loaded instance. After this method finishes, the best solution found can be retrieved by calling `get_solution`.

    ## Parameters

    `iteration_cb` :`IterationCallback`
      By default, InternalSolver makes a single call to the native `solve` method and returns the result. If an iteration callback is provided instead, InternalSolver enters a loop, where `solve` and `iteration_cb` are called alternatively. To stop the loop, `iteration_cb` should return False. Any other result causes the solver to loop again.
    `lazy_cb` :`LazyCallback`
      This function is called whenever the solver finds a new candidate solution and can be used to add lazy constraints to the model. Only the following operations within the callback are allowed: - Querying the value of a variable - Querying if a constraint is satisfied - Adding a new constraint to the problem Additional operations may be allowed by specific subclasses.
    `tee` :`bool`
      If true, prints the solver log to the screen.
    Expand source code

        @abstractmethod
        def solve(
            self,
            tee: bool = False,
            iteration_cb: IterationCallback = None,
            lazy_cb: LazyCallback = None,
        ) -> MIPSolveStats:
            """
            Solves the currently loaded instance. After this method finishes,
            the best solution found can be retrieved by calling `get_solution`.

            Parameters
            ----------
            iteration_cb: IterationCallback
                By default, InternalSolver makes a single call to the native `solve`
                method and returns the result. If an iteration callback is provided
                instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
                are called alternatively. To stop the loop, `iteration_cb` should return
                False. Any other result causes the solver to loop again.
            lazy_cb: LazyCallback
                This function is called whenever the solver finds a new candidate
                solution and can be used to add lazy constraints to the model. Only the
                following operations within the callback are allowed:
                    - Querying the value of a variable
                    - Querying if a constraint is satisfied
                    - Adding a new constraint to the problem
                Additional operations may be allowed by specific subclasses.
            tee: bool
                If true, prints the solver log to the screen.
            """
            pass

  `def solve_lp(self, tee=False)`

    Solves the LP relaxation of the currently loaded instance. After this method finishes, the solution can be retrieved by calling `get_solution`.

    This method should not permanently modify the problem. That is, subsequent calls to `solve` should solve the original MIP, not the LP relaxation.

    ## Parameters

    `tee`
      If true, prints the solver log to the screen.
    Expand source code

        @abstractmethod
        def solve_lp(
            self,
            tee: bool = False,
        ) -> LPSolveStats:
            """
            Solves the LP relaxation of the currently loaded instance. After this
            method finishes, the solution can be retrieved by calling `get_solution`.

            This method should not permanently modify the problem. That is, subsequent
            calls to `solve` should solve the original MIP, not the LP relaxation.

            Parameters
            ----------
            tee
                If true, prints the solver log to the screen.
            """
            pass