Skip to content

Commit d6f35c9

Browse files
author
Julian Blank
committed
Bug Fix of Verbose when pareto front is not known
1 parent 9b808e6 commit d6f35c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+16536
-137
lines changed

TODO

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22
# TODO
33

44

5+
- Algorithm workflow - initialize, run, finalize -- solve does everything
6+
57
- Write test suite for all algorithms (many problems just run once - no crash should occur)
8+
9+
- Infill Criteria Class and Mating inherits from it
10+
611
- Add global tests of all optimization methods
7-
- Elementwise function evaluation gradient is not working yet
12+
813
- Report None of infeasible or feasible
914

15+
- 1+1 and archive
16+
1017

1118

1219

pymoo/algorithms/genetic_algorithm.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(self,
6868
if self.n_offsprings is None:
6969
self.n_offsprings = pop_size
7070

71-
# other run specific data updated whenever solve is called - to share them in all methods
71+
# other run specific data updated whenever solve is called - to share them in all algorithms
7272
self.n_gen = None
7373
self.pop = None
7474
self.off = None
@@ -84,7 +84,7 @@ def _initialize(self):
8484
if isinstance(self.sampling, np.ndarray):
8585
pop = pop.new("X", self.sampling)
8686
else:
87-
pop = self.sampling.do(self.problem, pop, self.pop_size, algorithm=self)
87+
pop = self.sampling.do(self.problem, self.pop_size, pop=pop, algorithm=self)
8888

8989
# in case the initial population was not evaluated
9090
if np.any(pop.collect(lambda ind: ind.F is None, as_numpy_array=True)):
@@ -147,9 +147,10 @@ def _mating(self, pop):
147147
is_duplicate = self.eliminate_duplicates(_off, pop, off, algorithm=self)
148148
_off = _off[np.logical_not(is_duplicate)]
149149

150-
# if more offsprings than necessary - truncate them
151-
if len(_off) > self.n_offsprings - len(off):
152-
I = np.random.permutation(self.n_offsprings - len(off))
150+
# if more offsprings than necessary - truncate them randomly
151+
if len(off) + len(_off) > self.n_offsprings:
152+
n_remaining = self.n_offsprings - len(off)
153+
I = np.random.permutation(len(_off))[:n_remaining]
153154
_off = _off[I]
154155

155156
# add to the offsprings and increase the mating counter

pymoo/algorithms/so_global_optimization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _next(self):
131131
if not current_best and (has_finished or too_close_to_others):
132132
# find a suitable x0 which is far from other or has good expectations
133133
self.sampling.criterion = lambda X: vectorized_cdist(X, _X).min()
134-
X = self.sampling.do(self.problem, Population(), self.n_initial_samples).get("X")
134+
X = self.sampling.do(self.problem, self.n_initial_samples).get("X")
135135

136136
# distance in x space to other existing points
137137
x_dist = vectorized_cdist(X, _X, func_dist=norm_euclidean_distance(self.problem)).min(axis=1)
Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22
from scipy.spatial import cKDTree
33

4-
from pymoo.model.decision_making import DecisionMaking, normalize
4+
from pymoo.model.decision_making import DecisionMaking, normalize, find_outliers_upper_tail, NeighborFinder
55

66

77
class HighTradeoffPoints(DecisionMaking):
@@ -11,52 +11,32 @@ def __init__(self, epsilon=0.125, **kwargs) -> None:
1111
self.epsilon = epsilon
1212

1313
def _do(self, F, **kwargs):
14-
1514
n, m = F.shape
1615

1716
if self.normalize:
1817
F = normalize(F, self.ideal_point, self.nadir_point, estimate_bounds_if_none=True)
1918

20-
tree = cKDTree(F)
19+
neighbors_finder = NeighborFinder(F, epsilon=0.125, n_min_neigbors="auto", consider_2d=False)
2120

2221
mu = np.full(n, - np.inf)
2322

2423
# for each solution in the set calculate the least amount of improvement per unit deterioration
2524
for i in range(n):
2625

2726
# for each neighbour in a specific radius of that solution
28-
neighbours = tree.query_ball_point([F[i]], self.epsilon).tolist()[0]
29-
30-
# consider at least m+1 neighbours - if not found force it
31-
if len(neighbours) < 2 * m + 1:
32-
neighbours = tree.query([F[i]], k=m + 1)[1].tolist()[0]
27+
neighbors = neighbors_finder.find(i)
3328

3429
# calculate the trade-off to all neighbours
35-
diff = F[neighbours] - F[i]
30+
diff = F[neighbors] - F[i]
31+
32+
# calculate sacrifice and gain
33+
sacrifice = np.maximum(0, diff).sum(axis=1)
34+
gain = np.maximum(0, -diff).sum(axis=1)
3635

3736
np.warnings.filterwarnings('ignore')
38-
tradeoff = np.maximum(0, diff).sum(axis=1) / np.maximum(0, -diff).sum(axis=1)
37+
tradeoff = sacrifice / gain
3938

4039
# otherwise find the one with the smalled one
4140
mu[i] = np.nanmin(tradeoff)
4241

43-
# remove values that are nan
44-
I = np.where(np.logical_not(np.isnan(mu)))[0]
45-
mu = mu[I]
46-
47-
# calculate mean and sigma
48-
mean, sigma = mu.mean(), mu.std()
49-
50-
# calculate the deviation in terms of sigma
51-
deviation = (mu - mean) / sigma
52-
53-
# 2 * sigma is considered as an outlier
54-
S = I[np.where(deviation >= 2)[0]]
55-
56-
if len(S) == 0 and deviation.max() > 1:
57-
S = I[np.argmax(mu)]
58-
59-
if len(S) == 0:
60-
return None
61-
else:
62-
return S
42+
return find_outliers_upper_tail(mu)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import numpy as np
2+
3+
from pymoo.model.decision_making import DecisionMaking, normalize, NeighborFinder, find_outliers_upper_tail
4+
5+
6+
class HighTradeoffPointsInverted(DecisionMaking):
7+
8+
def __init__(self, **kwargs) -> None:
9+
super().__init__(**kwargs)
10+
11+
def _do(self, F, **kwargs):
12+
13+
n, m = F.shape
14+
15+
if self.normalize:
16+
F = normalize(F, self.ideal_point, self.nadir_point, estimate_bounds_if_none=True)
17+
18+
neighbors_finder = NeighborFinder(F, n_min_neigbors="auto")
19+
20+
mu = np.full(n, - np.inf)
21+
22+
# for each solution in the set calculate the least amount of improvement per unit deterioration
23+
for i in range(n):
24+
25+
# neighbors to the current point
26+
neighbors = neighbors_finder.find(i)
27+
28+
# calculate the trade-off to all neighbours
29+
diff = F[neighbors] - F[i]
30+
31+
# calculate sacrifice and gain
32+
sacrifice = np.maximum(0, diff).sum(axis=1)
33+
gain = np.maximum(0, -diff).sum(axis=1)
34+
35+
np.warnings.filterwarnings('ignore')
36+
tradeoff = sacrifice / gain
37+
38+
# otherwise find the one with the smalled one
39+
mu[i] = np.nanmean(tradeoff)
40+
41+
return find_outliers_upper_tail(mu)
42+
43+
if __name__ == '__main__':
44+
45+
F = np.array([[2, 13], [3, 11], [7, 9], [9, 4], [12, 3]])
46+
#_, w = PseudoWeights(np.array([0.5, 0.5])).do(F, return_pseudo_weights=True)
47+
HighTradeoffPointsInverted(normalize=False).do(F)

pymoo/interface.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import numpy as np
1010

11-
from pymoo.algorithms.nsga2 import NSGA2
1211
from pymoo.model.algorithm import filter_optimum
1312
from pymoo.model.individual import Individual
1413
from pymoo.model.population import Population
@@ -24,13 +23,12 @@ def get_problem_func(n_var, xl, xu, type_var):
2423
class P(Problem):
2524
def __init__(self) -> None:
2625
super().__init__(n_var=n_var, n_obj=1, n_constr=0, xl=xl, xu=xu, type_var=type_var)
27-
2826
return P
2927

3028

31-
def sample(sampling, n_samples, n_var, xl=0, xu=1, type_var=np.double, **kwargs):
32-
problem = get_problem_func(n_var, xl, xu, type_var)(**kwargs)
33-
return sampling.do(problem, Population(), n_samples, **kwargs).get("X")
29+
def sample(sampling, n_samples, n_var, xl=0, xu=1, **kwargs):
30+
problem = get_problem_func(n_var, xl, xu, None)(**kwargs)
31+
return sampling.do(problem, n_samples, pop=None, **kwargs)
3432

3533

3634
def crossover(crossover, a, b, c=None, xl=0, xu=1, type_var=np.double, **kwargs):

pymoo/model/algorithm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def _solve(self, problem):
210210
# finalize the algorithm and do postprocessing of desired
211211
self.finalize()
212212

213-
# method that is called each iteration to call some methods regularly
213+
# method that is called each iteration to call some algorithms regularly
214214
def _each_iteration(self, D, first=False, **kwargs):
215215

216216
# display the output if defined by the algorithm

pymoo/model/decision_making.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
from scipy.spatial.ckdtree import cKDTree
23

34

45
class DecisionMaking:
@@ -13,7 +14,7 @@ def do(self, F, *args, **kwargs):
1314

1415

1516
def normalize(F, ideal_point=None, nadir_point=None, estimate_bounds_if_none=True, return_bounds=False):
16-
N = np.copy(F)
17+
N = F.astype(np.float)
1718

1819
if estimate_bounds_if_none:
1920
if ideal_point is None:
@@ -43,3 +44,91 @@ def normalize(F, ideal_point=None, nadir_point=None, estimate_bounds_if_none=Tru
4344
return N, norm, ideal_point, nadir_point
4445
else:
4546
return N
47+
48+
49+
class NeighborFinder:
50+
51+
def __init__(self, N,
52+
epsilon=0.125,
53+
n_neighbors=None,
54+
n_min_neigbors=None,
55+
consider_2d=True):
56+
57+
super().__init__()
58+
self.N = N
59+
self.consider_2d = consider_2d
60+
61+
_, n_dim = N.shape
62+
63+
# at least find dimensionality times two neighbors - if enabled
64+
if n_min_neigbors == "auto":
65+
self.n_min_neigbors = 2 * n_dim
66+
67+
# disable the minimum neighbor variable
68+
else:
69+
self.n_min_neigbors = np.inf
70+
71+
# either choose epsilon
72+
self.epsilon = epsilon
73+
74+
# if none choose the number of neighbors
75+
self.n_neighbors = n_neighbors
76+
77+
if self.N.shape[1] == 1:
78+
raise Exception("At least 2 objectives must be provided.")
79+
80+
elif self.consider_2d and self.N.shape[1] == 2:
81+
self.min, self.max = N.min(), N.max()
82+
self.rank = np.argsort(N[:, 0])
83+
self.pos_in_rank = np.argsort(self.rank)
84+
85+
else:
86+
self.tree = cKDTree(N)
87+
88+
def find(self, i):
89+
90+
if self.consider_2d and self.N.shape[1] == 2:
91+
neighbours = []
92+
93+
pos = self.pos_in_rank[i]
94+
if pos > 0:
95+
neighbours.append(self.rank[pos - 1])
96+
if pos < len(self.N) - 1:
97+
neighbours.append(self.rank[pos + 1])
98+
99+
else:
100+
101+
# for each neighbour in a specific radius of that solution
102+
if self.epsilon is not None:
103+
neighbours = self.tree.query_ball_point([self.N[i]], self.epsilon).tolist()[0]
104+
elif self.n_neighbors is not None:
105+
neighbours = self.tree.query([self.N[i]], k=self.n_neighbors + 1)[1].tolist()[0]
106+
else:
107+
raise Exception("Either define epsilon or number of neighbors.")
108+
109+
# in case n_min_neigbors is enabled
110+
if len(neighbours) < self.n_min_neigbors:
111+
neighbours = self.tree.query([self.N[i]], k=self.n_min_neigbors + 1)[1].tolist()[0]
112+
113+
return neighbours
114+
115+
116+
def find_outliers_upper_tail(mu):
117+
118+
# remove values that are nan
119+
I = np.where(np.logical_and(np.logical_not(np.isnan(mu)), np.logical_not(np.isinf(mu))))[0]
120+
mu = mu[I]
121+
122+
# calculate mean and sigma
123+
mean, sigma = mu.mean(), mu.std()
124+
125+
# calculate the deviation in terms of sigma
126+
deviation = (mu - mean) / sigma
127+
128+
# 2 * sigma is considered as an outlier
129+
S = I[np.where(deviation >= 2)[0]]
130+
131+
if len(S) == 0 and deviation.max() > 1:
132+
S = I[[np.argmax(mu)]]
133+
134+
return S if len(S) > 0 else None

pymoo/model/problem.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,14 @@ def pareto_front(self, *args, use_cache=True, exception_if_failing=True, **kwarg
146146
"""
147147
if not use_cache or self._pareto_front is None:
148148
try:
149-
self._pareto_front = at_least_2d_array(self._calc_pareto_front(*args, **kwargs))
150-
self._ideal_point = np.min(self._pareto_front, axis=0)
151-
self._nadir_point = np.max(self._pareto_front, axis=0)
149+
pf = self._calc_pareto_front(*args, **kwargs)
150+
if pf is not None:
151+
self._pareto_front = at_least_2d_array(pf)
152+
self._ideal_point = np.min(self._pareto_front, axis=0)
153+
self._nadir_point = np.max(self._pareto_front, axis=0)
154+
else:
155+
self._pareto_front, self._ideal_point, self._nadir_point = None, None, None
156+
152157
except Exception as e:
153158
if exception_if_failing:
154159
raise e

pymoo/model/sampling.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from abc import abstractmethod
22

3+
from pymoo.model.population import Population
4+
35

46
class Sampling:
57
"""
@@ -9,12 +11,13 @@ class Sampling:
911
1012
"""
1113

12-
def do(self, problem, pop, n_samples, **kwargs):
14+
def do(self, problem, n_samples, pop=Population(), **kwargs):
1315
"""
1416
Sample new points with problem information if necessary.
1517
1618
Parameters
1719
----------
20+
1821
problem: class
1922
The problem to which points should be sampled. (lower and upper bounds, discrete, binary, ...)
2023
@@ -24,13 +27,22 @@ def do(self, problem, pop, n_samples, **kwargs):
2427
kwargs: class
2528
Any additional data that might be necessary. e.g. constants of the algorithm, ...
2629
30+
pop : Population
31+
The sampling results are stored in a population. The template of the population can be changed.
32+
If 'none' simply a numpy array is returned.
33+
34+
2735
Returns
2836
-------
2937
X : np.array
3038
Samples points in a two dimensional array
3139
3240
"""
3341
val = self._do(problem, n_samples, **kwargs)
42+
43+
if pop is None:
44+
return val
45+
3446
return pop.new("X", val)
3547

3648
@abstractmethod

0 commit comments

Comments
 (0)