desdeo 2.0.0__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.
Files changed (126) hide show
  1. desdeo/adm/ADMAfsar.py +551 -0
  2. desdeo/adm/ADMChen.py +414 -0
  3. desdeo/adm/BaseADM.py +119 -0
  4. desdeo/adm/__init__.py +11 -0
  5. desdeo/api/__init__.py +6 -6
  6. desdeo/api/app.py +38 -28
  7. desdeo/api/config.py +65 -44
  8. desdeo/api/config.toml +23 -12
  9. desdeo/api/db.py +10 -8
  10. desdeo/api/db_init.py +12 -6
  11. desdeo/api/models/__init__.py +220 -20
  12. desdeo/api/models/archive.py +16 -27
  13. desdeo/api/models/emo.py +128 -0
  14. desdeo/api/models/enautilus.py +69 -0
  15. desdeo/api/models/gdm/gdm_aggregate.py +139 -0
  16. desdeo/api/models/gdm/gdm_base.py +69 -0
  17. desdeo/api/models/gdm/gdm_score_bands.py +114 -0
  18. desdeo/api/models/gdm/gnimbus.py +138 -0
  19. desdeo/api/models/generic.py +104 -0
  20. desdeo/api/models/generic_states.py +401 -0
  21. desdeo/api/models/nimbus.py +158 -0
  22. desdeo/api/models/preference.py +44 -6
  23. desdeo/api/models/problem.py +274 -64
  24. desdeo/api/models/session.py +4 -1
  25. desdeo/api/models/state.py +419 -52
  26. desdeo/api/models/user.py +7 -6
  27. desdeo/api/models/utopia.py +25 -0
  28. desdeo/api/routers/_EMO.backup +309 -0
  29. desdeo/api/routers/_NIMBUS.py +6 -3
  30. desdeo/api/routers/emo.py +497 -0
  31. desdeo/api/routers/enautilus.py +237 -0
  32. desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
  33. desdeo/api/routers/gdm/gdm_base.py +420 -0
  34. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
  35. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
  36. desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
  37. desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
  38. desdeo/api/routers/generic.py +233 -0
  39. desdeo/api/routers/nimbus.py +705 -0
  40. desdeo/api/routers/problem.py +201 -4
  41. desdeo/api/routers/reference_point_method.py +20 -44
  42. desdeo/api/routers/session.py +50 -26
  43. desdeo/api/routers/user_authentication.py +180 -26
  44. desdeo/api/routers/utils.py +187 -0
  45. desdeo/api/routers/utopia.py +230 -0
  46. desdeo/api/schema.py +10 -4
  47. desdeo/api/tests/conftest.py +94 -2
  48. desdeo/api/tests/test_enautilus.py +330 -0
  49. desdeo/api/tests/test_models.py +550 -72
  50. desdeo/api/tests/test_routes.py +902 -43
  51. desdeo/api/utils/_database.py +263 -0
  52. desdeo/api/utils/database.py +28 -266
  53. desdeo/api/utils/emo_database.py +40 -0
  54. desdeo/core.py +7 -0
  55. desdeo/emo/__init__.py +154 -24
  56. desdeo/emo/hooks/archivers.py +18 -2
  57. desdeo/emo/methods/EAs.py +128 -5
  58. desdeo/emo/methods/bases.py +9 -56
  59. desdeo/emo/methods/templates.py +111 -0
  60. desdeo/emo/operators/crossover.py +544 -42
  61. desdeo/emo/operators/evaluator.py +10 -14
  62. desdeo/emo/operators/generator.py +127 -24
  63. desdeo/emo/operators/mutation.py +212 -41
  64. desdeo/emo/operators/scalar_selection.py +202 -0
  65. desdeo/emo/operators/selection.py +956 -214
  66. desdeo/emo/operators/termination.py +124 -16
  67. desdeo/emo/options/__init__.py +108 -0
  68. desdeo/emo/options/algorithms.py +435 -0
  69. desdeo/emo/options/crossover.py +164 -0
  70. desdeo/emo/options/generator.py +131 -0
  71. desdeo/emo/options/mutation.py +260 -0
  72. desdeo/emo/options/repair.py +61 -0
  73. desdeo/emo/options/scalar_selection.py +66 -0
  74. desdeo/emo/options/selection.py +127 -0
  75. desdeo/emo/options/templates.py +383 -0
  76. desdeo/emo/options/termination.py +143 -0
  77. desdeo/gdm/__init__.py +22 -0
  78. desdeo/gdm/gdmtools.py +45 -0
  79. desdeo/gdm/score_bands.py +114 -0
  80. desdeo/gdm/voting_rules.py +50 -0
  81. desdeo/mcdm/__init__.py +23 -1
  82. desdeo/mcdm/enautilus.py +338 -0
  83. desdeo/mcdm/gnimbus.py +484 -0
  84. desdeo/mcdm/nautilus_navigator.py +7 -6
  85. desdeo/mcdm/reference_point_method.py +70 -0
  86. desdeo/problem/__init__.py +5 -1
  87. desdeo/problem/external/__init__.py +18 -0
  88. desdeo/problem/external/core.py +356 -0
  89. desdeo/problem/external/pymoo_provider.py +266 -0
  90. desdeo/problem/external/runtime.py +44 -0
  91. desdeo/problem/infix_parser.py +2 -2
  92. desdeo/problem/pyomo_evaluator.py +25 -6
  93. desdeo/problem/schema.py +69 -48
  94. desdeo/problem/simulator_evaluator.py +65 -15
  95. desdeo/problem/testproblems/__init__.py +26 -11
  96. desdeo/problem/testproblems/benchmarks_server.py +120 -0
  97. desdeo/problem/testproblems/cake_problem.py +185 -0
  98. desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
  99. desdeo/problem/testproblems/forest_problem.py +77 -69
  100. desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
  101. desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
  102. desdeo/problem/testproblems/single_objective.py +289 -0
  103. desdeo/problem/testproblems/zdt_problem.py +4 -1
  104. desdeo/tools/__init__.py +39 -21
  105. desdeo/tools/desc_gen.py +22 -0
  106. desdeo/tools/generics.py +22 -2
  107. desdeo/tools/group_scalarization.py +3090 -0
  108. desdeo/tools/indicators_binary.py +107 -1
  109. desdeo/tools/indicators_unary.py +3 -16
  110. desdeo/tools/message.py +33 -2
  111. desdeo/tools/non_dominated_sorting.py +4 -3
  112. desdeo/tools/patterns.py +9 -7
  113. desdeo/tools/pyomo_solver_interfaces.py +48 -35
  114. desdeo/tools/reference_vectors.py +118 -351
  115. desdeo/tools/scalarization.py +340 -1413
  116. desdeo/tools/score_bands.py +491 -328
  117. desdeo/tools/utils.py +117 -49
  118. desdeo/tools/visualizations.py +67 -0
  119. desdeo/utopia_stuff/utopia_problem.py +1 -1
  120. desdeo/utopia_stuff/utopia_problem_old.py +1 -1
  121. {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/METADATA +46 -28
  122. desdeo-2.1.0.dist-info/RECORD +180 -0
  123. {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
  124. desdeo-2.0.0.dist-info/RECORD +0 -120
  125. /desdeo/api/utils/{logger.py → _logger.py} +0 -0
  126. {desdeo-2.0.0.dist-info → desdeo-2.1.0.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