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,258 @@
|
|
|
1
|
+
"""Defines solver interfaces for gurobipy."""
|
|
2
|
+
|
|
3
|
+
import gurobipy as gp
|
|
4
|
+
|
|
5
|
+
from desdeo.problem import (
|
|
6
|
+
Constraint,
|
|
7
|
+
GurobipyEvaluator,
|
|
8
|
+
Objective,
|
|
9
|
+
Problem,
|
|
10
|
+
ScalarizationFunction,
|
|
11
|
+
TensorVariable,
|
|
12
|
+
Variable,
|
|
13
|
+
)
|
|
14
|
+
from desdeo.tools.generics import BaseSolver, PersistentSolver, SolverResults
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_gurobipy_optimizer_results(problem: Problem, evaluator: GurobipyEvaluator) -> SolverResults:
|
|
18
|
+
"""Parses results from GurobipyEvaluator's model into DESDEO SolverResults.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
problem (Problem): the problem being solved.
|
|
22
|
+
evaluator (GurobipyEvaluator): the evaluator utilized to solve the problem.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
SolverResults: DESDEO solver results.
|
|
26
|
+
"""
|
|
27
|
+
results = evaluator.get_values()
|
|
28
|
+
|
|
29
|
+
variable_values = {var.symbol: results[var.symbol] for var in problem.variables}
|
|
30
|
+
objective_values = {obj.symbol: results[obj.symbol] for obj in problem.objectives}
|
|
31
|
+
constraint_values = (
|
|
32
|
+
{con.symbol: results[con.symbol] for con in problem.constraints} if problem.constraints is not None else None
|
|
33
|
+
)
|
|
34
|
+
extra_func_values = (
|
|
35
|
+
{extra.symbol: results[extra.symbol] for extra in problem.extra_funcs}
|
|
36
|
+
if problem.extra_funcs is not None
|
|
37
|
+
else None
|
|
38
|
+
)
|
|
39
|
+
scalarization_values = (
|
|
40
|
+
{scal.symbol: results[scal.symbol] for scal in problem.scalarization_funcs}
|
|
41
|
+
if problem.scalarization_funcs is not None
|
|
42
|
+
else None
|
|
43
|
+
)
|
|
44
|
+
success = evaluator.model.status == gp.GRB.OPTIMAL
|
|
45
|
+
if evaluator.model.status == gp.GRB.OPTIMAL:
|
|
46
|
+
status = "Optimal solution found."
|
|
47
|
+
elif evaluator.model.status == gp.GRB.INFEASIBLE:
|
|
48
|
+
status = "Model is infeasible."
|
|
49
|
+
elif evaluator.model.status == gp.GRB.UNBOUNDED:
|
|
50
|
+
status = "Model is unbounded."
|
|
51
|
+
elif evaluator.model.status == gp.GRB.INF_OR_UNBD:
|
|
52
|
+
status = "Model is either infeasible or unbounded."
|
|
53
|
+
else:
|
|
54
|
+
status = f"Optimization ended with status: {evaluator.model.status}"
|
|
55
|
+
msg = f"Gurobipy solver status is: '{status}'"
|
|
56
|
+
|
|
57
|
+
return SolverResults(
|
|
58
|
+
optimal_variables=variable_values,
|
|
59
|
+
optimal_objectives=objective_values,
|
|
60
|
+
constraint_values=constraint_values,
|
|
61
|
+
extra_func_values=extra_func_values,
|
|
62
|
+
scalarization_values=scalarization_values,
|
|
63
|
+
success=success,
|
|
64
|
+
message=msg,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class GurobipySolver(BaseSolver):
|
|
69
|
+
"""Creates a gurobipy solver that utilizes gurobi's own Python implementation."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, problem: Problem, options: dict[str, any] | None = None):
|
|
72
|
+
"""The solver is initialized by supplying a problem and options.
|
|
73
|
+
|
|
74
|
+
Unlike with Pyomo you do not need to have gurobi installed on your system
|
|
75
|
+
for this to work. Suitable for solving mixed-integer linear and quadratic optimization
|
|
76
|
+
problems.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
problem (Problem): the problem to be solved.
|
|
80
|
+
options (dict[str,any]): Dictionary of Gurobi parameters to set.
|
|
81
|
+
You probably don't need to set any of these and can just use the defaults.
|
|
82
|
+
For available parameters see https://www.gurobi.com/documentation/current/refman/parameters.html
|
|
83
|
+
"""
|
|
84
|
+
self.evaluator = GurobipyEvaluator(problem)
|
|
85
|
+
self.problem = problem
|
|
86
|
+
|
|
87
|
+
if options is not None:
|
|
88
|
+
for key, value in options.items():
|
|
89
|
+
self.evaluator.model.setParam(key, value)
|
|
90
|
+
|
|
91
|
+
def solve(self, target: str) -> SolverResults:
|
|
92
|
+
"""Solve the problem for the given target.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
target (str): the symbol of the function to be optimized, and which is
|
|
96
|
+
defined in the problem given when initializing the solver.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
SolverResults: the results of the optimization.
|
|
100
|
+
"""
|
|
101
|
+
self.evaluator.set_optimization_target(target)
|
|
102
|
+
self.evaluator.model.optimize()
|
|
103
|
+
return parse_gurobipy_optimizer_results(self.problem, self.evaluator)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class PersistentGurobipySolver(PersistentSolver):
|
|
107
|
+
"""A persistent solver class utlizing gurobipy.
|
|
108
|
+
|
|
109
|
+
Use this instead of create_gurobipy_solver when re-initializing the
|
|
110
|
+
solver every time the problem is changed is not practical.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
evaluator: GurobipyEvaluator
|
|
114
|
+
|
|
115
|
+
def __init__(self, problem: Problem, options: dict[str, any] | None = None):
|
|
116
|
+
"""Initializer for the persistent solver.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
problem (Problem): the problem to be transformed in a GurobipyModel.
|
|
120
|
+
options (dict[str,any]): Dictionary of Gurobi parameters to set.
|
|
121
|
+
You probably don't need to set any of these and can just use the defaults.
|
|
122
|
+
For available parameters see https://www.gurobi.com/documentation/current/refman/parameters.html
|
|
123
|
+
"""
|
|
124
|
+
self.problem = problem
|
|
125
|
+
self.evaluator = GurobipyEvaluator(problem)
|
|
126
|
+
if options is not None:
|
|
127
|
+
for key, value in options.items():
|
|
128
|
+
self.evaluator.model.setParam(key, value)
|
|
129
|
+
|
|
130
|
+
def add_constraint(self, constraint: Constraint | list[Constraint]) -> gp.Constr | list[gp.Constr]:
|
|
131
|
+
"""Add one or more constraint expressions to the solver.
|
|
132
|
+
|
|
133
|
+
If adding a lot of constraints or dealing with a large model, this function
|
|
134
|
+
may end up being very slow compared to adding the constraints to the model
|
|
135
|
+
stored in the evaluator directly.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
constraint (Constraint): the constraint function expression or a list of
|
|
139
|
+
constraint function expressions.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
GurobipyEvaluatorError: when an unsupported constraint type is encountered.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
gurobipy.Constr: The gurobipy constraint that was added or a list of gurobipy
|
|
146
|
+
constraints if the constraint argument was a list.
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(constraint, list):
|
|
149
|
+
cons_list = list[gp.Constr]
|
|
150
|
+
for cons in constraint:
|
|
151
|
+
cons_list.append(self.evaluator.add_constraint(cons))
|
|
152
|
+
return cons_list
|
|
153
|
+
|
|
154
|
+
return self.evaluator.add_constraint(constraint)
|
|
155
|
+
|
|
156
|
+
def add_objective(self, objective: Objective | list[Objective]):
|
|
157
|
+
"""Adds an objective function expression to the solver.
|
|
158
|
+
|
|
159
|
+
Does not yet add any actual gurobipy optimization objectives, only adds them to the dict
|
|
160
|
+
containing the expressions of the objectives. The objective expressions are stored in the
|
|
161
|
+
evaluator and the evaluator must add the appropiate gurobipy objective before solving.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
objective (Objective): an objective function expression or a list of objective function
|
|
165
|
+
expressions to be added.
|
|
166
|
+
"""
|
|
167
|
+
if not isinstance(objective, list):
|
|
168
|
+
objective = [objective]
|
|
169
|
+
|
|
170
|
+
for obj in objective:
|
|
171
|
+
self.evaluator.add_objective(obj)
|
|
172
|
+
|
|
173
|
+
def add_scalarization_function(self, scalarization: ScalarizationFunction | list[ScalarizationFunction]):
|
|
174
|
+
"""Adds a scalrization expression to the solver.
|
|
175
|
+
|
|
176
|
+
Scalarizations work identically to objectives, except they are stored in a different
|
|
177
|
+
dict in the evaluator. If you want to solve the problem using a scalarization, the
|
|
178
|
+
evaluator needs to set it as an optimization target first.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
scalarization (ScalarizationFunction): A scalarization function or a list of
|
|
182
|
+
scalarization functions to be added.
|
|
183
|
+
"""
|
|
184
|
+
if not isinstance(scalarization, list):
|
|
185
|
+
scalarization = [scalarization]
|
|
186
|
+
|
|
187
|
+
for scal in scalarization:
|
|
188
|
+
self.evaluator.add_scalarization_function(scal)
|
|
189
|
+
|
|
190
|
+
def add_variable(
|
|
191
|
+
self, variable: Variable | TensorVariable | list[Variable] | list[TensorVariable]
|
|
192
|
+
) -> gp.Var | gp.MVar | list[gp.Var] | list[gp.MVar]:
|
|
193
|
+
"""Add one or more variables to the solver.
|
|
194
|
+
|
|
195
|
+
If adding a lot of variables or dealing with a large model, this function
|
|
196
|
+
may end up being very slow compared to adding the variables to the model
|
|
197
|
+
stored in the evaluator directly.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
variable (Variable): The definition of the variable or a list of variables to be added.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
GurobipyEvaluatorError: when a problem in extracting the variables is encountered.
|
|
204
|
+
I.e., the variables are of a non supported type.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
gp.Var: the variable that was added to the model or a list of variables if
|
|
208
|
+
variable argument was a list.
|
|
209
|
+
"""
|
|
210
|
+
if isinstance(variable, list):
|
|
211
|
+
var_list = list[gp.Var | gp.MVar]
|
|
212
|
+
for var in variable:
|
|
213
|
+
var_list.append(self.evaluator.add_variable(var))
|
|
214
|
+
return var_list
|
|
215
|
+
|
|
216
|
+
return self.evaluator.add_variable(variable)
|
|
217
|
+
|
|
218
|
+
def remove_constraint(self, symbol: str | list[str]):
|
|
219
|
+
"""Removes a constraint from the solver.
|
|
220
|
+
|
|
221
|
+
If removing a lot of constraints or dealing with a very large model this function
|
|
222
|
+
may be slow because of the model.update() calls. Accessing the model stored in the
|
|
223
|
+
evaluator directly may be faster.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
symbol (str): a str representing the symbol of the constraint to be removed.
|
|
227
|
+
Can also be a list of multiple symbols.
|
|
228
|
+
"""
|
|
229
|
+
if not isinstance(symbol, list):
|
|
230
|
+
symbol = [symbol]
|
|
231
|
+
for s in symbol:
|
|
232
|
+
self.evaluator.remove_constraint(s)
|
|
233
|
+
|
|
234
|
+
def remove_variable(self, symbol: str | list[str]):
|
|
235
|
+
"""Removes a variable from the model.
|
|
236
|
+
|
|
237
|
+
If removing a lot of variables or dealing with a very large model this function
|
|
238
|
+
may be slow because of the model.update() calls. Accessing the model stored in
|
|
239
|
+
the evaluator directly may be faster.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
symbol (str): a str representing the symbol of the variable to be removed.
|
|
243
|
+
Can also be a list of multiple symbols.
|
|
244
|
+
"""
|
|
245
|
+
self.evaluator.remove_variable(symbol)
|
|
246
|
+
|
|
247
|
+
def solve(self, target: str) -> SolverResults:
|
|
248
|
+
"""Solves the current problem with the specified target.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
target (str): a str representing the symbol of the target function.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
SolverResults: The results of the solver
|
|
255
|
+
"""
|
|
256
|
+
self.evaluator.set_optimization_target(target)
|
|
257
|
+
self.evaluator.model.optimize()
|
|
258
|
+
return parse_gurobipy_optimizer_results(self.problem, self.evaluator)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""This module implements unary indicators that can be used to compare two solution sets.
|
|
2
|
+
|
|
3
|
+
It assumes that the solution set has been normalized just that _some_ ideal point (not necessarily the ideal point
|
|
4
|
+
of the set) is the origin and _some_ nadir point (not necessarily the nadir point of the set) is (1, 1, ..., 1).
|
|
5
|
+
The normalized solution set is assumed to be inside the bounding box [0, 1]^k where k is the number of objectives.
|
|
6
|
+
If these conditions are not met, the results of the indicators will not be meaningful.
|
|
7
|
+
|
|
8
|
+
Additionally, the set may be assumed to only contain mutually non-dominated solutions, depending on the indicator.
|
|
9
|
+
|
|
10
|
+
For now, we rely on pymoo for the implementation of many of the indicators.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from moocore import epsilon_additive, epsilon_mult
|
|
17
|
+
from numba import njit
|
|
18
|
+
from desdeo.tools.non_dominated_sorting import dominates
|
|
19
|
+
from desdeo.tools.indicators_unary import hv
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Note that the moocore package includes a more complex implementation for calculating the epsilon_indicator for two
|
|
23
|
+
solution *sets*.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@njit()
|
|
28
|
+
def epsilon_component(solution1: np.ndarray, solution2: np.ndarray) -> float:
|
|
29
|
+
"""Computes the additive epsilon-indicator between two solutions.
|
|
30
|
+
|
|
31
|
+
Basically, returns the minimum amount by which the values in solution1 must be translated (minimization assumed)
|
|
32
|
+
such that it (weakly) dominates solution2. If solution1 already dominates solution2, returns 0.0.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
solution1 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
36
|
+
solution2 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
float: The maximum distance between the values in s1 and s2.
|
|
40
|
+
"""
|
|
41
|
+
return max(0.0, max(solution1 - solution2))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@njit()
|
|
45
|
+
def self_epsilon(solution_set: np.ndarray) -> np.ndarray:
|
|
46
|
+
"""Computes the pairwise additive epsilon-indicator for a solution set.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
solution_set (np.ndarray): Should be a two-dimensional array, where each row is a
|
|
50
|
+
solution normalized between [0, 1].
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
np.ndarray: A two-dimensional array where the entry at (i, j) is the
|
|
54
|
+
additive epsilon-indicator between the i-th and j-th solution in the set.
|
|
55
|
+
"""
|
|
56
|
+
n_solutions = solution_set.shape[0]
|
|
57
|
+
eps_matrix = np.zeros((n_solutions, n_solutions), dtype=np.float64)
|
|
58
|
+
for i in range(n_solutions):
|
|
59
|
+
for j in range(n_solutions):
|
|
60
|
+
eps_matrix[i, j] = epsilon_component(solution_set[i], solution_set[j])
|
|
61
|
+
return eps_matrix
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def epsilon_indicator(
|
|
65
|
+
set1: np.ndarray, set2: np.ndarray, kind: Literal["additive", "multiplicative"] = "additive"
|
|
66
|
+
) -> float:
|
|
67
|
+
"""Computes the additive epsilon-indicator between two solution sets.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
set1 (np.ndarray): Should be a two-dimensional array, where each row is a solution normalized between [0, 1]
|
|
71
|
+
set2 (np.ndarray): Should be a two-dimensional array, where each row is a solution normalized between [0, 1]
|
|
72
|
+
kind (Literal["additive", "multiplicative"]): The kind of epsilon-indicator to compute. Defaults to "additive".
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
float: the epsilon-indicator between the two sets.
|
|
76
|
+
"""
|
|
77
|
+
if kind == "additive":
|
|
78
|
+
return epsilon_additive(set1, ref=set2)
|
|
79
|
+
if kind == "multiplicative":
|
|
80
|
+
return epsilon_mult(set1, ref=set2)
|
|
81
|
+
raise ValueError(f"Unknown kind: {kind}. Use 'additive' or 'multiplicative'.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def hv_component(solution1: np.ndarray, solution2: np.ndarray, ref: float = 2.0) -> float:
|
|
85
|
+
"""Computes the hypervolume contribution of solution1 with respect to solution2.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
solution1 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
89
|
+
solution2 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
90
|
+
ref (float): The reference point for the hypervolume calculation. Defaults to 2.0.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
float: The hypervolume contribution of solution1 with respect to solution2.
|
|
94
|
+
"""
|
|
95
|
+
if dominates(solution1, solution2):
|
|
96
|
+
return np.prod(ref - solution2) - np.prod(ref - solution1)
|
|
97
|
+
return hv(solution_set=np.array([solution1, solution2]), reference_point_component=ref)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def self_hv(solution_set: np.ndarray, ref: float = 2.0) -> np.ndarray:
|
|
101
|
+
"""Computes the pairwise hypervolume contribution for a solution set.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
solution_set (np.ndarray): Should be a two-dimensional array, where each row is a
|
|
105
|
+
solution normalized between [0, 1].
|
|
106
|
+
ref (float): The reference point for the hypervolume calculation. Defaults to 2.0.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
np.ndarray: A two-dimensional array where the entry at (i, j) is the
|
|
110
|
+
hypervolume contribution of the i-th solution with respect to the j-th solution in the set.
|
|
111
|
+
"""
|
|
112
|
+
n_solutions = solution_set.shape[0]
|
|
113
|
+
hv_matrix = np.zeros((n_solutions, n_solutions), dtype=np.float64)
|
|
114
|
+
for i in range(n_solutions):
|
|
115
|
+
for j in range(n_solutions):
|
|
116
|
+
hv_matrix[i, j] = hv_component(solution_set[i], solution_set[j], ref=ref)
|
|
117
|
+
return hv_matrix
|