desdeo 1.1.3__py3-none-any.whl → 2.0.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/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +40 -0
- desdeo/api/config.py +69 -0
- desdeo/api/config.toml +53 -0
- desdeo/api/db.py +25 -0
- desdeo/api/db_init.py +79 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +66 -0
- desdeo/api/models/archive.py +34 -0
- desdeo/api/models/preference.py +90 -0
- desdeo/api/models/problem.py +507 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +46 -0
- desdeo/api/models/state.py +96 -0
- desdeo/api/models/user.py +51 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +762 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/problem.py +110 -0
- desdeo/api/routers/reference_point_method.py +117 -0
- desdeo/api/routers/session.py +76 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +366 -0
- desdeo/api/schema.py +94 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +59 -0
- desdeo/api/tests/test_models.py +701 -0
- desdeo/api/tests/test_routes.py +216 -0
- desdeo/api/utils/database.py +274 -0
- desdeo/api/utils/logger.py +29 -0
- desdeo/core.py +27 -0
- desdeo/emo/__init__.py +29 -0
- desdeo/emo/hooks/archivers.py +172 -0
- desdeo/emo/methods/EAs.py +418 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +59 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +780 -0
- desdeo/emo/operators/evaluator.py +118 -0
- desdeo/emo/operators/generator.py +356 -0
- desdeo/emo/operators/mutation.py +1053 -0
- desdeo/emo/operators/selection.py +1036 -0
- desdeo/emo/operators/termination.py +178 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/mcdm/__init__.py +19 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +655 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +116 -0
- desdeo/problem/__init__.py +79 -0
- desdeo/problem/evaluator.py +561 -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 +468 -0
- desdeo/problem/schema.py +1808 -0
- desdeo/problem/simulator_evaluator.py +298 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +73 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +275 -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/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_problem.py +434 -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/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +271 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +102 -0
- desdeo/tools/generics.py +145 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +11 -0
- desdeo/tools/indicators_unary.py +375 -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 +234 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +133 -0
- desdeo/tools/patterns.py +281 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +464 -0
- desdeo/tools/reference_vectors.py +462 -0
- desdeo/tools/scalarization.py +3138 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +464 -0
- desdeo/tools/utils.py +320 -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.0.0.dist-info/LICENSE +21 -0
- desdeo-2.0.0.dist-info/METADATA +168 -0
- desdeo-2.0.0.dist-info/RECORD +120 -0
- {desdeo-1.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
- desdeo-1.1.3.dist-info/METADATA +0 -18
- desdeo-1.1.3.dist-info/RECORD +0 -4
desdeo/mcdm/nautili.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Methods for the NAUTILI (a group decision making variant for NAUTILUS) method."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from desdeo.mcdm.nautilus_navigator import (
|
|
7
|
+
calculate_distance_to_front,
|
|
8
|
+
calculate_navigation_point,
|
|
9
|
+
)
|
|
10
|
+
from desdeo.problem import (
|
|
11
|
+
Constraint,
|
|
12
|
+
ConstraintTypeEnum,
|
|
13
|
+
Problem,
|
|
14
|
+
get_nadir_dict,
|
|
15
|
+
)
|
|
16
|
+
from desdeo.tools.generics import BaseSolver, SolverResults, SolverOptions
|
|
17
|
+
from desdeo.tools.scalarization import (
|
|
18
|
+
add_asf_generic_nondiff,
|
|
19
|
+
add_asf_generic_diff,
|
|
20
|
+
add_epsilon_constraints,
|
|
21
|
+
)
|
|
22
|
+
from desdeo.tools.utils import guess_best_solver
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NAUTILI_Response(BaseModel):
|
|
26
|
+
"""The response of the NAUTILI method."""
|
|
27
|
+
|
|
28
|
+
step_number: int = Field(description="The step number associted with this response.")
|
|
29
|
+
distance_to_front: float = Field(
|
|
30
|
+
description=(
|
|
31
|
+
"The distance travelled to the Pareto front. "
|
|
32
|
+
"The distance is a ratio of the distances between the nadir and navigation point, and "
|
|
33
|
+
"the nadir and the reachable objective vector. The distance is given in percentage."
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
reference_points: dict | None = Field(description="The reference points used in the step.")
|
|
37
|
+
improvement_directions: dict | None = Field(description="The improvement directions for each DM.")
|
|
38
|
+
group_improvement_direction: dict | None = Field(description="The group improvement direction.")
|
|
39
|
+
navigation_point: dict = Field(description="The navigation point used in the step.")
|
|
40
|
+
reachable_solution: dict | None = Field(description="The reachable solution found in the step.")
|
|
41
|
+
reachable_bounds: dict = Field(description="The reachable bounds found in the step.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NautiliError(Exception):
|
|
45
|
+
"""Exception raised for errors in the NAUTILI method."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def solve_reachable_bounds(
|
|
49
|
+
problem: Problem,
|
|
50
|
+
navigation_point: dict[str, float],
|
|
51
|
+
solver: BaseSolver | None = None
|
|
52
|
+
) -> tuple[dict[str, float], dict[str, float]]:
|
|
53
|
+
"""Computes the current reachable (upper and lower) bounds of the solutions in the objective space.
|
|
54
|
+
|
|
55
|
+
The reachable bound are computed based on the current navigation point. The bounds are computed by
|
|
56
|
+
solving an epsilon constraint problem.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
problem (Problem): the problem being solved.
|
|
60
|
+
navigation_point (dict[str, float]): the navigation point limiting the
|
|
61
|
+
reachable area. The key is the objective function's symbol and the value
|
|
62
|
+
the navigation point.
|
|
63
|
+
solver (BaseSolver | None, optional): solver based on BaseSolver used to solve the problem.
|
|
64
|
+
If None, then a solver is utilized bases on the problem's properties. Defaults to None.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
NautiliError: when optimization of an epsilon constraint problem is not successful.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
tuple[dict[str, float], dict[str, float]]: a tuple of dicts, where the first dict are the lower bounds and the
|
|
71
|
+
second element the upper bounds, the key is the symbol of each objective.
|
|
72
|
+
"""
|
|
73
|
+
# If an objective is to be maximized, then the navigation point component of that objective should be
|
|
74
|
+
# multiplied by -1.
|
|
75
|
+
const_bounds = {
|
|
76
|
+
objective.symbol: -1 * navigation_point[objective.symbol]
|
|
77
|
+
if objective.maximize
|
|
78
|
+
else navigation_point[objective.symbol]
|
|
79
|
+
for objective in problem.objectives
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# if a solver creator was provided, use that, else, guess the best one
|
|
83
|
+
solver_init = guess_best_solver(problem) if solver is None else solver
|
|
84
|
+
|
|
85
|
+
lower_bounds = {}
|
|
86
|
+
upper_bounds = {}
|
|
87
|
+
for objective in problem.objectives:
|
|
88
|
+
# Lower bounds
|
|
89
|
+
eps_problem, target, _ = add_epsilon_constraints(
|
|
90
|
+
problem,
|
|
91
|
+
"target",
|
|
92
|
+
{f"{obj.symbol}": f"{obj.symbol}_eps" for obj in problem.objectives},
|
|
93
|
+
objective.symbol,
|
|
94
|
+
const_bounds,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# solve
|
|
98
|
+
solver = solver_init(eps_problem)
|
|
99
|
+
res = solver.solve(target)
|
|
100
|
+
|
|
101
|
+
if not res.success:
|
|
102
|
+
# could not optimize eps problem
|
|
103
|
+
msg = (
|
|
104
|
+
f"Optimizing the epsilon constrait problem for the objective "
|
|
105
|
+
f"{objective.symbol} was not successful. Reason: {res.message}"
|
|
106
|
+
)
|
|
107
|
+
raise NautiliError(msg)
|
|
108
|
+
|
|
109
|
+
lower_bound = res.optimal_objectives[objective.symbol]
|
|
110
|
+
|
|
111
|
+
if isinstance(lower_bound, list):
|
|
112
|
+
lower_bound = lower_bound[0]
|
|
113
|
+
|
|
114
|
+
# solver upper bounds
|
|
115
|
+
# the lower bounds is set as in the NAUTILUS method, e.g., taken from
|
|
116
|
+
# the current iteration/navigation point
|
|
117
|
+
if isinstance(navigation_point[objective.symbol], list):
|
|
118
|
+
# It should never be a list accordint to the type hints
|
|
119
|
+
upper_bound = navigation_point[objective.symbol][0]
|
|
120
|
+
else:
|
|
121
|
+
upper_bound = navigation_point[objective.symbol]
|
|
122
|
+
|
|
123
|
+
# add the lower and upper bounds logically depending whether an objective is to be maximized or minimized
|
|
124
|
+
lower_bounds[objective.symbol] = lower_bound if not objective.maximize else upper_bound
|
|
125
|
+
upper_bounds[objective.symbol] = upper_bound if not objective.maximize else lower_bound
|
|
126
|
+
|
|
127
|
+
return lower_bounds, upper_bounds
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def solve_reachable_solution(
|
|
131
|
+
problem: Problem,
|
|
132
|
+
group_improvement_direction: dict[str, float],
|
|
133
|
+
previous_nav_point: dict[str, float],
|
|
134
|
+
solver: BaseSolver | None = None,
|
|
135
|
+
solver_options: SolverOptions | None = None,
|
|
136
|
+
) -> SolverResults:
|
|
137
|
+
"""Calculates the reachable solution on the Pareto optimal front.
|
|
138
|
+
|
|
139
|
+
For the calculation to make sense in the context of NAUTILI, the reference point
|
|
140
|
+
should be bounded by the reachable bounds present at the navigation step the
|
|
141
|
+
reference point has been given.
|
|
142
|
+
|
|
143
|
+
In practice, the reachable solution is calculated by solving an achievement
|
|
144
|
+
scalarizing function.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
problem (Problem): the problem being solved.
|
|
148
|
+
group_improvement_direction (dict[str, float]): the improvement direction for the group.
|
|
149
|
+
previous_nav_point (dict[str, float]): the previous navigation point. The reachable solution found
|
|
150
|
+
is always better than the previous navigation point.
|
|
151
|
+
solver (BaseSolver | None, optional): solver based on BaseSolver used to solve the problem.
|
|
152
|
+
If None, then a solver is utilized bases on the problem's properties. Defaults to None.
|
|
153
|
+
solver_options (SolverOptions | None, optional): optional options passed
|
|
154
|
+
to the `solver`. Ignored if `solver` is `None`.
|
|
155
|
+
Defaults to None.
|
|
156
|
+
Returns:
|
|
157
|
+
SolverResults: the results of the projection.
|
|
158
|
+
"""
|
|
159
|
+
# check solver
|
|
160
|
+
init_solver = guess_best_solver(problem) if solver is None else solver
|
|
161
|
+
|
|
162
|
+
# create and add scalarization function
|
|
163
|
+
# previous_nav_point = objective_dict_to_numpy_array(problem, previous_nav_point).tolist()
|
|
164
|
+
# weights = objective_dict_to_numpy_array(problem, group_improvement_direction).tolist()
|
|
165
|
+
if problem.is_twice_differentiable:
|
|
166
|
+
problem_w_asf, target = add_asf_generic_diff(
|
|
167
|
+
problem,
|
|
168
|
+
symbol="asf",
|
|
169
|
+
reference_point=previous_nav_point,
|
|
170
|
+
weights=group_improvement_direction,
|
|
171
|
+
reference_point_aug=previous_nav_point,
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
problem_w_asf, target = add_asf_generic_nondiff(
|
|
175
|
+
problem,
|
|
176
|
+
symbol="asf",
|
|
177
|
+
reference_point=previous_nav_point,
|
|
178
|
+
weights=group_improvement_direction,
|
|
179
|
+
reference_point_aug=previous_nav_point,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Note: We do not solve the global problem. Instead, we solve this constrained problem:
|
|
183
|
+
problem_w_asf = problem_w_asf.add_constraints(
|
|
184
|
+
[
|
|
185
|
+
Constraint(
|
|
186
|
+
name=f"_const_{i+1}",
|
|
187
|
+
symbol=f"_const_{i+1}",
|
|
188
|
+
func=f"{obj.symbol}_min - {previous_nav_point[obj.symbol] * (-1 if obj.maximize else 1)}",
|
|
189
|
+
cons_type=ConstraintTypeEnum.LTE,
|
|
190
|
+
is_linear=obj.is_linear,
|
|
191
|
+
is_convex=obj.is_convex,
|
|
192
|
+
is_twice_differentiable=obj.is_twice_differentiable,
|
|
193
|
+
)
|
|
194
|
+
for i, obj in enumerate(problem_w_asf.objectives)
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# solve the problem
|
|
199
|
+
solver = init_solver(problem_w_asf)
|
|
200
|
+
return solver.solve(target)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def nautili_init(problem: Problem, solver: BaseSolver | None = None) -> NAUTILI_Response:
|
|
204
|
+
"""Initializes the NAUTILI method.
|
|
205
|
+
|
|
206
|
+
Creates the initial response of the method, which sets the navigation point to the nadir point
|
|
207
|
+
and the reachable bounds to the ideal and nadir points.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
problem (Problem): The problem to be solved.
|
|
211
|
+
solver (BaseSolver | None, optional): The solver to use. Defaults to None.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
NAUTILUS_Response: The initial response of the method.
|
|
215
|
+
"""
|
|
216
|
+
nav_point = get_nadir_dict(problem)
|
|
217
|
+
lower_bounds, upper_bounds = solve_reachable_bounds(problem, nav_point, solver=solver)
|
|
218
|
+
return NAUTILI_Response(
|
|
219
|
+
distance_to_front=0,
|
|
220
|
+
navigation_point=nav_point,
|
|
221
|
+
reachable_bounds={"lower_bounds": lower_bounds, "upper_bounds": upper_bounds},
|
|
222
|
+
reachable_solution=None,
|
|
223
|
+
reference_points=None,
|
|
224
|
+
improvement_directions=None,
|
|
225
|
+
group_improvement_direction=None,
|
|
226
|
+
step_number=0,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def nautili_step( # NOQA: PLR0913
|
|
231
|
+
problem: Problem,
|
|
232
|
+
steps_remaining: int,
|
|
233
|
+
step_number: int,
|
|
234
|
+
nav_point: dict,
|
|
235
|
+
solver: BaseSolver | None = None,
|
|
236
|
+
group_improvement_direction: dict | None = None,
|
|
237
|
+
reachable_solution: dict | None = None,
|
|
238
|
+
) -> NAUTILI_Response:
|
|
239
|
+
if group_improvement_direction is None and reachable_solution is None:
|
|
240
|
+
raise NautiliError("Either group_improvement_direction or reachable_solution must be provided.")
|
|
241
|
+
|
|
242
|
+
if group_improvement_direction is not None and reachable_solution is not None:
|
|
243
|
+
raise NautiliError("Only one of group_improvement_direction or reachable_solution should be provided.")
|
|
244
|
+
|
|
245
|
+
if group_improvement_direction is not None:
|
|
246
|
+
opt_result = solve_reachable_solution(problem, group_improvement_direction, nav_point, solver)
|
|
247
|
+
reachable_solution = opt_result.optimal_objectives
|
|
248
|
+
|
|
249
|
+
# update nav point
|
|
250
|
+
new_nav_point = calculate_navigation_point(problem, nav_point, reachable_solution, steps_remaining)
|
|
251
|
+
|
|
252
|
+
# update_bounds
|
|
253
|
+
lower_bounds, upper_bounds = solve_reachable_bounds(problem, new_nav_point, solver=solver)
|
|
254
|
+
|
|
255
|
+
distance = calculate_distance_to_front(problem, new_nav_point, reachable_solution)
|
|
256
|
+
|
|
257
|
+
return NAUTILI_Response(
|
|
258
|
+
step_number=step_number,
|
|
259
|
+
distance_to_front=distance,
|
|
260
|
+
reference_points=None,
|
|
261
|
+
improvement_directions=None,
|
|
262
|
+
group_improvement_direction=group_improvement_direction,
|
|
263
|
+
navigation_point=new_nav_point,
|
|
264
|
+
reachable_solution=reachable_solution,
|
|
265
|
+
reachable_bounds={"lower_bounds": lower_bounds, "upper_bounds": upper_bounds},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def nautili_all_steps(
|
|
270
|
+
problem: Problem,
|
|
271
|
+
steps_remaining: int,
|
|
272
|
+
reference_points: dict[str, dict[str, float]],
|
|
273
|
+
previous_responses: list[NAUTILI_Response],
|
|
274
|
+
solver: BaseSolver | None = None,
|
|
275
|
+
) -> [NAUTILI_Response]:
|
|
276
|
+
responses = []
|
|
277
|
+
nav_point = previous_responses[-1].navigation_point
|
|
278
|
+
step_number = previous_responses[-1].step_number + 1
|
|
279
|
+
first_iteration = True
|
|
280
|
+
reachable_solution = dict
|
|
281
|
+
|
|
282
|
+
# Calculate the improvement directions for each DM
|
|
283
|
+
|
|
284
|
+
improvement_directions = {}
|
|
285
|
+
for dm in reference_points:
|
|
286
|
+
if reference_points[dm] is None:
|
|
287
|
+
# If no reference point is provided, use the previous improvement direction
|
|
288
|
+
if previous_responses[-1].reference_points is None:
|
|
289
|
+
raise NautiliError("A reference point must be provided for the first iteration.")
|
|
290
|
+
if previous_responses[-1].improvement_directions is None:
|
|
291
|
+
raise NautiliError("An improvement direction must be provided for the first iteration.")
|
|
292
|
+
reference_points[dm] = previous_responses[-1].reference_points[dm]
|
|
293
|
+
improvement_directions[dm] = previous_responses[-1].improvement_directions[dm]
|
|
294
|
+
else:
|
|
295
|
+
# If a reference point is provided, calculate the improvement direction
|
|
296
|
+
# First, check if the reference point is better than the navigation point
|
|
297
|
+
max_multiplier = [-1 if obj.maximize else 1 for obj in problem.objectives]
|
|
298
|
+
reference_point = (
|
|
299
|
+
np.array([reference_points[dm][obj.symbol] for obj in problem.objectives]) * max_multiplier
|
|
300
|
+
)
|
|
301
|
+
nav_point_arr = np.array([nav_point[obj.symbol] for obj in problem.objectives]) * max_multiplier
|
|
302
|
+
improvement = nav_point_arr - reference_point
|
|
303
|
+
if np.any(improvement < 0):
|
|
304
|
+
msg = (
|
|
305
|
+
f"If a reference point is provided, it must be better than the navigation point.\n"
|
|
306
|
+
f" The reference point for {dm} is not better than the navigation point.\n"
|
|
307
|
+
f" Reference point: {reference_point}, Navigation point: {nav_point}\n"
|
|
308
|
+
f"Check objectives {np.where(improvement < 0)}"
|
|
309
|
+
)
|
|
310
|
+
raise NautiliError(msg)
|
|
311
|
+
# The improvement direction is in the true objective space
|
|
312
|
+
improvement_directions[dm] = improvement * max_multiplier
|
|
313
|
+
mean_improvement_direction = np.mean(list(improvement_directions.values()), axis=0)
|
|
314
|
+
group_improvement_direction = {
|
|
315
|
+
obj.symbol: mean_improvement_direction[i] for i, obj in enumerate(problem.objectives)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
while steps_remaining > 0:
|
|
319
|
+
if first_iteration:
|
|
320
|
+
response = nautili_step(
|
|
321
|
+
problem,
|
|
322
|
+
steps_remaining=steps_remaining,
|
|
323
|
+
step_number=step_number,
|
|
324
|
+
nav_point=nav_point,
|
|
325
|
+
group_improvement_direction=group_improvement_direction,
|
|
326
|
+
solver=solver,
|
|
327
|
+
)
|
|
328
|
+
first_iteration = False
|
|
329
|
+
else:
|
|
330
|
+
response = nautili_step(
|
|
331
|
+
problem,
|
|
332
|
+
steps_remaining=steps_remaining,
|
|
333
|
+
step_number=step_number,
|
|
334
|
+
nav_point=nav_point,
|
|
335
|
+
reachable_solution=reachable_solution,
|
|
336
|
+
solver=solver,
|
|
337
|
+
)
|
|
338
|
+
response.reference_points = reference_points
|
|
339
|
+
response.improvement_directions = improvement_directions
|
|
340
|
+
responses.append(response)
|
|
341
|
+
reachable_solution = response.reachable_solution
|
|
342
|
+
nav_point = response.navigation_point
|
|
343
|
+
steps_remaining -= 1
|
|
344
|
+
step_number += 1
|
|
345
|
+
return responses
|