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/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)]