diff --git a/miplearn/solvers/gurobi.py b/miplearn/solvers/gurobi.py index 5f203c9..2633c9b 100644 --- a/miplearn/solvers/gurobi.py +++ b/miplearn/solvers/gurobi.py @@ -315,22 +315,6 @@ class GurobiSolver(InternalSolver): self._raise_if_callback() logger.warning("set_branching_priorities not implemented") - def set_threads(self, threads): - self._raise_if_callback() - self.params["Threads"] = threads - - def set_time_limit(self, time_limit): - self._raise_if_callback() - self.params["TimeLimit"] = time_limit - - def set_node_limit(self, node_limit): - self._raise_if_callback() - self.params["NodeLimit"] = node_limit - - def set_gap_tolerance(self, gap_tolerance): - self._raise_if_callback() - self.params["MIPGap"] = gap_tolerance - def _extract_warm_start_value(self, log): ws = self.__extract(log, "MIP start with objective ([0-9.e+-]*)") if ws is not None: diff --git a/miplearn/solvers/internal.py b/miplearn/solvers/internal.py index 4d8233b..1be81dc 100644 --- a/miplearn/solvers/internal.py +++ b/miplearn/solvers/internal.py @@ -36,6 +36,40 @@ class InternalSolver(ABC): """ pass + @abstractmethod + 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: () -> 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. + lazy_cb: (internal_solver, model) -> None + 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, through `get_value(var, idx)` + - Querying if a constraint is satisfied, through `is_constraint_satisfied(cobj)` + - Adding a new constraint to the problem, through `add_constraint` + Additional operations may be allowed by specific subclasses. + 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_solution(self): """ @@ -43,7 +77,7 @@ class InternalSolver(ABC): 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. + 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]`. @@ -62,13 +96,6 @@ class InternalSolver(ABC): """ pass - @abstractmethod - def clear_warm_start(self): - """ - Removes any existing warm start from the solver. - """ - pass - @abstractmethod def set_instance(self, instance, model=None): """ @@ -76,7 +103,7 @@ class InternalSolver(ABC): Parameters ---------- - instance: miplearn.Instance + instance: Instance The instance to be loaded. model: The concrete optimization model corresponding to this instance @@ -118,40 +145,6 @@ class InternalSolver(ABC): """ pass - @abstractmethod - 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: () -> 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. - lazy_cb: (internal_solver, model) -> None - 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, through `get_value(var, idx)` - - Querying if a constraint is satisfied, through `is_constraint_satisfied(cobj)` - - Adding a new constraint to the problem, through `add_constraint` - Additional operations may be allowed by specific subclasses. - 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_value(self, var_name, index): """ @@ -206,7 +199,7 @@ class InternalSolver(ABC): 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. - Solve must be called prior to this method. + Must be called after solve. """ pass @@ -219,6 +212,7 @@ class InternalSolver(ABC): @abstractmethod def is_constraint_satisfied(self, cobj): + """Returns True if the current solution satisfies the given constraint.""" pass @abstractmethod @@ -233,22 +227,6 @@ class InternalSolver(ABC): def set_constraint_rhs(self, cid, rhs): 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 diff --git a/miplearn/solvers/pyomo/base.py b/miplearn/solvers/pyomo/base.py index f8d2745..d236039 100644 --- a/miplearn/solvers/pyomo/base.py +++ b/miplearn/solvers/pyomo/base.py @@ -204,22 +204,6 @@ class BasePyomoSolver(InternalSolver): def _extract_node_count(self, log): return int(self.__extract(log, self._get_node_count_regexp(), default=1)) - def set_threads(self, threads): - key = self._get_threads_option_name() - self._pyomo_solver.options[key] = threads - - def set_time_limit(self, time_limit): - key = self._get_time_limit_option_name() - self._pyomo_solver.options[key] = time_limit - - def set_node_limit(self, node_limit): - key = self._get_node_limit_option_name() - self._pyomo_solver.options[key] = node_limit - - def set_gap_tolerance(self, gap_tolerance): - key = self._get_gap_tolerance_option_name() - self._pyomo_solver.options[key] = gap_tolerance - def get_constraint_ids(self): return list(self._cname_to_constr.keys()) @@ -231,22 +215,6 @@ class BasePyomoSolver(InternalSolver): def _get_node_count_regexp(self): pass - @abstractmethod - def _get_threads_option_name(self): - pass - - @abstractmethod - def _get_time_limit_option_name(self): - pass - - @abstractmethod - def _get_node_limit_option_name(self): - pass - - @abstractmethod - def _get_gap_tolerance_option_name(self): - pass - def extract_constraint(self, cid): raise Exception("Not implemented") diff --git a/miplearn/solvers/pyomo/cplex.py b/miplearn/solvers/pyomo/cplex.py index a6e2831..a1afc48 100644 --- a/miplearn/solvers/pyomo/cplex.py +++ b/miplearn/solvers/pyomo/cplex.py @@ -33,17 +33,5 @@ class CplexPyomoSolver(BasePyomoSolver): def _get_node_count_regexp(self): return "^[ *] *([0-9]+)" - def _get_threads_option_name(self): - return "threads" - - def _get_time_limit_option_name(self): - return "timelimit" - - def _get_node_limit_option_name(self): - return "mip_limits_nodes" - - def _get_gap_tolerance_option_name(self): - return "mip_tolerances_mipgap" - def set_branching_priorities(self, priorities): raise NotImplementedError diff --git a/miplearn/solvers/pyomo/gurobi.py b/miplearn/solvers/pyomo/gurobi.py index ac73bf9..e059a14 100644 --- a/miplearn/solvers/pyomo/gurobi.py +++ b/miplearn/solvers/pyomo/gurobi.py @@ -41,18 +41,6 @@ class GurobiPyomoSolver(BasePyomoSolver): def _get_node_count_regexp(self): return None - def _get_threads_option_name(self): - return "Threads" - - def _get_time_limit_option_name(self): - return "TimeLimit" - - def _get_node_limit_option_name(self): - return "NodeLimit" - - def _get_gap_tolerance_option_name(self): - return "MIPGap" - def set_branching_priorities(self, priorities): from gurobipy import GRB