desdeo 1.2__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- desdeo/__init__.py +8 -8
- desdeo/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +50 -0
- desdeo/api/config.py +90 -0
- desdeo/api/config.toml +64 -0
- desdeo/api/db.py +27 -0
- desdeo/api/db_init.py +85 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +266 -0
- desdeo/api/models/archive.py +23 -0
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +128 -0
- desdeo/api/models/problem.py +717 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +49 -0
- desdeo/api/models/state.py +463 -0
- desdeo/api/models/user.py +52 -0
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +765 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +307 -0
- desdeo/api/routers/reference_point_method.py +93 -0
- desdeo/api/routers/session.py +100 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +520 -0
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +100 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +151 -0
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +1179 -0
- desdeo/api/tests/test_routes.py +1075 -0
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/_logger.py +29 -0
- desdeo/api/utils/database.py +36 -0
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +34 -0
- desdeo/emo/__init__.py +159 -0
- desdeo/emo/hooks/archivers.py +188 -0
- desdeo/emo/methods/EAs.py +541 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +12 -0
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +1282 -0
- desdeo/emo/operators/evaluator.py +114 -0
- desdeo/emo/operators/generator.py +459 -0
- desdeo/emo/operators/mutation.py +1224 -0
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +1778 -0
- desdeo/emo/operators/termination.py +286 -0
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +41 -0
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +656 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +186 -0
- desdeo/problem/__init__.py +83 -0
- desdeo/problem/evaluator.py +561 -0
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/gurobipy_evaluator.py +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +487 -0
- desdeo/problem/schema.py +1829 -0
- desdeo/problem/simulator_evaluator.py +348 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +88 -0
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +283 -0
- desdeo/problem/testproblems/knapsack_problem.py +163 -0
- desdeo/problem/testproblems/mcwb_problem.py +831 -0
- desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
- desdeo/problem/testproblems/momip_problem.py +172 -0
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/nimbus_problem.py +143 -0
- desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
- desdeo/problem/testproblems/re_problem.py +492 -0
- desdeo/problem/testproblems/river_pollution_problems.py +440 -0
- desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
- desdeo/problem/testproblems/simple_problem.py +351 -0
- desdeo/problem/testproblems/simulator_problem.py +92 -0
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +274 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +120 -0
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +165 -0
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +117 -0
- desdeo/tools/indicators_unary.py +362 -0
- desdeo/tools/interaction_schema.py +38 -0
- desdeo/tools/intersection.py +54 -0
- desdeo/tools/iterative_pareto_representer.py +99 -0
- desdeo/tools/message.py +265 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +134 -0
- desdeo/tools/patterns.py +283 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +477 -0
- desdeo/tools/reference_vectors.py +229 -0
- desdeo/tools/scalarization.py +2065 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +627 -0
- desdeo/tools/utils.py +388 -0
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/__init__.py +0 -0
- desdeo/utopia_stuff/data/1.json +15 -0
- desdeo/utopia_stuff/data/2.json +13 -0
- desdeo/utopia_stuff/data/3.json +15 -0
- desdeo/utopia_stuff/data/4.json +17 -0
- desdeo/utopia_stuff/data/5.json +15 -0
- desdeo/utopia_stuff/from_json.py +40 -0
- desdeo/utopia_stuff/reinit_user.py +38 -0
- desdeo/utopia_stuff/utopia_db_init.py +212 -0
- desdeo/utopia_stuff/utopia_problem.py +403 -0
- desdeo/utopia_stuff/utopia_problem_old.py +415 -0
- desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
- desdeo-2.1.0.dist-info/METADATA +186 -0
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-1.2.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.1.0.dist-info/licenses/LICENSE +21 -0
- desdeo-1.2.dist-info/METADATA +0 -16
- desdeo-1.2.dist-info/RECORD +0 -4
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""Solver interfaces to the optimization routines found in scipy.
|
|
2
|
+
|
|
3
|
+
These solvers can solve various scalarized problems of multiobjective optimization problems.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy.optimize import NonlinearConstraint
|
|
11
|
+
from scipy.optimize import OptimizeResult as _ScipyOptimizeResult
|
|
12
|
+
from scipy.optimize import differential_evolution as _scipy_de
|
|
13
|
+
from scipy.optimize import minimize as _scipy_minimize
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from desdeo.problem import ConstraintTypeEnum, PolarsEvaluator, Problem, variable_dimension_enumerate
|
|
18
|
+
from desdeo.tools.generics import BaseSolver, SolverError, SolverResults
|
|
19
|
+
|
|
20
|
+
SUPPORTED_VAR_DIMENSIONS = ["scalar"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ScipyDeOptions(BaseModel):
|
|
24
|
+
"""
|
|
25
|
+
Defines a pydantic model to store and pass options to the Scipy differential evolution solver.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
initial_guess: dict[str, float | None] | None = Field(
|
|
29
|
+
description="The initial guess to be utilized in the solver. For variables with a None as their initial "
|
|
30
|
+
"guess, the mid-point of the variable's lower and upper bound is utilized as the initial"
|
|
31
|
+
"guess. If None, it is assumed that there are no initial guesses for any of the variables.",
|
|
32
|
+
default=None
|
|
33
|
+
)
|
|
34
|
+
de_kwargs: dict | None = Field(
|
|
35
|
+
description="Custom keyword arguments to be forwarded to `scipy.optimize.differential_evolution`.",
|
|
36
|
+
default=None
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ScipyMinimizeOptions(BaseModel):
|
|
41
|
+
"""
|
|
42
|
+
Defines a pydantic model to store and pass options to the Scipy Minimize solver.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
initial_guess: dict[str, float | None] | None = Field(
|
|
46
|
+
description="The initial guess to be utilized in the solver. For variables with a None as their"
|
|
47
|
+
"initial guess, the mid-point of the variable's lower and upper bound is utilized as the "
|
|
48
|
+
"initial guess. If None, it is assumed that there are no initial guesses for any of the variables.",
|
|
49
|
+
default=None
|
|
50
|
+
)
|
|
51
|
+
method: str | None = Field(
|
|
52
|
+
description="The scipy.optimize.minimize method to beused. If None, a method is selected "
|
|
53
|
+
"automatically based on the properties of the objective (does it have constraints?).",
|
|
54
|
+
default=None
|
|
55
|
+
)
|
|
56
|
+
method_kwargs: dict | None = Field(
|
|
57
|
+
description="The keyword arguments passed to the scipy.optimize.minimize method.",
|
|
58
|
+
default=None
|
|
59
|
+
)
|
|
60
|
+
tol: float | None = Field(
|
|
61
|
+
description="Tolerance for termination.",
|
|
62
|
+
default=None
|
|
63
|
+
)
|
|
64
|
+
additional_options: dict | None = Field(
|
|
65
|
+
description="Additional solver options.",
|
|
66
|
+
default=None
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class EvalTargetEnum(str, Enum):
|
|
71
|
+
"""An enum that describe whether the evaluator target is an objective or a constraint."""
|
|
72
|
+
|
|
73
|
+
objective = "objective"
|
|
74
|
+
constraint = "constraint"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_variable_bounds_pairs(problem: Problem) -> list[tuple[float | int, float | int]]:
|
|
78
|
+
"""Returns the variable bounds defined in a Problem as a list of tuples.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
problem (Problem): the problem with the variables of interest.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
list[tuple[float | int, float | int]]: a list of tuples, the first
|
|
85
|
+
element of each tuple is the lower bound of a variable and the second
|
|
86
|
+
its upper bound. Each tuple corresponds to a variable.
|
|
87
|
+
"""
|
|
88
|
+
return [(variable.lowerbound, variable.upperbound) for variable in problem.variables]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def set_initial_guess(problem: Problem) -> list[float | int]:
|
|
92
|
+
"""Sets or gets the initial guess for each variable defined in a Problem.
|
|
93
|
+
|
|
94
|
+
For variables without initial guess, the initial guess is set to be the middle point of said
|
|
95
|
+
variable's lower and upper bound.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
problem (Problem): the problem with the variables of which the initial values are of interest.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
list[float | int]: a list of numbers, each number represents the initial guess of each variable in the problem.
|
|
102
|
+
"""
|
|
103
|
+
return [
|
|
104
|
+
variable.initial_value
|
|
105
|
+
if variable.initial_value is not None
|
|
106
|
+
else ((variable.upperbound - variable.lowerbound) / 2 + variable.lowerbound)
|
|
107
|
+
for variable in problem.variables
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def create_scipy_dict_constraints(problem: Problem, evaluator: PolarsEvaluator) -> dict:
|
|
112
|
+
"""Creates a dict with scipy compatible constraints.
|
|
113
|
+
|
|
114
|
+
It is assumed that there are constraints defined in problem.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
problem (Problem): the Problem with the constraints.
|
|
118
|
+
evaluator (GenericEvaluator): the evaluator utilized to evaluate problem.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
dict: a dict with scipy compatible constraints.
|
|
122
|
+
"""
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
"type": "ineq" if constraint.cons_type == ConstraintTypeEnum.LTE else "eq",
|
|
126
|
+
"fun": get_scipy_eval(problem, evaluator, constraint.symbol, eval_target=EvalTargetEnum.constraint),
|
|
127
|
+
}
|
|
128
|
+
for constraint in problem.constraints
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def create_scipy_object_constraints(problem: Problem, evaluator: PolarsEvaluator) -> list[NonlinearConstraint]:
|
|
133
|
+
"""Creates a list with scipy constraint object `NonLinearConstraints` used by some scipy routines.
|
|
134
|
+
|
|
135
|
+
For more infor, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.NonlinearConstraint.html#scipy-optimize-nonlinearconstraint
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
problem (Problem): the problem with the original constraint to be utilized in creating the list of constraints.
|
|
139
|
+
evaluator (GenericEvaluator): the evaluator corresponding to problem that can be used to evaluate
|
|
140
|
+
the constraints.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
list[NonlinearConstraint]: a list of scipy's NonLinearConstraint objects.
|
|
144
|
+
"""
|
|
145
|
+
return NonlinearConstraint(
|
|
146
|
+
fun=get_scipy_eval(problem, evaluator, "", eval_target=EvalTargetEnum.constraint),
|
|
147
|
+
lb=0, # constraint value must be between 0 and inf, e.g., positive.
|
|
148
|
+
ub=float("inf"), # since in scipy, a constraint is respected when its value is positive. See scipy_eval.
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_scipy_eval(
|
|
153
|
+
problem: Problem,
|
|
154
|
+
evaluator: PolarsEvaluator,
|
|
155
|
+
target: str,
|
|
156
|
+
eval_target: EvalTargetEnum,
|
|
157
|
+
) -> Callable[[list[float | int]], list[float | int]]:
|
|
158
|
+
"""Wraps the problem and evaluator into a callable function that can be used by scipy routines.
|
|
159
|
+
|
|
160
|
+
The returned function expects an array-like argument, such as a numpy array or list.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
problem (Problem): the problem being solved.
|
|
164
|
+
evaluator (GenericEvaluator): the evaluator to evaluate the problem being solved.
|
|
165
|
+
target (str): the symbol of the objective to of the optimization, defined in problem.
|
|
166
|
+
eval_target (EvalTargetEnum): either objective or constraints. If objective,
|
|
167
|
+
it is assumed that the evalution is about evaluating the objective function
|
|
168
|
+
of the single-objective optimization problem being solved, e.g., a scalarization function
|
|
169
|
+
defined in problem. If constraint, then the evalution is assumed to be about evaluating
|
|
170
|
+
the constraints defined in problem.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Callable[[list[float | int]], list[float | int]]: a function that takes as its argument
|
|
174
|
+
an array like object.
|
|
175
|
+
|
|
176
|
+
Note:
|
|
177
|
+
Constraints in scipy are defined such that a positive number means the constraint
|
|
178
|
+
is respected. In DESDEO, this is the opposite, e.g., a positive number means
|
|
179
|
+
a constraint is breached. We take this into account when returning the
|
|
180
|
+
constraint values, but this does not affect the constraint values computed
|
|
181
|
+
for the true constraints.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def scipy_eval(x: list[float | int]) -> list[float | int]:
|
|
185
|
+
"""An evaluator to be used in scipy routines.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
x (list[float | int]): an array like, such as a numpy array or list.append
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
SolverError: when an invalid evaluator target is specified.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
list[float | int]: an array like.
|
|
195
|
+
"""
|
|
196
|
+
# TODO: Consider caching the results of evaluator.evaluate
|
|
197
|
+
evalutor_args = {
|
|
198
|
+
problem.variables[i].symbol: [x[i]] if isinstance(x[i], float | int) else x[i]
|
|
199
|
+
for i in range(len(problem.variables))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if eval_target == EvalTargetEnum.objective:
|
|
203
|
+
evaluator_res = evaluator.evaluate(evalutor_args)
|
|
204
|
+
|
|
205
|
+
# evaluata objective (scalarized)
|
|
206
|
+
return evaluator_res.to_dict(as_series=False)[target]
|
|
207
|
+
|
|
208
|
+
if eval_target == EvalTargetEnum.constraint:
|
|
209
|
+
evaluator_df = evaluator.evaluate(evalutor_args)
|
|
210
|
+
# evaluate constraint
|
|
211
|
+
# put the minus here because scipy expect positive constraints values when the constraint
|
|
212
|
+
# is respected. But in DESDEO, we define constraints s.t., a negative value means the constraint
|
|
213
|
+
# is recpected, therefore, it needs to be flipped here.
|
|
214
|
+
con_symbols = [constraint.symbol for constraint in problem.constraints]
|
|
215
|
+
res_dict = evaluator_df[con_symbols].to_dict(as_series=False)
|
|
216
|
+
|
|
217
|
+
res = np.array([np.array(res_dict[symbol]) for symbol in con_symbols])
|
|
218
|
+
|
|
219
|
+
# squeeze important for minimization routines
|
|
220
|
+
return -np.squeeze(res, axis=-1) if res.shape[-1] == 1 else -res
|
|
221
|
+
|
|
222
|
+
# non-existing eval_target
|
|
223
|
+
msg = f"'eval_target' = '{eval_target} not supported. Must be one of {list(EvalTargetEnum)}."
|
|
224
|
+
raise SolverError(msg)
|
|
225
|
+
|
|
226
|
+
return scipy_eval
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def parse_scipy_optimization_result(
|
|
230
|
+
optimization_result: _ScipyOptimizeResult, problem: Problem, evaluator: PolarsEvaluator
|
|
231
|
+
) -> SolverResults:
|
|
232
|
+
"""Parses the optimization results returned by various scipy methods.
|
|
233
|
+
|
|
234
|
+
For documentation, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.OptimizeResult.html#scipy.optimize.OptimizeResult
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
optimization_result (_ScipyOptimizeResult): the optimization results.
|
|
238
|
+
problem (Problem): the problem to which the optimization results correspond to.
|
|
239
|
+
evaluator (GenericEvaluator): the evaluator that has been used in computing the optimization results.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
SolverResults: a pydantic dataclass with the relevant optimization results.
|
|
243
|
+
"""
|
|
244
|
+
x_opt = optimization_result.x
|
|
245
|
+
success_opt = optimization_result.success
|
|
246
|
+
msg_opt = optimization_result.message
|
|
247
|
+
|
|
248
|
+
results = evaluator.evaluate({problem.variables[i].symbol: [x_opt[i]] for i in range(len(problem.variables))})
|
|
249
|
+
|
|
250
|
+
optimal_objectives = {obj.symbol: results[obj.symbol][0] for obj in problem.objectives}
|
|
251
|
+
constraint_values = (
|
|
252
|
+
{con.symbol: results[con.symbol][0] for con in problem.constraints} if problem.constraints is not None else None
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
objective_symbols = [obj.symbol for obj in problem.objectives]
|
|
257
|
+
f_res = results[objective_symbols]
|
|
258
|
+
optimal_objectives = {symbol: f_res[symbol][0] for symbol in objective_symbols}
|
|
259
|
+
|
|
260
|
+
if problem.constraints is not None:
|
|
261
|
+
const_symbols = [const.symbol for const in problem.constraints]
|
|
262
|
+
const_res = results[const_symbols]
|
|
263
|
+
constraint_values = {symbol: const_res[symbol][0] for symbol in const_symbols}
|
|
264
|
+
else:
|
|
265
|
+
constraint_values = None
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
extra_func_values = (
|
|
269
|
+
{extra.symbol: results[extra.symbol][0] for extra in problem.extra_funcs}
|
|
270
|
+
if problem.extra_funcs is not None
|
|
271
|
+
else None
|
|
272
|
+
)
|
|
273
|
+
scalarization_values = (
|
|
274
|
+
{scal.symbol: results[scal.symbol][0] for scal in problem.scalarization_funcs}
|
|
275
|
+
if problem.scalarization_funcs is not None
|
|
276
|
+
else None
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return SolverResults(
|
|
280
|
+
optimal_variables={problem.variables[i].symbol: x_opt[i] for i in range(len(problem.variables))},
|
|
281
|
+
optimal_objectives=optimal_objectives,
|
|
282
|
+
constraint_values=constraint_values,
|
|
283
|
+
extra_func_values=extra_func_values,
|
|
284
|
+
scalarization_values=scalarization_values,
|
|
285
|
+
success=success_opt,
|
|
286
|
+
message=msg_opt,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class ScipyMinimizeSolver(BaseSolver):
|
|
291
|
+
"""Creates a scipy solver that utilizes the `minimization` routine."""
|
|
292
|
+
|
|
293
|
+
def __init__(
|
|
294
|
+
self,
|
|
295
|
+
problem: Problem,
|
|
296
|
+
options: ScipyMinimizeOptions = ScipyMinimizeOptions()
|
|
297
|
+
):
|
|
298
|
+
"""Initializes a solver that utilizes the `scipy.optimize.minimize` routine.
|
|
299
|
+
|
|
300
|
+
The `scipy.optimize.minimze` routine is fully accessible through this function.
|
|
301
|
+
For additional details and explanation of some of the argumetns, see
|
|
302
|
+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
problem (Problem): the multiobjective optimization problem to be solved.
|
|
306
|
+
options: (ScipyMinimizeOptions): Pydantic model containing args for scipy minimize solver.
|
|
307
|
+
subscriber (str | None, optional): not used right now. WIP. Defaults to None.
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
if variable_dimension_enumerate(problem) not in SUPPORTED_VAR_DIMENSIONS:
|
|
311
|
+
msg = "ScipyMinimizeSolver only supports scalar variables."
|
|
312
|
+
raise SolverError(msg)
|
|
313
|
+
|
|
314
|
+
initial_guess = options.initial_guess
|
|
315
|
+
self.problem = problem
|
|
316
|
+
self.method = options.method
|
|
317
|
+
self.method_kwargs = options.method_kwargs
|
|
318
|
+
self.tol = options.tol
|
|
319
|
+
self.additional_options = options.additional_options
|
|
320
|
+
|
|
321
|
+
# variables bounds as (min, max pairs)
|
|
322
|
+
self.bounds = get_variable_bounds_pairs(problem)
|
|
323
|
+
|
|
324
|
+
# the initial guess as a simple sequence. If no initial value is set for some variable,
|
|
325
|
+
# then the initial value defaults to middle of the upper and lower bounds.
|
|
326
|
+
if initial_guess is not None:
|
|
327
|
+
self.initial_guess = [initial_guess[var.symbol] for var in self.problem.variables]
|
|
328
|
+
else:
|
|
329
|
+
self.initial_guess = set_initial_guess(problem)
|
|
330
|
+
|
|
331
|
+
self.evaluator = PolarsEvaluator(problem)
|
|
332
|
+
|
|
333
|
+
self.constraints = (
|
|
334
|
+
create_scipy_dict_constraints(self.problem, self.evaluator)
|
|
335
|
+
if self.problem.constraints is not None
|
|
336
|
+
else None
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def solve(self, target: str) -> SolverResults:
|
|
340
|
+
"""Solves the problem for a given target.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
target (str): the sumbol of the objective function to be optimized.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
SolverResults: results of the optimization.
|
|
347
|
+
"""
|
|
348
|
+
# add constraints if there are any
|
|
349
|
+
|
|
350
|
+
optimization_result: _ScipyOptimizeResult = _scipy_minimize(
|
|
351
|
+
get_scipy_eval(self.problem, self.evaluator, target, EvalTargetEnum.objective),
|
|
352
|
+
self.initial_guess,
|
|
353
|
+
method=self.method,
|
|
354
|
+
bounds=self.bounds,
|
|
355
|
+
constraints=self.constraints,
|
|
356
|
+
options=self.additional_options,
|
|
357
|
+
tol=self.tol,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# pare and return the results
|
|
361
|
+
return parse_scipy_optimization_result(optimization_result, self.problem, self.evaluator)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class ScipyDeSolver(BaseSolver):
|
|
365
|
+
"""Creates a scipy solver that utilizes differential evolution."""
|
|
366
|
+
|
|
367
|
+
def __init__(
|
|
368
|
+
self,
|
|
369
|
+
problem: Problem,
|
|
370
|
+
options: ScipyDeOptions = ScipyDeOptions(),
|
|
371
|
+
):
|
|
372
|
+
"""Creates a solver that utilizes the `scipy.optimize.differential_evolution` routine.
|
|
373
|
+
|
|
374
|
+
The `scipy.optimize.differential_evolution` routine is fully accessible through this function.
|
|
375
|
+
For additional details and explanation of some of the argumetns, see
|
|
376
|
+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
problem (Problem): the multiobjective optimization problem to be solved.
|
|
380
|
+
initial_guess (dict[str, float, None] | None, optional): The initial
|
|
381
|
+
guess to be utilized in the solver. For variables with a None as their
|
|
382
|
+
initial guess, the mid-point of the variable's lower and upper bound is
|
|
383
|
+
utilzied as the initial guess. If None, it is assumed that there are
|
|
384
|
+
no initial guesses for any of the variables. Defaults to None.
|
|
385
|
+
options (ScipyDeOptions): Pydantic model containing arguments used by scipy DE solver.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
initial_guess = options.initial_guess
|
|
389
|
+
de_kwargs = options.de_kwargs
|
|
390
|
+
|
|
391
|
+
if variable_dimension_enumerate(problem) not in SUPPORTED_VAR_DIMENSIONS:
|
|
392
|
+
msg = "ScipyDeSolver only supports scalar variables."
|
|
393
|
+
raise SolverError(msg)
|
|
394
|
+
|
|
395
|
+
self.problem = problem
|
|
396
|
+
if de_kwargs is None:
|
|
397
|
+
de_kwargs = {
|
|
398
|
+
"strategy": "best1bin",
|
|
399
|
+
"maxiter": 1000,
|
|
400
|
+
"popsize": 15,
|
|
401
|
+
"tol": 0.01,
|
|
402
|
+
"mutation": (0.5, 1),
|
|
403
|
+
"recombination": 0.7,
|
|
404
|
+
"seed": None,
|
|
405
|
+
"callback": None,
|
|
406
|
+
"disp": False,
|
|
407
|
+
"polish": True,
|
|
408
|
+
"init": "latinhypercube",
|
|
409
|
+
"atol": 0,
|
|
410
|
+
"updating": "deferred",
|
|
411
|
+
"workers": 1,
|
|
412
|
+
"integrality": None,
|
|
413
|
+
"vectorized": True, # the constraints for scipy_de need to be fixed first for this to work
|
|
414
|
+
}
|
|
415
|
+
self.de_kwargs = de_kwargs
|
|
416
|
+
|
|
417
|
+
# variable bounds
|
|
418
|
+
self.bounds = get_variable_bounds_pairs(problem)
|
|
419
|
+
|
|
420
|
+
# initial guess. If no guess is present for a variable, said variable's mid point of its
|
|
421
|
+
# lower abd upper bound is used instead
|
|
422
|
+
if initial_guess is None:
|
|
423
|
+
self.initial_guess = set_initial_guess(problem)
|
|
424
|
+
else:
|
|
425
|
+
self.initial_guess = initial_guess
|
|
426
|
+
|
|
427
|
+
self.evaluator = PolarsEvaluator(problem)
|
|
428
|
+
self.constraints = (
|
|
429
|
+
create_scipy_object_constraints(self.problem, self.evaluator)
|
|
430
|
+
if self.problem.constraints is not None
|
|
431
|
+
else ()
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def solve(self, target: str) -> SolverResults:
|
|
435
|
+
"""Solve the problem for a given target.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
target (str): the symbol of the objective function to be optimized.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
SolverResults: results of the optimization.
|
|
442
|
+
"""
|
|
443
|
+
# add constraints if there are any
|
|
444
|
+
|
|
445
|
+
optimization_result: _ScipyOptimizeResult = _scipy_de(
|
|
446
|
+
get_scipy_eval(self.problem, self.evaluator, target, EvalTargetEnum.objective),
|
|
447
|
+
bounds=self.bounds,
|
|
448
|
+
x0=self.initial_guess,
|
|
449
|
+
constraints=self.constraints,
|
|
450
|
+
**self.de_kwargs,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# parse the results
|
|
454
|
+
return parse_scipy_optimization_result(optimization_result, self.problem, self.evaluator)
|