pymoo 0.6.1.3__cp311-cp311-macosx_10_9_universal2.whl → 0.6.1.5.dev0__cp311-cp311-macosx_10_9_universal2.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/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.cpython-311-darwin.so +0 -0
- pymoo/cython/calc_perpendicular_distance.pyx +67 -0
- pymoo/cython/decomposition.cpython-311-darwin.so +0 -0
- pymoo/cython/decomposition.pyx +165 -0
- pymoo/cython/hv.cpython-311-darwin.so +0 -0
- pymoo/cython/hv.pyx +18 -0
- pymoo/cython/info.cpython-311-darwin.so +0 -0
- pymoo/cython/info.pyx +5 -0
- pymoo/cython/mnn.cpython-311-darwin.so +0 -0
- pymoo/cython/mnn.pyx +273 -0
- pymoo/cython/non_dominated_sorting.cpython-311-darwin.so +0 -0
- pymoo/cython/non_dominated_sorting.pyx +645 -0
- pymoo/cython/pruning_cd.cpython-311-darwin.so +0 -0
- pymoo/cython/pruning_cd.pyx +197 -0
- pymoo/cython/stochastic_ranking.cpython-311-darwin.so +0 -0
- pymoo/cython/stochastic_ranking.pyx +49 -0
- pymoo/cython/vendor/hypervolume.cpp +1621 -0
- pymoo/docs.py +1 -1
- 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.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/METADATA +12 -13
- {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/RECORD +328 -316
- {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/WHEEL +2 -1
- {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info/licenses}/LICENSE +0 -0
- {pymoo-0.6.1.3.dist-info → pymoo-0.6.1.5.dev0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pymoo
|
|
3
|
+
import scipy
|
|
4
|
+
from pymoo.optimize import minimize as moomin
|
|
5
|
+
from scipy.optimize import minimize as scimin
|
|
6
|
+
from pymoo.core.problem import Problem
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
from scipy.optimize import NonlinearConstraint
|
|
9
|
+
from scipy.optimize import Bounds
|
|
10
|
+
from pymoo.algorithms.soo.nonconvex.es import ES
|
|
11
|
+
from pymoo.algorithms.soo.nonconvex.ga import GA
|
|
12
|
+
import math
|
|
13
|
+
from operator import mul
|
|
14
|
+
from functools import reduce
|
|
15
|
+
from pymoo.termination.default import DefaultSingleObjectiveTermination
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Input 1: A list of non-dominated points
|
|
20
|
+
# Input 2: The ranking of the given non-dominated points
|
|
21
|
+
# Input 3: constraint function for optimizing the value func
|
|
22
|
+
# Input 4: the skeleton utility function that we're trying to optimize
|
|
23
|
+
def create_vf(P, ranks, ineq_constr, vf="linear", delta=0.1, eps_max=1000, method="trust-constr", verbose=False):
|
|
24
|
+
|
|
25
|
+
if vf == "linear":
|
|
26
|
+
return create_linear_vf(P, ranks, delta, eps_max, method, verbose)
|
|
27
|
+
elif vf == "poly":
|
|
28
|
+
return create_poly_vf(P, ranks, delta, eps_max, method, verbose)
|
|
29
|
+
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError("Value function '%d' not supported." % vf)
|
|
32
|
+
|
|
33
|
+
return lambda f_new: np.sum(f_new)
|
|
34
|
+
|
|
35
|
+
def create_poly_vf(P, ranks, delta=0.1, eps_max=1000, method="trust-constr", verbose=False):
|
|
36
|
+
|
|
37
|
+
if method == "trust-constr" or method == "SLSQP":
|
|
38
|
+
return create_vf_scipy_poly(P, ranks, delta, eps_max, method=method, verbose=verbose)
|
|
39
|
+
elif method == "ES":
|
|
40
|
+
return create_vf_pymoo_poly(P, ranks, delta, eps_max, method=method, verbose=verbose)
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError("Optimization method %s not supported" % method)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_linear_vf(P, ranks, delta=0.1, eps_max=1000, method="trust-constr"):
|
|
47
|
+
|
|
48
|
+
if method == "trust-constr" or method == "SLSQP":
|
|
49
|
+
return create_vf_scipy_linear(P, ranks, delta, eps_max, method)
|
|
50
|
+
elif method == "ES":
|
|
51
|
+
return create_vf_pymoo_linear(P, ranks, delta, eps_max, method)
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError("Optimization method %s not supported" % method)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_vf_scipy_poly(P, ranks, delta, eps_max, method="trust-constr", verbose=False):
|
|
57
|
+
|
|
58
|
+
# Gathering basic info
|
|
59
|
+
M = P.shape[1]
|
|
60
|
+
|
|
61
|
+
P_count = P.shape[0]
|
|
62
|
+
|
|
63
|
+
# Inequality constraints - check that each term of S in our obj is non-negative for each P
|
|
64
|
+
ineq_lb = [-np.inf] * (P_count*M)
|
|
65
|
+
ineq_ub = [0] * (P_count*M)
|
|
66
|
+
|
|
67
|
+
# Inequality constraints - check VF is monotonically increasing with user preference
|
|
68
|
+
ineq_lb += [-np.inf] * (P_count - 1)
|
|
69
|
+
ineq_ub += [0] * (P_count - 1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Equality constraints (Make sure all terms in VF add up to 1 per term of product)
|
|
73
|
+
for m in range(M):
|
|
74
|
+
ineq_lb.append(0)
|
|
75
|
+
ineq_ub.append(0)
|
|
76
|
+
|
|
77
|
+
P_sorted = _sort_P(P, ranks)
|
|
78
|
+
ranks.sort()
|
|
79
|
+
|
|
80
|
+
constr = NonlinearConstraint(_build_constr_poly(P_sorted, poly_vf, ranks, delta), ineq_lb, ineq_ub)
|
|
81
|
+
|
|
82
|
+
# Bounds on x
|
|
83
|
+
x_lb = []
|
|
84
|
+
x_ub = []
|
|
85
|
+
for m in range(M**2):
|
|
86
|
+
x_lb.append(0)
|
|
87
|
+
x_ub.append(1)
|
|
88
|
+
|
|
89
|
+
for m in range(M):
|
|
90
|
+
x_lb.append(-1000)
|
|
91
|
+
x_ub.append(1000)
|
|
92
|
+
|
|
93
|
+
x_lb.append(-1000)
|
|
94
|
+
x_ub.append(eps_max)
|
|
95
|
+
|
|
96
|
+
bounds = Bounds(x_lb, x_ub)
|
|
97
|
+
|
|
98
|
+
# Initial position
|
|
99
|
+
x0 = [1] * (M**2 + M + 1)
|
|
100
|
+
|
|
101
|
+
if method == 'trust-constr':
|
|
102
|
+
# The trust-constr method always finds the decision space linear
|
|
103
|
+
hess = lambda x: np.zeros((len(x0), len(x0)))
|
|
104
|
+
else:
|
|
105
|
+
hess = None
|
|
106
|
+
|
|
107
|
+
res = scimin(_obj_func,
|
|
108
|
+
x0,
|
|
109
|
+
constraints=constr,
|
|
110
|
+
bounds=bounds,
|
|
111
|
+
method=method,
|
|
112
|
+
hess=hess)
|
|
113
|
+
|
|
114
|
+
# package up results
|
|
115
|
+
vf = lambda P_in: poly_vf(P_in, res.x[0:-1])
|
|
116
|
+
params = res.x[0:-1]
|
|
117
|
+
epsilon = res.x[-1]
|
|
118
|
+
|
|
119
|
+
fit = _validate_vf(res, verbose)
|
|
120
|
+
|
|
121
|
+
return vfResults(vf, params, epsilon, fit)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_vf_scipy_linear(P, ranks, delta, eps_max, method="trust-constr", verbose=False):
|
|
125
|
+
|
|
126
|
+
# Gathering basic info
|
|
127
|
+
M = P.shape[1]
|
|
128
|
+
|
|
129
|
+
# Sort P
|
|
130
|
+
P_sorted = _sort_P(P, ranks)
|
|
131
|
+
ranks.sort()
|
|
132
|
+
|
|
133
|
+
# Inequality constraints
|
|
134
|
+
lb = [-np.inf] * (P.shape[0] - 1)
|
|
135
|
+
ub = [0] * (P.shape[0] - 1)
|
|
136
|
+
|
|
137
|
+
# Equality constraints
|
|
138
|
+
lb.append(0)
|
|
139
|
+
ub.append(0)
|
|
140
|
+
|
|
141
|
+
constr = NonlinearConstraint(_build_constr_linear(P_sorted, linear_vf, ranks, delta), lb, ub)
|
|
142
|
+
|
|
143
|
+
# Bounds on x
|
|
144
|
+
x_lb = []
|
|
145
|
+
x_ub = []
|
|
146
|
+
|
|
147
|
+
for m in range(M):
|
|
148
|
+
x_lb.append(0)
|
|
149
|
+
x_ub.append(1)
|
|
150
|
+
|
|
151
|
+
x_lb.append(-1000)
|
|
152
|
+
x_ub.append(eps_max)
|
|
153
|
+
|
|
154
|
+
bounds = Bounds(x_lb, x_ub)
|
|
155
|
+
|
|
156
|
+
# Initial position
|
|
157
|
+
x0 = [0.5] * (M+1)
|
|
158
|
+
|
|
159
|
+
if method == 'trust-constr':
|
|
160
|
+
# The trust-constr method always finds the decision space linear
|
|
161
|
+
hess = lambda x: np.zeros((len(x0), len(x0)))
|
|
162
|
+
else:
|
|
163
|
+
hess = None
|
|
164
|
+
|
|
165
|
+
res = scimin(_obj_func,
|
|
166
|
+
x0,
|
|
167
|
+
constraints= constr,
|
|
168
|
+
bounds=bounds,
|
|
169
|
+
method=method,
|
|
170
|
+
hess=hess)
|
|
171
|
+
|
|
172
|
+
# package up results
|
|
173
|
+
vf = lambda P_in: linear_vf(P_in, res.x[0:-1])
|
|
174
|
+
params = res.x[0:-1]
|
|
175
|
+
epsilon = res.x[-1]
|
|
176
|
+
fit = _validate_vf(res, verbose)
|
|
177
|
+
|
|
178
|
+
return vfResults(vf, params, epsilon, fit)
|
|
179
|
+
|
|
180
|
+
def create_vf_pymoo_linear(P, ranks, delta, eps_max, method="ES", verbose=False):
|
|
181
|
+
|
|
182
|
+
vf_prob = OptimizeLinearVF(P, ranks, delta, eps_max, linear_vf)
|
|
183
|
+
|
|
184
|
+
if method == "ES":
|
|
185
|
+
algorithm = ES()
|
|
186
|
+
elif method == "GA":
|
|
187
|
+
algorithm = GA()
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Optimization method %s not supported" % method)
|
|
190
|
+
|
|
191
|
+
res = moomin(vf_prob,
|
|
192
|
+
algorithm,
|
|
193
|
+
('n_gen', 200),
|
|
194
|
+
verbose=verbose,
|
|
195
|
+
seed=1)
|
|
196
|
+
|
|
197
|
+
vf = lambda P_in: linear_vf(P_in, res.X[0:-1])
|
|
198
|
+
|
|
199
|
+
if res.X is not None:
|
|
200
|
+
params = res.X[0:-1]
|
|
201
|
+
epsilon = res.X[-1]
|
|
202
|
+
else:
|
|
203
|
+
params = None
|
|
204
|
+
epsilon = -1000
|
|
205
|
+
|
|
206
|
+
fit = _validate_vf(res, verbose)
|
|
207
|
+
|
|
208
|
+
return vfResults(vf, params, epsilon, fit)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def create_vf_pymoo_poly(P, ranks, delta, eps_max, method="trust-constr", verbose=False):
|
|
212
|
+
|
|
213
|
+
vf_prob = OptimizePolyVF(P, ranks, delta, eps_max, poly_vf)
|
|
214
|
+
|
|
215
|
+
if method == "ES":
|
|
216
|
+
algorithm = ES()
|
|
217
|
+
elif method == "GA":
|
|
218
|
+
algorithm = GA()
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError("Optimization method %s not supported" % method)
|
|
221
|
+
|
|
222
|
+
res = moomin(vf_prob,
|
|
223
|
+
algorithm,
|
|
224
|
+
('n_gen', 100),
|
|
225
|
+
verbose=verbose,
|
|
226
|
+
seed=1)
|
|
227
|
+
|
|
228
|
+
vf = lambda P_in: poly_vf(P_in, res.X[0:-1])
|
|
229
|
+
|
|
230
|
+
if res.X is not None:
|
|
231
|
+
params = res.X[0:-1]
|
|
232
|
+
epsilon = res.X[-1]
|
|
233
|
+
else:
|
|
234
|
+
params = None
|
|
235
|
+
epsilon = -1000
|
|
236
|
+
|
|
237
|
+
fit = _validate_vf(res, verbose)
|
|
238
|
+
|
|
239
|
+
return vfResults(vf, params, epsilon, fit)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def linear_vf(P, x):
|
|
243
|
+
|
|
244
|
+
return np.matmul(P, x.T)
|
|
245
|
+
|
|
246
|
+
def poly_vf(P, x):
|
|
247
|
+
|
|
248
|
+
# find out M
|
|
249
|
+
M = P.shape[-1]
|
|
250
|
+
|
|
251
|
+
result = []
|
|
252
|
+
|
|
253
|
+
if len(x.shape) == 1:
|
|
254
|
+
x_len = 1
|
|
255
|
+
else:
|
|
256
|
+
x_len = x.shape[0]
|
|
257
|
+
|
|
258
|
+
# Calculate value for each row of x
|
|
259
|
+
for xi in range(x_len):
|
|
260
|
+
|
|
261
|
+
running_product = 1
|
|
262
|
+
|
|
263
|
+
# Get current x
|
|
264
|
+
if x_len == 1:
|
|
265
|
+
curr_x = x
|
|
266
|
+
else:
|
|
267
|
+
curr_x = x[xi, :]
|
|
268
|
+
|
|
269
|
+
S = _calc_S(P, curr_x)
|
|
270
|
+
|
|
271
|
+
# Multiply all of the S terms together along the final axis
|
|
272
|
+
product = np.prod(S, axis = len(np.shape(S))-1)
|
|
273
|
+
|
|
274
|
+
result.append(product)
|
|
275
|
+
|
|
276
|
+
return np.squeeze(np.array(result))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def plot_vf(P, vf, show=True):
|
|
280
|
+
|
|
281
|
+
plt.scatter(P[:,0], P[:,1], marker=".", color="red", s=200 )
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
for i in range(np.size(P,0)):
|
|
285
|
+
plt.annotate("P%d" % (i+1), (P[i,0], P[i,1]))
|
|
286
|
+
|
|
287
|
+
min_x = min(P[:,0])
|
|
288
|
+
min_y = min(P[:,1])
|
|
289
|
+
max_x = max(P[:,0])
|
|
290
|
+
max_y = max(P[:,1])
|
|
291
|
+
|
|
292
|
+
x,y = np.meshgrid(np.linspace(min_x, max_x, 1000), np.linspace(min_y, max_y, 1000))
|
|
293
|
+
|
|
294
|
+
z = vf(np.stack((x,y), axis=2))
|
|
295
|
+
|
|
296
|
+
values_at_P = []
|
|
297
|
+
for p in range(np.size(P,0)):
|
|
298
|
+
values_at_P.append(vf(P[p,:]))
|
|
299
|
+
|
|
300
|
+
values_at_P.sort()
|
|
301
|
+
|
|
302
|
+
plt.contour(x,y,z, levels=values_at_P)
|
|
303
|
+
|
|
304
|
+
plt.colorbar()
|
|
305
|
+
|
|
306
|
+
if show:
|
|
307
|
+
plt.show()
|
|
308
|
+
|
|
309
|
+
return plt
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
## ---------------- Polynomial VF creation functions ------------------
|
|
314
|
+
|
|
315
|
+
def _ineq_constr_poly(x, P, vf, ranks, delta):
|
|
316
|
+
if len(x.shape) == 1:
|
|
317
|
+
return _ineq_constr_1D_poly(x, P, vf, ranks, delta)
|
|
318
|
+
else:
|
|
319
|
+
return _ineq_constr_2D_poly(x, P, vf, ranks, delta)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _build_ineq_constr_poly(P, vf, ranks, delta):
|
|
323
|
+
|
|
324
|
+
ineq_func = lambda x : _ineq_constr_poly(x, P, vf, ranks, delta)
|
|
325
|
+
|
|
326
|
+
return ineq_func
|
|
327
|
+
|
|
328
|
+
def _eq_constr_poly(x):
|
|
329
|
+
|
|
330
|
+
M = math.floor(math.sqrt(6))
|
|
331
|
+
|
|
332
|
+
if len(x.shape) == 1:
|
|
333
|
+
result = []
|
|
334
|
+
for m in range(M):
|
|
335
|
+
result.append(-(sum(x[m*M:m*M+M]) - 1))
|
|
336
|
+
|
|
337
|
+
else:
|
|
338
|
+
|
|
339
|
+
pop_size = np.size(x,0)
|
|
340
|
+
|
|
341
|
+
result = []
|
|
342
|
+
|
|
343
|
+
for xi in range(pop_size):
|
|
344
|
+
|
|
345
|
+
result_for_xi = []
|
|
346
|
+
|
|
347
|
+
for m in range(M):
|
|
348
|
+
|
|
349
|
+
result_for_xi.append(-(sum(x[xi,m*M:m*M+M]) - 1))
|
|
350
|
+
|
|
351
|
+
result.append(result_for_xi)
|
|
352
|
+
|
|
353
|
+
result = np.array(result)
|
|
354
|
+
|
|
355
|
+
return result
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _build_constr_poly(P, vf, ranks, delta):
|
|
359
|
+
|
|
360
|
+
ineq_constr_func = _build_ineq_constr_poly(P, vf, ranks, delta)
|
|
361
|
+
|
|
362
|
+
return lambda x : np.append(ineq_constr_func(x), _eq_constr_poly(x))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _ineq_constr_2D_poly(x, P, vf, ranks, delta):
|
|
366
|
+
|
|
367
|
+
pop_size = np.size(x,0)
|
|
368
|
+
|
|
369
|
+
P_count = P.shape[0]
|
|
370
|
+
M = P.shape[1]
|
|
371
|
+
|
|
372
|
+
S_constr_len = M * P_count
|
|
373
|
+
increasing_len = P_count - 1
|
|
374
|
+
|
|
375
|
+
G = np.ones((pop_size, S_constr_len + increasing_len))*-99
|
|
376
|
+
|
|
377
|
+
for xi in range(pop_size):
|
|
378
|
+
|
|
379
|
+
G[xi, :] = _ineq_constr_1D_poly(x[xi, :], P, vf, ranks, delta)
|
|
380
|
+
|
|
381
|
+
return G
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _ineq_constr_1D_poly(x, P, vf, ranks, delta):
|
|
385
|
+
|
|
386
|
+
ep = x[-1]
|
|
387
|
+
|
|
388
|
+
P_count = P.shape[0]
|
|
389
|
+
M = P.shape[1]
|
|
390
|
+
|
|
391
|
+
S_constr_len = M * P_count
|
|
392
|
+
increasing_len = P_count - 1
|
|
393
|
+
|
|
394
|
+
G = np.ones((1, S_constr_len + increasing_len))*-99
|
|
395
|
+
|
|
396
|
+
# Checking to make sure each S term in the polynomial objective function is non-negative
|
|
397
|
+
S = _calc_S(P, x[0:-1]) * -1
|
|
398
|
+
|
|
399
|
+
G[:, 0:S_constr_len] = S.reshape(1, S_constr_len)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# Pair-wise compare each ranked member of P, seeing if our proposed utility
|
|
403
|
+
# function increases monotonically as rank increases
|
|
404
|
+
for p in range(P_count - 1):
|
|
405
|
+
|
|
406
|
+
current_P_val = vf(P[[p],:], x[0:-1])
|
|
407
|
+
next_P_val = vf(P[[p+1],:], x[0:-1])
|
|
408
|
+
|
|
409
|
+
current_rank = ranks[p]
|
|
410
|
+
next_rank = ranks[p+1]
|
|
411
|
+
|
|
412
|
+
if current_rank == next_rank:
|
|
413
|
+
# Handle ties
|
|
414
|
+
G[:,[p + S_constr_len]] = np.abs(current_P_val - next_P_val) - delta*ep
|
|
415
|
+
else:
|
|
416
|
+
G[:,[p + S_constr_len]] = -(current_P_val - next_P_val) + ep
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
return G
|
|
420
|
+
|
|
421
|
+
## ---------------- Linear VF creation functions ------------------
|
|
422
|
+
|
|
423
|
+
def _build_ineq_constr_linear(P, vf, ranks, delta):
|
|
424
|
+
|
|
425
|
+
ineq_func = lambda x : _ineq_constr_linear(x, P, vf, ranks, delta)
|
|
426
|
+
|
|
427
|
+
return ineq_func
|
|
428
|
+
|
|
429
|
+
def _build_constr_linear(P, vf, ranks, delta):
|
|
430
|
+
|
|
431
|
+
ineq_constr_func = _build_ineq_constr_linear(P, vf, ranks, delta);
|
|
432
|
+
|
|
433
|
+
return lambda x : np.append(ineq_constr_func(x), _eq_constr_linear(x))
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _ineq_constr_linear(x, P, vf, ranks, delta):
|
|
437
|
+
if len(x.shape) == 1:
|
|
438
|
+
return _ineq_constr_1D_linear(x, P, vf, ranks, delta)
|
|
439
|
+
else:
|
|
440
|
+
return _ineq_constr_2D_linear(x, P, vf, ranks, delta)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _ineq_constr_2D_linear(x, P, vf, ranks, delta):
|
|
444
|
+
|
|
445
|
+
ep = np.column_stack([x[:,-1]])
|
|
446
|
+
pop_size = np.size(x,0)
|
|
447
|
+
|
|
448
|
+
G = np.ones((pop_size, np.size(P,0)-1))*-99
|
|
449
|
+
|
|
450
|
+
# Pair-wise compare each ranked member of P, seeing if our proposed utility
|
|
451
|
+
# function increases monotonically as rank increases
|
|
452
|
+
for p in range(np.size(P,0) - 1):
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
current_P_val = vf(P[[p],:], x[:, 0:-1])
|
|
456
|
+
next_P = vf(P[[p+1],:], x[:, 0:-1])
|
|
457
|
+
|
|
458
|
+
current_rank = ranks[p]
|
|
459
|
+
next_rank = ranks[p+1]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
if current_rank == next_rank:
|
|
463
|
+
# Handle ties
|
|
464
|
+
G[:,[p]] = np.abs(current_P_val.T - next_P.T) - delta*ep
|
|
465
|
+
else:
|
|
466
|
+
# As vf returns, each column is an value of P for a given x in the population
|
|
467
|
+
# We transpose to make each ROW the value of P
|
|
468
|
+
G[:,[p]] = -(current_P_val.T - next_P.T) + ep
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
return G
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _ineq_constr_1D_linear(x, P, vf, ranks, delta):
|
|
475
|
+
|
|
476
|
+
ep = x[-1]
|
|
477
|
+
|
|
478
|
+
G = np.ones((1, np.size(P,0)-1))*-99
|
|
479
|
+
|
|
480
|
+
# Pair-wise compare each ranked member of P, seeing if our proposed utility
|
|
481
|
+
# function increases monotonically as rank increases
|
|
482
|
+
for p in range(np.size(P,0) - 1):
|
|
483
|
+
|
|
484
|
+
current_P_val = vf(P[[p],:], x[0:-1])
|
|
485
|
+
next_P = vf(P[[p+1],:], x[0:-1])
|
|
486
|
+
|
|
487
|
+
current_rank = ranks[p]
|
|
488
|
+
next_rank = ranks[p+1]
|
|
489
|
+
|
|
490
|
+
if current_rank == next_rank:
|
|
491
|
+
# Handle ties
|
|
492
|
+
G[:,[p]] = np.abs(current_P_val - next_P) - delta*ep
|
|
493
|
+
else:
|
|
494
|
+
G[:,[p]] = -(current_P_val - next_P) + ep
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
return G
|
|
499
|
+
|
|
500
|
+
def _obj_func(x):
|
|
501
|
+
|
|
502
|
+
# check if x is a 1D array or a 2D array
|
|
503
|
+
if len(x.shape) == 1:
|
|
504
|
+
ep = x[-1]
|
|
505
|
+
else:
|
|
506
|
+
ep = np.column_stack([x[:,-1]])
|
|
507
|
+
|
|
508
|
+
return -ep
|
|
509
|
+
|
|
510
|
+
def _sort_P(P, ranks):
|
|
511
|
+
P_with_rank = np.hstack((P, np.array([ranks]).T))
|
|
512
|
+
|
|
513
|
+
P_sorted = P[P_with_rank[:, -1].argsort()]
|
|
514
|
+
|
|
515
|
+
return P_sorted
|
|
516
|
+
|
|
517
|
+
# Can have several P instances, but assumes one x instance
|
|
518
|
+
def _calc_S(P, x):
|
|
519
|
+
|
|
520
|
+
M = P.shape[-1]
|
|
521
|
+
|
|
522
|
+
# reshape x into a matrix called k
|
|
523
|
+
k = np.array(x[0:M*M].reshape(M,M), copy=True)
|
|
524
|
+
k = k.T
|
|
525
|
+
|
|
526
|
+
# roll each column down to work with equation 5
|
|
527
|
+
for col in range(1, k.shape[1]):
|
|
528
|
+
k[:,[col]] = np.roll(k[:,[col]], col)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
l = x[M*M:(M*M)+M].reshape(M,1)
|
|
532
|
+
l = l.T
|
|
533
|
+
|
|
534
|
+
# Calc S for an arbitrary dimensional space of P
|
|
535
|
+
S = np.matmul(P, k) + (l * 2)
|
|
536
|
+
|
|
537
|
+
return np.squeeze(S)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# Constraint that states that the x values must add up to 1
|
|
541
|
+
def _eq_constr_linear(x):
|
|
542
|
+
|
|
543
|
+
if len(x.shape) == 1:
|
|
544
|
+
eq_cons = sum(x[0:-1]) - 1
|
|
545
|
+
else:
|
|
546
|
+
eq_cons = np.sum(x[:,0:-1],1, keepdims=True) - 1
|
|
547
|
+
|
|
548
|
+
return eq_cons
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# Makes a comparator for a given value function and the P that is ranked second in the
|
|
552
|
+
def make_vf_comparator(vf, P_rank_2):
|
|
553
|
+
|
|
554
|
+
return lambda P : vf_comparator(vf, P_rank_2, P)
|
|
555
|
+
|
|
556
|
+
def vf_comparator(vf, P_rank_2, P):
|
|
557
|
+
|
|
558
|
+
reference_value = vf(P_rank_2)
|
|
559
|
+
|
|
560
|
+
if reference_value > vf(P):
|
|
561
|
+
return -1
|
|
562
|
+
elif reference_value < vf(P):
|
|
563
|
+
return 1
|
|
564
|
+
else:
|
|
565
|
+
return 0
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class OptimizeLinearVF(Problem):
|
|
569
|
+
|
|
570
|
+
def __init__(self, P, ranks, delta, eps_max, vf):
|
|
571
|
+
|
|
572
|
+
# One var for each dimension of the object space, plus epsilon
|
|
573
|
+
n_var_vf = np.size(P, 1) + 1
|
|
574
|
+
|
|
575
|
+
# it has one inequality constraints for every pair of solutions in P
|
|
576
|
+
n_ieq_c_vf = np.size(P,0) - 1
|
|
577
|
+
|
|
578
|
+
xl_vf = [0.0] * n_var_vf
|
|
579
|
+
xu_vf = [1.0] * n_var_vf
|
|
580
|
+
|
|
581
|
+
# upper/lower bound on the epsilon variable is -1000/1000
|
|
582
|
+
xl_vf[-1] = -1000
|
|
583
|
+
xu_vf[-1] = eps_max
|
|
584
|
+
|
|
585
|
+
# TODO start everything at 0.5
|
|
586
|
+
|
|
587
|
+
self.P = _sort_P(P, ranks)
|
|
588
|
+
|
|
589
|
+
self.ranks = ranks
|
|
590
|
+
self.ranks.sort()
|
|
591
|
+
|
|
592
|
+
self.vf = vf
|
|
593
|
+
|
|
594
|
+
self.ranks = ranks
|
|
595
|
+
self.delta = delta
|
|
596
|
+
|
|
597
|
+
super().__init__(n_var_vf, n_obj=1, n_ieq_constr=n_ieq_c_vf, n_eq_constr=1, xl=xl_vf, xu=xu_vf)
|
|
598
|
+
|
|
599
|
+
def _evaluate(self, x, out, *args, **kwargs):
|
|
600
|
+
|
|
601
|
+
## Objective function:
|
|
602
|
+
obj = _obj_func(x)
|
|
603
|
+
|
|
604
|
+
# The objective function above returns a negated version of epsilon
|
|
605
|
+
ep = -obj
|
|
606
|
+
|
|
607
|
+
# maximize epsilon, or the minimum distance between each contour
|
|
608
|
+
out["F"] = obj
|
|
609
|
+
|
|
610
|
+
## Inequality
|
|
611
|
+
|
|
612
|
+
ineq_func = _build_ineq_constr_linear(self.P, self.vf, self.ranks, self.delta)
|
|
613
|
+
|
|
614
|
+
out["G"] = ineq_func(x)
|
|
615
|
+
|
|
616
|
+
## Equality constraint that keeps sum of x under 1
|
|
617
|
+
out["H"] = _eq_constr_linear(x)
|
|
618
|
+
|
|
619
|
+
def _validate_vf(res, verbose):
|
|
620
|
+
|
|
621
|
+
message = ""
|
|
622
|
+
|
|
623
|
+
if isinstance(res, pymoo.core.result.Result):
|
|
624
|
+
success = np.all(res.G <= 0)
|
|
625
|
+
epsilon = res.X[-1]
|
|
626
|
+
if not success:
|
|
627
|
+
message = "Constraints not met\n"
|
|
628
|
+
if epsilon < 0:
|
|
629
|
+
message = message + "Epsilon negative\n"
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
elif isinstance(res, scipy.optimize.optimize.OptimizeResult):
|
|
633
|
+
success = res.success and res.constr_violation <= 0
|
|
634
|
+
epsilon = res.x[-1]
|
|
635
|
+
|
|
636
|
+
if not (res.constr_violation <= 0):
|
|
637
|
+
message = "Constraints not met."
|
|
638
|
+
else:
|
|
639
|
+
message = res.message
|
|
640
|
+
else:
|
|
641
|
+
ValueError("Internal error: bad result objective given for validation")
|
|
642
|
+
|
|
643
|
+
if epsilon < 0 or not success:
|
|
644
|
+
|
|
645
|
+
if verbose:
|
|
646
|
+
sys.stderr.write("WARNING: Unable to fit value function\n")
|
|
647
|
+
sys.stderr.write(message + "\n")
|
|
648
|
+
|
|
649
|
+
return False
|
|
650
|
+
else:
|
|
651
|
+
return True
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class OptimizePolyVF(Problem):
|
|
655
|
+
|
|
656
|
+
def __init__(self, P, ranks, delta, eps_max, vf):
|
|
657
|
+
|
|
658
|
+
M = P.shape[1]
|
|
659
|
+
|
|
660
|
+
P_count = P.shape[0]
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
# One var for each dimension of the object space, plus epsilon
|
|
664
|
+
n_var_vf = (M**2 + M + 1)
|
|
665
|
+
|
|
666
|
+
# it has one inequality constraints for every pair of solutions in P
|
|
667
|
+
n_ieq_c_vf = (P_count*M) + (P_count - 1)
|
|
668
|
+
|
|
669
|
+
xl_vf = [0.0] * n_var_vf
|
|
670
|
+
xu_vf = [1.0] * n_var_vf
|
|
671
|
+
|
|
672
|
+
# upper/lower bound on the epsilon variable is -1000/1000
|
|
673
|
+
xl_vf[-1] = -1000
|
|
674
|
+
xu_vf[-1] = eps_max
|
|
675
|
+
|
|
676
|
+
# TODO start everything at 0.5
|
|
677
|
+
|
|
678
|
+
self.P = _sort_P(P, ranks)
|
|
679
|
+
|
|
680
|
+
self.ranks = ranks
|
|
681
|
+
self.ranks.sort()
|
|
682
|
+
|
|
683
|
+
self.vf = vf
|
|
684
|
+
|
|
685
|
+
self.delta = delta
|
|
686
|
+
|
|
687
|
+
super().__init__(n_var_vf, n_obj=1, n_ieq_constr=n_ieq_c_vf, n_eq_constr=M, xl=xl_vf, xu=xu_vf)
|
|
688
|
+
|
|
689
|
+
def _evaluate(self, x, out, *args, **kwargs):
|
|
690
|
+
|
|
691
|
+
## Objective function:
|
|
692
|
+
obj = _obj_func(x)
|
|
693
|
+
|
|
694
|
+
# The objective function above returns a negated version of epsilon
|
|
695
|
+
ep = -obj
|
|
696
|
+
|
|
697
|
+
# maximize epsilon, or the minimum distance between each contour
|
|
698
|
+
out["F"] = obj
|
|
699
|
+
|
|
700
|
+
## Inequality
|
|
701
|
+
ineq_func = _build_ineq_constr_poly(self.P, self.vf, self.ranks, self.delta)
|
|
702
|
+
|
|
703
|
+
out["G"] = ineq_func(x)
|
|
704
|
+
|
|
705
|
+
## Equality constraint that keeps sum of x under 1
|
|
706
|
+
out["H"] = _eq_constr_poly(x)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class vfResults():
|
|
710
|
+
|
|
711
|
+
def __init__(self, vf, params, epsilon, fit):
|
|
712
|
+
|
|
713
|
+
self.vf = vf
|
|
714
|
+
self.params = params
|
|
715
|
+
self.epsilon = epsilon
|
|
716
|
+
self.fit = fit
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|