pymoo 0.6.1.3__cp311-cp311-win_amd64.whl → 0.6.1.5.dev0__cp311-cp311-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.
Files changed (65) hide show
  1. pymoo/algorithms/moo/age.py +13 -7
  2. pymoo/algorithms/moo/age2.py +49 -19
  3. pymoo/algorithms/moo/ctaea.py +2 -2
  4. pymoo/algorithms/moo/kgb.py +9 -9
  5. pymoo/algorithms/moo/nsga3.py +2 -2
  6. pymoo/algorithms/moo/pinsga2.py +370 -0
  7. pymoo/algorithms/moo/rnsga3.py +2 -2
  8. pymoo/algorithms/soo/nonconvex/es.py +3 -2
  9. pymoo/config.py +1 -1
  10. pymoo/core/algorithm.py +1 -1
  11. pymoo/core/individual.py +8 -7
  12. pymoo/core/replacement.py +5 -5
  13. pymoo/core/survival.py +1 -1
  14. pymoo/core/variable.py +9 -9
  15. pymoo/cython/calc_perpendicular_distance.cp311-win_amd64.pyd +0 -0
  16. pymoo/cython/calc_perpendicular_distance.cpp +27467 -0
  17. pymoo/cython/calc_perpendicular_distance.pyx +67 -0
  18. pymoo/cython/decomposition.cp311-win_amd64.pyd +0 -0
  19. pymoo/cython/decomposition.cpp +28877 -0
  20. pymoo/cython/decomposition.pyx +165 -0
  21. pymoo/cython/hv.cp311-win_amd64.pyd +0 -0
  22. pymoo/cython/hv.cpp +27559 -0
  23. pymoo/cython/hv.pyx +18 -0
  24. pymoo/cython/info.cp311-win_amd64.pyd +0 -0
  25. pymoo/cython/info.cpp +6653 -0
  26. pymoo/cython/info.pyx +5 -0
  27. pymoo/cython/mnn.cp311-win_amd64.pyd +0 -0
  28. pymoo/cython/mnn.cpp +30117 -0
  29. pymoo/cython/mnn.pyx +273 -0
  30. pymoo/cython/non_dominated_sorting.cp311-win_amd64.pyd +0 -0
  31. pymoo/cython/non_dominated_sorting.cpp +35256 -0
  32. pymoo/cython/non_dominated_sorting.pyx +645 -0
  33. pymoo/cython/pruning_cd.cp311-win_amd64.pyd +0 -0
  34. pymoo/cython/pruning_cd.cpp +29277 -0
  35. pymoo/cython/pruning_cd.pyx +197 -0
  36. pymoo/cython/stochastic_ranking.cp311-win_amd64.pyd +0 -0
  37. pymoo/cython/stochastic_ranking.cpp +27872 -0
  38. pymoo/cython/stochastic_ranking.pyx +49 -0
  39. pymoo/cython/vendor/hypervolume.cpp +1621 -0
  40. pymoo/docs.py +1 -1
  41. pymoo/operators/crossover/ox.py +1 -1
  42. pymoo/operators/selection/rnd.py +2 -2
  43. pymoo/operators/selection/tournament.py +5 -5
  44. pymoo/optimize.py +2 -2
  45. pymoo/problems/dynamic/df.py +4 -4
  46. pymoo/problems/single/traveling_salesman.py +1 -1
  47. pymoo/util/misc.py +2 -2
  48. pymoo/util/mnn.py +2 -2
  49. pymoo/util/nds/fast_non_dominated_sort.py +5 -3
  50. pymoo/util/nds/non_dominated_sorting.py +2 -2
  51. pymoo/util/normalization.py +5 -8
  52. pymoo/util/ref_dirs/energy.py +4 -2
  53. pymoo/util/ref_dirs/reduction.py +1 -1
  54. pymoo/util/reference_direction.py +3 -2
  55. pymoo/util/value_functions.py +719 -0
  56. pymoo/util/vf_dominator.py +99 -0
  57. pymoo/version.py +1 -1
  58. pymoo/visualization/heatmap.py +3 -3
  59. pymoo/visualization/pcp.py +1 -1
  60. pymoo/visualization/radar.py +1 -1
  61. {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/METADATA +12 -13
  62. {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/RECORD +65 -45
  63. {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/WHEEL +1 -1
  64. {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info/licenses}/LICENSE +0 -0
  65. {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/top_level.txt +0 -0
@@ -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
- distances = self.pairwise_distances(front, p) / (nn[:, None])
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
- @jit(fastmath=True)
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
 
@@ -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
- f += np.power(point[obj_index], x)
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
- numerator = numerator + np.power(point[obj_index], x) * np.log(point[obj_index])
93
- denominator = denominator + np.power(point[obj_index], x)
94
-
95
- if denominator == 0:
96
- return 1
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 of function
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
- paste_value = x # update current point
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
 
@@ -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, random_permuations
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 = random_permuations(n_perms, n_pop)[:n_random]
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
@@ -24,13 +24,13 @@ class KGB(NSGA2):
24
24
  c_size=13,
25
25
  eps=0.0,
26
26
  ps={},
27
- pertub_dev=0.1,
27
+ perturb_dev=0.1,
28
28
  save_ps=False,
29
29
  **kwargs,
30
30
  ):
31
31
 
32
32
  super().__init__(**kwargs)
33
- self.PERTUB_DEV = pertub_dev
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 wether numpy array or pymoo population is given
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 of the bounds
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 of the bounds
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.PERTUB_DEV, self.problem.n_var)
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 wether solutions are within bounds
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 wether random solutions are useful or useless
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 pop_usefull
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)
@@ -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 if randomly if more niches than remaining individuals)
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 loose them for normalization
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
+
@@ -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 = tigher convergence, Larger= larger convergence
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: infront of p1.
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