desdeo 2.0.0__py3-none-any.whl → 2.1.1__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/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/__init__.py +6 -6
- desdeo/api/app.py +38 -28
- desdeo/api/config.py +65 -44
- desdeo/api/config.toml +23 -12
- desdeo/api/db.py +10 -8
- desdeo/api/db_init.py +12 -6
- desdeo/api/models/__init__.py +220 -20
- desdeo/api/models/archive.py +16 -27
- 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 +44 -6
- desdeo/api/models/problem.py +274 -64
- desdeo/api/models/session.py +4 -1
- desdeo/api/models/state.py +419 -52
- desdeo/api/models/user.py +7 -6
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NIMBUS.py +6 -3
- 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 +201 -4
- desdeo/api/routers/reference_point_method.py +20 -44
- desdeo/api/routers/session.py +50 -26
- desdeo/api/routers/user_authentication.py +180 -26
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +10 -4
- desdeo/api/tests/conftest.py +94 -2
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +550 -72
- desdeo/api/tests/test_routes.py +902 -43
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/database.py +28 -266
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +7 -0
- desdeo/emo/__init__.py +154 -24
- desdeo/emo/hooks/archivers.py +18 -2
- desdeo/emo/methods/EAs.py +128 -5
- desdeo/emo/methods/bases.py +9 -56
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/crossover.py +544 -42
- desdeo/emo/operators/evaluator.py +10 -14
- desdeo/emo/operators/generator.py +127 -24
- desdeo/emo/operators/mutation.py +212 -41
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +956 -214
- desdeo/emo/operators/termination.py +124 -16
- 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/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 +23 -1
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautilus_navigator.py +7 -6
- desdeo/mcdm/reference_point_method.py +70 -0
- desdeo/problem/__init__.py +16 -11
- desdeo/problem/evaluator.py +4 -5
- 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 +37 -12
- desdeo/problem/infix_parser.py +1 -16
- desdeo/problem/json_parser.py +7 -11
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +73 -55
- desdeo/problem/simulator_evaluator.py +65 -15
- desdeo/problem/testproblems/__init__.py +26 -11
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/forest_problem.py +77 -69
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/zdt_problem.py +4 -1
- desdeo/problem/utils.py +1 -1
- desdeo/tools/__init__.py +39 -21
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +22 -2
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/indicators_binary.py +107 -1
- desdeo/tools/indicators_unary.py +3 -16
- desdeo/tools/message.py +33 -2
- desdeo/tools/non_dominated_sorting.py +4 -3
- desdeo/tools/patterns.py +9 -7
- desdeo/tools/pyomo_solver_interfaces.py +49 -36
- desdeo/tools/reference_vectors.py +118 -351
- desdeo/tools/scalarization.py +340 -1413
- desdeo/tools/score_bands.py +491 -328
- desdeo/tools/utils.py +117 -49
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/utopia_problem.py +1 -1
- desdeo/utopia_stuff/utopia_problem_old.py +1 -1
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
- desdeo-2.1.1.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
- desdeo-2.0.0.dist-info/RECORD +0 -120
- /desdeo/api/utils/{logger.py → _logger.py} +0 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info/licenses}/LICENSE +0 -0
desdeo/adm/ADMAfsar.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
from desdeo.adm import BaseADM
|
|
2
|
+
import numpy as np
|
|
3
|
+
from desdeo.tools.reference_vectors import create_simplex
|
|
4
|
+
from desdeo.tools.non_dominated_sorting import non_dominated as nds
|
|
5
|
+
from desdeo.problem.schema import Problem
|
|
6
|
+
from desdeo.tools import payoff_table_method
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ADMAfsar(BaseADM):
|
|
10
|
+
"""
|
|
11
|
+
Adaptive Decision Maker using the AFSAR approach.
|
|
12
|
+
|
|
13
|
+
This ADM generates preferences for interactive evolutionary multiobjective optimization
|
|
14
|
+
based on the method described in:
|
|
15
|
+
|
|
16
|
+
> Afsar, B., Miettinen, K., & Ruiz, A. B. (2021).
|
|
17
|
+
> An Artificial Decision Maker for Comparing Reference Point Based Interactive Evolutionary Multiobjective Optimization Methods.
|
|
18
|
+
> In: Ishibuchi, H., et al. Evolutionary Multi-Criterion Optimization. EMO 2021. Lecture Notes in Computer Science, vol 12654. Springer, Cham.
|
|
19
|
+
|
|
20
|
+
> Afsar, B., Ruiz, A. B., & Miettinen, K. (2023).
|
|
21
|
+
> Comparing interactive evolutionary multiobjective optimization methods with an artificial decision maker.
|
|
22
|
+
> Complex & Intelligent Systems, Volume 9, pages 1165–1181. Springer.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
composite_front (list): Stores the composite front of solutions.
|
|
26
|
+
max_assigned_vector (int or None): Index of the vector with the maximum assigned solutions.
|
|
27
|
+
reference_vectors (np.ndarray): Array of reference vectors.
|
|
28
|
+
preference (dict): Current preference information.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
problem: Problem,
|
|
34
|
+
it_learning_phase: int,
|
|
35
|
+
it_decision_phase: int,
|
|
36
|
+
lattice_resolution: int = None,
|
|
37
|
+
number_of_vectors: int = None,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the artificial decision maker proposed by Afsar et al.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
problem (Problem): The optimization problem to solve.
|
|
44
|
+
it_learning_phase (int): Number of iterations for the learning phase.
|
|
45
|
+
it_decision_phase (int): Number of iterations for the decision phase.
|
|
46
|
+
lattice_resolution (int, optional): Lattice resolution for reference vectors.
|
|
47
|
+
number_of_vectors (int, optional): Number of reference vectors.
|
|
48
|
+
"""
|
|
49
|
+
self.true_ideal, self.true_nadir = payoff_table_method(problem)
|
|
50
|
+
problem = problem.update_ideal_and_nadir(
|
|
51
|
+
new_ideal=self.true_ideal, new_nadir=self.true_nadir
|
|
52
|
+
)
|
|
53
|
+
super().__init__(problem, it_learning_phase, it_decision_phase)
|
|
54
|
+
self.composite_front = []
|
|
55
|
+
self.max_assigned_vector = None
|
|
56
|
+
self.preference_type = "reference_point"
|
|
57
|
+
number_of_objectives = len(problem.objectives)
|
|
58
|
+
|
|
59
|
+
self.reference_vectors = create_simplex(
|
|
60
|
+
number_of_objectives, lattice_resolution, number_of_vectors
|
|
61
|
+
)
|
|
62
|
+
self.true_ideal, self.true_nadir = payoff_table_method(problem)
|
|
63
|
+
|
|
64
|
+
self.generate_initial_preference()
|
|
65
|
+
|
|
66
|
+
def generate_initial_preference(self):
|
|
67
|
+
"""
|
|
68
|
+
Generate the initial preference as a random point between the ideal and nadir points.
|
|
69
|
+
|
|
70
|
+
The preference is stored in self.preference as a numpy array.
|
|
71
|
+
"""
|
|
72
|
+
self.preference = np.array(
|
|
73
|
+
[
|
|
74
|
+
np.random.uniform(min_val, max_val)
|
|
75
|
+
for min_val, max_val in zip(
|
|
76
|
+
self.problem.get_ideal_point().values(),
|
|
77
|
+
self.problem.get_nadir_point().values(),
|
|
78
|
+
)
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def get_next_preference(self, *fronts, preference_type: str = "reference_point"):
|
|
83
|
+
"""
|
|
84
|
+
Generate the next preference based on the current phase and provided solution fronts.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
*fronts: One or more solution fronts (arrays) to be considered.
|
|
88
|
+
preference_type (str): The type of preference to generate.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
dict: The generated preference information.
|
|
92
|
+
"""
|
|
93
|
+
self.preference_type = preference_type
|
|
94
|
+
if len(self.composite_front) == 0:
|
|
95
|
+
self.composite_front = self.generate_composite_front(*fronts)
|
|
96
|
+
else:
|
|
97
|
+
self.composite_front = self.generate_composite_front(
|
|
98
|
+
self.composite_front, *fronts
|
|
99
|
+
)
|
|
100
|
+
ideal_point = self.composite_front.min(axis=0)
|
|
101
|
+
translated_front = self.translate_front(self.composite_front, ideal_point)
|
|
102
|
+
normalized_front = self.normalize_front(self.composite_front, translated_front)
|
|
103
|
+
assigned_vectors = self.assign_vectors(normalized_front)
|
|
104
|
+
if self.iteration_counter < self.it_learning_phase:
|
|
105
|
+
self.preference = self.generate_preference_learning(
|
|
106
|
+
ideal_point, translated_front, assigned_vectors
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
if self.iteration_counter == self.it_learning_phase:
|
|
110
|
+
self.max_assigned_vector = self.get_max_assigned_vector(
|
|
111
|
+
assigned_vectors
|
|
112
|
+
)
|
|
113
|
+
self.preference = self.generate_preference_decision(
|
|
114
|
+
ideal_point,
|
|
115
|
+
translated_front,
|
|
116
|
+
assigned_vectors,
|
|
117
|
+
self.max_assigned_vector,
|
|
118
|
+
)
|
|
119
|
+
self.iteration_counter += 1
|
|
120
|
+
return self.preference
|
|
121
|
+
|
|
122
|
+
def assign_vectors(self, front):
|
|
123
|
+
"""
|
|
124
|
+
Assign each solution in the front to the closest reference vector using cosine similarity.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
front (np.ndarray): The normalized solution front.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
np.ndarray: Indices of the assigned reference vectors for each solution.
|
|
131
|
+
"""
|
|
132
|
+
cosine = np.dot(front, np.transpose(self.reference_vectors))
|
|
133
|
+
if cosine[np.where(cosine > 1)].size:
|
|
134
|
+
cosine[np.where(cosine > 1)] = 1
|
|
135
|
+
if cosine[np.where(cosine < 0)].size:
|
|
136
|
+
cosine[np.where(cosine < 0)] = 0
|
|
137
|
+
|
|
138
|
+
assigned_vectors = np.argmax(cosine, axis=1)
|
|
139
|
+
return assigned_vectors
|
|
140
|
+
|
|
141
|
+
def normalize_front(self, front, translated_front):
|
|
142
|
+
"""
|
|
143
|
+
Normalize the translated front so that each solution has unit length.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
front (np.ndarray): The original solution front.
|
|
147
|
+
translated_front (np.ndarray): The translated solution front.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
np.ndarray: The normalized solution front.
|
|
151
|
+
"""
|
|
152
|
+
translated_norm = np.linalg.norm(translated_front, axis=1)
|
|
153
|
+
translated_norm = np.repeat(
|
|
154
|
+
translated_norm, len(translated_front[0, :])
|
|
155
|
+
).reshape(len(front), len(front[0, :]))
|
|
156
|
+
|
|
157
|
+
translated_norm[translated_norm == 0] = np.finfo(float).eps
|
|
158
|
+
normalized_front = np.divide(translated_front, translated_norm)
|
|
159
|
+
return normalized_front
|
|
160
|
+
|
|
161
|
+
def generate_composite_front(self, *fronts):
|
|
162
|
+
"""
|
|
163
|
+
Generate the composite front by stacking and extracting the non-dominated solutions.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
*fronts: One or more solution fronts (arrays).
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
np.ndarray: The composite non-dominated front.
|
|
170
|
+
"""
|
|
171
|
+
_fronts = np.vstack(fronts)
|
|
172
|
+
cf = _fronts[nds(_fronts)]
|
|
173
|
+
return cf
|
|
174
|
+
|
|
175
|
+
def translate_front(self, front, ideal):
|
|
176
|
+
"""
|
|
177
|
+
Translate the front by subtracting the ideal point from each solution.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
front (np.ndarray): The solution front.
|
|
181
|
+
ideal (np.ndarray): The ideal point.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
np.ndarray: The translated front.
|
|
185
|
+
"""
|
|
186
|
+
translated_front = np.subtract(front, ideal)
|
|
187
|
+
return translated_front
|
|
188
|
+
|
|
189
|
+
def generate_preference_learning(
|
|
190
|
+
self, ideal_point, translated_front, assigned_vectors
|
|
191
|
+
):
|
|
192
|
+
"""
|
|
193
|
+
Generate preference information during the learning phase.
|
|
194
|
+
|
|
195
|
+
The preference is generated according to the selected preference type:
|
|
196
|
+
- 'reference_point': Returns a reference point.
|
|
197
|
+
- 'preferred_ranges': Returns a preferred range.
|
|
198
|
+
- 'preferred_solutions': Returns preferred solutions.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
ideal_point (np.ndarray): The ideal point.
|
|
202
|
+
translated_front (np.ndarray): The translated solution front.
|
|
203
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
np.ndarray: The generated preference information.
|
|
207
|
+
"""
|
|
208
|
+
if self.preference_type == "reference_point":
|
|
209
|
+
return self.generate_reference_point_learning(
|
|
210
|
+
ideal_point, translated_front, assigned_vectors
|
|
211
|
+
)
|
|
212
|
+
elif self.preference_type == "preferred_ranges":
|
|
213
|
+
return self.generate_ranges_learning(
|
|
214
|
+
ideal_point, translated_front, assigned_vectors
|
|
215
|
+
)
|
|
216
|
+
elif self.preference_type == "preferred_solutions":
|
|
217
|
+
return self.generate_preferred_solutions_learning(
|
|
218
|
+
ideal_point, translated_front, assigned_vectors
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Invalid preference type: {self.preference_type}. "
|
|
224
|
+
"Valid options are 'reference_point', 'preferred_ranges', 'preferred_solutions', or 'non_preferred_solutions'."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def generate_preference_decision(
|
|
228
|
+
self, ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
229
|
+
):
|
|
230
|
+
"""
|
|
231
|
+
Generate preference information during the decision phase.
|
|
232
|
+
|
|
233
|
+
The preference is generated according to the selected preference type:
|
|
234
|
+
- 'reference_point': Returns a reference point.
|
|
235
|
+
- 'preferred_ranges': Returns a preferred range.
|
|
236
|
+
- 'preferred_solutions': Returns preferred solutions.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
ideal_point (np.ndarray): The ideal point.
|
|
240
|
+
translated_front (np.ndarray): The translated solution front.
|
|
241
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
242
|
+
max_assigned_vector (int): Index of the reference vector with the maximum assigned solutions.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
np.ndarray: The generated preference information.
|
|
246
|
+
"""
|
|
247
|
+
if self.preference_type == "reference_point":
|
|
248
|
+
return self.generate_reference_point_decision(
|
|
249
|
+
ideal_point,
|
|
250
|
+
translated_front,
|
|
251
|
+
assigned_vectors,
|
|
252
|
+
max_assigned_vector,
|
|
253
|
+
)
|
|
254
|
+
elif self.preference_type == "preferred_ranges":
|
|
255
|
+
return self.generate_ranges_decision(
|
|
256
|
+
ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
257
|
+
)
|
|
258
|
+
elif self.preference_type == "preferred_solutions":
|
|
259
|
+
return self.generate_preferred_solutions_decision(
|
|
260
|
+
ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"Invalid preference type: {self.preference_type}. "
|
|
265
|
+
"Valid options are 'reference_point', 'preferred_ranges', 'preferred_solutions', or 'non_preferred_solutions'."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def get_max_assigned_vector(self, assigned_vectors):
|
|
269
|
+
"""
|
|
270
|
+
Find the reference vector with the maximum number of assigned solutions.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
np.ndarray: Indices of the reference vector(s) with the maximum assignments.
|
|
277
|
+
"""
|
|
278
|
+
number_assigned = np.bincount(assigned_vectors)
|
|
279
|
+
max_assigned_vector = np.atleast_1d(
|
|
280
|
+
np.squeeze(
|
|
281
|
+
np.where(
|
|
282
|
+
number_assigned
|
|
283
|
+
== np.max(number_assigned[np.nonzero(number_assigned)])
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
return max_assigned_vector
|
|
288
|
+
|
|
289
|
+
def generate_reference_point_learning(
|
|
290
|
+
self, ideal_point, translated_front, assigned_vectors
|
|
291
|
+
):
|
|
292
|
+
"""
|
|
293
|
+
Generate a reference point for the learning phase.
|
|
294
|
+
|
|
295
|
+
The reference point is based on the solution assigned to the reference vector with the minimum
|
|
296
|
+
number of assigned solutions and closest to the origin.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
ideal_point (np.ndarray): The ideal point.
|
|
300
|
+
translated_front (np.ndarray): The translated solution front.
|
|
301
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
np.array: The generated reference point.
|
|
305
|
+
"""
|
|
306
|
+
ideal_cf = ideal_point
|
|
307
|
+
translated_cf = translated_front
|
|
308
|
+
number_assigned = np.bincount(assigned_vectors)
|
|
309
|
+
min_assigned_vector = np.atleast_1d(
|
|
310
|
+
np.squeeze(
|
|
311
|
+
np.where(
|
|
312
|
+
number_assigned
|
|
313
|
+
== np.min(number_assigned[np.nonzero(number_assigned)])
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
sub_population_index = np.atleast_1d(
|
|
318
|
+
np.squeeze(np.where(assigned_vectors == min_assigned_vector[0]))
|
|
319
|
+
)
|
|
320
|
+
sub_population_fitness = translated_cf[sub_population_index]
|
|
321
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
322
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
323
|
+
)
|
|
324
|
+
minidx = np.where(
|
|
325
|
+
sub_pop_fitness_magnitude == np.nanmin(sub_pop_fitness_magnitude)
|
|
326
|
+
)
|
|
327
|
+
distance_selected = sub_pop_fitness_magnitude[minidx]
|
|
328
|
+
reference_point = (
|
|
329
|
+
distance_selected[0] * self.reference_vectors[min_assigned_vector[0]]
|
|
330
|
+
)
|
|
331
|
+
reference_point = np.squeeze(reference_point + ideal_cf)
|
|
332
|
+
return reference_point
|
|
333
|
+
|
|
334
|
+
def generate_reference_point_decision(
|
|
335
|
+
self, ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
336
|
+
):
|
|
337
|
+
"""
|
|
338
|
+
Generate a reference point for the decision phase.
|
|
339
|
+
|
|
340
|
+
The reference point is based on the solution assigned to the reference vector with the maximum
|
|
341
|
+
number of assigned solutions and closest to the origin.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
ideal_point (np.ndarray): The ideal point.
|
|
345
|
+
translated_front (np.ndarray): The translated solution front.
|
|
346
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
347
|
+
max_assigned_vector (int): Index of the reference vector with the maximum assigned solutions.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
dict: The generated reference point.
|
|
351
|
+
"""
|
|
352
|
+
ideal_cf = ideal_point
|
|
353
|
+
translated_cf = translated_front
|
|
354
|
+
sub_population_index = np.atleast_1d(
|
|
355
|
+
np.squeeze(np.where(assigned_vectors == max_assigned_vector))
|
|
356
|
+
)
|
|
357
|
+
sub_population_fitness = translated_cf[sub_population_index]
|
|
358
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
359
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
360
|
+
)
|
|
361
|
+
minidx = np.where(
|
|
362
|
+
sub_pop_fitness_magnitude == np.nanmin(sub_pop_fitness_magnitude)
|
|
363
|
+
)
|
|
364
|
+
distance_selected = sub_pop_fitness_magnitude[minidx]
|
|
365
|
+
reference_point = (
|
|
366
|
+
distance_selected[0] * self.reference_vectors[max_assigned_vector]
|
|
367
|
+
)
|
|
368
|
+
reference_point = np.squeeze(reference_point + ideal_cf)
|
|
369
|
+
return reference_point
|
|
370
|
+
|
|
371
|
+
def generate_ranges_learning(self, ideal_point, translated_front, assigned_vectors):
|
|
372
|
+
"""
|
|
373
|
+
Generate the preferred ranges for the learning phase.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
ideal_point (np.ndarray): The ideal point.
|
|
377
|
+
translated_front (np.ndarray): The translated solution front.
|
|
378
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
np.ndarray: an array of ranges.
|
|
382
|
+
"""
|
|
383
|
+
number_assigned = np.bincount(assigned_vectors)
|
|
384
|
+
min_assigned_vector = np.atleast_1d(
|
|
385
|
+
np.squeeze(
|
|
386
|
+
np.where(
|
|
387
|
+
number_assigned
|
|
388
|
+
== np.min(number_assigned[np.nonzero(number_assigned)])
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
sub_population_index = np.atleast_1d(
|
|
393
|
+
np.squeeze(np.where(assigned_vectors == min_assigned_vector[0]))
|
|
394
|
+
)
|
|
395
|
+
sub_population_fitness = translated_front[sub_population_index]
|
|
396
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
397
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
398
|
+
)
|
|
399
|
+
minidx = np.where(
|
|
400
|
+
sub_pop_fitness_magnitude == np.nanmin(sub_pop_fitness_magnitude)
|
|
401
|
+
)
|
|
402
|
+
distance_selected = sub_pop_fitness_magnitude[minidx]
|
|
403
|
+
reference_point = (
|
|
404
|
+
distance_selected[0] * self.reference_vectors[min_assigned_vector[0]]
|
|
405
|
+
)
|
|
406
|
+
distance = min(
|
|
407
|
+
np.linalg.norm(reference_point - i) for i in sub_population_fitness
|
|
408
|
+
)
|
|
409
|
+
reference_point = np.squeeze(reference_point + ideal_point)
|
|
410
|
+
temp = reference_point - distance
|
|
411
|
+
temp2 = reference_point + distance
|
|
412
|
+
|
|
413
|
+
true_ideal = np.array(list(self.problem.get_ideal_point().values()))
|
|
414
|
+
true_nadir = np.array(list(self.problem.get_nadir_point().values()))
|
|
415
|
+
|
|
416
|
+
for i in range(reference_point.shape[0]):
|
|
417
|
+
if reference_point[i] < true_ideal[i]:
|
|
418
|
+
reference_point[i] = true_ideal[i]
|
|
419
|
+
if reference_point[i] > true_nadir[i]:
|
|
420
|
+
reference_point[i] = true_nadir[i]
|
|
421
|
+
if temp[i] < true_ideal[i]:
|
|
422
|
+
temp[i] = true_ideal[i]
|
|
423
|
+
if temp[i] > true_nadir[i]:
|
|
424
|
+
temp[i] = true_nadir[i]
|
|
425
|
+
if temp2[i] < true_ideal[i]:
|
|
426
|
+
temp2[i] = true_ideal[i]
|
|
427
|
+
if temp2[i] > true_nadir[i]:
|
|
428
|
+
temp2[i] = true_nadir[i]
|
|
429
|
+
|
|
430
|
+
preferred_range = np.vstack((temp, temp2)).T
|
|
431
|
+
# TODO (giomara): return the reference point in some other place
|
|
432
|
+
return preferred_range
|
|
433
|
+
|
|
434
|
+
def generate_ranges_decision(
|
|
435
|
+
self, ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
436
|
+
):
|
|
437
|
+
"""
|
|
438
|
+
Generate the preferred ranges for the decision phase.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
ideal_point (np.ndarray): The ideal point.
|
|
442
|
+
translated_front (np.ndarray): The translated solution front.
|
|
443
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
444
|
+
max_assigned_vector (int): Index of the reference vector with the maximum assigned solutions.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
np.ndarray: an array of ranges.
|
|
448
|
+
"""
|
|
449
|
+
sub_population_index = np.atleast_1d(
|
|
450
|
+
np.squeeze(np.where(assigned_vectors == max_assigned_vector))
|
|
451
|
+
)
|
|
452
|
+
sub_population_fitness = translated_front[sub_population_index]
|
|
453
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
454
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
455
|
+
)
|
|
456
|
+
minidx = np.where(
|
|
457
|
+
sub_pop_fitness_magnitude == np.nanmin(sub_pop_fitness_magnitude)
|
|
458
|
+
)
|
|
459
|
+
distance_selected = sub_pop_fitness_magnitude[minidx]
|
|
460
|
+
reference_point = (
|
|
461
|
+
distance_selected[0] * self.reference_vectors[max_assigned_vector]
|
|
462
|
+
)
|
|
463
|
+
distance = min(
|
|
464
|
+
np.linalg.norm(reference_point - i) for i in sub_population_fitness
|
|
465
|
+
)
|
|
466
|
+
reference_point = np.squeeze(reference_point + ideal_point)
|
|
467
|
+
reference_point = np.squeeze(reference_point - distance)
|
|
468
|
+
temp = reference_point - distance
|
|
469
|
+
temp2 = reference_point + distance
|
|
470
|
+
|
|
471
|
+
true_ideal = np.array(list(self.problem.get_ideal_point().values()))
|
|
472
|
+
true_nadir = np.array(list(self.problem.get_nadir_point().values()))
|
|
473
|
+
|
|
474
|
+
for i in range(reference_point.shape[0]):
|
|
475
|
+
if reference_point[i] < true_ideal[i]:
|
|
476
|
+
reference_point[i] = true_ideal[i]
|
|
477
|
+
if reference_point[i] > true_nadir[i]:
|
|
478
|
+
reference_point[i] = true_nadir[i]
|
|
479
|
+
if temp[i] < true_ideal[i]:
|
|
480
|
+
temp[i] = true_ideal[i]
|
|
481
|
+
if temp[i] > true_nadir[i]:
|
|
482
|
+
temp[i] = true_nadir[i]
|
|
483
|
+
if temp2[i] < true_ideal[i]:
|
|
484
|
+
temp2[i] = true_ideal[i]
|
|
485
|
+
if temp2[i] > true_nadir[i]:
|
|
486
|
+
temp2[i] = true_nadir[i]
|
|
487
|
+
|
|
488
|
+
preferred_range = np.vstack((temp, temp2)).T
|
|
489
|
+
return preferred_range
|
|
490
|
+
|
|
491
|
+
def generate_preferred_solutions_learning(
|
|
492
|
+
self, ideal_point, translated_front, assigned_vectors
|
|
493
|
+
):
|
|
494
|
+
"""
|
|
495
|
+
Generate the preferred solutions during the learning phase.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
ideal_point (np.ndarray): The ideal point.
|
|
499
|
+
translated_front (np.ndarray): The translated solution front.
|
|
500
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
np.ndarray: The preferred solutions.
|
|
504
|
+
"""
|
|
505
|
+
number_assigned = np.bincount(assigned_vectors)
|
|
506
|
+
min_assigned_vector = np.atleast_1d(
|
|
507
|
+
np.squeeze(
|
|
508
|
+
np.where(
|
|
509
|
+
number_assigned
|
|
510
|
+
== np.min(number_assigned[np.nonzero(number_assigned)])
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
sub_population_index = np.atleast_1d(
|
|
515
|
+
np.squeeze(np.where(assigned_vectors == min_assigned_vector[0]))
|
|
516
|
+
)
|
|
517
|
+
sub_population_fitness = translated_front[sub_population_index]
|
|
518
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
519
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
520
|
+
)
|
|
521
|
+
solution_selected = sub_population_fitness
|
|
522
|
+
preferred_solution = np.squeeze(solution_selected + ideal_point)
|
|
523
|
+
|
|
524
|
+
return preferred_solution
|
|
525
|
+
|
|
526
|
+
def generate_preferred_solutions_decision(
|
|
527
|
+
self, ideal_point, translated_front, assigned_vectors, max_assigned_vector
|
|
528
|
+
):
|
|
529
|
+
"""
|
|
530
|
+
Generate the preferred solutions during the decision phase.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
ideal_point (np.ndarray): The ideal point.
|
|
534
|
+
translated_front (np.ndarray): The translated solution front.
|
|
535
|
+
assigned_vectors (np.ndarray): Indices of assigned reference vectors.
|
|
536
|
+
max_assigned_vector (int): Index of the reference vector with the maximum assigned solutions.
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
np.ndarray: The preferred solutions.
|
|
540
|
+
"""
|
|
541
|
+
sub_population_index = np.atleast_1d(
|
|
542
|
+
np.squeeze(np.where(assigned_vectors == max_assigned_vector))
|
|
543
|
+
)
|
|
544
|
+
sub_population_fitness = translated_front[sub_population_index]
|
|
545
|
+
sub_pop_fitness_magnitude = np.sqrt(
|
|
546
|
+
np.sum(np.power(sub_population_fitness, 2), axis=1)
|
|
547
|
+
)
|
|
548
|
+
minidx = np.argpartition(sub_pop_fitness_magnitude, 4)
|
|
549
|
+
solution_selected = sub_population_fitness[minidx[:4]]
|
|
550
|
+
preferred_solution = np.squeeze(solution_selected + ideal_point)
|
|
551
|
+
return preferred_solution
|