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/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,31 +13,70 @@
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
- for i in range(popsize):
23
- evaluator.pipe[0].send((i, xs[i]))
24
- for i in range(popsize):
25
- i, y = evaluator.pipe[0].recv()
26
- ys[i] = y
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)
27
52
  return ys
28
-
53
+
54
+ def eval_parallel_mo(xs: ArrayLike,
55
+ evaluator: Evaluator,
56
+ nobj: int):
57
+ popsize = len(xs)
58
+ ys = np.empty((popsize,nobj))
59
+ i0 = 0
60
+ i1 = min(popsize, pipe_limit)
61
+ while True:
62
+ _eval_parallel_segment(xs, ys, i0, i1, evaluator)
63
+ if i1 >= popsize:
64
+ break;
65
+ i0 += pipe_limit
66
+ i1 = min(popsize, i1 + pipe_limit)
67
+ return ys
68
+
29
69
  class Evaluator(object):
30
70
 
31
71
  def __init__(self,
32
- fun, # objective function
72
+ fun: Callable[[ArrayLike], float], # objective function
33
73
  ):
34
74
  self.fun = fun
35
75
  self.pipe = Pipe()
36
76
  self.read_mutex = mp.Lock()
37
77
  self.write_mutex = mp.Lock()
38
78
 
39
- def start(self, workers=mp.cpu_count()):
79
+ def start(self, workers: Optional[int] = mp.cpu_count()):
40
80
  self.workers = workers
41
81
  self.proc=[Process(target=_evaluate, args=(self.fun,
42
82
  self.pipe, self.read_mutex, self.write_mutex)) for _ in range(workers)]
@@ -49,6 +89,14 @@ class Evaluator(object):
49
89
  for p in self.pipe:
50
90
  p.close()
51
91
 
92
+ def _eval_parallel_segment(xs, ys, i0, i1, evaluator):
93
+ for i in range(i0, i1):
94
+ evaluator.pipe[0].send((i, xs[i]))
95
+ for _ in range(i0, i1):
96
+ i, y = evaluator.pipe[0].recv()
97
+ ys[i] = y
98
+ return ys
99
+
52
100
  def _evaluate(fun, pipe, read_mutex, write_mutex): # worker
53
101
  while True:
54
102
  with read_mutex:
@@ -58,9 +106,244 @@ def _evaluate(fun, pipe, read_mutex, write_mutex): # worker
58
106
  try:
59
107
  i, x = msg
60
108
  y = fun(x)
61
- if not math.isfinite(y):
62
- y = sys.float_info.max
63
- except Exception:
109
+ except Exception as ex:
64
110
  y = sys.float_info.max
65
111
  with write_mutex:
66
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
Binary file
Binary file