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/cmaescpp.py
CHANGED
|
@@ -13,26 +13,32 @@ import os
|
|
|
13
13
|
import math
|
|
14
14
|
import ctypes as ct
|
|
15
15
|
import numpy as np
|
|
16
|
-
from numpy.random import
|
|
17
|
-
from scipy.optimize import OptimizeResult
|
|
18
|
-
from fcmaes.
|
|
19
|
-
|
|
16
|
+
from numpy.random import PCG64DXSM, Generator
|
|
17
|
+
from scipy.optimize import OptimizeResult, Bounds
|
|
18
|
+
from fcmaes.evaluator import _check_bounds, _get_bounds, mo_call_back_type, callback_so, callback_par, call_back_par, parallel, libcmalib
|
|
19
|
+
|
|
20
|
+
from typing import Optional, Callable, Union
|
|
21
|
+
from numpy.typing import ArrayLike
|
|
20
22
|
|
|
21
23
|
os.environ['MKL_DEBUG_CPU_TYPE'] = '5'
|
|
22
24
|
|
|
23
|
-
def minimize(fun,
|
|
24
|
-
bounds=None,
|
|
25
|
-
x0=None,
|
|
26
|
-
input_sigma = 0.3,
|
|
27
|
-
popsize = 31,
|
|
28
|
-
max_evaluations = 100000,
|
|
29
|
-
accuracy = 1.0,
|
|
30
|
-
stop_fitness =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
def minimize(fun: Callable[[ArrayLike], float],
|
|
26
|
+
bounds: Optional[Bounds] = None,
|
|
27
|
+
x0: Optional[ArrayLike] = None,
|
|
28
|
+
input_sigma: Optional[Union[float, ArrayLike, Callable]] = 0.3,
|
|
29
|
+
popsize: Optional[int] = 31,
|
|
30
|
+
max_evaluations: Optional[int] = 100000,
|
|
31
|
+
accuracy: Optional[float] = 1.0,
|
|
32
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
33
|
+
stop_hist: Optional[float] = -1,
|
|
34
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
35
|
+
runid: Optional[int] = 0,
|
|
36
|
+
workers: Optional[int] = 1,
|
|
37
|
+
normalize: Optional[bool] = True,
|
|
38
|
+
delayed_update: Optional[bool] = True,
|
|
39
|
+
update_gap: Optional[int] = None
|
|
40
|
+
) -> OptimizeResult:
|
|
41
|
+
|
|
36
42
|
"""Minimization of a scalar function of one or more variables using a
|
|
37
43
|
C++ CMA-ES implementation called via ctypes.
|
|
38
44
|
|
|
@@ -40,10 +46,8 @@ def minimize(fun,
|
|
|
40
46
|
----------
|
|
41
47
|
fun : callable
|
|
42
48
|
The objective function to be minimized.
|
|
43
|
-
``fun(x
|
|
44
|
-
where ``x`` is an 1-D array with shape (dim,)
|
|
45
|
-
is a tuple of the fixed parameters needed to completely
|
|
46
|
-
specify the function.
|
|
49
|
+
``fun(x) -> float``
|
|
50
|
+
where ``x`` is an 1-D array with shape (dim,)
|
|
47
51
|
bounds : sequence or `Bounds`, optional
|
|
48
52
|
Bounds on variables. There are two ways to specify the bounds:
|
|
49
53
|
1. Instance of the `scipy.Bounds` class.
|
|
@@ -62,6 +66,9 @@ def minimize(fun,
|
|
|
62
66
|
values > 1.0 reduce the accuracy.
|
|
63
67
|
stop_fitness : float, optional
|
|
64
68
|
Limit for fitness value. If reached minimize terminates.
|
|
69
|
+
stop_hist : float, optional
|
|
70
|
+
Set to 0 if you want to prevent premature termination because
|
|
71
|
+
there is no progress
|
|
65
72
|
rg = numpy.random.Generator, optional
|
|
66
73
|
Random generator for creating random guesses.
|
|
67
74
|
runid : int, optional
|
|
@@ -70,7 +77,9 @@ def minimize(fun,
|
|
|
70
77
|
If not workers is None, function evaluation is performed in parallel for the whole population.
|
|
71
78
|
Useful for costly objective functions but is deactivated for parallel retry.
|
|
72
79
|
normalize : boolean, optional
|
|
73
|
-
pheno ->
|
|
80
|
+
if true pheno -> geno transformation maps arguments to interval [-1,1]
|
|
81
|
+
delayed_update : boolean, optional
|
|
82
|
+
if true uses delayed update / C++ parallelism, i false uses Python multithreading
|
|
74
83
|
update_gap : int, optional
|
|
75
84
|
number of iterations without distribution update
|
|
76
85
|
|
|
@@ -87,9 +96,6 @@ def minimize(fun,
|
|
|
87
96
|
|
|
88
97
|
lower, upper, guess = _check_bounds(bounds, x0, rg)
|
|
89
98
|
dim = guess.size
|
|
90
|
-
if lower is None:
|
|
91
|
-
lower = [0]*dim
|
|
92
|
-
upper = [0]*dim
|
|
93
99
|
if workers is None:
|
|
94
100
|
workers = 0
|
|
95
101
|
mu = int(popsize/2)
|
|
@@ -97,31 +103,217 @@ def minimize(fun,
|
|
|
97
103
|
input_sigma=input_sigma()
|
|
98
104
|
if np.ndim(input_sigma) == 0:
|
|
99
105
|
input_sigma = [input_sigma] * dim
|
|
100
|
-
if
|
|
101
|
-
|
|
106
|
+
if stop_hist is None:
|
|
107
|
+
stop_hist = -1;
|
|
102
108
|
array_type = ct.c_double * dim
|
|
103
|
-
c_callback = mo_call_back_type(
|
|
109
|
+
c_callback = mo_call_back_type(callback_so(fun, dim))
|
|
110
|
+
parfun = None if delayed_update == True or workers is None or workers <= 1 else parallel(fun, workers)
|
|
111
|
+
c_callback_par = call_back_par(callback_par(fun, parfun))
|
|
104
112
|
res = np.empty(dim+4)
|
|
105
113
|
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
106
114
|
try:
|
|
107
|
-
optimizeACMA_C(runid, c_callback,
|
|
108
|
-
array_type(*
|
|
109
|
-
|
|
115
|
+
optimizeACMA_C(runid, c_callback, c_callback_par,
|
|
116
|
+
dim, array_type(*guess),
|
|
117
|
+
None if lower is None else array_type(*lower),
|
|
118
|
+
None if upper is None else array_type(*upper),
|
|
119
|
+
array_type(*input_sigma), max_evaluations, stop_fitness, stop_hist, mu,
|
|
120
|
+
popsize, accuracy, int(rg.uniform(0, 2**32 - 1)),
|
|
121
|
+
normalize, delayed_update, -1 if update_gap is None else update_gap,
|
|
110
122
|
workers, res_p)
|
|
111
123
|
x = res[:dim]
|
|
112
124
|
val = res[dim]
|
|
113
125
|
evals = int(res[dim+1])
|
|
114
126
|
iterations = int(res[dim+2])
|
|
115
127
|
stop = int(res[dim+3])
|
|
116
|
-
|
|
128
|
+
res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
|
|
117
129
|
except Exception as ex:
|
|
118
|
-
|
|
130
|
+
res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
|
|
131
|
+
if not parfun is None:
|
|
132
|
+
parfun.stop()
|
|
133
|
+
return res
|
|
134
|
+
|
|
135
|
+
class ACMA_C:
|
|
136
|
+
|
|
137
|
+
def __init__(self,
|
|
138
|
+
dim,
|
|
139
|
+
bounds: Optional[Bounds] = None,
|
|
140
|
+
x0: Optional[ArrayLike] = None,
|
|
141
|
+
input_sigma: Optional[Union[float, ArrayLike, Callable]] = 0.3,
|
|
142
|
+
popsize: Optional[int] = 31,
|
|
143
|
+
max_evaluations: Optional[int] = 100000,
|
|
144
|
+
accuracy: Optional[float] = 1.0,
|
|
145
|
+
stop_fitness: Optional[float] = -np.inf,
|
|
146
|
+
stop_hist: Optional[float] = -1,
|
|
147
|
+
rg: Optional[Generator] = Generator(PCG64DXSM()),
|
|
148
|
+
runid: Optional[int] = 0,
|
|
149
|
+
normalize: Optional[bool] = True,
|
|
150
|
+
delayed_update: Optional[bool] = True,
|
|
151
|
+
update_gap: Optional[int] = None
|
|
152
|
+
):
|
|
153
|
+
|
|
154
|
+
"""Parameters
|
|
155
|
+
----------
|
|
156
|
+
dim : int
|
|
157
|
+
dimension of the argument of the objective function
|
|
158
|
+
bounds : sequence or `Bounds`, optional
|
|
159
|
+
Bounds on variables. There are two ways to specify the bounds:
|
|
160
|
+
1. Instance of the `scipy.Bounds` class.
|
|
161
|
+
2. Sequence of ``(min, max)`` pairs for each element in `x`. None
|
|
162
|
+
is used to specify no bound.
|
|
163
|
+
x0 : ndarray, shape (dim,)
|
|
164
|
+
Initial guess. Array of real elements of size (dim,),
|
|
165
|
+
where 'dim' is the number of independent variables.
|
|
166
|
+
input_sigma : ndarray, shape (dim,) or scalar
|
|
167
|
+
Initial step size for each dimension.
|
|
168
|
+
popsize = int, optional
|
|
169
|
+
CMA-ES population size.
|
|
170
|
+
max_evaluations : int, optional
|
|
171
|
+
Forced termination after ``max_evaluations`` function evaluations.
|
|
172
|
+
accuracy : float, optional
|
|
173
|
+
values > 1.0 reduce the accuracy.
|
|
174
|
+
stop_fitness : float, optional
|
|
175
|
+
Limit for fitness value. If reached minimize terminates.
|
|
176
|
+
stop_hist : float, optional
|
|
177
|
+
Set to 0 if you want to prevent premature termination because
|
|
178
|
+
there is no progress
|
|
179
|
+
rg = numpy.random.Generator, optional
|
|
180
|
+
Random generator for creating random guesses.
|
|
181
|
+
runid : int, optional
|
|
182
|
+
id used by the is_terminate callback to identify the CMA-ES run.
|
|
183
|
+
normalize : boolean, optional
|
|
184
|
+
if true pheno -> geno transformation maps arguments to interval [-1,1]
|
|
185
|
+
delayed_update : boolean, optional
|
|
186
|
+
if true uses delayed update / C++ parallelism, i false uses Python multithreading
|
|
187
|
+
update_gap : int, optional
|
|
188
|
+
number of iterations without distribution update"""
|
|
189
|
+
|
|
190
|
+
lower, upper, guess = _get_bounds(dim, bounds, x0, rg)
|
|
191
|
+
if lower is None:
|
|
192
|
+
lower = [0]*dim
|
|
193
|
+
upper = [0]*dim
|
|
194
|
+
mu = int(popsize/2)
|
|
195
|
+
if callable(input_sigma):
|
|
196
|
+
input_sigma=input_sigma()
|
|
197
|
+
if np.ndim(input_sigma) == 0:
|
|
198
|
+
input_sigma = [input_sigma] * dim
|
|
199
|
+
if stop_hist is None:
|
|
200
|
+
stop_hist = -1;
|
|
201
|
+
array_type = ct.c_double * dim
|
|
202
|
+
try:
|
|
203
|
+
self.ptr = initACMA_C(runid,
|
|
204
|
+
dim, array_type(*guess), array_type(*lower), array_type(*upper),
|
|
205
|
+
array_type(*input_sigma), max_evaluations, stop_fitness, stop_hist, mu,
|
|
206
|
+
popsize, accuracy, int(rg.uniform(0, 2**32 - 1)),
|
|
207
|
+
normalize, delayed_update, -1 if update_gap is None else update_gap)
|
|
208
|
+
self.popsize = popsize
|
|
209
|
+
self.dim = dim
|
|
210
|
+
except Exception as ex:
|
|
211
|
+
print (ex)
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
def __del__(self):
|
|
215
|
+
destroyACMA_C(self.ptr)
|
|
216
|
+
|
|
217
|
+
def ask(self) -> np.array:
|
|
218
|
+
try:
|
|
219
|
+
popsize = self.popsize
|
|
220
|
+
n = self.dim
|
|
221
|
+
res = np.empty(popsize*n)
|
|
222
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
223
|
+
askACMA_C(self.ptr, res_p)
|
|
224
|
+
xs = np.empty((popsize, n))
|
|
225
|
+
for p in range(popsize):
|
|
226
|
+
xs[p,:] = res[p*n : (p+1)*n]
|
|
227
|
+
return xs
|
|
228
|
+
except Exception as ex:
|
|
229
|
+
print (ex)
|
|
230
|
+
return None
|
|
119
231
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
232
|
+
def tell(self,
|
|
233
|
+
ys: np.ndarray,
|
|
234
|
+
xs: Optional[np.ndarray] = None) -> int:
|
|
235
|
+
if not xs is None:
|
|
236
|
+
return self.tell_x_(ys, xs)
|
|
237
|
+
try:
|
|
238
|
+
array_type_ys = ct.c_double * len(ys)
|
|
239
|
+
return tellACMA_C(self.ptr, array_type_ys(*ys))
|
|
240
|
+
except Exception as ex:
|
|
241
|
+
print (ex)
|
|
242
|
+
return -1
|
|
126
243
|
|
|
244
|
+
def tell_x_(self, ys: np.ndarray, xs: np.ndarray):
|
|
245
|
+
try:
|
|
246
|
+
flat_xs = xs.flatten()
|
|
247
|
+
array_type_xs = ct.c_double * len(flat_xs)
|
|
248
|
+
array_type_ys = ct.c_double * len(ys)
|
|
249
|
+
return tellXACMA_C(self.ptr, array_type_ys(*ys), array_type_xs(*flat_xs))
|
|
250
|
+
except Exception as ex:
|
|
251
|
+
print (ex)
|
|
252
|
+
return -1
|
|
253
|
+
|
|
254
|
+
def population(self) -> np.array:
|
|
255
|
+
try:
|
|
256
|
+
popsize = self.popsize
|
|
257
|
+
n = self.dim
|
|
258
|
+
res = np.empty(popsize*n)
|
|
259
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
260
|
+
populationACMA_C(self.ptr, res_p)
|
|
261
|
+
xs = np.array(popsize, n)
|
|
262
|
+
for p in range(popsize):
|
|
263
|
+
xs[p] = res[p*n : (p+1)*n]
|
|
264
|
+
return xs
|
|
265
|
+
except Exception as ex:
|
|
266
|
+
print (ex)
|
|
267
|
+
return None
|
|
127
268
|
|
|
269
|
+
def result(self) -> OptimizeResult:
|
|
270
|
+
res = np.empty(self.dim+4)
|
|
271
|
+
res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
|
|
272
|
+
try:
|
|
273
|
+
resultACMA_C(self.ptr, res_p)
|
|
274
|
+
x = res[:self.dim]
|
|
275
|
+
val = res[self.dim]
|
|
276
|
+
evals = int(res[self.dim+1])
|
|
277
|
+
iterations = int(res[self.dim+2])
|
|
278
|
+
stop = int(res[self.dim+3])
|
|
279
|
+
res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
|
|
280
|
+
except Exception as ex:
|
|
281
|
+
res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
|
|
282
|
+
return res
|
|
283
|
+
|
|
284
|
+
if not libcmalib is None:
|
|
285
|
+
|
|
286
|
+
optimizeACMA_C = libcmalib.optimizeACMA_C
|
|
287
|
+
optimizeACMA_C.argtypes = [ct.c_long, mo_call_back_type, call_back_par, ct.c_int, \
|
|
288
|
+
ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
|
|
289
|
+
ct.POINTER(ct.c_double), ct.c_int, ct.c_double, ct.c_double, ct.c_int, ct.c_int, \
|
|
290
|
+
ct.c_double, ct.c_long, ct.c_bool, ct.c_bool, ct.c_int,
|
|
291
|
+
ct.c_int, ct.POINTER(ct.c_double)]
|
|
292
|
+
|
|
293
|
+
initACMA_C = libcmalib.initACMA_C
|
|
294
|
+
initACMA_C.argtypes = [ct.c_long, ct.c_int, \
|
|
295
|
+
ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
|
|
296
|
+
ct.POINTER(ct.c_double), ct.c_int, ct.c_double, ct.c_double, ct.c_int,
|
|
297
|
+
ct.c_int, ct.c_double, ct.c_long, ct.c_bool, ct.c_bool, ct.c_int]
|
|
298
|
+
|
|
299
|
+
initACMA_C.restype = ct.c_void_p
|
|
300
|
+
|
|
301
|
+
destroyACMA_C = libcmalib.destroyACMA_C
|
|
302
|
+
destroyACMA_C.argtypes = [ct.c_void_p]
|
|
303
|
+
|
|
304
|
+
askACMA_C = libcmalib.askACMA_C
|
|
305
|
+
askACMA_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
306
|
+
|
|
307
|
+
tellACMA_C = libcmalib.tellACMA_C
|
|
308
|
+
tellACMA_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
309
|
+
tellACMA_C.restype = ct.c_int
|
|
310
|
+
|
|
311
|
+
tellXACMA_C = libcmalib.tellXACMA_C
|
|
312
|
+
tellXACMA_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double), ct.POINTER(ct.c_double)]
|
|
313
|
+
tellXACMA_C.restype = ct.c_int
|
|
314
|
+
|
|
315
|
+
populationACMA_C = libcmalib.populationACMA_C
|
|
316
|
+
populationACMA_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
|
317
|
+
|
|
318
|
+
resultACMA_C = libcmalib.resultACMA_C
|
|
319
|
+
resultACMA_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
|
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
|