fcmaes 1.1.3__py3-none-any.whl → 1.6.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fcmaes/__init__.py +12 -2
- fcmaes/advretry.py +217 -159
- fcmaes/astro.py +143 -27
- fcmaes/bitecpp.py +107 -0
- fcmaes/cmaes.py +204 -173
- fcmaes/cmaescpp.py +253 -87
- fcmaes/crfmnes.py +339 -0
- fcmaes/crfmnescpp.py +273 -0
- fcmaes/dacpp.py +39 -51
- fcmaes/de.py +472 -0
- fcmaes/decpp.py +222 -64
- fcmaes/diversifier.py +357 -0
- fcmaes/evaluator.py +297 -14
- fcmaes/lib/libacmalib.dll +0 -0
- fcmaes/lib/libacmalib.dylib +0 -0
- fcmaes/lib/libacmalib.so +0 -0
- fcmaes/lib/libhbv.so +0 -0
- fcmaes/lib/liblrgv.so +0 -0
- fcmaes/lib/librw_top_trumps.dll +0 -0
- fcmaes/lib/librw_top_trumps.so +0 -0
- fcmaes/mapelites.py +737 -0
- fcmaes/mode.py +719 -0
- fcmaes/modecpp.py +470 -0
- fcmaes/moretry.py +270 -0
- fcmaes/multiretry.py +195 -0
- fcmaes/optimizer.py +883 -112
- fcmaes/pgpecpp.py +340 -0
- fcmaes/pygmoretry.py +10 -19
- fcmaes/retry.py +248 -121
- fcmaes/test_cma.py +207 -30
- fcmaes/testfun.py +38 -1
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/METADATA +22 -12
- fcmaes-1.6.9.dist-info/RECORD +36 -0
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/WHEEL +1 -1
- fcmaes/hhcpp.py +0 -114
- fcmaes/lib/libgtoplib.dll +0 -0
- fcmaes/lib/libgtoplib.so +0 -0
- fcmaes-1.1.3.dist-info/RECORD +0 -23
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/LICENSE +0 -0
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/top_level.txt +0 -0
fcmaes/crfmnes.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
|
|
2
|
+
import math
|
|
3
|
+
import numpy as np
|
|
4
|
+
import os
|
|
5
|
+
from scipy.optimize import OptimizeResult, Bounds
|
|
6
|
+
from numpy.random import PCG64DXSM, Generator
|
|
7
|
+
from fcmaes.evaluator import _get_bounds, _fitness, serial, parallel
|
|
8
|
+
|
|
9
|
+
from typing import Optional, Callable, Union, Dict
|
|
10
|
+
from numpy.typing import ArrayLike
|
|
11
|
+
|
|
12
|
+
""" Numpy based implementation of Fast Moving Natural Evolution Strategy
|
|
13
|
+
for High-Dimensional Problems (CR-FM-NES), see https://arxiv.org/abs/2201.11422 .
|
|
14
|
+
Derived from https://github.com/nomuramasahir0/crfmnes .
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# evaluation value of the infeasible solution
|
|
18
|
+
INFEASIBLE = np.inf
|
|
19
|
+
|
|
20
|
+
os.environ['MKL_DEBUG_CPU_TYPE'] = '5'
|
|
21
|
+
|
|
22
|
+
def minimize(fun: Callable[[ArrayLike], float],
|
|
23
|
+
bounds: Optional[Bounds] = None,
|
|
24
|
+
x0: Optional[ArrayLike] = None,
|
|
25
|
+
input_sigma: Optional[float] = 0.3,
|
|
26
|
+
popsize: Optional[int] = 32,
|
|
27
|
+
max_evaluations: Optional[int] = 100000,
|
|
28
|
+
workers: Optional[int] = None,
|
|
29
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
30
|
+
is_terminate: Optional[Callable[[ArrayLike, float], bool]] = None,
|
|
31
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
32
|
+
runid: Optional[int] = 0,
|
|
33
|
+
normalize: Optional[bool] = False,
|
|
34
|
+
options: Optional[Dict] = {}
|
|
35
|
+
) -> OptimizeResult:
|
|
36
|
+
"""Minimization of a scalar function of one or more variables using CMA-ES.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
fun : callable
|
|
41
|
+
The objective function to be minimized.
|
|
42
|
+
``fun(x) -> float``
|
|
43
|
+
where ``x`` is an 1-D array with shape (n,)
|
|
44
|
+
bounds : sequence or `Bounds`, optional
|
|
45
|
+
Bounds on variables. There are two ways to specify the bounds:
|
|
46
|
+
1. Instance of the `scipy.Bounds` class.
|
|
47
|
+
2. Sequence of ``(min, max)`` pairs for each element in `x`. None
|
|
48
|
+
is used to specify no bound.
|
|
49
|
+
x0 : ndarray, shape (n,)
|
|
50
|
+
Initial guess. Array of real elements of size (n,),
|
|
51
|
+
where 'n' is the number of independent variables.
|
|
52
|
+
input_sigma : ndarray, shape (n,) or scalar
|
|
53
|
+
Initial step size.
|
|
54
|
+
popsize = int, optional
|
|
55
|
+
CMA-ES population size.
|
|
56
|
+
max_evaluations : int, optional
|
|
57
|
+
Forced termination after ``max_evaluations`` function evaluations.
|
|
58
|
+
workers : int or None, optional
|
|
59
|
+
If not workers is None, function evaluation is performed in parallel for the whole population.
|
|
60
|
+
Useful for costly objective functions but is deactivated for parallel retry.
|
|
61
|
+
stop_fitness : float, optional
|
|
62
|
+
Limit for fitness value. If reached minimize terminates.
|
|
63
|
+
is_terminate : callable, optional
|
|
64
|
+
Callback to be used if the caller of minimize wants to
|
|
65
|
+
decide when to terminate.
|
|
66
|
+
rg = numpy.random.Generator, optional
|
|
67
|
+
Random generator for creating random guesses.
|
|
68
|
+
runid : int, optional
|
|
69
|
+
id used by the is_terminate callback to identify the optimization run.
|
|
70
|
+
normalize : boolean, optional
|
|
71
|
+
if true pheno -> geno transformation maps arguments to interval [-1,1]
|
|
72
|
+
options : dict, optional
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
res : scipy.OptimizeResult
|
|
77
|
+
The optimization result is represented as an ``OptimizeResult`` object"""
|
|
78
|
+
|
|
79
|
+
cr = CRFMNES(None, bounds, x0, input_sigma, popsize,
|
|
80
|
+
max_evaluations, stop_fitness, is_terminate, runid, normalize, options, rg, workers, fun)
|
|
81
|
+
|
|
82
|
+
cr.optimize()
|
|
83
|
+
|
|
84
|
+
return OptimizeResult(x=cr.f.decode(cr.x_best), fun=cr.f_best, nfev=cr.no_of_evals,
|
|
85
|
+
nit=cr.g, status=cr.stop,
|
|
86
|
+
success=True)
|
|
87
|
+
|
|
88
|
+
class CRFMNES:
|
|
89
|
+
|
|
90
|
+
def __init__(self,
|
|
91
|
+
dim = None,
|
|
92
|
+
bounds: Optional[Bounds] = None,
|
|
93
|
+
x0: Optional[ArrayLike] = None,
|
|
94
|
+
input_sigma: Optional[Union[float, ArrayLike, Callable]] = 0.3,
|
|
95
|
+
popsize: Optional[int] = 32,
|
|
96
|
+
max_evaluations: Optional[int] = 100000,
|
|
97
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
98
|
+
is_terminate: Optional[bool] = None,
|
|
99
|
+
runid: Optional[int] = 0,
|
|
100
|
+
normalize: Optional[bool] = False,
|
|
101
|
+
options: Optional[Dict] = {},
|
|
102
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
103
|
+
workers: Optional[int] = None,
|
|
104
|
+
fun: Optional[Callable[[ArrayLike], float]] = lambda x: 0):
|
|
105
|
+
|
|
106
|
+
if popsize is None:
|
|
107
|
+
popsize = 32
|
|
108
|
+
if popsize % 2 == 1: # requires even popsize
|
|
109
|
+
popsize += 1
|
|
110
|
+
if dim is None:
|
|
111
|
+
if not x0 is None: dim = len(x0)
|
|
112
|
+
else:
|
|
113
|
+
if not bounds is None: dim = len(bounds.lb)
|
|
114
|
+
lower, upper, guess = _get_bounds(dim, bounds, x0, rg)
|
|
115
|
+
self.fun = serial(fun) if (workers is None or workers <= 1) else parallel(fun, workers)
|
|
116
|
+
self.f = _fitness(self.fun, lower, upper, normalize)
|
|
117
|
+
if options is None:
|
|
118
|
+
options = {}
|
|
119
|
+
if not lower is None:
|
|
120
|
+
options['constraint'] = [ [lower[i], upper[i]] for i in range(dim)]
|
|
121
|
+
self.constraint = options.get('constraint', [[-np.inf, np.inf] for _ in range(dim)])
|
|
122
|
+
if 'seed' in options.keys():
|
|
123
|
+
np.random.seed(options['seed'])
|
|
124
|
+
sigma = input_sigma
|
|
125
|
+
if not np.isscalar(sigma):
|
|
126
|
+
sigma = np.mean(sigma)
|
|
127
|
+
self.m = np.array([self.f.encode(guess)]).T
|
|
128
|
+
|
|
129
|
+
self.dim = dim
|
|
130
|
+
self.sigma = sigma
|
|
131
|
+
self.popsize = popsize
|
|
132
|
+
|
|
133
|
+
self.max_evaluations = max_evaluations
|
|
134
|
+
self.stop_fitness = stop_fitness
|
|
135
|
+
self.is_terminate = is_terminate
|
|
136
|
+
self.rg = rg
|
|
137
|
+
self.runid = runid
|
|
138
|
+
|
|
139
|
+
self.v = options.get('v', self.rg.normal(0,1,(dim, 1)) / np.sqrt(dim))
|
|
140
|
+
|
|
141
|
+
self.D = np.ones([dim, 1])
|
|
142
|
+
self.penalty_coef = options.get('penalty_coef', 1e5)
|
|
143
|
+
self.use_constraint_violation = options.get('use_constraint_violation', True)
|
|
144
|
+
|
|
145
|
+
self.w_rank_hat = (np.log(self.popsize / 2 + 1) - np.log(np.arange(1, self.popsize + 1))).reshape(self.popsize, 1)
|
|
146
|
+
self.w_rank_hat[np.where(self.w_rank_hat < 0)] = 0
|
|
147
|
+
self.w_rank = self.w_rank_hat / sum(self.w_rank_hat) - (1. / self.popsize)
|
|
148
|
+
self.mueff = 1 / ((self.w_rank + (1 / self.popsize)).T @ (self.w_rank + (1 / self.popsize)))[0][0]
|
|
149
|
+
self.cs = (self.mueff + 2.) / (self.dim + self.mueff + 5.)
|
|
150
|
+
self.cc = (4. + self.mueff / self.dim) / (self.dim + 4. + 2. * self.mueff / self.dim)
|
|
151
|
+
self.c1_cma = 2. / (math.pow(self.dim + 1.3, 2) + self.mueff)
|
|
152
|
+
# initialization
|
|
153
|
+
self.chiN = np.sqrt(self.dim) * (1. - 1. / (4. * self.dim) + 1. / (21. * self.dim * self.dim))
|
|
154
|
+
self.pc = np.zeros([self.dim, 1])
|
|
155
|
+
self.ps = np.zeros([self.dim, 1])
|
|
156
|
+
# distance weight parameter
|
|
157
|
+
self.h_inv = get_h_inv(self.dim)
|
|
158
|
+
self.alpha_dist = lambda lambF: self.h_inv * min(1., math.sqrt(self.popsize / self.dim)) * math.sqrt(
|
|
159
|
+
lambF / self.popsize)
|
|
160
|
+
self.w_dist_hat = lambda z, lambF: exp(self.alpha_dist(lambF) * np.linalg.norm(z))
|
|
161
|
+
# learning rate
|
|
162
|
+
self.eta_m = 1.0
|
|
163
|
+
self.eta_move_sigma = 1.
|
|
164
|
+
self.eta_stag_sigma = lambda lambF: math.tanh((0.024 * lambF + 0.7 * self.dim + 20.) / (self.dim + 12.))
|
|
165
|
+
self.eta_conv_sigma = lambda lambF: 2. * math.tanh((0.025 * lambF + 0.75 * self.dim + 10.) / (self.dim + 4.))
|
|
166
|
+
self.c1 = lambda lambF: self.c1_cma * (self.dim - 5) / 6 * (lambF / self.popsize)
|
|
167
|
+
self.eta_B = lambda lambF: np.tanh((min(0.02 * lambF, 3 * np.log(self.dim)) + 5) / (0.23 * self.dim + 25))
|
|
168
|
+
|
|
169
|
+
self.g = 0
|
|
170
|
+
self.no_of_evals = 0
|
|
171
|
+
self.iteration = 0
|
|
172
|
+
self.stop = 0
|
|
173
|
+
|
|
174
|
+
self.idxp = np.arange(self.popsize / 2, dtype=int)
|
|
175
|
+
self.idxm = np.arange(self.popsize / 2, self.popsize, dtype=int)
|
|
176
|
+
self.z = np.zeros([self.dim, self.popsize])
|
|
177
|
+
|
|
178
|
+
self.f_best = float('inf')
|
|
179
|
+
self.x_best = np.empty(self.dim)
|
|
180
|
+
|
|
181
|
+
def __del__(self):
|
|
182
|
+
if isinstance(self.fun, parallel):
|
|
183
|
+
self.fun.stop()
|
|
184
|
+
|
|
185
|
+
def calc_violations(self, x):
|
|
186
|
+
violations = np.zeros(self.popsize)
|
|
187
|
+
for i in range(self.popsize):
|
|
188
|
+
for j in range(self.dim):
|
|
189
|
+
violations[i] += (- min(0, x[j][i] - self.constraint[j][0]) + max(0, x[j][i] - self.constraint[j][1])) * self.penalty_coef
|
|
190
|
+
return violations
|
|
191
|
+
|
|
192
|
+
def optimize(self) -> int:
|
|
193
|
+
# -------------------- Generation Loop --------------------------------
|
|
194
|
+
while True:
|
|
195
|
+
if self.no_of_evals > self.max_evaluations:
|
|
196
|
+
break
|
|
197
|
+
if self.stop != 0:
|
|
198
|
+
break
|
|
199
|
+
try:
|
|
200
|
+
x = self.ask()
|
|
201
|
+
y = self.f.values(self.f.decode(self.f.closestFeasible(x)))
|
|
202
|
+
self.tell(y)
|
|
203
|
+
if self.stop != 0:
|
|
204
|
+
break
|
|
205
|
+
except Exception as ex:
|
|
206
|
+
self.stop = -1
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
def ask(self) -> np.ndarray:
|
|
210
|
+
d = self.dim
|
|
211
|
+
popsize = self.popsize
|
|
212
|
+
zhalf = self.rg.normal(0,1,(d, int(popsize / 2))) # dim x popsize/2
|
|
213
|
+
self.z[:, self.idxp] = zhalf
|
|
214
|
+
self.z[:, self.idxm] = -zhalf
|
|
215
|
+
self.normv = np.linalg.norm(self.v)
|
|
216
|
+
self.normv2 = self.normv ** 2
|
|
217
|
+
self.vbar = self.v / self.normv
|
|
218
|
+
self.y = self.z + ((np.sqrt(1 + self.normv2) - 1) * (self.vbar @ (self.vbar.T @ self.z)))
|
|
219
|
+
self.x = self.m + (self.sigma * self.y) * self.D
|
|
220
|
+
return self.x.T
|
|
221
|
+
|
|
222
|
+
def tell(self, evals_no_sort: np.ndarray) -> int:
|
|
223
|
+
violations = np.zeros(self.popsize)
|
|
224
|
+
if self.use_constraint_violation:
|
|
225
|
+
violations = self.calc_violations(self.x)
|
|
226
|
+
sorted_indices = sort_indices_by(evals_no_sort + violations, self.z)
|
|
227
|
+
else:
|
|
228
|
+
sorted_indices = sort_indices_by(evals_no_sort, self.z)
|
|
229
|
+
best_eval_id = sorted_indices[0]
|
|
230
|
+
f_best = evals_no_sort[best_eval_id]
|
|
231
|
+
x_best = self.x[:, best_eval_id]
|
|
232
|
+
self.z = self.z[:, sorted_indices]
|
|
233
|
+
y = self.y[:, sorted_indices]
|
|
234
|
+
x = self.x[:, sorted_indices]
|
|
235
|
+
|
|
236
|
+
self.no_of_evals += self.popsize
|
|
237
|
+
self.g += 1
|
|
238
|
+
|
|
239
|
+
if f_best < self.f_best:
|
|
240
|
+
self.f_best = f_best
|
|
241
|
+
self.x_best = x_best
|
|
242
|
+
# print(self.no_of_evals, self.g, self.f_best)
|
|
243
|
+
|
|
244
|
+
# This operation assumes that if the solution is infeasible, infinity comes in as input.
|
|
245
|
+
lambF = np.sum(evals_no_sort < np.finfo(float).max)
|
|
246
|
+
|
|
247
|
+
# evolution path p_sigma
|
|
248
|
+
self.ps = (1 - self.cs) * self.ps + np.sqrt(self.cs * (2. - self.cs) * self.mueff) * (self.z @ self.w_rank)
|
|
249
|
+
ps_norm = np.linalg.norm(self.ps)
|
|
250
|
+
# distance weight
|
|
251
|
+
f1 = self.h_inv * min(1., math.sqrt(self.popsize / self.dim)) * math.sqrt(lambF / self.popsize)
|
|
252
|
+
w_tmp = self.w_rank_hat * np.exp(np.linalg.norm(self.z, axis = 0) * f1).reshape((self.popsize,1))
|
|
253
|
+
weights_dist = w_tmp / sum(w_tmp) - 1. / self.popsize
|
|
254
|
+
# switching weights and learning rate
|
|
255
|
+
weights = weights_dist if ps_norm >= self.chiN else self.w_rank
|
|
256
|
+
eta_sigma = self.eta_move_sigma if ps_norm >= self.chiN else self.eta_stag_sigma(
|
|
257
|
+
lambF) if ps_norm >= 0.1 * self.chiN else self.eta_conv_sigma(lambF)
|
|
258
|
+
# update pc, m
|
|
259
|
+
wxm = (x - self.m) @ weights
|
|
260
|
+
self.pc = (1. - self.cc) * self.pc + np.sqrt(self.cc * (2. - self.cc) * self.mueff) * wxm / self.sigma
|
|
261
|
+
self.m += self.eta_m * wxm
|
|
262
|
+
# calculate s, t
|
|
263
|
+
# step1
|
|
264
|
+
normv4 = self.normv2 ** 2
|
|
265
|
+
exY = np.append(y, self.pc / self.D, axis=1) # dim x popsize+1
|
|
266
|
+
yy = exY * exY # dim x popsize+1
|
|
267
|
+
ip_yvbar = self.vbar.T @ exY
|
|
268
|
+
yvbar = exY * self.vbar # dim x popsize+1. exYのそれぞれの列にvbarがかかる
|
|
269
|
+
gammav = 1. + self.normv2
|
|
270
|
+
vbarbar = self.vbar * self.vbar
|
|
271
|
+
alphavd = min(1, np.sqrt(normv4 + (2 * gammav - np.sqrt(gammav)) / np.max(vbarbar)) / (2 + self.normv2)) # scalar
|
|
272
|
+
|
|
273
|
+
t = exY * ip_yvbar - self.vbar * (ip_yvbar ** 2 + gammav) / 2 # dim x popsize+1
|
|
274
|
+
b = -(1 - alphavd ** 2) * normv4 / gammav + 2 * alphavd ** 2
|
|
275
|
+
H = np.ones([self.dim, 1]) * 2 - (b + 2 * alphavd ** 2) * vbarbar # dim x 1
|
|
276
|
+
invH = H ** (-1)
|
|
277
|
+
s_step1 = yy - self.normv2 / gammav * (yvbar * ip_yvbar) - np.ones([self.dim, self.popsize + 1]) # dim x popsize+1
|
|
278
|
+
ip_vbart = self.vbar.T @ t # 1 x popsize+1
|
|
279
|
+
|
|
280
|
+
s_step2 = s_step1 - alphavd / gammav * ((2 + self.normv2) * (t * self.vbar) - self.normv2 * vbarbar @ ip_vbart) # dim x popsize+1
|
|
281
|
+
invHvbarbar = invH * vbarbar
|
|
282
|
+
ip_s_step2invHvbarbar = invHvbarbar.T @ s_step2 # 1 x popsize+1
|
|
283
|
+
|
|
284
|
+
div = 1 + b * vbarbar.T @ invHvbarbar
|
|
285
|
+
if np.amin(abs(div)) == 0:
|
|
286
|
+
return -1
|
|
287
|
+
|
|
288
|
+
s = (s_step2 * invH) - b / div * invHvbarbar @ ip_s_step2invHvbarbar # dim x popsize+1
|
|
289
|
+
ip_svbarbar = vbarbar.T @ s # 1 x popsize+1
|
|
290
|
+
t = t - alphavd * ((2 + self.normv2) * (s * self.vbar) - self.vbar @ ip_svbarbar) # dim x popsize+1
|
|
291
|
+
# update v, D
|
|
292
|
+
exw = np.append(self.eta_B(lambF) * weights, np.array([self.c1(lambF)]).reshape(1, 1),
|
|
293
|
+
axis=0) # popsize+1 x 1
|
|
294
|
+
self.v = self.v + (t @ exw) / self.normv
|
|
295
|
+
self.D = self.D + (s @ exw) * self.D
|
|
296
|
+
# calculate detA
|
|
297
|
+
if np.amin(self.D) < 0:
|
|
298
|
+
return -1
|
|
299
|
+
|
|
300
|
+
nthrootdetA = exp(np.sum(np.log(self.D)) / self.dim + np.log(1 + (self.v.T @ self.v)[0][0]) / (2 * self.dim))
|
|
301
|
+
|
|
302
|
+
self.D = self.D / nthrootdetA
|
|
303
|
+
|
|
304
|
+
# update sigma
|
|
305
|
+
G_s = np.sum((self.z * self.z - np.ones([self.dim, self.popsize])) @ weights) / self.dim
|
|
306
|
+
self.sigma = self.sigma * exp(eta_sigma / 2 * G_s)
|
|
307
|
+
return self.stop
|
|
308
|
+
|
|
309
|
+
def population(self) -> np.ndarray:
|
|
310
|
+
return self.x
|
|
311
|
+
|
|
312
|
+
def result(self) -> OptimizeResult:
|
|
313
|
+
return OptimizeResult(x=self.x_best, fun=self.f_best, nfev=self.no_of_evals,
|
|
314
|
+
nit=self.g, status=self.stop, success=True)
|
|
315
|
+
|
|
316
|
+
def exp(a):
|
|
317
|
+
return math.exp(min(100, a)) # avoid overflow
|
|
318
|
+
|
|
319
|
+
def get_h_inv(dim):
|
|
320
|
+
f = lambda a, b: ((1. + a * a) * exp(a * a / 2.) / 0.24) - 10. - dim
|
|
321
|
+
f_prime = lambda a: (1. / 0.24) * a * exp(a * a / 2.) * (3. + a * a)
|
|
322
|
+
h_inv = 1.0
|
|
323
|
+
while abs(f(h_inv, dim)) > 1e-10:
|
|
324
|
+
h_inv = h_inv - 0.5 * (f(h_inv, dim) / f_prime(h_inv))
|
|
325
|
+
return h_inv
|
|
326
|
+
|
|
327
|
+
def sort_indices_by(evals, z):
|
|
328
|
+
lam = len(evals)
|
|
329
|
+
evals = np.array(evals)
|
|
330
|
+
sorted_indices = np.argsort(evals)
|
|
331
|
+
sorted_evals = evals[sorted_indices]
|
|
332
|
+
no_of_feasible_solutions = np.where(sorted_evals != INFEASIBLE)[0].size
|
|
333
|
+
if no_of_feasible_solutions != lam:
|
|
334
|
+
infeasible_z = z[:, np.where(evals == INFEASIBLE)[0]]
|
|
335
|
+
distances = np.sum(infeasible_z ** 2, axis=0)
|
|
336
|
+
infeasible_indices = sorted_indices[no_of_feasible_solutions:]
|
|
337
|
+
indices_sorted_by_distance = np.argsort(distances)
|
|
338
|
+
sorted_indices[no_of_feasible_solutions:] = infeasible_indices[indices_sorted_by_distance]
|
|
339
|
+
return sorted_indices
|
fcmaes/crfmnescpp.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Copyright (c) Dietmar Wolz.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the MIT license found in the
|
|
4
|
+
# LICENSE file in the root directory.
|
|
5
|
+
|
|
6
|
+
""" Eigen based implementation of Fast Moving Natural Evolution Strategy
|
|
7
|
+
for High-Dimensional Problems (CR-FM-NES), see https://arxiv.org/abs/2201.11422 .
|
|
8
|
+
Derived from https://github.com/nomuramasahir0/crfmnes .
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
import math
|
|
14
|
+
import ctypes as ct
|
|
15
|
+
import numpy as np
|
|
16
|
+
from numpy.random import PCG64DXSM, Generator
|
|
17
|
+
from scipy.optimize import OptimizeResult, Bounds
|
|
18
|
+
from fcmaes.evaluator import _check_bounds, _get_bounds, callback_par, parallel, call_back_par, libcmalib
|
|
19
|
+
|
|
20
|
+
from typing import Optional, Callable, Union
|
|
21
|
+
from numpy.typing import ArrayLike
|
|
22
|
+
|
|
23
|
+
os.environ['MKL_DEBUG_CPU_TYPE'] = '5'
|
|
24
|
+
|
|
25
|
+
def minimize(fun: Callable[[ArrayLike], float],
|
|
26
|
+
bounds: Optional[Bounds] = None,
|
|
27
|
+
x0: Optional[ArrayLike] = None,
|
|
28
|
+
input_sigma = 0.3,
|
|
29
|
+
popsize = 32,
|
|
30
|
+
max_evaluations = 100000,
|
|
31
|
+
workers = None,
|
|
32
|
+
stop_fitness = -np.inf,
|
|
33
|
+
rg = Generator(PCG64DXSM()),
|
|
34
|
+
runid=0,
|
|
35
|
+
normalize = False,
|
|
36
|
+
use_constraint_violation = True,
|
|
37
|
+
penalty_coef = 1E5
|
|
38
|
+
) -> OptimizeResult:
|
|
39
|
+
|
|
40
|
+
"""Minimization of a scalar function of one or more variables using a
|
|
41
|
+
C++ CR-FM-NES implementation called via ctypes.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
fun : callable
|
|
46
|
+
The objective function to be minimized.
|
|
47
|
+
``fun(x) -> float``
|
|
48
|
+
where ``x`` is an 1-D array with shape (dim,)
|
|
49
|
+
bounds : sequence or `Bounds`, optional
|
|
50
|
+
Bounds on variables. There are two ways to specify the bounds:
|
|
51
|
+
1. Instance of the `scipy.Bounds` class.
|
|
52
|
+
2. Sequence of ``(min, max)`` pairs for each element in `x`. None
|
|
53
|
+
is used to specify no bound.
|
|
54
|
+
x0 : ndarray, shape (dim,)
|
|
55
|
+
Initial guess. Array of real elements of size (dim,),
|
|
56
|
+
where 'dim' is the number of independent variables.
|
|
57
|
+
input_sigma : float, optional
|
|
58
|
+
Initial step size.
|
|
59
|
+
popsize = int, optional
|
|
60
|
+
CMA-ES population size.
|
|
61
|
+
max_evaluations : int, optional
|
|
62
|
+
Forced termination after ``max_evaluations`` function evaluations.
|
|
63
|
+
workers : int or None, optional
|
|
64
|
+
If workers > 1, function evaluation is performed in parallel for the whole population.
|
|
65
|
+
Useful for costly objective functions but is deactivated for parallel retry.
|
|
66
|
+
stop_fitness : float, optional
|
|
67
|
+
Limit for fitness value. If reached minimize terminates.
|
|
68
|
+
rg = numpy.random.Generator, optional
|
|
69
|
+
Random generator for creating random guesses.
|
|
70
|
+
runid : int, optional
|
|
71
|
+
id used by the is_terminate callback to identify the CMA-ES run.
|
|
72
|
+
normalize : boolean, optional
|
|
73
|
+
if true pheno -> geno transformation maps arguments to interval [-1,1]
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
res : scipy.OptimizeResult
|
|
78
|
+
The optimization result is represented as an ``OptimizeResult`` object.
|
|
79
|
+
Important attributes are: ``x`` the solution array,
|
|
80
|
+
``fun`` the best function value,
|
|
81
|
+
``nfev`` the number of function evaluations,
|
|
82
|
+
``nit`` the number of CMA-ES iterations,
|
|
83
|
+
``status`` the stopping critera and
|
|
84
|
+
``success`` a Boolean flag indicating if the optimizer exited successfully. """
|
|
85
|
+
|
|
86
|
+
lower, upper, guess = _check_bounds(bounds, x0, rg)
|
|
87
|
+
dim = guess.size
|
|
88
|
+
if popsize is None:
|
|
89
|
+
popsize = 32
|
|
90
|
+
if popsize % 2 == 1: # requires even popsize
|
|
91
|
+
popsize += 1
|
|
92
|
+
if callable(input_sigma):
|
|
93
|
+
input_sigma=input_sigma()
|
|
94
|
+
if np.ndim(input_sigma) > 0:
|
|
95
|
+
input_sigma = np.mean(input_sigma)
|
|
96
|
+
array_type = ct.c_double * dim
|
|
97
|
+
parfun = None if (workers is None or workers <= 1) else parallel(fun, workers)
|
|
98
|
+
c_callback_par = call_back_par(callback_par(fun, parfun))
|
|
99
|
+
res = np.empty(dim+4)
|
|
100
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
101
|
+
try:
|
|
102
|
+
optimizeCRFMNES_C(runid, c_callback_par, dim, array_type(*guess),
|
|
103
|
+
None if lower is None else array_type(*lower),
|
|
104
|
+
None if upper is None else array_type(*upper),
|
|
105
|
+
input_sigma, max_evaluations, stop_fitness,
|
|
106
|
+
popsize, int(rg.uniform(0, 2**32 - 1)), penalty_coef,
|
|
107
|
+
use_constraint_violation, normalize, res_p)
|
|
108
|
+
x = res[:dim]
|
|
109
|
+
val = res[dim]
|
|
110
|
+
evals = int(res[dim+1])
|
|
111
|
+
iterations = int(res[dim+2])
|
|
112
|
+
stop = int(res[dim+3])
|
|
113
|
+
res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
|
|
114
|
+
except Exception as ex:
|
|
115
|
+
res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
|
|
116
|
+
if not parfun is None:
|
|
117
|
+
parfun.stop()
|
|
118
|
+
return res
|
|
119
|
+
|
|
120
|
+
class CRFMNES_C:
|
|
121
|
+
|
|
122
|
+
def __init__(self,
|
|
123
|
+
dim: int,
|
|
124
|
+
bounds: Optional[Bounds] = None,
|
|
125
|
+
x0: Optional[ArrayLike] = None,
|
|
126
|
+
input_sigma: Optional[Union[float, ArrayLike, Callable]] = 0.3,
|
|
127
|
+
popsize: Optional[int] = 32,
|
|
128
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
129
|
+
runid: Optional[int] = 0,
|
|
130
|
+
normalize: Optional[bool] = False,
|
|
131
|
+
use_constraint_violation: Optional[bool] = True,
|
|
132
|
+
penalty_coef: Optional[float] = 1E5
|
|
133
|
+
):
|
|
134
|
+
|
|
135
|
+
"""Minimization of a scalar function of one or more variables using a
|
|
136
|
+
C++ CR-FM-NES implementation called via ctypes.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
dim : int
|
|
141
|
+
dimension of the argument of the objective function
|
|
142
|
+
bounds : sequence or `Bounds`, optional
|
|
143
|
+
Bounds on variables. There are two ways to specify the bounds:
|
|
144
|
+
1. Instance of the `scipy.Bounds` class.
|
|
145
|
+
2. Sequence of ``(min, max)`` pairs for each element in `x`. None
|
|
146
|
+
is used to specify no bound.
|
|
147
|
+
x0 : ndarray, shape (dim,)
|
|
148
|
+
Initial guess. Array of real elements of size (dim,),
|
|
149
|
+
where 'dim' is the number of independent variables.
|
|
150
|
+
input_sigma : float, optional
|
|
151
|
+
Initial step size.
|
|
152
|
+
popsize = int, optional
|
|
153
|
+
CMA-ES population size.
|
|
154
|
+
rg = numpy.random.Generator, optional
|
|
155
|
+
Random generator for creating random guesses.
|
|
156
|
+
runid : int, optional
|
|
157
|
+
id used by the is_terminate callback to identify the CMA-ES run.
|
|
158
|
+
normalize : boolean, optional
|
|
159
|
+
if true pheno -> geno transformation maps arguments to interval [-1,1]"""
|
|
160
|
+
|
|
161
|
+
lower, upper, guess = _get_bounds(dim, bounds, x0, rg)
|
|
162
|
+
if popsize is None:
|
|
163
|
+
popsize = 32
|
|
164
|
+
if popsize % 2 == 1: # requires even popsize
|
|
165
|
+
popsize += 1
|
|
166
|
+
if lower is None:
|
|
167
|
+
lower = [0]*dim
|
|
168
|
+
upper = [0]*dim
|
|
169
|
+
if callable(input_sigma):
|
|
170
|
+
input_sigma=input_sigma()
|
|
171
|
+
if np.ndim(input_sigma) > 0:
|
|
172
|
+
input_sigma = np.mean(input_sigma)
|
|
173
|
+
array_type = ct.c_double * dim
|
|
174
|
+
try:
|
|
175
|
+
self.ptr = initCRFMNES_C(runid, dim, array_type(*guess),
|
|
176
|
+
array_type(*lower), array_type(*upper),
|
|
177
|
+
input_sigma, popsize, int(rg.uniform(0, 2**32 - 1)), penalty_coef,
|
|
178
|
+
use_constraint_violation, normalize)
|
|
179
|
+
self.popsize = popsize
|
|
180
|
+
self.dim = dim
|
|
181
|
+
except Exception as ex:
|
|
182
|
+
print (ex)
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def __del__(self):
|
|
186
|
+
destroyCRFMNES_C(self.ptr)
|
|
187
|
+
|
|
188
|
+
def ask(self) -> np.ndarray:
|
|
189
|
+
try:
|
|
190
|
+
lamb = self.popsize
|
|
191
|
+
n = self.dim
|
|
192
|
+
res = np.empty(lamb*n)
|
|
193
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
194
|
+
askCRFMNES_C(self.ptr, res_p)
|
|
195
|
+
xs = np.empty((lamb, n))
|
|
196
|
+
for p in range(lamb):
|
|
197
|
+
xs[p,:] = res[p*n : (p+1)*n]
|
|
198
|
+
return xs
|
|
199
|
+
except Exception as ex:
|
|
200
|
+
print (ex)
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
def tell(self, ys: np.ndarray):
|
|
204
|
+
try:
|
|
205
|
+
array_type_ys = ct.c_double * len(ys)
|
|
206
|
+
return tellCRFMNES_C(self.ptr, array_type_ys(*ys))
|
|
207
|
+
except Exception as ex:
|
|
208
|
+
print (ex)
|
|
209
|
+
return -1
|
|
210
|
+
|
|
211
|
+
def population(self) -> np.ndarray:
|
|
212
|
+
try:
|
|
213
|
+
lamb = self.popsize
|
|
214
|
+
n = self.dim
|
|
215
|
+
res = np.empty(lamb*n)
|
|
216
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
217
|
+
populationCRFMNES_C(self.ptr, res_p)
|
|
218
|
+
xs = np.array(lamb, n)
|
|
219
|
+
for p in range(lamb):
|
|
220
|
+
xs[p] = res[p*n : (p+1)*n]
|
|
221
|
+
return xs
|
|
222
|
+
except Exception as ex:
|
|
223
|
+
print (ex)
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
def result(self) -> OptimizeResult:
|
|
227
|
+
res = np.empty(self.dim+4)
|
|
228
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
229
|
+
try:
|
|
230
|
+
resultCRFMNES_C(self.ptr, res_p)
|
|
231
|
+
x = res[:self.dim]
|
|
232
|
+
val = res[self.dim]
|
|
233
|
+
evals = int(res[self.dim+1])
|
|
234
|
+
iterations = int(res[self.dim+2])
|
|
235
|
+
stop = int(res[self.dim+3])
|
|
236
|
+
res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
|
|
237
|
+
except Exception as ex:
|
|
238
|
+
res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
|
|
239
|
+
return res
|
|
240
|
+
|
|
241
|
+
if not libcmalib is None:
|
|
242
|
+
|
|
243
|
+
optimizeCRFMNES_C = libcmalib.optimizeCRFMNES_C
|
|
244
|
+
optimizeCRFMNES_C.argtypes = [ct.c_long, call_back_par, ct.c_int, \
|
|
245
|
+
ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
|
|
246
|
+
ct.c_double, ct.c_int, ct.c_double, ct.c_int,
|
|
247
|
+
ct.c_long, ct.c_double,
|
|
248
|
+
ct.c_bool, ct.c_bool, ct.POINTER(ct.c_double)]
|
|
249
|
+
|
|
250
|
+
initCRFMNES_C = libcmalib.initCRFMNES_C
|
|
251
|
+
initCRFMNES_C.argtypes = [ct.c_long, ct.c_int, \
|
|
252
|
+
ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
|
|
253
|
+
ct.c_double, ct.c_int,
|
|
254
|
+
ct.c_long, ct.c_double,
|
|
255
|
+
ct.c_bool, ct.c_bool]
|
|
256
|
+
|
|
257
|
+
initCRFMNES_C.restype = ct.c_void_p
|
|
258
|
+
|
|
259
|
+
destroyCRFMNES_C = libcmalib.destroyCRFMNES_C
|
|
260
|
+
destroyCRFMNES_C.argtypes = [ct.c_void_p]
|
|
261
|
+
|
|
262
|
+
askCRFMNES_C = libcmalib.askCRFMNES_C
|
|
263
|
+
askCRFMNES_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
264
|
+
|
|
265
|
+
tellCRFMNES_C = libcmalib.tellCRFMNES_C
|
|
266
|
+
tellCRFMNES_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
267
|
+
tellCRFMNES_C.restype = ct.c_int
|
|
268
|
+
|
|
269
|
+
populationCRFMNES_C = libcmalib.populationCRFMNES_C
|
|
270
|
+
populationCRFMNES_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
271
|
+
|
|
272
|
+
resultCRFMNES_C = libcmalib.resultCRFMNES_C
|
|
273
|
+
resultCRFMNES_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|