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/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 MT19937, Generator
17
- from scipy.optimize import OptimizeResult
18
- from fcmaes.cmaes import _check_bounds
19
- from fcmaes.decpp import mo_call_back_type, callback, libcmalib
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 = None,
31
- rg = Generator(MT19937()),
32
- runid=0,
33
- workers = 1,
34
- normalize = True,
35
- update_gap = None):
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, *args) -> float``
44
- where ``x`` is an 1-D array with shape (dim,) and ``args``
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 -> if true geno transformation maps arguments to interval [-1,1]
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 stop_fitness is None:
101
- stop_fitness = math.inf
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(callback(fun, dim))
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, dim, array_type(*guess), array_type(*lower), array_type(*upper),
108
- array_type(*input_sigma), max_evaluations, stop_fitness, mu,
109
- popsize, accuracy, int(rg.uniform(0, 2**32 - 1)), normalize, -1 if update_gap is None else update_gap,
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
- return OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
128
+ res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
117
129
  except Exception as ex:
118
- return OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
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
- optimizeACMA_C = libcmalib.optimizeACMA_C
121
- optimizeACMA_C.argtypes = [ct.c_long, mo_call_back_type, ct.c_int, \
122
- ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
123
- ct.POINTER(ct.c_double), ct.c_int, ct.c_double, ct.c_int, ct.c_int, \
124
- ct.c_double, ct.c_long, ct.c_bool, ct.c_int,
125
- ct.c_int, ct.POINTER(ct.c_double)]
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