You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.8 KiB
196 lines
5.8 KiB
# 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
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ExtractedConstraint(ABC):
|
|
pass
|
|
|
|
|
|
class InternalSolver(ABC):
|
|
"""
|
|
Abstract class representing the MIP solver used internally by LearningSolver.
|
|
"""
|
|
|
|
@abstractmethod
|
|
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`.
|
|
|
|
Parameters
|
|
----------
|
|
tee: bool
|
|
If true, prints the solver log to the screen.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
A dictionary of solver statistics containing the following keys:
|
|
"Optimal value".
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
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.
|
|
|
|
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):
|
|
"""
|
|
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 clear_warm_start(self):
|
|
"""
|
|
Removes any existing warm start from the solver.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def set_instance(self, instance, model=None):
|
|
"""
|
|
Loads the given instance into the solver.
|
|
|
|
Parameters
|
|
----------
|
|
instance: miplearn.Instance
|
|
The instance to be loaded.
|
|
model:
|
|
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):
|
|
"""
|
|
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
|
|
|
|
@abstractmethod
|
|
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.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def add_constraint(self, constraint):
|
|
"""
|
|
Adds a single constraint to the model.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def solve(self, tee=False, iteration_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: () -> Bool
|
|
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.
|
|
tee: bool
|
|
If true, prints the solver log to the screen.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
A dictionary of solver statistics containing the following keys:
|
|
"Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense",
|
|
"Log" and "Warm start value".
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_constraints_ids(self):
|
|
"""
|
|
Returns a list of ids, which uniquely identify each constraint in the model.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
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)`.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def is_constraint_satisfied(self, cobj):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def set_threads(self, threads):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def set_time_limit(self, time_limit):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def set_node_limit(self, node_limit):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def set_gap_tolerance(self, gap_tolerance):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_variables(self):
|
|
pass
|
|
|
|
def get_empty_solution(self):
|
|
solution = {}
|
|
for (var, indices) in self.get_variables().items():
|
|
solution[var] = {}
|
|
for idx in indices:
|
|
solution[var][idx] = 0.0
|
|
return solution
|
|
|