Solvers¶
While DESDEO is a framework for interactive multiobjective optimization, it does not implement any optimizers per se. Instead, DESDEO provides interfaces to many existing solvers. How these interfaces work in general is explained in the section Solver interfaces, while the currently implemented solvers in DESDEO are introduced and a couple of examples on how to use the solvers are given in the section Solver examples.
Solver interfaces¶
The solver interfaces rely internally heavily on the evaluators discussed
in the section Parsing and evaluating. It is
the evaluators that make sure the Problem being solved is in a format that
can be evaluated by a solver. The solver interfaces discussed here are
in charge of making sure that when an outside solver evaluates a problem, the information
from the solver is passed to the evaluator in a correct format, and that the
output of the solver is then processed in a way that it can be utilized elsewhere
in DESDEO. The interfaces also pass information from DESDEO to solvers
in a compatible format. To put it simply, the solver interfaces are translators between the
evaluators in DESDEO and the solvers found outside of DESDEO.
Solver examples¶
How solvers can be utilized in practice is best illustrated with examples. We provide here a few examples on how to utilize different solvers through the solver interfaces available in DESDEO. We will use the Binh and Korn test problem throughout:
from desdeo.problem.testproblems import binh_and_korn
problem = binh_and_korn()
Scipy solvers¶
The Scipy solvers in DESDEO, found in the module
Scipy solver interfaces,
can be used to interface with
the optimization routines found in the Scipy library. There are interfaces to
the solvers
minimize
and differential_evolution.
Scipy solvers do not support TensorVariables.
First, to illustrate the usage of the minimize interface, and the interfaces in general, consider the following
example:
from desdeo.tools import ScipyMinimizeSolver
solver = ScipyMinimizeSolver(problem)
results = solver.solve("f_1")
results
SolverResults(optimal_variables={'x_1': 1.1510792319313623e-12, 'x_2': 7.140954494389007e-13}, optimal_objectives={'f_1': 7.339662836372162e-24, 'f_2': 49.99999999998135}, constraint_values={'g_1': -1.1510792319313623e-11, 'g_2': -65.29999999998587}, extra_func_values=None, scalarization_values=None, lagrange_multipliers=None, success=True, message='Optimization terminated successfully')
In the above example, we consider the Binh and Korn Problem with two objectives.
We then create a solver by calling
ScipyMinimizeSolver
and supplying it the problem we want to solve. This makes all the necessary setups for the solver and then
returns an instance of the ScipyMinimizeSolver class, which we have stored in solver. To run the solver, we call the solve function
with the symbol of the function defined in the problem we wish to minimize. Then, the solver returns a pydantic
dataclass of type SolverResults with the results of the optimization. It
is important to know that whichever function we request the solver to optimize, will be minimized. Therefore,
in the example, if f_1 was to be maximized instead, we should have called solve with the argument f_1_min.
The results contained in SolverResults will then correspond to the original maximized function f_1. It is
the jobs of the evaluators to make sure that f_1_min is available. Likewise, if we have
Scalarized the problem, we can give the solver the symbol of the added
scalarization function.
Likewise for ScipyDeSolver utilizing
differential_evolution:
from desdeo.tools import ScipyDeSolver
solver = ScipyDeSolver(problem)
results = solver.solve("f_1")
results
SolverResults(optimal_variables={'x_1': 0.0, 'x_2': 0.0}, optimal_objectives={'f_1': 0.0, 'f_2': 50.0}, constraint_values={'g_1': 0.0, 'g_2': -65.3}, extra_func_values=None, scalarization_values=None, lagrange_multipliers=None, success=True, message='Optimization terminated successfully.')
Note
Whichever function we request a solver to optimize, it will be minimized.
Proximal solver¶
The ProximalSolver
is useful when a Problem has been defined such that all of
its objective functions have been defined with a
DiscreteRepresentation. The
proximal solver takes a symbol to optimize, and will return the decision
variable values that correspond to the lowest value found for the symbol in the
data. It works identically to the scipy solvers in the previous example:
from desdeo.problem.testproblems import river_pollution_problem_discrete
from desdeo.tools import ProximalSolver
discrete_problem = river_pollution_problem_discrete()
solver = ProximalSolver(discrete_problem)
results = solver.solve("f1")
results
SolverResults(optimal_variables={'x_1': 0.3, 'x_2': 0.8583012325357192}, optimal_objectives={'f1': 4.751, 'f2': 3.0930520840181153, 'f3': 7.500000000000001, 'f4': -1.757091398096227, 'f5': 0.35000000000000003}, constraint_values=None, extra_func_values=None, scalarization_values=None, lagrange_multipliers=None, success=True, message="Optimal value found from tabular data minimizing the column 'f1'.")
Pyomo solvers¶
The Pyomo solvers in DESDEO, found in the module
Pyomo solver interfaces,
use Pyomo
as a backend to interface with external optimization solvers. The
PyomoEvaluator transforms
a Problem into a Pyomo ConcreteModel, which is then passed to the chosen
external solver. Unlike the Scipy solvers, the Pyomo solvers support
TensorVariables.
There are four Pyomo-based solver interfaces:
PyomoIpoptSolver: uses the IPOPT interior-point optimizer. Suitable for nonlinear, twice continuously differentiable problems (convex or non-convex).PyomoBonminSolver: uses the Bonmin solver. Suitable for mixed-integer nonlinear problems requiring twice continuous differentiability.PyomoCBCSolver: uses the CBC branch-and-cut solver. Suitable for linear and mixed-integer linear problems.PyomoGurobiSolver: uses Gurobi via the Pyomo interface. Suitable for mixed-integer linear and quadratic problems.
The usage follows the same pattern as the Scipy solvers:
from desdeo.tools import PyomoIpoptSolver
solver = PyomoIpoptSolver(problem)
results = solver.solve("f_1")
results
SolverResults(optimal_variables={'x_1': 2.6242912620049366e-05, 'x_2': 1.7251689761471255e-05}, optimal_objectives={'f_1': 3.945245049638394e-09, 'f_2': 49.9995650549625}, constraint_values={'g_1': -0.00026242813988798855, 'g_2': -65.29968362452297}, extra_func_values=None, scalarization_values=None, lagrange_multipliers={'mu_g_1': -1.2372885782595755e-05, 'mu_g_2': -1.3912637951331793e-11}, success=True, message="Pyomo solver status is: 'ok', with termination condition: 'optimal'.")
Note
The Pyomo solvers require the corresponding external solver binaries
(e.g., ipopt, bonmin, cbc, or gurobi) to be
installed and available on the system PATH.
Gurobipy solver¶
The GurobipySolver
is suitable for solving mixed-integer
linear and quadratic optimization problems. GurobipySolver also does not support non-differentiable problems, for
example, problems with some max terms, like many (non-differentiable) functions.
Like the other solvers, the gurobipy solver takes the symbol of
the objective function minimized (or maximized, with the above mentioned added _min) and returns the results of
the optimization:
from desdeo.problem.testproblems import simple_linear_test_problem
from desdeo.tools import GurobipySolver
linear_problem = simple_linear_test_problem()
solver = GurobipySolver(linear_problem)
results = solver.solve("f_1")
results
Restricted license - for non-production use only - expires 2027-11-29
SolverResults(optimal_variables={'x_1': 4.199999999999999, 'x_2': 2.0999999999999996}, optimal_objectives={'f_1': 6.299999999999999}, constraint_values={'g_1': -0.0, 'g_2': -0.0}, extra_func_values=None, scalarization_values=None, lagrange_multipliers={}, success=True, message="Gurobipy solver status is: 'Optimal solution found.'")
Nevergrad solver¶
The NevergradGenericSolver
provides an interface to the gradient-free optimization platform
Nevergrad (Facebook Research).
It is useful when the problem is expensive to evaluate, non-differentiable, or
otherwise unsuitable for gradient-based methods. Internally, the solver uses
the SympyEvaluator,
and therefore only supports scalar variables (no TensorVariables).
The solver supports multiple optimizer choices, including NGOpt (default), CMA,
PSO, TwoPointsDE, and others. It also supports parallel function evaluations
via a thread pool by setting the num_workers option.
import warnings
from desdeo.tools import NevergradGenericSolver
solver = NevergradGenericSolver(problem)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
results = solver.solve("f_1")
results
SolverResults(optimal_variables={'x_1': 1.4142025106522738e-07, 'x_2': 3.0402165540621695e-07}, optimal_objectives={'f_1': 4.4971541746915384e-13, 'f_2': 49.999995545581044}, constraint_values={'g_1': -1.4142024014063281e-06, 'g_2': -65.29999956140603}, extra_func_values=None, scalarization_values=None, lagrange_multipliers=None, success=True, message='Recommendation found by NGOpt.')
Summary of Solvers¶
The table below summarizes the solvers currently available in DESDEO.
| Solver | Library | Problem type | Differentiability | Variables | License |
|---|---|---|---|---|---|
ScipyMinimizeSolver |
SciPy | Nonlinear | Gradient-based | Scalar | Open-source |
ScipyDeSolver |
SciPy | Nonlinear / black-box | Gradient-free | Scalar | Open-source |
ProximalSolver |
— | Discrete | Not required | Scalar | Open-source |
PyomoIpoptSolver |
Pyomo / IPOPT | Nonlinear | Twice differentiable | Scalar + Tensor | Open-source |
PyomoBonminSolver |
Pyomo / Bonmin | Mixed-integer nonlinear | Twice differentiable | Scalar + Tensor | Open-source |
PyomoCBCSolver |
Pyomo / CBC | Linear / mixed-integer linear | Not required | Scalar + Tensor | Open-source |
PyomoGurobiSolver |
Pyomo / Gurobi | Linear / quadratic | Not required | Scalar + Tensor | Commercial |
GurobipySolver |
Gurobipy | Linear / quadratic | Not required | Scalar + Tensor | Commercial |
PersistentGurobipySolver |
Gurobipy | Linear / quadratic | Not required | Scalar + Tensor | Commercial |
NevergradGenericSolver |
Nevergrad | Black-box | Gradient-free | Scalar | Open-source |