-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement lexicographic optimization
- Add a LexicoSolver which makes a given subsolver solve sequentially several specified objectives, while adding a constraint at each step to avoid worsening results according to previous objectives - Add a bunch of methods to implement (with partial default implementation) so that a solver can be used as a subsolver by LexicoSolver: - implements_lexico_api: returns True if the solver is lexico ready (ie implement subsequent methods). LexicoSolver raise a warning if this is not the case - get_model_objectives_available(): list of labels available corresponding to the intern objectives the solver can optimize. Defaults to keys of the problem's ObjectiveRegister (via new method problem.get_objective_names()) - set_model_objective(): update the intern objective the subsolver will optimize - get_model_objective_value(): retrieve the value of the intern objective currently optimized - add_model_constraint(): add a constraint to the intern model on the given objective to avoid worsening it. - Implement them for ortools-csat solvers on knapsack and rcpsp
- Loading branch information
Showing
9 changed files
with
734 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# Copyright (c) 2024 AIRBUS and its affiliates. | ||
# This source code is licensed under the MIT license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
"""Tools for lexicographic optimization.""" | ||
import logging | ||
from typing import Any, Iterable, List, Optional, Protocol | ||
|
||
from discrete_optimization.generic_tools.callbacks.callback import ( | ||
Callback, | ||
CallbackList, | ||
) | ||
from discrete_optimization.generic_tools.do_problem import ( | ||
ObjectiveHandling, | ||
ParamsObjectiveFunction, | ||
Problem, | ||
get_default_objective_setup, | ||
) | ||
from discrete_optimization.generic_tools.do_solver import SolverDO | ||
from discrete_optimization.generic_tools.result_storage.result_storage import ( | ||
ResultStorage, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class LexicoSolver(SolverDO): | ||
|
||
subsolver: SolverDO | ||
|
||
def __init__( | ||
self, | ||
subsolver: Optional[SolverDO], | ||
problem: Problem, | ||
params_objective_function: Optional[ParamsObjectiveFunction] = None, | ||
**kwargs: Any, | ||
): | ||
# ensure no aggregation performed | ||
if params_objective_function is None: | ||
params_objective_function = get_default_objective_setup(problem) | ||
params_objective_function.objective_handling = ObjectiveHandling.MULTI_OBJ | ||
|
||
# SolverDO init after updating params_objective_function | ||
super().__init__( | ||
problem=problem, | ||
params_objective_function=params_objective_function, | ||
**kwargs, | ||
) | ||
|
||
# get subsolver (directly or from its hyperparameters to allow optuna tuning) | ||
kwargs = self.complete_with_default_hyperparameters(kwargs) | ||
if subsolver is None: | ||
if kwargs["subsolver_kwargs"] is None: | ||
subsolver_kwargs = kwargs | ||
else: | ||
subsolver_kwargs = kwargs["subsolver_kwargs"] | ||
if kwargs["subsolver_cls"] is None: | ||
if "build_default_subsolver" in kwargs: | ||
subsolver = kwargs["build_default_subsolver"]( | ||
self.problem, **subsolver_kwargs | ||
) | ||
else: | ||
raise ValueError( | ||
"`subsolver_cls` cannot be None if `subsolver` is not specified." | ||
) | ||
else: | ||
subsolver_cls = kwargs["subsolver_cls"] | ||
subsolver = subsolver_cls(problem=self.problem, **subsolver_kwargs) | ||
subsolver.init_model(**subsolver_kwargs) | ||
self.subsolver = subsolver | ||
|
||
# check compatibility with lexico optimization | ||
if not subsolver.implements_lexico_api(): | ||
logger.warning( | ||
"The chosen subsolver may not be implementing the api needed by LexicoSolver!" | ||
) | ||
|
||
def init_model(self, **kwargs: Any) -> None: | ||
self.subsolver.init_model(**kwargs) | ||
|
||
def solve( | ||
self, | ||
callbacks: Optional[List[Callback]] = None, | ||
objectives: Optional[Iterable[str]] = None, | ||
**kwargs: Any, | ||
) -> ResultStorage: | ||
# wrap all callbacks in a single one | ||
callbacks_list = CallbackList(callbacks=callbacks) | ||
# start of solve callback | ||
callbacks_list.on_solve_start(solver=self) | ||
|
||
if objectives is None: | ||
objectives = self.subsolver.get_model_objectives_available() | ||
|
||
res = ResultStorage( | ||
mode_optim=self.params_objective_function.sense_function, | ||
list_solution_fits=[], | ||
) | ||
|
||
for i_obj, obj in enumerate(objectives): | ||
|
||
# log | ||
logger.debug(f"Optimizing on {obj}") | ||
|
||
# optimize next objective | ||
self.subsolver.set_model_objective(obj) | ||
res.extend(self.subsolver.solve(**kwargs)) | ||
|
||
# end of step callback: stopping? | ||
stopping = callbacks_list.on_step_end(step=i_obj, res=res, solver=self) | ||
if stopping: | ||
break | ||
|
||
# add constraint on current objective for next one | ||
fit = self.subsolver.get_model_objective_value(obj, res) | ||
self.subsolver.add_model_constraint(obj, fit) | ||
|
||
# end of solve callback | ||
callbacks_list.on_solve_end(res=res, solver=self) | ||
|
||
return res |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.