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/diversifier.py ADDED
@@ -0,0 +1,357 @@
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
+ from __future__ import annotations
6
+
7
+ """ Numpy based implementation of an diversifying wrapper / parallel retry mechanism.
8
+
9
+ Uses the archive from CVT MAP-Elites (https://arxiv.org/abs/1610.05729)
10
+ and generalizes ideas from CMA-ME (https://arxiv.org/pdf/1912.02400.pdf)
11
+ to other wrapped algorithms.
12
+
13
+ Both the parallel retry and the archive based modification of the fitness
14
+ function enhance the diversification of the optimization result.
15
+ The resulting archive may be stored and can be used to continue the
16
+ optimization later.
17
+
18
+ Requires a QD-fitness function returning both an fitness value and a
19
+ behavior vector used to determine the corresponding archive niche using
20
+ Voronoi tesselation.
21
+
22
+ Returns an archive of niche-elites containing also for each niche statistics
23
+ about the associated solutions.
24
+ """
25
+
26
+ import numpy as np
27
+ from numpy.random import Generator, PCG64DXSM, SeedSequence
28
+ from multiprocessing import Process
29
+ from scipy.optimize import Bounds
30
+ from fcmaes.optimizer import dtime, de_cma, Optimizer
31
+ import multiprocessing as mp
32
+ import ctypes as ct
33
+ from time import perf_counter
34
+ from fcmaes.mapelites import Archive, update_archive, rng
35
+ from fcmaes import advretry
36
+ from fcmaes.evaluator import is_debug_active
37
+ from loguru import logger
38
+ import threadpoolctl
39
+
40
+ from typing import Optional, Callable, Tuple, Dict
41
+ from numpy.typing import ArrayLike
42
+
43
+ def minimize(qd_fitness: Callable[[ArrayLike], Tuple[float, np.ndarray]],
44
+ bounds: Bounds,
45
+ qd_bounds: Bounds,
46
+ niche_num: Optional[int] = 10000,
47
+ samples_per_niche: Optional[int] = 20,
48
+ max_evals: Optional[int] = None,
49
+ workers: Optional[int] = mp.cpu_count(),
50
+ archive: Optional[Archive] = None,
51
+ opt_params: Optional[Dict] = {},
52
+ use_stats: Optional[bool] = False,
53
+ ) -> Archive:
54
+
55
+ """Wraps an fcmaes optmizer/solver by hijacking its tell function.
56
+ Works as CVT Map-Elites in maintaining an archive of diverse elites.
57
+ But this archive is not used to derive solution vectors, but to reevaluate them.
58
+ For each fitness result it determines its niche. The "told" fitness is
59
+ determined relative to its local elite. If it is better the evaluated solution
60
+ becomes the new niche-elite.
61
+ This way the wrapped solver is "tricked" to follow a QD-goal: Finding empty niches
62
+ and improving all niches. This works not only for CMA-ES, but also for other
63
+ solvers: DE, CR-FM-NES and PGPE. Both their Python and C++ versions are supported.
64
+
65
+ Parameters
66
+ ----------
67
+ solver : evolutionary algorithm, needs to support ask/tell
68
+ qd_fitness : callable
69
+ The objective function to be minimized. Returns a fitness value and a behavior vector.
70
+ ``qd_fitness(x) -> float, array``
71
+ where ``x`` is an 1-D array with shape (n,)
72
+ bounds : `Bounds`
73
+ Bounds on variables. Instance of the `scipy.Bounds` class.
74
+ qd_bounds : `Bounds`
75
+ Bounds on behavior descriptors. Instance of the `scipy.Bounds` class.
76
+ niche_num : int, optional
77
+ Number of niches.
78
+ samples_per_niche : int, optional
79
+ Number of samples used for niche computation.
80
+ If samples_per_niche > 0 cvt-clustering is used, else grid-clustering is used.
81
+ max_evals : int, optional
82
+ Number of fitness evaluations.
83
+ workers : int, optional
84
+ Number of spawned parallel worker processes.
85
+ archive : Archive, optional
86
+ If defined MAP-elites is continued for this archive.
87
+ opt_params : dictionary, optional (or a list/tuple/array of these)
88
+ Parameters selecting and configuring the wrapped solver.
89
+ 'solver' - supported are 'CMA','CMA_CPP','CRMFNES','CRMFNES_CPP','DE','DE_CPP','PGPE'
90
+ default is 'CMA_CPP'
91
+ 'popsize' - population size, default = 32
92
+ 'sigma' - initial distribution sigma, default = rg.uniform(0.03, 0.3)**2)
93
+ 'mean' - initial distribution mean, default=rg.uniform(bounds.lb, bounds.ub))
94
+ 'max_evals' - maximal number of evaluations per run, default = 50000
95
+ 'stall_criterion' - how many iterations without progress allowed, default = 50 iterations
96
+ If a list/tuple/array of parameters are given, the corresponding solvers are called in a
97
+ sequence.
98
+ use_stats : bool, optional
99
+ If True, archive accumulates statistics of the solutions
100
+
101
+ Returns
102
+ -------
103
+ archive : Archive
104
+ Resulting archive of niches. Can be stored for later continuation of MAP-elites."""
105
+
106
+ if max_evals is None:
107
+ max_evals = workers*50000
108
+ dim = len(bounds.lb)
109
+ if archive is None:
110
+ archive = Archive(dim, qd_bounds, niche_num, use_stats)
111
+ archive.init_niches(samples_per_niche)
112
+ # initialize archive with random values
113
+ archive.xs_view[:] = rng.uniform(bounds.lb, bounds.ub, (niche_num, dim))
114
+ t0 = perf_counter()
115
+ qd_fitness.archive = archive # attach archive for logging
116
+ minimize_parallel_(archive, qd_fitness, bounds, workers, opt_params, max_evals)
117
+ if is_debug_active():
118
+ ys = np.sort(archive.get_ys())[:min(100, archive.capacity)] # best fitness values
119
+ logger.debug(f'best {min(ys):.3f} worst {max(ys):.3f} ' +
120
+ f'mean {np.mean(ys):.3f} stdev {np.std(ys):.3f} time {dtime(t0)} s')
121
+ return archive
122
+
123
+ def apply_advretry(fitness: Callable[[ArrayLike], float],
124
+ qd_fitness: Callable[[ArrayLike], Tuple[float, np.ndarray]],
125
+ bounds: Bounds,
126
+ archive: Archive,
127
+ optimizer: Optional[Optimizer] = None,
128
+ num_retries: Optional[int] = 1000,
129
+ workers: Optional[int] = mp.cpu_count(),
130
+ max_eval_fac: Optional[float] = 5.0,
131
+ xs: Optional[np.ndarray] = None,
132
+ ys: Optional[np.ndarray] = None,
133
+ x_conv: Callable[[ArrayLike], ArrayLike] = None):
134
+
135
+ """Unifies the QD world with traditional optimization. It converts
136
+ a QD-archive into a multiprocessing store used by the fcmaes smart
137
+ boundary management meta algorithm (advretry). Then advretry is applied
138
+ to find the global optimum. Finally the updated store is feed back into
139
+ the QD-archive. For this we need a descriptor generating function
140
+ 'descriptors' which may require reevaluation of the new solutions.
141
+
142
+ Parameters
143
+ ----------
144
+ solver : evolutionary algorithm, needs to support ask/tell
145
+ fitness : callable
146
+ The objective function to be minimized. Returns a fitness value.
147
+ ``fitness(x) -> float``
148
+ qf_fun : callable
149
+ Generates the descriptors for a solution. Returns a behavior vector.
150
+ ``descriptors(x) -> array``
151
+ where ``x`` is an 1-D array with shape (n,)
152
+ bounds : `Bounds`
153
+ Bounds on variables. Instance of the `scipy.Bounds` class.
154
+ archive : Archive
155
+ Improves the solutions if this archive.
156
+ optimizer : optimizer.Optimizer, optional
157
+ Optimizer to use. Default is a sequence of differential evolution and CMA-ES.
158
+ num_retries : int, optional
159
+ Number of optimization runs.
160
+ workers : int, optional
161
+ Number of spawned parallel worker processes.
162
+ max_eval_fac : int, optional
163
+ Final limit of the number of function evaluations = max_eval_fac*min_evaluations
164
+ xs : ndarray, optional
165
+ Used to initialize advretry. If undefined the archive content is used.
166
+ If xs is defined, ys must be too
167
+ ys : ndarray, optional
168
+ Used to initialize advretry. If undefined the archive content is used.
169
+ x_conv : callable, optional
170
+ If defined converts the x in xs to solutions suitable for the given archive.
171
+ If undefined it is assumed that the x in xs are valid archive solutons.
172
+ """
173
+
174
+ if optimizer is None:
175
+ optimizer = de_cma(1500)
176
+ # generate advretry store
177
+ store = advretry.Store(fitness, bounds, num_retries=num_retries,
178
+ max_eval_fac=max_eval_fac)
179
+
180
+ # select only occupied entries
181
+ if xs is None:
182
+ ys = archive.get_ys()
183
+ valid = (ys < np.inf)
184
+ ys = ys[valid]
185
+ xs = archive.xs_view[valid]
186
+ t0 = perf_counter()
187
+ # transfer to advretry store
188
+ for i in range(len(ys)):
189
+ store.add_result(ys[i], xs[i], 1)
190
+ # perform parallel retry
191
+ advretry.retry(store, optimizer.minimize, workers=workers)
192
+ # transfer back to archive
193
+ xs = store.xs_view
194
+ if not x_conv is None:
195
+ xs = [x_conv(x) for x in xs]
196
+ yds = [qd_fitness(x) for x in xs]
197
+ descs = np.array([yd[1] for yd in yds])
198
+ ys = np.array([yd[0] for yd in yds])
199
+ niches = archive.index_of_niches(descs)
200
+ for i in range(len(ys)):
201
+ archive.set(niches[i], (ys[i], descs[i]), xs[i])
202
+ archive.argsort()
203
+ if is_debug_active():
204
+ ys = np.sort(archive.get_ys())[:min(100, archive.capacity)] # best fitness values
205
+ logger.debug(f'best {min(ys):.3f} worst {max(ys):.3f} ' +
206
+ f'mean {np.mean(ys):.3f} stdev {np.std(ys):.3f} time {dtime(t0)} s')
207
+
208
+ def minimize_parallel_(archive, fitness, bounds, workers, opt_params, max_evals):
209
+ sg = SeedSequence()
210
+ rgs = [Generator(PCG64DXSM(s)) for s in sg.spawn(workers)]
211
+ evals = mp.RawValue(ct.c_long, 0)
212
+ proc=[Process(target=run_minimize_,
213
+ args=(archive, fitness, bounds, rgs[p],
214
+ opt_params, p, workers, evals, max_evals)) for p in range(workers)]
215
+ [p.start() for p in proc]
216
+ [p.join() for p in proc]
217
+
218
+ def run_minimize_(archive, fitness, bounds, rg, opt_params, p, workers, evals, max_evals):
219
+ with threadpoolctl.threadpool_limits(limits=1, user_api="blas"):
220
+ if isinstance(opt_params, (list, tuple, np.ndarray)):
221
+ default_workers = int(workers/2) if len(opt_params) > 1 else workers
222
+ for params in opt_params: # call MAP-Elites
223
+ if 'elites' == params.get('solver'):
224
+ elites_workers = params.get('workers', default_workers)
225
+ if p < elites_workers:
226
+ run_map_elites_(archive, fitness, bounds, rg, evals, max_evals, params)
227
+ return
228
+ while evals.value < max_evals: # call solvers in loop
229
+ best_x = None
230
+ if isinstance(opt_params, (list, tuple, np.ndarray)):
231
+ for params in opt_params: # call in sequence
232
+ if 'elites' == params.get('solver'):
233
+ continue # ignore in loop
234
+ if best_x is None:
235
+ # selecting a niche elite is no improvement over random x0
236
+ x0 = None#, _, _ = archive.random_xs_one(select_n, rg)
237
+ best_x = minimize_(archive, fitness, bounds, rg, evals, max_evals, params,
238
+ x0 = x0)
239
+ else:
240
+ best_x = minimize_(archive, fitness, bounds, rg, evals, max_evals, params, x0 = best_x)
241
+ else:
242
+ minimize_(archive, fitness, bounds, rg, evals, max_evals, opt_params)
243
+
244
+ from fcmaes.mapelites import variation_, iso_dd_
245
+
246
+ def run_map_elites_(archive, fitness, bounds, rg, evals, max_evals, opt_params = {}):
247
+ popsize = opt_params.get('popsize', 32)
248
+ use_sbx = opt_params.get('use_sbx', True)
249
+ dis_c = opt_params.get('dis_c', 20)
250
+ dis_m = opt_params.get('dis_m', 20)
251
+ iso_sigma = opt_params.get('iso_sigma', 0.01)
252
+ line_sigma = opt_params.get('line_sigma', 0.2)
253
+ select_n = archive.capacity
254
+ while evals.value < max_evals:
255
+ if use_sbx:
256
+ pop = archive.random_xs(select_n, popsize, rg)
257
+ xs = variation_(pop, bounds.lb, bounds.ub, rg, dis_c, dis_m)
258
+ else:
259
+ x1 = archive.random_xs(select_n, popsize, rg)
260
+ x2 = archive.random_xs(select_n, popsize, rg)
261
+ xs = iso_dd_(x1, x2, bounds.lb, bounds.ub, rg, iso_sigma, line_sigma)
262
+ yds = [fitness(x) for x in xs]
263
+ evals.value += popsize
264
+ descs = np.array([yd[1] for yd in yds])
265
+ niches = archive.index_of_niches(descs)
266
+ for i in range(len(yds)):
267
+ archive.set(niches[i], yds[i], xs[i])
268
+ archive.argsort()
269
+ select_n = archive.get_occupied()
270
+
271
+ def minimize_(archive, fitness, bounds, rg, evals, max_evals, opt_params, x0 = None):
272
+ if 'BITE_CPP' == opt_params.get('solver'):
273
+ return run_bite_(archive, fitness, bounds, rg, evals, max_evals, opt_params, x0 = None)
274
+ else:
275
+ es = get_solver_(bounds, opt_params, rg, x0)
276
+ stall_criterion = opt_params.get('stall_criterion', 20)
277
+ max_evals_iter = opt_params.get('max_evals', 50000)
278
+ max_iters = int(max_evals_iter/es.popsize)
279
+ old_ys = None
280
+ last_improve = 0
281
+ best_x = None
282
+ best_y = np.inf
283
+ for iter in range(max_iters):
284
+ xs = es.ask()
285
+ ys, real_ys = update_archive(archive, xs, fitness)
286
+ evals.value += es.popsize
287
+ # update best real fitness
288
+ yi = np.argmin(real_ys)
289
+ ybest = real_ys[yi]
290
+ if ybest < best_y:
291
+ best_y = ybest
292
+ best_x = xs[yi]
293
+ if not old_ys is None:
294
+ if (np.sort(ys) < old_ys).any():
295
+ last_improve = iter
296
+ if last_improve + stall_criterion < iter:
297
+ break
298
+ stop = es.tell(ys)
299
+ if stop != 0 or evals.value >= max_evals:
300
+ break
301
+ old_ys = np.sort(ys)
302
+ return best_x # real best solution
303
+
304
+ from fcmaes import cmaes, cmaescpp, crfmnescpp, pgpecpp, decpp, crfmnes, de, bitecpp
305
+
306
+ def run_bite_(archive, fitness, bounds, rg, evals, max_evals, opt_params, x0 = None):
307
+ # BiteOpt doesn't support ask/tell, so we have to "patch" fitness. Note that Voronoi
308
+ # tesselation is more expensive if called for single behavior vectors and not for batches.
309
+
310
+ def fit(x: Callable[[ArrayLike], float]):
311
+ if evals.value >= max_evals:
312
+ return np.inf
313
+ evals.value += 1
314
+ ys, _ = update_archive(archive, [x], fitness)
315
+ return ys[0]
316
+
317
+ max_evals_iter = opt_params.get('max_evals', 50000)
318
+ stall_criterion = opt_params.get('stall_criterion', 20)
319
+ #popsize = opt_params.get('popsize', 0)
320
+ ret = bitecpp.minimize(fit, bounds, x0 = x0, M = 1,
321
+ stall_criterion = stall_criterion,
322
+ max_evaluations = max_evals_iter, rg = rg)
323
+ return ret.x
324
+
325
+ def get_solver_(bounds, opt_params, rg, x0 = None):
326
+ dim = len(bounds.lb)
327
+ popsize = opt_params.get('popsize', 31)
328
+ #sigma = opt_params.get('sigma',rg.uniform(0.03, 0.3)**2)
329
+ sigma = opt_params.get('sigma',rg.uniform(0.1, 0.5)**2)
330
+ #sigma = opt_params.get('sigma',rg.uniform(0.2, 0.5)**2)
331
+ #sigma = opt_params.get('sigma',rg.uniform(0.1, 0.5))
332
+ mean = opt_params.get('mean', rg.uniform(bounds.lb, bounds.ub)) \
333
+ if x0 is None else x0
334
+ name = opt_params.get('solver', 'CMA_CPP')
335
+ if name == 'CMA':
336
+ return cmaes.Cmaes(bounds, x0 = mean,
337
+ popsize = popsize, input_sigma = sigma, rg = rg)
338
+ elif name == 'CMA_CPP':
339
+ return cmaescpp.ACMA_C(dim, bounds, x0 = mean, #stop_hist = 0,
340
+ popsize = popsize, input_sigma = sigma, rg = rg)
341
+ elif name == 'CRMFNES':
342
+ return crfmnes.CRFMNES(dim, bounds, x0 = mean,
343
+ popsize = popsize, input_sigma = sigma, rg = rg)
344
+ elif name == 'CRMFNES_CPP':
345
+ return crfmnescpp.CRFMNES_C(dim, bounds, x0 = mean,
346
+ popsize = popsize, input_sigma = sigma, rg = rg)
347
+ elif name == 'DE':
348
+ return de.DE(dim, bounds, popsize = popsize, rg = rg)
349
+ elif name == 'DE_CPP':
350
+ return decpp.DE_C(dim, bounds, popsize = popsize, rg = rg)
351
+ elif name == 'PGPE':
352
+ return pgpecpp.PGPE_C(dim, bounds, x0 = mean,
353
+ popsize = popsize, input_sigma = sigma, rg = rg)
354
+ else:
355
+ print ("invalid solver")
356
+ return None
357
+
fcmaes/evaluator.py CHANGED
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # This source code is licensed under the MIT license found in the
4
4
  # LICENSE file in the root directory.
5
+ from __future__ import annotations
5
6
 
6
7
  """ Parallel objective function evaluator.
7
8
  Uses pipes to avoid re-spawning new processes for each eval_parallel call.
@@ -12,14 +13,49 @@
12
13
 
13
14
  from multiprocessing import Process, Pipe
14
15
  import multiprocessing as mp
16
+ import ctypes as ct
15
17
  import numpy as np
16
- import sys
17
- import math
18
+ import sys, math, os
19
+ from loguru import logger
20
+ from typing import Optional, Callable, Tuple
21
+ from numpy.typing import ArrayLike
18
22
 
19
- def eval_parallel(xs, evaluator):
23
+ pipe_limit = 64 # higher values can cause issues
24
+
25
+ def is_log_level_active(level):
26
+ try: # nasty but currently there is no other way
27
+ for handler in logger._core.handlers.values():
28
+ if handler._levelno <= logger.level(level).no:
29
+ return True
30
+ except Exception as ex:
31
+ pass
32
+ return False
33
+
34
+ def is_debug_active():
35
+ return is_log_level_active("DEBUG")
36
+
37
+ def is_trace_active():
38
+ return is_log_level_active("TRACE")
39
+
40
+ def eval_parallel(xs: ArrayLike,
41
+ evaluator: Evaluator):
20
42
  popsize = len(xs)
21
43
  ys = np.empty(popsize)
22
- pipe_limit = 256
44
+ i0 = 0
45
+ i1 = min(popsize, pipe_limit)
46
+ while True:
47
+ _eval_parallel_segment(xs, ys, i0, i1, evaluator)
48
+ if i1 >= popsize:
49
+ break;
50
+ i0 += pipe_limit
51
+ i1 = min(popsize, i1 + pipe_limit)
52
+ return ys
53
+
54
+ def eval_parallel_mo(xs: ArrayLike,
55
+ evaluator: Evaluator,
56
+ nobj: int):
57
+ popsize = len(xs)
58
+ ys = np.empty((popsize,nobj))
23
59
  i0 = 0
24
60
  i1 = min(popsize, pipe_limit)
25
61
  while True:
@@ -33,14 +69,14 @@ def eval_parallel(xs, evaluator):
33
69
  class Evaluator(object):
34
70
 
35
71
  def __init__(self,
36
- fun, # objective function
72
+ fun: Callable[[ArrayLike], float], # objective function
37
73
  ):
38
74
  self.fun = fun
39
75
  self.pipe = Pipe()
40
76
  self.read_mutex = mp.Lock()
41
77
  self.write_mutex = mp.Lock()
42
78
 
43
- def start(self, workers=mp.cpu_count()):
79
+ def start(self, workers: Optional[int] = mp.cpu_count()):
44
80
  self.workers = workers
45
81
  self.proc=[Process(target=_evaluate, args=(self.fun,
46
82
  self.pipe, self.read_mutex, self.write_mutex)) for _ in range(workers)]
@@ -74,3 +110,240 @@ def _evaluate(fun, pipe, read_mutex, write_mutex): # worker
74
110
  y = sys.float_info.max
75
111
  with write_mutex:
76
112
  pipe[1].send((i, y)) # Send result
113
+
114
+ def _check_bounds(bounds, guess, rg):
115
+ if bounds is None and guess is None:
116
+ raise ValueError('either guess or bounds need to be defined')
117
+ if bounds is None:
118
+ return None, None, np.asarray(guess)
119
+ if guess is None:
120
+ guess = rg.uniform(bounds.lb, bounds.ub)
121
+ return np.asarray(bounds.lb), np.asarray(bounds.ub), np.asarray(guess)
122
+
123
+ def _get_bounds(dim, bounds, guess, rg):
124
+ if bounds is None:
125
+ if guess is None:
126
+ guess = np.asarray(np.zeros(dim))
127
+ return None, None, guess
128
+ if guess is None:
129
+ guess = rg.uniform(bounds.lb, bounds.ub)
130
+ return np.asarray(bounds.lb), np.asarray(bounds.ub), np.asarray(guess)
131
+
132
+ class _fitness(object):
133
+ """wrapper around the objective function, scales relative to boundaries."""
134
+
135
+ def __init__(self, fun, lower, upper, normalize = None):
136
+ self.fun = fun
137
+ self.evaluation_counter = 0
138
+ self.lower = lower
139
+ self.normalize = False
140
+ if not (lower is None or normalize is None):
141
+ self.normalize = normalize
142
+ if not lower is None:
143
+ self.upper = upper
144
+ self.scale = 0.5 * (upper - lower)
145
+ self.typx = 0.5 * (upper + lower)
146
+
147
+ def values(self, Xs): #enables parallel evaluation
148
+ values = self.fun(Xs)
149
+ self.evaluation_counter += len(Xs)
150
+ return np.array(values)
151
+
152
+ def closestFeasible(self, X):
153
+ if self.lower is None:
154
+ return X
155
+ else:
156
+ if self.normalize:
157
+ return np.clip(X, -1.0, 1.0)
158
+ else:
159
+ return np.clip(X, self.lower, self.upper)
160
+
161
+ def encode(self, X):
162
+ if self.normalize:
163
+ return (X - self.typx) / self.scale
164
+ else:
165
+ return X
166
+
167
+ def decode(self, X):
168
+ if self.normalize:
169
+ return (X * self.scale) + self.typx
170
+ else:
171
+ return X
172
+
173
+ def serial(fun):
174
+ """Convert an objective function for serial execution for cmaes.minimize.
175
+
176
+ Parameters
177
+ ----------
178
+ fun : objective function mapping a list of float arguments to a float value
179
+
180
+ Returns
181
+ -------
182
+ out : function
183
+ A function mapping a list of lists of float arguments to a list of float values
184
+ by applying the input function in a loop."""
185
+
186
+ return lambda xs : [_tryfun(fun, x) for x in xs]
187
+
188
+ def _func_serial(fun, num, pid, xs, ys):
189
+ for i in range(pid, len(xs), num):
190
+ ys[i] = _tryfun(fun, xs[i])
191
+
192
+ def _tryfun(fun, x):
193
+ try:
194
+ fit = fun(x)
195
+ return fit if math.isfinite(fit) else sys.float_info.max
196
+ except Exception:
197
+ return sys.float_info.max
198
+
199
+ class parallel(object):
200
+ """Convert an objective function for parallel execution for cmaes.minimize.
201
+
202
+ Parameters
203
+ ----------
204
+ fun : objective function mapping a list of float arguments to a float value.
205
+
206
+ represents a function mapping a list of lists of float arguments to a list of float values
207
+ by applying the input function using parallel processes. stop needs to be called to avoid
208
+ a resource leak"""
209
+
210
+ def __init__(self,
211
+ fun: Callable[[ArrayLike], float],
212
+ workers: Optional[int] = mp.cpu_count()):
213
+ self.evaluator = Evaluator(fun)
214
+ self.evaluator.start(workers)
215
+
216
+ def __call__(self, xs: ArrayLike) -> np.ndarray:
217
+ return eval_parallel(xs, self.evaluator)
218
+
219
+ def stop(self):
220
+ self.evaluator.stop()
221
+
222
+ class parallel_mo(object):
223
+
224
+ def __init__(self,
225
+ fun: Callable[[ArrayLike], ArrayLike],
226
+ nobj: int,
227
+ workers: Optional[int] = mp.cpu_count()):
228
+ self.nobj = nobj
229
+ self.evaluator = Evaluator(fun)
230
+ self.evaluator.start(workers)
231
+
232
+ def __call__(self, xs: ArrayLike) -> np.ndarray:
233
+ return eval_parallel_mo(xs, self.evaluator, self.nobj)
234
+
235
+ def stop(self):
236
+ self.evaluator.stop()
237
+
238
+ class callback(object):
239
+
240
+ def __init__(self, fun: Callable[[ArrayLike], float]):
241
+ self.fun = fun
242
+
243
+ def __call__(self, n: int, x: ArrayLike) -> float:
244
+ try:
245
+ fit = self.fun(np.fromiter((x[i] for i in range(n)), dtype=float))
246
+ return fit if math.isfinite(fit) else sys.float_info.max
247
+ except Exception as ex:
248
+ return sys.float_info.max
249
+
250
+ class callback_so(object):
251
+
252
+ def __init__(self,
253
+ fun: Callable[[ArrayLike], float],
254
+ dim: int,
255
+ is_terminate: Optional[Callable[[ArrayLike, float], bool]] = None):
256
+ self.fun = fun
257
+ self.dim = dim
258
+ self.nobj = 1
259
+ self.is_terminate = is_terminate
260
+
261
+ def __call__(self, dim, x, y):
262
+ try:
263
+ arrTypeX = ct.c_double*(self.dim)
264
+ xaddr = ct.addressof(x.contents)
265
+ xbuf = np.frombuffer(arrTypeX.from_address(xaddr))
266
+ arrTypeY = ct.c_double*(self.nobj)
267
+ yaddr = ct.addressof(y.contents)
268
+ ybuf = np.frombuffer(arrTypeY.from_address(yaddr))
269
+ fit = self.fun(xbuf)
270
+ ybuf[0] = fit if math.isfinite(fit) else sys.float_info.max
271
+ return False if self.is_terminate is None else self.is_terminate(xbuf, ybuf)
272
+ except Exception as ex:
273
+ print (ex)
274
+ return False
275
+
276
+ class callback_mo(object):
277
+
278
+ def __init__(self,
279
+ fun: Callable[[ArrayLike], ArrayLike],
280
+ dim: int,
281
+ nobj: int,
282
+ is_terminate: Optional[bool] = None):
283
+ self.fun = fun
284
+ self.dim = dim
285
+ self.nobj = nobj
286
+ self.is_terminate = is_terminate
287
+
288
+ def __call__(self, dim: int, x, y):
289
+ try:
290
+ arrTypeX = ct.c_double*(dim)
291
+ xaddr = ct.addressof(x.contents)
292
+ xbuf = np.frombuffer(arrTypeX.from_address(xaddr))
293
+ arrTypeY = ct.c_double*(self.nobj)
294
+ yaddr = ct.addressof(y.contents)
295
+ ybuf = np.frombuffer(arrTypeY.from_address(yaddr))
296
+ ybuf[:] = self.fun(xbuf)[:]
297
+ return False if self.is_terminate is None else self.is_terminate(xbuf, ybuf)
298
+ except Exception as ex:
299
+ print (ex)
300
+ return False
301
+
302
+ class callback_par(object):
303
+
304
+ def __init__(self,
305
+ fun: Callable[[ArrayLike], float],
306
+ parfun: Callable[[ArrayLike], ArrayLike]):
307
+ self.fun = fun
308
+ self.parfun = parfun
309
+
310
+ def __call__(self, popsize, n, xs_, ys_):
311
+ try:
312
+ arrType = ct.c_double*(popsize*n)
313
+ addr = ct.addressof(xs_.contents)
314
+ xall = np.frombuffer(arrType.from_address(addr))
315
+
316
+ if self.parfun is None:
317
+ for p in range(popsize):
318
+ ys_[p] = self.fun(xall[p*n : (p+1)*n])
319
+ else:
320
+ xs = []
321
+ for p in range(popsize):
322
+ x = xall[p*n : (p+1)*n]
323
+ xs.append(x)
324
+ ys = self.parfun(xs)
325
+ for p in range(popsize):
326
+ ys_[p] = ys[p]
327
+ except Exception as ex:
328
+ print (ex)
329
+
330
+ basepath = os.path.dirname(os.path.abspath(__file__))
331
+
332
+ try:
333
+ if sys.platform.startswith('linux'):
334
+ libcmalib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.so')
335
+ elif 'mac' in sys.platform or 'darwin' in sys.platform:
336
+ libcmalib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.dylib')
337
+ else:
338
+ os.environ['PATH'] = (basepath + '/lib') + os.pathsep + os.environ['PATH']
339
+ libcmalib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.dll')
340
+ except Exception as ex:
341
+ libcmalib = None
342
+
343
+ mo_call_back_type = ct.CFUNCTYPE(ct.c_bool, ct.c_int, ct.POINTER(ct.c_double), ct.POINTER(ct.c_double))
344
+
345
+ call_back_type = ct.CFUNCTYPE(ct.c_double, ct.c_int, ct.POINTER(ct.c_double))
346
+
347
+ call_back_par = ct.CFUNCTYPE(None, ct.c_int, ct.c_int, \
348
+ ct.POINTER(ct.c_double), ct.POINTER(ct.c_double))
349
+
fcmaes/lib/libacmalib.dll CHANGED
Binary file
Binary file
fcmaes/lib/libacmalib.so CHANGED
Binary file
fcmaes/lib/libhbv.so ADDED
Binary file
fcmaes/lib/liblrgv.so ADDED
Binary file