fcmaes 1.3.17__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 +5 -4
- fcmaes/advretry.py +135 -141
- fcmaes/astro.py +64 -40
- fcmaes/bitecpp.py +33 -32
- fcmaes/cmaes.py +69 -142
- fcmaes/cmaescpp.py +231 -39
- fcmaes/crfmnes.py +339 -0
- fcmaes/crfmnescpp.py +273 -0
- fcmaes/dacpp.py +26 -27
- fcmaes/de.py +163 -56
- fcmaes/decpp.py +188 -179
- fcmaes/diversifier.py +357 -0
- fcmaes/evaluator.py +279 -6
- 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/mapelites.py +737 -0
- fcmaes/mode.py +399 -256
- fcmaes/modecpp.py +326 -149
- fcmaes/moretry.py +107 -77
- fcmaes/multiretry.py +37 -30
- fcmaes/optimizer.py +695 -271
- fcmaes/pgpecpp.py +340 -0
- fcmaes/pygmoretry.py +8 -17
- fcmaes/retry.py +161 -139
- fcmaes/test_cma.py +45 -25
- fcmaes-1.6.9.dist-info/METADATA +47 -0
- fcmaes-1.6.9.dist-info/RECORD +36 -0
- {fcmaes-1.3.17.dist-info → fcmaes-1.6.9.dist-info}/WHEEL +1 -1
- fcmaes/csmacpp.py +0 -108
- fcmaes/gcldecpp.py +0 -148
- fcmaes/lcldecpp.py +0 -138
- fcmaes/ldecpp.py +0 -172
- fcmaes/lib/libgcc_s_seh-1.dll +0 -0
- fcmaes/lib/libgtoplib.dll +0 -0
- fcmaes/lib/libgtoplib.so +0 -0
- fcmaes/lib/libstdc++-6.dll +0 -0
- fcmaes/lib/libwinpthread-1.dll +0 -0
- fcmaes-1.3.17.dist-info/METADATA +0 -55
- fcmaes-1.3.17.dist-info/RECORD +0 -37
- {fcmaes-1.3.17.dist-info → fcmaes-1.6.9.dist-info}/LICENSE +0 -0
- {fcmaes-1.3.17.dist-info → fcmaes-1.6.9.dist-info}/top_level.txt +0 -0
fcmaes/de.py
CHANGED
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
You may keep parameters F and Cr at their defaults since this implementation works well with the given settings for most problems,
|
|
17
17
|
since the algorithm oscillates between different F and Cr settings.
|
|
18
18
|
|
|
19
|
+
The filter parameter is inspired by "Surrogate-based Optimisation for a Hospital Simulation"
|
|
20
|
+
(https://dl.acm.org/doi/10.1145/3449726.3463283) where a machine learning classifier is used to
|
|
21
|
+
filter candidate solutions for DE. A filter object needs to provide function add(x, y) to enable learning and
|
|
22
|
+
a predicate is_improve(x, x_old, y_old) used to decide if function evaluation of x is worth the effort.
|
|
23
|
+
|
|
24
|
+
The ints parameter is a boolean array indicating which parameters are discrete integer values. This
|
|
25
|
+
parameter was introduced after observing non optimal results for the ESP2 benchmark problem:
|
|
26
|
+
https://github.com/AlgTUDelft/ExpensiveOptimBenchmark/blob/master/expensiveoptimbenchmark/problems/DockerCFDBenchmark.py
|
|
27
|
+
If defined it causes a "special treatment" for discrete variables: They are rounded to the next integer value and
|
|
28
|
+
there is an additional mutation to avoid getting stuck at local minima. This behavior is specified by the internal
|
|
29
|
+
function _modifier which can be overwritten by providing the optional modifier argument. If modifier is defined,
|
|
30
|
+
ints is ignored.
|
|
31
|
+
|
|
19
32
|
Use the C++ implementation combined with parallel retry instead for objective functions which are fast to evaluate.
|
|
20
33
|
For expensive objective functions (e.g. machine learning parameter optimization) use the workers
|
|
21
34
|
parameter to parallelize objective function evaluation. This causes delayed population update.
|
|
@@ -26,25 +39,31 @@ import numpy as np
|
|
|
26
39
|
import math, sys
|
|
27
40
|
from time import time
|
|
28
41
|
import ctypes as ct
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from fcmaes.evaluator import Evaluator
|
|
42
|
+
from numpy.random import Generator, PCG64DXSM
|
|
43
|
+
from scipy.optimize import OptimizeResult, Bounds
|
|
44
|
+
from fcmaes.evaluator import Evaluator, is_debug_active
|
|
33
45
|
import multiprocessing as mp
|
|
34
46
|
from collections import deque
|
|
47
|
+
from loguru import logger
|
|
48
|
+
from typing import Optional, Callable, Tuple, Union
|
|
49
|
+
from numpy.typing import ArrayLike
|
|
35
50
|
|
|
36
|
-
def minimize(fun,
|
|
37
|
-
dim = None,
|
|
38
|
-
bounds = None,
|
|
39
|
-
popsize = 31,
|
|
40
|
-
max_evaluations = 100000,
|
|
41
|
-
workers = None,
|
|
42
|
-
stop_fitness =
|
|
43
|
-
keep = 200,
|
|
44
|
-
f = 0.5,
|
|
45
|
-
cr = 0.9,
|
|
46
|
-
rg = Generator(
|
|
47
|
-
|
|
51
|
+
def minimize(fun: Callable[[ArrayLike], float],
|
|
52
|
+
dim: Optional[int] = None,
|
|
53
|
+
bounds: Optional[Bounds] = None,
|
|
54
|
+
popsize: Optional[int] = 31,
|
|
55
|
+
max_evaluations: Optional[int] = 100000,
|
|
56
|
+
workers: Optional[int] = None,
|
|
57
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
58
|
+
keep: Optional[int] = 200,
|
|
59
|
+
f: Optional[float] = 0.5,
|
|
60
|
+
cr: Optional[float] = 0.9,
|
|
61
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
62
|
+
filter = None,
|
|
63
|
+
ints: Optional[ArrayLike] = None,
|
|
64
|
+
min_mutate: Optional[float] = 0.1,
|
|
65
|
+
max_mutate: Optional[float] = 0.5,
|
|
66
|
+
modifier: Optional[Callable] = None) -> OptimizeResult:
|
|
48
67
|
"""Minimization of a scalar function of one or more variables using
|
|
49
68
|
Differential Evolution.
|
|
50
69
|
|
|
@@ -52,10 +71,8 @@ def minimize(fun,
|
|
|
52
71
|
----------
|
|
53
72
|
fun : callable
|
|
54
73
|
The objective function to be minimized.
|
|
55
|
-
``fun(x
|
|
56
|
-
where ``x`` is an 1-D array with shape (n,)
|
|
57
|
-
is a tuple of the fixed parameters needed to completely
|
|
58
|
-
specify the function.
|
|
74
|
+
``fun(x) -> float``
|
|
75
|
+
where ``x`` is an 1-D array with shape (n,)
|
|
59
76
|
dim : int
|
|
60
77
|
dimension of the argument of the objective function
|
|
61
78
|
either dim or bounds need to be defined
|
|
@@ -84,10 +101,21 @@ def minimize(fun,
|
|
|
84
101
|
In the literature this is also known as the crossover probability.
|
|
85
102
|
rg = numpy.random.Generator, optional
|
|
86
103
|
Random generator for creating random guesses.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
filter = filter object, optional
|
|
105
|
+
needs to provide function add(x, y) and predicate is_improve(x, x_old, y_old).
|
|
106
|
+
used to decide if function evaluation of x is worth the effort.
|
|
107
|
+
Either f(x) < f(x_old) or f(x) < y_old need to be approximated.
|
|
108
|
+
add(x, y) can be used to learn from past results.
|
|
109
|
+
ints = list or array of bool, optional
|
|
110
|
+
indicating which parameters are discrete integer values. If defined these parameters will be
|
|
111
|
+
rounded to the next integer and some additional mutation of discrete parameters are performed.
|
|
112
|
+
min_mutate = float, optional
|
|
113
|
+
Determines the minimal mutation rate for discrete integer parameters.
|
|
114
|
+
max_mutate = float, optional
|
|
115
|
+
Determines the maximal mutation rate for discrete integer parameters.
|
|
116
|
+
modifier = callable, optional
|
|
117
|
+
used to overwrite the default behaviour induced by ints. If defined, the ints parameter is
|
|
118
|
+
ignored. Modifies all generated x vectors.
|
|
91
119
|
|
|
92
120
|
Returns
|
|
93
121
|
-------
|
|
@@ -100,7 +128,8 @@ def minimize(fun,
|
|
|
100
128
|
``success`` a Boolean flag indicating if the optimizer exited successfully. """
|
|
101
129
|
|
|
102
130
|
|
|
103
|
-
de = DE(dim, bounds, popsize, stop_fitness, keep, f, cr, rg,
|
|
131
|
+
de = DE(dim, bounds, popsize, stop_fitness, keep, f, cr, rg, filter, ints,
|
|
132
|
+
min_mutate, max_mutate, modifier)
|
|
104
133
|
try:
|
|
105
134
|
if workers and workers > 1:
|
|
106
135
|
x, val, evals, iterations, stop = de.do_optimize_delayed_update(fun, max_evaluations, workers)
|
|
@@ -113,8 +142,21 @@ def minimize(fun,
|
|
|
113
142
|
|
|
114
143
|
class DE(object):
|
|
115
144
|
|
|
116
|
-
def __init__(self,
|
|
117
|
-
|
|
145
|
+
def __init__(self,
|
|
146
|
+
dim: int,
|
|
147
|
+
bounds: Bounds,
|
|
148
|
+
popsize: Optional[int] = 31,
|
|
149
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
150
|
+
keep: Optional[int] = 200,
|
|
151
|
+
F: Optional[float] = 0.5,
|
|
152
|
+
Cr: Optional[float] = 0.9,
|
|
153
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
154
|
+
filter: Optional = None,
|
|
155
|
+
ints: Optional[ArrayLike] = None,
|
|
156
|
+
min_mutate: Optional[float] = 0.1,
|
|
157
|
+
max_mutate: Optional[float] = 0.5,
|
|
158
|
+
modifier: Optional[Callable] = None):
|
|
159
|
+
|
|
118
160
|
self.dim, self.lower, self.upper = _check_bounds(bounds, dim)
|
|
119
161
|
if popsize is None:
|
|
120
162
|
popsize = 31
|
|
@@ -129,14 +171,24 @@ class DE(object):
|
|
|
129
171
|
self.evals = 0
|
|
130
172
|
self.p = 0
|
|
131
173
|
self.improves = deque()
|
|
132
|
-
self.
|
|
133
|
-
|
|
134
|
-
|
|
174
|
+
self.filter = filter
|
|
175
|
+
self.ints = np.array(ints)
|
|
176
|
+
self.min_mutate = min_mutate
|
|
177
|
+
self.max_mutate = max_mutate
|
|
178
|
+
# use default variable modifier for int variables if modifier is None
|
|
179
|
+
if modifier is None and not ints is None:
|
|
180
|
+
self.lower = self.lower.astype(float)
|
|
181
|
+
self.upper = self.upper.astype(float)
|
|
182
|
+
self.modifier = self._modifier
|
|
183
|
+
else:
|
|
184
|
+
self.modifier = modifier
|
|
185
|
+
self._init()
|
|
186
|
+
if is_debug_active():
|
|
135
187
|
self.best_y = mp.RawValue(ct.c_double, 1E99)
|
|
136
188
|
self.n_evals = mp.RawValue(ct.c_long, 0)
|
|
137
189
|
self.time_0 = time()
|
|
138
|
-
|
|
139
|
-
def ask(self):
|
|
190
|
+
|
|
191
|
+
def ask(self) -> np.ndarray:
|
|
140
192
|
"""ask for popsize new argument vectors.
|
|
141
193
|
|
|
142
194
|
Returns
|
|
@@ -144,7 +196,7 @@ class DE(object):
|
|
|
144
196
|
xs : popsize sized array of dim sized argument lists."""
|
|
145
197
|
|
|
146
198
|
xs = [None] * self.popsize
|
|
147
|
-
for
|
|
199
|
+
for _ in range(self.popsize):
|
|
148
200
|
if self.improves:
|
|
149
201
|
p, x = self.improves[0]
|
|
150
202
|
if xs[p] is None:
|
|
@@ -157,9 +209,13 @@ class DE(object):
|
|
|
157
209
|
for p in range(self.popsize):
|
|
158
210
|
if xs[p] is None:
|
|
159
211
|
_, _, xs[p] = self._next_x(p)
|
|
212
|
+
self.asked = xs
|
|
160
213
|
return xs
|
|
161
214
|
|
|
162
|
-
def tell(self,
|
|
215
|
+
def tell(self,
|
|
216
|
+
ys:ArrayLike,
|
|
217
|
+
xs:Optional[ArrayLike] = None) -> int:
|
|
218
|
+
|
|
163
219
|
"""tell function values for the argument lists retrieved by ask().
|
|
164
220
|
|
|
165
221
|
Parameters
|
|
@@ -171,12 +227,22 @@ class DE(object):
|
|
|
171
227
|
-------
|
|
172
228
|
stop : int termination criteria, if != 0 loop should stop."""
|
|
173
229
|
|
|
230
|
+
if xs is None:
|
|
231
|
+
xs = self.asked
|
|
174
232
|
self.evals += len(ys)
|
|
175
233
|
for p in range(len(ys)):
|
|
176
234
|
self.tell_one(p, ys[p], xs[p])
|
|
177
235
|
return self.stop
|
|
178
236
|
|
|
179
|
-
def
|
|
237
|
+
def population(self) -> np.ndarray:
|
|
238
|
+
return self.x
|
|
239
|
+
|
|
240
|
+
def result(self) -> OptimizeResult:
|
|
241
|
+
return OptimizeResult(x=self.best_x, fun=self.best_value,
|
|
242
|
+
nfev=self.iterations*self.popsize,
|
|
243
|
+
nit=self.iterations, status=self.stop, success=True)
|
|
244
|
+
|
|
245
|
+
def ask_one(self) -> Tuple[int, np.ndarray]:
|
|
180
246
|
"""ask for one new argument vector.
|
|
181
247
|
|
|
182
248
|
Returns
|
|
@@ -192,7 +258,7 @@ class DE(object):
|
|
|
192
258
|
self.p = (self.p + 1) % self.popsize
|
|
193
259
|
return p, x
|
|
194
260
|
|
|
195
|
-
def tell_one(self, p, y, x):
|
|
261
|
+
def tell_one(self, p: int, y:float , x:ArrayLike) -> int:
|
|
196
262
|
"""tell function value for a argument list retrieved by ask_one().
|
|
197
263
|
|
|
198
264
|
Parameters
|
|
@@ -204,6 +270,9 @@ class DE(object):
|
|
|
204
270
|
Returns
|
|
205
271
|
-------
|
|
206
272
|
stop : int termination criteria, if != 0 loop should stop."""
|
|
273
|
+
|
|
274
|
+
if not self.filter is None:
|
|
275
|
+
self.filter.add(x, y)
|
|
207
276
|
|
|
208
277
|
if (self.y[p] > y):
|
|
209
278
|
# temporal locality
|
|
@@ -217,23 +286,23 @@ class DE(object):
|
|
|
217
286
|
if self.best_value > y:
|
|
218
287
|
self.best_x = x
|
|
219
288
|
self.best_value = y
|
|
220
|
-
if
|
|
289
|
+
if self.stop_fitness > y:
|
|
221
290
|
self.stop = 1
|
|
222
291
|
self.pop_iter[p] = self.iterations
|
|
223
292
|
else:
|
|
224
293
|
if self.rg.uniform(0, self.keep) < self.iterations - self.pop_iter[p]:
|
|
225
294
|
self.x[p] = self._sample()
|
|
226
|
-
self.y[p] =
|
|
295
|
+
self.y[p] = np.inf
|
|
227
296
|
|
|
228
|
-
if
|
|
297
|
+
if is_debug_active():
|
|
229
298
|
self.n_evals.value += 1
|
|
230
299
|
if y < self.best_y.value or self.n_evals.value % 1000 == 999:
|
|
231
300
|
if y < self.best_y.value: self.best_y.value = y
|
|
232
|
-
t = time() - self.time_0
|
|
301
|
+
t = time() - self.time_0 + 1E-9
|
|
233
302
|
c = self.n_evals.value
|
|
234
303
|
message = '"c/t={0:.2f} c={1:d} t={2:.2f} y={3:.5f} yb={4:.5f} x={5!s}'.format(
|
|
235
304
|
c/t, c, t, y, self.best_y.value, x)
|
|
236
|
-
|
|
305
|
+
logger.debug(message)
|
|
237
306
|
|
|
238
307
|
return self.stop
|
|
239
308
|
|
|
@@ -243,12 +312,25 @@ class DE(object):
|
|
|
243
312
|
self.y = np.empty(self.popsize)
|
|
244
313
|
for i in range(self.popsize):
|
|
245
314
|
self.x[i] = self.x0[i] = self._sample()
|
|
246
|
-
self.y[i] =
|
|
315
|
+
self.y[i] = np.inf
|
|
247
316
|
self.best_x = self.x[0]
|
|
248
|
-
self.best_value =
|
|
317
|
+
self.best_value = np.inf
|
|
249
318
|
self.best_i = 0
|
|
250
319
|
self.pop_iter = np.zeros(self.popsize)
|
|
251
320
|
|
|
321
|
+
def apply_fun(self, x, x_old, y_old):
|
|
322
|
+
if self.filter is None:
|
|
323
|
+
self.evals += 1
|
|
324
|
+
return self.fun(x)
|
|
325
|
+
else:
|
|
326
|
+
if self.filter.is_improve(x, x_old, y_old):
|
|
327
|
+
self.evals += 1
|
|
328
|
+
y = self.fun(x)
|
|
329
|
+
self.filter.add(x, y)
|
|
330
|
+
return y
|
|
331
|
+
else:
|
|
332
|
+
return 1E99
|
|
333
|
+
|
|
252
334
|
def do_optimize(self, fun, max_evals):
|
|
253
335
|
self.fun = fun
|
|
254
336
|
self.max_evals = max_evals
|
|
@@ -257,14 +339,12 @@ class DE(object):
|
|
|
257
339
|
while self.evals < self.max_evals:
|
|
258
340
|
for p in range(self.popsize):
|
|
259
341
|
xb, xi, x = self._next_x(p)
|
|
260
|
-
y = self.
|
|
261
|
-
self.evals += 1
|
|
342
|
+
y = self.apply_fun(x, xi, self.y[p])
|
|
262
343
|
if y < self.y[p]:
|
|
263
344
|
# temporal locality
|
|
264
345
|
if self.iterations > 1:
|
|
265
346
|
x2 = self._next_improve(xb, x, xi)
|
|
266
|
-
y2 = self.
|
|
267
|
-
self.evals += 1
|
|
347
|
+
y2 = self.apply_fun(x2, x, y)
|
|
268
348
|
if y2 < y:
|
|
269
349
|
y = y2
|
|
270
350
|
x = x2
|
|
@@ -276,13 +356,13 @@ class DE(object):
|
|
|
276
356
|
if y < self.best_value:
|
|
277
357
|
self.best_value = y;
|
|
278
358
|
self.best_x = x;
|
|
279
|
-
if
|
|
359
|
+
if self.stop_fitness > y:
|
|
280
360
|
self.stop = 1
|
|
281
361
|
else:
|
|
282
362
|
# reinitialize individual
|
|
283
363
|
if self.rg.uniform(0, self.keep) < self.iterations - self.pop_iter[p]:
|
|
284
364
|
self.x[p] = self._sample()
|
|
285
|
-
self.y[p] =
|
|
365
|
+
self.y[p] = np.inf
|
|
286
366
|
if self.evals >= self.max_evals:
|
|
287
367
|
break
|
|
288
368
|
|
|
@@ -312,7 +392,11 @@ class DE(object):
|
|
|
312
392
|
if self.stop != 0 or self.evals >= self.max_evals:
|
|
313
393
|
break # shutdown worker if stop criteria met
|
|
314
394
|
|
|
315
|
-
|
|
395
|
+
for _ in range(workers):
|
|
396
|
+
p, x = self.ask_one() # create new x
|
|
397
|
+
if self.filter is None or \
|
|
398
|
+
self.filter.is_improve(x, self.x[p], self.y[p]):
|
|
399
|
+
break
|
|
316
400
|
evaluator.pipe[0].send((self.evals, x))
|
|
317
401
|
evals_x[self.evals] = p, x # store x
|
|
318
402
|
self.evals += 1
|
|
@@ -338,24 +422,45 @@ class DE(object):
|
|
|
338
422
|
r = self.rg.integers(0, self.dim)
|
|
339
423
|
tr = np.array(
|
|
340
424
|
[i != r and self.rg.random() > self.Cr for i in range(self.dim)])
|
|
341
|
-
x[tr] = xp[tr]
|
|
425
|
+
x[tr] = xp[tr]
|
|
426
|
+
if not self.modifier is None:
|
|
427
|
+
x = self.modifier(x)
|
|
342
428
|
return xb, xp, x
|
|
343
429
|
|
|
344
430
|
def _next_improve(self, xb, x, xi):
|
|
345
|
-
|
|
346
|
-
|
|
431
|
+
x = self._feasible(xb + ((x - xi) * 0.5))
|
|
432
|
+
if not self.modifier is None:
|
|
433
|
+
x = self.modifier(x)
|
|
434
|
+
return x
|
|
435
|
+
|
|
347
436
|
def _sample(self):
|
|
348
437
|
if self.upper is None:
|
|
349
438
|
return self.rg.normal()
|
|
350
439
|
else:
|
|
351
|
-
|
|
440
|
+
x = self.rg.uniform(self.lower, self.upper)
|
|
441
|
+
if not self.modifier is None:
|
|
442
|
+
x = self.modifier(x)
|
|
443
|
+
return x
|
|
352
444
|
|
|
353
445
|
def _feasible(self, x):
|
|
354
446
|
if self.upper is None:
|
|
355
447
|
return x
|
|
356
448
|
else:
|
|
357
|
-
return np.
|
|
358
|
-
|
|
449
|
+
return np.clip(x, self.lower, self.upper)
|
|
450
|
+
|
|
451
|
+
# default modifier for integer variables
|
|
452
|
+
def _modifier(self, x):
|
|
453
|
+
x_ints = x[self.ints]
|
|
454
|
+
n_ints = len(self.ints)
|
|
455
|
+
lb = self.lower[self.ints]
|
|
456
|
+
ub = self.upper[self.ints]
|
|
457
|
+
to_mutate = self.rg.uniform(self.min_mutate, self.max_mutate)
|
|
458
|
+
# mututate some integer variables
|
|
459
|
+
x[self.ints] = np.array([x if self.rg.random() > to_mutate/n_ints else
|
|
460
|
+
int(self.rg.uniform(lb[i], ub[i]))
|
|
461
|
+
for i, x in enumerate(x_ints)])
|
|
462
|
+
return x
|
|
463
|
+
|
|
359
464
|
def _check_bounds(bounds, dim):
|
|
360
465
|
if bounds is None and dim is None:
|
|
361
466
|
raise ValueError('either dim or bounds need to be defined')
|
|
@@ -363,3 +468,5 @@ def _check_bounds(bounds, dim):
|
|
|
363
468
|
return dim, None, None
|
|
364
469
|
else:
|
|
365
470
|
return len(bounds.ub), np.asarray(bounds.lb), np.asarray(bounds.ub)
|
|
471
|
+
|
|
472
|
+
|