pymoo 0.6.1.2__cp312-cp312-win_amd64.whl → 0.6.1.5.dev0__cp312-cp312-win_amd64.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.
Potentially problematic release.
This version of pymoo might be problematic. Click here for more details.
- pymoo/algorithms/moo/age.py +13 -7
- pymoo/algorithms/moo/age2.py +49 -19
- pymoo/algorithms/moo/ctaea.py +2 -2
- pymoo/algorithms/moo/kgb.py +9 -9
- pymoo/algorithms/moo/nsga2.py +0 -4
- pymoo/algorithms/moo/nsga3.py +2 -2
- pymoo/algorithms/moo/pinsga2.py +370 -0
- pymoo/algorithms/moo/rnsga3.py +2 -2
- pymoo/algorithms/soo/nonconvex/es.py +3 -2
- pymoo/config.py +1 -1
- pymoo/core/algorithm.py +1 -1
- pymoo/core/individual.py +8 -7
- pymoo/core/replacement.py +5 -5
- pymoo/core/survival.py +1 -1
- pymoo/core/variable.py +9 -9
- pymoo/cython/calc_perpendicular_distance.cp312-win_amd64.pyd +0 -0
- pymoo/cython/calc_perpendicular_distance.cpp +27467 -0
- pymoo/cython/calc_perpendicular_distance.pyx +67 -0
- pymoo/cython/decomposition.cp312-win_amd64.pyd +0 -0
- pymoo/cython/decomposition.cpp +28877 -0
- pymoo/cython/decomposition.pyx +165 -0
- pymoo/cython/hv.cp312-win_amd64.pyd +0 -0
- pymoo/cython/hv.cpp +27559 -0
- pymoo/cython/hv.pyx +18 -0
- pymoo/cython/info.cp312-win_amd64.pyd +0 -0
- pymoo/cython/info.cpp +6653 -0
- pymoo/cython/info.pyx +5 -0
- pymoo/cython/mnn.cp312-win_amd64.pyd +0 -0
- pymoo/cython/mnn.cpp +30117 -0
- pymoo/cython/mnn.pyx +273 -0
- pymoo/cython/non_dominated_sorting.cp312-win_amd64.pyd +0 -0
- pymoo/cython/non_dominated_sorting.cpp +35256 -0
- pymoo/cython/non_dominated_sorting.pyx +645 -0
- pymoo/cython/pruning_cd.cp312-win_amd64.pyd +0 -0
- pymoo/cython/pruning_cd.cpp +29277 -0
- pymoo/cython/pruning_cd.pyx +197 -0
- pymoo/cython/stochastic_ranking.cp312-win_amd64.pyd +0 -0
- pymoo/cython/stochastic_ranking.cpp +27872 -0
- pymoo/cython/stochastic_ranking.pyx +49 -0
- pymoo/cython/vendor/hypervolume.cpp +1621 -0
- pymoo/docs.py +1 -1
- pymoo/gradient/grad_autograd.py +2 -2
- pymoo/operators/crossover/ox.py +1 -1
- pymoo/operators/selection/rnd.py +2 -2
- pymoo/operators/selection/tournament.py +5 -5
- pymoo/optimize.py +2 -2
- pymoo/problems/dynamic/df.py +4 -4
- pymoo/problems/single/traveling_salesman.py +1 -1
- pymoo/util/misc.py +2 -2
- pymoo/util/mnn.py +2 -2
- pymoo/util/nds/fast_non_dominated_sort.py +5 -3
- pymoo/util/nds/non_dominated_sorting.py +2 -2
- pymoo/util/normalization.py +5 -8
- pymoo/util/ref_dirs/energy.py +4 -2
- pymoo/util/ref_dirs/reduction.py +1 -1
- pymoo/util/reference_direction.py +3 -2
- pymoo/util/value_functions.py +719 -0
- pymoo/util/vf_dominator.py +99 -0
- pymoo/version.py +1 -1
- pymoo/visualization/heatmap.py +3 -3
- pymoo/visualization/pcp.py +1 -1
- pymoo/visualization/radar.py +1 -1
- {pymoo-0.6.1.2.dist-info → pymoo-0.6.1.5.dev0.dist-info}/METADATA +13 -14
- {pymoo-0.6.1.2.dist-info → pymoo-0.6.1.5.dev0.dist-info}/RECORD +67 -47
- {pymoo-0.6.1.2.dist-info → pymoo-0.6.1.5.dev0.dist-info}/WHEEL +1 -1
- {pymoo-0.6.1.2.dist-info → pymoo-0.6.1.5.dev0.dist-info/licenses}/LICENSE +0 -0
- {pymoo-0.6.1.2.dist-info → pymoo-0.6.1.5.dev0.dist-info}/top_level.txt +0 -0
pymoo/algorithms/moo/age.py
CHANGED
|
@@ -167,7 +167,13 @@ class AGEMOEASurvival(Survival):
|
|
|
167
167
|
p = self.compute_geometry(front, extreme, n)
|
|
168
168
|
|
|
169
169
|
nn = np.linalg.norm(front, p, axis=1)
|
|
170
|
-
|
|
170
|
+
# Replace very small norms with 1 to prevent division by zero
|
|
171
|
+
nn[nn < 1e-8] = 1
|
|
172
|
+
|
|
173
|
+
distances = self.pairwise_distances(front, p)
|
|
174
|
+
distances[distances < 1e-8] = 1e-8 # Replace very small entries to prevent division by zero
|
|
175
|
+
|
|
176
|
+
distances = distances / (nn[:, None])
|
|
171
177
|
|
|
172
178
|
neighbors = 2
|
|
173
179
|
remaining = np.arange(m)
|
|
@@ -209,7 +215,7 @@ class AGEMOEASurvival(Survival):
|
|
|
209
215
|
return p
|
|
210
216
|
|
|
211
217
|
@staticmethod
|
|
212
|
-
|
|
218
|
+
#@jit(nopython=True, fastmath=True)
|
|
213
219
|
def pairwise_distances(front, p):
|
|
214
220
|
m = np.shape(front)[0]
|
|
215
221
|
distances = np.zeros((m, m))
|
|
@@ -219,14 +225,14 @@ class AGEMOEASurvival(Survival):
|
|
|
219
225
|
return distances
|
|
220
226
|
|
|
221
227
|
@staticmethod
|
|
222
|
-
@jit(fastmath=True)
|
|
228
|
+
@jit(nopython=True, fastmath=True)
|
|
223
229
|
def minkowski_distances(A, B, p):
|
|
224
230
|
m1 = np.shape(A)[0]
|
|
225
231
|
m2 = np.shape(B)[0]
|
|
226
232
|
distances = np.zeros((m1, m2))
|
|
227
233
|
for i in range(m1):
|
|
228
234
|
for j in range(m2):
|
|
229
|
-
distances[i][j] = sum(np.abs(A[i] - B[j]) ** p) ** (1 / p)
|
|
235
|
+
distances[i][j] = np.sum(np.abs(A[i] - B[j]) ** p) ** (1 / p)
|
|
230
236
|
|
|
231
237
|
return distances
|
|
232
238
|
|
|
@@ -254,15 +260,15 @@ def find_corner_solutions(front):
|
|
|
254
260
|
return indexes
|
|
255
261
|
|
|
256
262
|
|
|
257
|
-
@jit(fastmath=True)
|
|
263
|
+
@jit(nopython=True, fastmath=True)
|
|
258
264
|
def point_2_line_distance(P, A, B):
|
|
259
|
-
d = np.zeros(P.shape[0])
|
|
265
|
+
d = np.zeros(P.shape[0], dtype=numba.float64)
|
|
260
266
|
|
|
261
267
|
for i in range(P.shape[0]):
|
|
262
268
|
pa = P[i] - A
|
|
263
269
|
ba = B - A
|
|
264
270
|
t = np.dot(pa, ba) / np.dot(ba, ba)
|
|
265
|
-
d[i] = sum((pa - t * ba) ** 2)
|
|
271
|
+
d[i] = np.sum((pa - t * ba) ** 2)
|
|
266
272
|
|
|
267
273
|
return d
|
|
268
274
|
|
pymoo/algorithms/moo/age2.py
CHANGED
|
@@ -64,48 +64,78 @@ class AGEMOEA2(GeneticAlgorithm):
|
|
|
64
64
|
self.tournament_type = 'comp_by_rank_and_crowding'
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
@jit(fastmath=True)
|
|
67
|
+
@jit(nopython=True, fastmath=True)
|
|
68
68
|
def project_on_manifold(point, p):
|
|
69
|
-
dist = sum(point[point > 0] ** p) ** (1/p)
|
|
69
|
+
dist = np.sum(point[point > 0] ** p) ** (1/p)
|
|
70
70
|
return np.multiply(point, 1 / dist)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
import numpy as np
|
|
74
|
+
|
|
75
|
+
|
|
73
76
|
def find_zero(point, n, precision):
|
|
74
77
|
x = 1
|
|
75
|
-
|
|
78
|
+
epsilon = 1e-10 # Small constant for regularization
|
|
76
79
|
past_value = x
|
|
80
|
+
max_float = np.finfo(np.float64).max # Maximum representable float value
|
|
81
|
+
log_max_float = np.log(max_float) # Logarithm of the maximum float
|
|
82
|
+
|
|
77
83
|
for i in range(0, 100):
|
|
78
84
|
|
|
79
|
-
# Original function
|
|
85
|
+
# Original function with regularization
|
|
80
86
|
f = 0.0
|
|
81
87
|
for obj_index in range(0, n):
|
|
82
88
|
if point[obj_index] > 0:
|
|
83
|
-
|
|
89
|
+
log_value = x * np.log(point[obj_index] + epsilon)
|
|
90
|
+
if log_value < log_max_float:
|
|
91
|
+
f += np.exp(log_value)
|
|
92
|
+
else:
|
|
93
|
+
return 1 # Handle overflow by returning a fallback value
|
|
84
94
|
|
|
85
|
-
f = np.log(f)
|
|
95
|
+
f = np.log(f) if f > 0 else 0 # Avoid log of non-positive numbers
|
|
86
96
|
|
|
87
|
-
# Derivative
|
|
97
|
+
# Derivative with regularization
|
|
88
98
|
numerator = 0
|
|
89
99
|
denominator = 0
|
|
90
100
|
for obj_index in range(0, n):
|
|
91
101
|
if point[obj_index] > 0:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
log_value = x * np.log(point[obj_index] + epsilon)
|
|
103
|
+
if log_value < log_max_float:
|
|
104
|
+
power_value = np.exp(log_value)
|
|
105
|
+
log_term = np.log(point[obj_index] + epsilon)
|
|
106
|
+
|
|
107
|
+
# Use logarithmic comparison to avoid overflow
|
|
108
|
+
if log_value + np.log(abs(log_term) + epsilon) < log_max_float:
|
|
109
|
+
result = power_value * log_term
|
|
110
|
+
numerator += result
|
|
111
|
+
denominator += power_value
|
|
112
|
+
else:
|
|
113
|
+
# Handle extreme cases by capping the result
|
|
114
|
+
numerator += max_float
|
|
115
|
+
denominator += power_value
|
|
116
|
+
else:
|
|
117
|
+
return 1 # Handle overflow by returning a fallback value
|
|
118
|
+
|
|
119
|
+
if denominator == 0 or np.isnan(denominator) or np.isinf(denominator):
|
|
120
|
+
return 1 # Handle division by zero or NaN
|
|
121
|
+
|
|
122
|
+
if np.isnan(numerator) or np.isinf(numerator):
|
|
123
|
+
return 1 # Handle invalid numerator
|
|
97
124
|
|
|
98
125
|
ff = numerator / denominator
|
|
99
126
|
|
|
100
|
-
# zero
|
|
127
|
+
if ff == 0: # Check for zero before division
|
|
128
|
+
return 1 # Handle by returning a fallback value
|
|
129
|
+
|
|
130
|
+
# Update x using Newton's method
|
|
101
131
|
x = x - f / ff
|
|
102
132
|
|
|
103
133
|
if abs(x - past_value) <= precision:
|
|
104
134
|
break
|
|
105
135
|
else:
|
|
106
|
-
|
|
136
|
+
past_value = x # Update current point
|
|
107
137
|
|
|
108
|
-
if isinstance(x, complex):
|
|
138
|
+
if isinstance(x, complex) or np.isinf(x) or np.isnan(x):
|
|
109
139
|
return 1
|
|
110
140
|
else:
|
|
111
141
|
return x
|
|
@@ -135,7 +165,7 @@ class AGEMOEA2Survival(AGEMOEASurvival):
|
|
|
135
165
|
return p
|
|
136
166
|
|
|
137
167
|
@staticmethod
|
|
138
|
-
@jit(fastmath=True)
|
|
168
|
+
@jit(nopython=True, fastmath=True)
|
|
139
169
|
def pairwise_distances(front, p):
|
|
140
170
|
m, n = front.shape
|
|
141
171
|
projected_front = front.copy()
|
|
@@ -148,7 +178,7 @@ class AGEMOEA2Survival(AGEMOEASurvival):
|
|
|
148
178
|
if 0.95 < p < 1.05:
|
|
149
179
|
for row in range(0, m - 1):
|
|
150
180
|
for column in range(row + 1, m):
|
|
151
|
-
distances[row][column] = sum(np.abs(projected_front[row] - projected_front[column]) ** 2) ** 0.5
|
|
181
|
+
distances[row][column] = np.sum(np.abs(projected_front[row] - projected_front[column]) ** 2) ** 0.5
|
|
152
182
|
|
|
153
183
|
else:
|
|
154
184
|
for row in range(0, m-1):
|
|
@@ -156,8 +186,8 @@ class AGEMOEA2Survival(AGEMOEASurvival):
|
|
|
156
186
|
mid_point = projected_front[row] * 0.5 + projected_front[column] * 0.5
|
|
157
187
|
mid_point = project_on_manifold(mid_point, p)
|
|
158
188
|
|
|
159
|
-
distances[row][column] = sum(np.abs(projected_front[row] - mid_point) ** 2) ** 0.5 + \
|
|
160
|
-
sum(np.abs(projected_front[column] - mid_point) ** 2) ** 0.5
|
|
189
|
+
distances[row][column] = np.sum(np.abs(projected_front[row] - mid_point) ** 2) ** 0.5 + \
|
|
190
|
+
np.sum(np.abs(projected_front[column] - mid_point) ** 2) ** 0.5
|
|
161
191
|
|
|
162
192
|
return distances + distances.T
|
|
163
193
|
|
pymoo/algorithms/moo/ctaea.py
CHANGED
|
@@ -14,7 +14,7 @@ from pymoo.operators.selection.tournament import TournamentSelection
|
|
|
14
14
|
from pymoo.util.display.multi import MultiObjectiveOutput
|
|
15
15
|
from pymoo.util.dominator import Dominator
|
|
16
16
|
from pymoo.util.function_loader import load_function
|
|
17
|
-
from pymoo.util.misc import has_feasible,
|
|
17
|
+
from pymoo.util.misc import has_feasible, random_permutations
|
|
18
18
|
from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
|
|
19
19
|
|
|
20
20
|
|
|
@@ -63,7 +63,7 @@ class RestrictedMating(TournamentSelection):
|
|
|
63
63
|
n_random = n_select * n_parents * self.pressure
|
|
64
64
|
n_perms = math.ceil(n_random / n_pop)
|
|
65
65
|
# get random permutations and reshape them
|
|
66
|
-
P =
|
|
66
|
+
P = random_permutations(n_perms, n_pop)[:n_random]
|
|
67
67
|
P = np.reshape(P, (n_select * n_parents, self.pressure))
|
|
68
68
|
if Pc <= Pd:
|
|
69
69
|
# Choose from DA
|
pymoo/algorithms/moo/kgb.py
CHANGED
|
@@ -24,13 +24,13 @@ class KGB(NSGA2):
|
|
|
24
24
|
c_size=13,
|
|
25
25
|
eps=0.0,
|
|
26
26
|
ps={},
|
|
27
|
-
|
|
27
|
+
perturb_dev=0.1,
|
|
28
28
|
save_ps=False,
|
|
29
29
|
**kwargs,
|
|
30
30
|
):
|
|
31
31
|
|
|
32
32
|
super().__init__(**kwargs)
|
|
33
|
-
self.
|
|
33
|
+
self.PERTURB_DEV = perturb_dev
|
|
34
34
|
self.PERC_DIVERSITY = perc_diversity
|
|
35
35
|
self.PERC_DETECT_CHANGE = perc_detect_change
|
|
36
36
|
self.EPS = eps
|
|
@@ -258,11 +258,11 @@ class KGB(NSGA2):
|
|
|
258
258
|
:param pop: Population to check and fix boundaries
|
|
259
259
|
:return: Population with corrected boundaries
|
|
260
260
|
"""
|
|
261
|
-
# check
|
|
261
|
+
# check whether numpy array or pymoo population is given
|
|
262
262
|
if isinstance(pop, Population):
|
|
263
263
|
pop = pop.get("X")
|
|
264
264
|
|
|
265
|
-
# check if any solution is outside
|
|
265
|
+
# check if any solution is outside the bounds
|
|
266
266
|
for individual in pop:
|
|
267
267
|
for i in range(len(individual)):
|
|
268
268
|
if individual[i] > self.problem.xu[i]:
|
|
@@ -281,7 +281,7 @@ class KGB(NSGA2):
|
|
|
281
281
|
# TODO: Check boundaries
|
|
282
282
|
random_pop = np.random.random((N_r, self.problem.n_var))
|
|
283
283
|
|
|
284
|
-
# check if any solution is outside
|
|
284
|
+
# check if any solution is outside the bounds
|
|
285
285
|
for individual in random_pop:
|
|
286
286
|
for i in range(len(individual)):
|
|
287
287
|
if individual[i] > self.problem.xu[i]:
|
|
@@ -341,16 +341,16 @@ class KGB(NSGA2):
|
|
|
341
341
|
X_test = self.random_strategy(self.nr_rand_solutions)
|
|
342
342
|
|
|
343
343
|
# introduce noise to vary previously useful solutions
|
|
344
|
-
noise = np.random.normal(0, self.
|
|
344
|
+
noise = np.random.normal(0, self.PERTURB_DEV, self.problem.n_var)
|
|
345
345
|
noisy_useful_history = np.asarray(pop_useful) + noise
|
|
346
346
|
|
|
347
|
-
# check
|
|
347
|
+
# check whether solutions are within bounds
|
|
348
348
|
noisy_useful_history = self.check_boundaries(noisy_useful_history)
|
|
349
349
|
|
|
350
350
|
# add noisy useful history to randomly generated solutions
|
|
351
351
|
X_test = np.vstack((X_test, noisy_useful_history))
|
|
352
352
|
|
|
353
|
-
# predict
|
|
353
|
+
# predict whether random solutions are useful or useless
|
|
354
354
|
Y_test = model.predict(X_test)
|
|
355
355
|
|
|
356
356
|
# create list of useful predicted solutions
|
|
@@ -391,7 +391,7 @@ class KGB(NSGA2):
|
|
|
391
391
|
# if there are still not enough solutions in init_pop randomly sample previously useful solutions directly without noise to init_pop
|
|
392
392
|
if len(init_pop) < self.pop_size:
|
|
393
393
|
|
|
394
|
-
# fill up init_pop with randomly sampled solutions from
|
|
394
|
+
# fill up init_pop with randomly sampled solutions from pop_useful
|
|
395
395
|
if len(pop_useful) >= self.pop_size - len(init_pop):
|
|
396
396
|
|
|
397
397
|
nr_sampled_pop_useful = self.pop_size - len(init_pop)
|
pymoo/algorithms/moo/nsga2.py
CHANGED
|
@@ -70,10 +70,6 @@ def binary_tournament(pop, P, algorithm, **kwargs):
|
|
|
70
70
|
class RankAndCrowdingSurvival(RankAndCrowding):
|
|
71
71
|
|
|
72
72
|
def __init__(self, nds=None, crowding_func="cd"):
|
|
73
|
-
warnings.warn(
|
|
74
|
-
"RankAndCrowdingSurvival is deprecated and will be removed in version 0.8.*; use RankAndCrowding operator instead, which supports several and custom crowding diversity metrics.",
|
|
75
|
-
DeprecationWarning, 2
|
|
76
|
-
)
|
|
77
73
|
super().__init__(nds, crowding_func)
|
|
78
74
|
|
|
79
75
|
# =========================================================================================================
|
pymoo/algorithms/moo/nsga3.py
CHANGED
|
@@ -212,7 +212,7 @@ def niching(pop, n_remaining, niche_count, niche_of_individuals, dist_to_niche):
|
|
|
212
212
|
# the minimum niche count
|
|
213
213
|
min_niche_count = next_niche_count.min()
|
|
214
214
|
|
|
215
|
-
# all niches with the minimum niche count (truncate
|
|
215
|
+
# all niches with the minimum niche count (truncate randomly if there are more niches than remaining individuals)
|
|
216
216
|
next_niches = next_niches_list[np.where(next_niche_count == min_niche_count)[0]]
|
|
217
217
|
next_niches = next_niches[np.random.permutation(len(next_niches))[:n_select]]
|
|
218
218
|
|
|
@@ -303,7 +303,7 @@ def get_extreme_points_c(F, ideal_point, extreme_points=None):
|
|
|
303
303
|
weights = np.eye(F.shape[1])
|
|
304
304
|
weights[weights == 0] = 1e6
|
|
305
305
|
|
|
306
|
-
# add the old extreme points to never
|
|
306
|
+
# add the old extreme points to never lose them for normalization
|
|
307
307
|
_F = F
|
|
308
308
|
if extreme_points is not None:
|
|
309
309
|
_F = np.concatenate([extreme_points, _F], axis=0)
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from pymoo.algorithms.base.genetic import GeneticAlgorithm
|
|
7
|
+
from pymoo.algorithms.moo.nsga2 import binary_tournament
|
|
8
|
+
from pymoo.docs import parse_doc_string
|
|
9
|
+
from pymoo.operators.crossover.sbx import SBX
|
|
10
|
+
from pymoo.operators.mutation.pm import PM
|
|
11
|
+
from pymoo.operators.sampling.rnd import FloatRandomSampling
|
|
12
|
+
from pymoo.operators.selection.tournament import TournamentSelection
|
|
13
|
+
from pymoo.operators.survival.rank_and_crowding import RankAndCrowding
|
|
14
|
+
from pymoo.termination.default import DefaultMultiObjectiveTermination
|
|
15
|
+
from pymoo.util import value_functions as mvf
|
|
16
|
+
from pymoo.util.display.multi import MultiObjectiveOutput
|
|
17
|
+
from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
|
|
18
|
+
from pymoo.util.reference_direction import select_points_with_maximum_distance
|
|
19
|
+
from pymoo.util.vf_dominator import VFDominator
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# =========================================================================================================
|
|
23
|
+
# Implementation
|
|
24
|
+
# =========================================================================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AutomatedDM(ABC):
|
|
28
|
+
|
|
29
|
+
def __init__(self, get_pairwise_ranks_func=None):
|
|
30
|
+
self.get_pairwise_ranks_func = get_pairwise_ranks_func
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def makeDecision(self, F):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def makePairwiseDecision(self, F):
|
|
37
|
+
|
|
38
|
+
dm = lambda F: self.makeDecision(F)
|
|
39
|
+
ranks = self.get_pairwise_ranks_func(F, 1, dm=dm)
|
|
40
|
+
|
|
41
|
+
return ranks
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PINSGA2(GeneticAlgorithm):
|
|
46
|
+
|
|
47
|
+
def __init__(self,
|
|
48
|
+
pop_size=100,
|
|
49
|
+
sampling=FloatRandomSampling(),
|
|
50
|
+
selection=TournamentSelection(func_comp=binary_tournament),
|
|
51
|
+
crossover=SBX(eta=15, prob=0.9),
|
|
52
|
+
mutation=PM(eta=20),
|
|
53
|
+
output=MultiObjectiveOutput(),
|
|
54
|
+
tau=10,
|
|
55
|
+
eta=4,
|
|
56
|
+
opt_method="trust-constr",
|
|
57
|
+
vf_type="poly",
|
|
58
|
+
eps_max=1000,
|
|
59
|
+
ranking_type='pairwise',
|
|
60
|
+
presi_signs=None,
|
|
61
|
+
automated_dm=None,
|
|
62
|
+
verbose=False,
|
|
63
|
+
**kwargs):
|
|
64
|
+
|
|
65
|
+
self.survival = RankAndCrowding(nds=NonDominatedSorting(dominator=VFDominator(self)))
|
|
66
|
+
|
|
67
|
+
super().__init__(
|
|
68
|
+
pop_size=pop_size,
|
|
69
|
+
sampling=sampling,
|
|
70
|
+
selection=selection,
|
|
71
|
+
crossover=crossover,
|
|
72
|
+
mutation=mutation,
|
|
73
|
+
survival=self.survival,
|
|
74
|
+
output=output,
|
|
75
|
+
advance_after_initial_infill=True,
|
|
76
|
+
**kwargs)
|
|
77
|
+
|
|
78
|
+
self.termination = DefaultMultiObjectiveTermination()
|
|
79
|
+
self.tournament_type = 'comp_by_dom_and_crowding'
|
|
80
|
+
|
|
81
|
+
self.ranking_type=ranking_type
|
|
82
|
+
self.presi_signs=presi_signs
|
|
83
|
+
|
|
84
|
+
self.vf_type = vf_type
|
|
85
|
+
self.opt_method = opt_method
|
|
86
|
+
self.tau = tau
|
|
87
|
+
self.eta = eta
|
|
88
|
+
self.eta_F = []
|
|
89
|
+
self.vf_res = None
|
|
90
|
+
self.v2 = None
|
|
91
|
+
self.vf_plot_flag = False
|
|
92
|
+
self.vf_plot = None
|
|
93
|
+
self.historical_F = None
|
|
94
|
+
self.prev_pop = None
|
|
95
|
+
self.fronts = []
|
|
96
|
+
self.eps_max = eps_max
|
|
97
|
+
|
|
98
|
+
self.verbose = verbose
|
|
99
|
+
|
|
100
|
+
if automated_dm is not None:
|
|
101
|
+
automated_dm.get_pairwise_ranks_func = self._get_pairwise_ranks
|
|
102
|
+
self.automated_dm = automated_dm
|
|
103
|
+
|
|
104
|
+
def _warn(self, msg):
|
|
105
|
+
if self.verbose:
|
|
106
|
+
sys.stderr.write(msg)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _prompt_for_ranks(F, presi_signs):
|
|
110
|
+
|
|
111
|
+
for (e, f) in enumerate(F):
|
|
112
|
+
print("Solution %d %s" % (e + 1, f * presi_signs))
|
|
113
|
+
|
|
114
|
+
dim = F.shape[0]
|
|
115
|
+
|
|
116
|
+
raw_ranks = input(f"Ranks (e.g., \"3, {dim}, ..., 1\" for 3rd best, {dim}th best, ..., 1st best): ")
|
|
117
|
+
|
|
118
|
+
if raw_ranks == "":
|
|
119
|
+
ranks = []
|
|
120
|
+
else:
|
|
121
|
+
ranks = [int(raw_rank) for raw_rank in raw_ranks.split() ]
|
|
122
|
+
|
|
123
|
+
return ranks
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _present_ranks(F, dm_ranks, presi_signs):
|
|
127
|
+
|
|
128
|
+
print("Solutions are ranked as:")
|
|
129
|
+
|
|
130
|
+
for (e, f) in enumerate(F):
|
|
131
|
+
print("Solution %d %s: Rank %d" % (e + 1, f * presi_signs, dm_ranks[e]))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _get_pairwise_ranks(F, presi_signs, dm=None):
|
|
136
|
+
|
|
137
|
+
if not dm:
|
|
138
|
+
|
|
139
|
+
dm = lambda F: input("\nWhich solution do you like best?\n" + \
|
|
140
|
+
f"[a] {F[0]}\n" + \
|
|
141
|
+
f"[b] {F[1]}\n" + \
|
|
142
|
+
"[c] These solutions are equivalent.\n--> " )
|
|
143
|
+
|
|
144
|
+
# initialize empty ranking
|
|
145
|
+
_ranks = []
|
|
146
|
+
for i, f in enumerate( F ):
|
|
147
|
+
|
|
148
|
+
# handle empty case, put first element in first place
|
|
149
|
+
if not _ranks:
|
|
150
|
+
_ranks.append( [i] )
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
inserted = False
|
|
154
|
+
|
|
155
|
+
# for each remaining elements, compare to all currently ranked elements
|
|
156
|
+
for j, group in enumerate( _ranks ):
|
|
157
|
+
|
|
158
|
+
# get pairwise preference from user
|
|
159
|
+
while True:
|
|
160
|
+
|
|
161
|
+
points_to_compare = np.array( [f*presi_signs, F[ group[0] ]*presi_signs] )
|
|
162
|
+
preference_raw = dm( points_to_compare )
|
|
163
|
+
|
|
164
|
+
preference = preference_raw.strip().lower()
|
|
165
|
+
|
|
166
|
+
if preference in ['a', 'b', 'c']:
|
|
167
|
+
break
|
|
168
|
+
print("Invalid input. Please enter 'a', 'b', or 'c'.")
|
|
169
|
+
|
|
170
|
+
# if better than currently ranked element place before that element
|
|
171
|
+
if preference == 'a':
|
|
172
|
+
_ranks.insert( j, [i] )
|
|
173
|
+
inserted = True
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
# if equal to currently ranked element place with that element
|
|
177
|
+
elif preference == 'c':
|
|
178
|
+
group.append( i )
|
|
179
|
+
inserted = True
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
# if found to be worse than all place at the end
|
|
183
|
+
if not inserted:
|
|
184
|
+
_ranks.append( [i] )
|
|
185
|
+
|
|
186
|
+
ranks = np.zeros ( len( F ), dtype=int )
|
|
187
|
+
|
|
188
|
+
for rank, group in enumerate( _ranks ):
|
|
189
|
+
for index in group:
|
|
190
|
+
ranks[index] = rank
|
|
191
|
+
|
|
192
|
+
return np.array( ranks ) + 1
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _get_ranks(F, presi_signs):
|
|
197
|
+
|
|
198
|
+
ranks_invalid = True
|
|
199
|
+
|
|
200
|
+
dim = F.shape[0]
|
|
201
|
+
|
|
202
|
+
print(f"Give each solution a ranking, with 1 being the highest score, and {dim} being the lowest score:")
|
|
203
|
+
|
|
204
|
+
ranks = PINSGA2._prompt_for_ranks(F, presi_signs)
|
|
205
|
+
|
|
206
|
+
while ranks_invalid:
|
|
207
|
+
|
|
208
|
+
fc = F.shape[0]
|
|
209
|
+
|
|
210
|
+
if len(ranks) > 0 and max(ranks) <= fc and min(ranks) >= 1:
|
|
211
|
+
|
|
212
|
+
ranks_invalid = False
|
|
213
|
+
|
|
214
|
+
else:
|
|
215
|
+
|
|
216
|
+
print("Invalid ranks given. Please try again")
|
|
217
|
+
|
|
218
|
+
ranks = PINSGA2._prompt_for_ranks(F, presi_signs)
|
|
219
|
+
|
|
220
|
+
return np.array(ranks);
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _reset_dm_preference(self):
|
|
224
|
+
|
|
225
|
+
self._warn("Back-tracking and removing DM preference from search.")
|
|
226
|
+
|
|
227
|
+
self.eta_F = []
|
|
228
|
+
self.vf_res = None
|
|
229
|
+
self.v2 = None
|
|
230
|
+
self.vf_plot_flag = False
|
|
231
|
+
self.vf_plot = None
|
|
232
|
+
self.pop = self.prev_pop
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _advance(self, infills=None, **kwargs):
|
|
236
|
+
|
|
237
|
+
super()._advance(infills=infills, **kwargs)
|
|
238
|
+
|
|
239
|
+
rank, F = self.pop.get("rank", "F")
|
|
240
|
+
|
|
241
|
+
self.fronts = rank
|
|
242
|
+
|
|
243
|
+
F = F[rank == 0]
|
|
244
|
+
|
|
245
|
+
if self.historical_F is not None:
|
|
246
|
+
self.historical_F = np.vstack((self.historical_F, F))
|
|
247
|
+
else:
|
|
248
|
+
self.historical_F = F
|
|
249
|
+
|
|
250
|
+
to_find = self.eta if F.shape[0] >= self.eta else F.shape[0]
|
|
251
|
+
|
|
252
|
+
if self.presi_signs is None:
|
|
253
|
+
self.presi_signs = np.ones(F.shape[1])
|
|
254
|
+
|
|
255
|
+
# Eta is the number of solutions displayed to the DM
|
|
256
|
+
eta_F_indices = select_points_with_maximum_distance(F, to_find)
|
|
257
|
+
|
|
258
|
+
self.eta_F = F[eta_F_indices]
|
|
259
|
+
self.eta_F = self.eta_F[self.eta_F[:,0].argsort()]
|
|
260
|
+
|
|
261
|
+
# Remove duplicate rows
|
|
262
|
+
self.eta_F = np.unique(self.eta_F, axis=0)
|
|
263
|
+
|
|
264
|
+
# A frozen view of the optimization each tau generations
|
|
265
|
+
self.paused_F = F
|
|
266
|
+
|
|
267
|
+
# Record the previous population in case we need to back track
|
|
268
|
+
self.prev_pop = self.pop
|
|
269
|
+
|
|
270
|
+
dm_time = self.n_gen % self.tau == 0
|
|
271
|
+
|
|
272
|
+
# Check whether we have more than one solution
|
|
273
|
+
if dm_time and len(self.eta_F) < 2:
|
|
274
|
+
|
|
275
|
+
self._warn("Population only contains one non-dominated solution. ")
|
|
276
|
+
|
|
277
|
+
self._reset_dm_preference()
|
|
278
|
+
|
|
279
|
+
elif dm_time:
|
|
280
|
+
|
|
281
|
+
# Check if the DM is a machine or a human
|
|
282
|
+
if self.automated_dm is None:
|
|
283
|
+
|
|
284
|
+
# Human DM
|
|
285
|
+
if self.ranking_type == "absolute":
|
|
286
|
+
dm_ranks = PINSGA2._get_ranks(self.eta_F, self.presi_signs)
|
|
287
|
+
elif self.ranking_type == "pairwise":
|
|
288
|
+
dm_ranks = PINSGA2._get_pairwise_ranks(self.eta_F, self.presi_signs)
|
|
289
|
+
PINSGA2._present_ranks(self.eta_F, dm_ranks, self.presi_signs)
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError("Invalid ranking type [%s] given." % self.ranking_type)
|
|
292
|
+
else:
|
|
293
|
+
|
|
294
|
+
# Automated DM
|
|
295
|
+
if self.ranking_type == "absolute":
|
|
296
|
+
dm_ranks = self.automated_dm.makeDecision(self.eta_F)
|
|
297
|
+
elif self.ranking_type == "pairwise":
|
|
298
|
+
dm_ranks = self.automated_dm.makePairwiseDecision(self.eta_F)
|
|
299
|
+
else:
|
|
300
|
+
raise ValueError("Invalid ranking type [%s] given." % self.ranking_type)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if len(set(rank)) == 0:
|
|
305
|
+
|
|
306
|
+
self._warn("No preference between any two points provided.")
|
|
307
|
+
|
|
308
|
+
self._reset_dm_preference()
|
|
309
|
+
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
eta_F = self.eta_F
|
|
313
|
+
|
|
314
|
+
while eta_F.shape[0] > 1:
|
|
315
|
+
|
|
316
|
+
if self.vf_type == "linear":
|
|
317
|
+
|
|
318
|
+
vf_res = mvf.create_linear_vf(eta_F * -1,
|
|
319
|
+
dm_ranks.tolist(),
|
|
320
|
+
eps_max=self.eps_max,
|
|
321
|
+
method=self.opt_method)
|
|
322
|
+
|
|
323
|
+
elif self.vf_type == "poly":
|
|
324
|
+
|
|
325
|
+
vf_res = mvf.create_poly_vf(eta_F * -1,
|
|
326
|
+
dm_ranks.tolist(),
|
|
327
|
+
eps_max=self.eps_max,
|
|
328
|
+
method=self.opt_method)
|
|
329
|
+
|
|
330
|
+
else:
|
|
331
|
+
|
|
332
|
+
raise ValueError("Value function %s not supported" % self.vf_type)
|
|
333
|
+
|
|
334
|
+
# check if we were able to model the VF
|
|
335
|
+
if vf_res.fit:
|
|
336
|
+
|
|
337
|
+
self.vf_res = vf_res
|
|
338
|
+
self.vf_plot_flag = True
|
|
339
|
+
self.v2 = self.vf_res.vf(eta_F[dm_ranks[1] - 1] * -1).item()
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
else:
|
|
343
|
+
|
|
344
|
+
# If we didn't the model, try to remove the least preferred point and try to refit
|
|
345
|
+
self._warn("Could not fit a function to the DM preference")
|
|
346
|
+
|
|
347
|
+
if eta_F.shape[0] == 2:
|
|
348
|
+
|
|
349
|
+
# If not, reset and use normal domination
|
|
350
|
+
self._warn("Removing DM preference")
|
|
351
|
+
self._reset_dm_preference()
|
|
352
|
+
break
|
|
353
|
+
|
|
354
|
+
else:
|
|
355
|
+
|
|
356
|
+
self._warn("Removing the second best preferred solution from the fit.")
|
|
357
|
+
|
|
358
|
+
# ranks start at 1, not zero
|
|
359
|
+
rank_to_remove = dm_ranks[1]
|
|
360
|
+
eta_F = np.delete(eta_F, rank_to_remove - 1, axis=0)
|
|
361
|
+
|
|
362
|
+
dm_ranks = np.concatenate(([dm_ranks[0]], dm_ranks[2:]))
|
|
363
|
+
|
|
364
|
+
# update the ranks, since we just removed one
|
|
365
|
+
dm_ranks[dm_ranks > rank_to_remove] = dm_ranks[dm_ranks > rank_to_remove] - 1
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
parse_doc_string(PINSGA2.__init__)
|
|
369
|
+
|
|
370
|
+
|
pymoo/algorithms/moo/rnsga3.py
CHANGED
|
@@ -176,7 +176,7 @@ def get_ref_dirs_from_points(ref_point, ref_dirs, mu=0.1):
|
|
|
176
176
|
Das-Dennis points around the projection of user points on the Das-Dennis hyperplane
|
|
177
177
|
:param ref_point: List of user specified reference points
|
|
178
178
|
:param n_obj: Number of objectives to consider
|
|
179
|
-
:param mu: Shrinkage factor (0-1), Smaller =
|
|
179
|
+
:param mu: Shrinkage factor (0-1), Smaller = tighter convergence, Larger= larger convergence
|
|
180
180
|
:return: Set of reference points
|
|
181
181
|
"""
|
|
182
182
|
|
|
@@ -232,7 +232,7 @@ def line_plane_intersection(l0, l1, p0, p_no, epsilon=1e-6):
|
|
|
232
232
|
# if 'fac' is between (0 - 1) the point intersects with the segment.
|
|
233
233
|
# otherwise:
|
|
234
234
|
# < 0.0: behind p0.
|
|
235
|
-
# > 1.0:
|
|
235
|
+
# > 1.0: in front of p1.
|
|
236
236
|
w = p0 - l0
|
|
237
237
|
d = np.dot(w, p_no) / dot
|
|
238
238
|
l = l * d
|