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/pgpecpp.py ADDED
@@ -0,0 +1,340 @@
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
+ """
7
+ Eigen based implementation of PGPE see http://mediatum.ub.tum.de/doc/1099128/631352.pdf .
8
+ Derived from https://github.com/google/evojax/blob/main/evojax/algo/pgpe.py .
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: Optional[Union[float, ArrayLike, Callable]] = 0.1,
29
+ popsize: Optional[int] = 32,
30
+ max_evaluations: Optional[int] = 100000,
31
+ workers: Optional[int] = None,
32
+ stop_fitness: Optional[float] = -np.inf,
33
+ rg: Optional[Generator] = Generator(PCG64DXSM()),
34
+ runid: Optional[int] = 0,
35
+ normalize: Optional[bool] = True,
36
+ lr_decay_steps: Optional[int] = 1000,
37
+ use_ranking: Optional[bool] = True,
38
+ center_learning_rate: Optional[float] = 0.15,
39
+ stdev_learning_rate: Optional[float] = 0.1,
40
+ stdev_max_change: Optional[float] = 0.2,
41
+ b1: Optional[float] = 0.9,
42
+ b2: Optional[float] = 0.999,
43
+ eps: Optional[float] = 1e-8,
44
+ decay_coef: Optional[float] = 1.0,
45
+ ) -> OptimizeResult:
46
+
47
+ """Minimization of a scalar function of one or more variables using a
48
+ C++ PGPE implementation called via ctypes.
49
+
50
+ Parameters
51
+ ----------
52
+ fun : callable
53
+ The objective function to be minimized.
54
+ ``fun(x) -> float``
55
+ where ``x`` is an 1-D array with shape (dim,)
56
+ bounds : sequence or `Bounds`, optional
57
+ Bounds on variables. There are two ways to specify the bounds:
58
+ 1. Instance of the `scipy.Bounds` class.
59
+ 2. Sequence of ``(min, max)`` pairs for each element in `x`. None
60
+ is used to specify no bound.
61
+ x0 : ndarray, shape (dim,)
62
+ Initial guess. Array of real elements of size (dim,),
63
+ where 'dim' is the number of independent variables.
64
+ input_sigma : float or np.array, optional
65
+ Initial step size.
66
+ popsize = int, optional
67
+ CMA-ES population size.
68
+ max_evaluations : int, optional
69
+ Forced termination after ``max_evaluations`` function evaluations.
70
+ workers : int or None, optional
71
+ If workers > 1, function evaluation is performed in parallel for the whole population.
72
+ Useful for costly objective functions but is deactivated for parallel retry.
73
+ stop_fitness : float, optional
74
+ Limit for fitness value. If reached minimize terminates.
75
+ rg = numpy.random.Generator, optional
76
+ Random generator for creating random guesses.
77
+ runid : int, optional
78
+ id used by the is_terminate callback to identify the CMA-ES run.
79
+ normalize : boolean, optional
80
+ if true pheno -> geno transformation maps arguments to interval [-1,1]
81
+ lr_decay_steps int, optional - ADAM optimizer configuration
82
+ use_ranking : boolean, optional - Should we treat the fitness as rankings or not.
83
+ center_learning_rate : float, optional - Learning rate for the Gaussian mean.
84
+ stdev_learning_rate : float, optional - Learning rate for the Gaussian stdev.
85
+ init_stdev : float, optional - Initial stdev for the Gaussian distribution.
86
+ stdev_max_change : float, optional - Maximum allowed change for stdev in abs values.
87
+ b1 : float, optional - ADAM optimizer configuration
88
+ b2 : float, optional - ADAM optimizer configuration
89
+ eps : float, optional - ADAM optimizer configuration
90
+ decay_coef : float, optional - ADAM optimizer configuration
91
+
92
+ Returns
93
+ -------
94
+ res : scipy.OptimizeResult
95
+ The optimization result is represented as an ``OptimizeResult`` object.
96
+ Important attributes are: ``x`` the solution array,
97
+ ``fun`` the best function value,
98
+ ``nfev`` the number of function evaluations,
99
+ ``nit`` the number of CMA-ES iterations,
100
+ ``status`` the stopping critera and
101
+ ``success`` a Boolean flag indicating if the optimizer exited successfully. """
102
+
103
+ lower, upper, guess = _check_bounds(bounds, x0, rg)
104
+ dim = guess.size
105
+ if popsize is None:
106
+ popsize = 32
107
+ if popsize % 2 == 1: # requires even popsize
108
+ popsize += 1
109
+ if lower is None:
110
+ lower = [0]*dim
111
+ upper = [0]*dim
112
+ if callable(input_sigma):
113
+ input_sigma=input_sigma()
114
+ if np.ndim(input_sigma) == 0:
115
+ input_sigma = [input_sigma] * dim
116
+ parfun = None if (workers is None or workers <= 1) else parallel(fun, workers)
117
+ array_type = ct.c_double * dim
118
+ c_callback_par = call_back_par(callback_par(fun, parfun))
119
+ res = np.empty(dim+4)
120
+ res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
121
+ try:
122
+ optimizePGPE_C(runid, c_callback_par, dim, array_type(*guess),
123
+ array_type(*lower), array_type(*upper),
124
+ array_type(*input_sigma), max_evaluations, stop_fitness,
125
+ popsize, int(rg.uniform(0, 2**32 - 1)),
126
+ lr_decay_steps, use_ranking, center_learning_rate,
127
+ stdev_learning_rate, stdev_max_change, b1, b2, eps, decay_coef,
128
+ normalize, res_p)
129
+ x = res[:dim]
130
+ val = res[dim]
131
+ evals = int(res[dim+1])
132
+ iterations = int(res[dim+2])
133
+ stop = int(res[dim+3])
134
+ res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
135
+ except Exception as ex:
136
+ res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
137
+ if not parfun is None:
138
+ parfun.stop()
139
+ return res
140
+
141
+ class PGPE_C:
142
+
143
+ def __init__(self,
144
+ dim: int,
145
+ bounds: Optional[Bounds] = None,
146
+ x0: Optional[ArrayLike] = None,
147
+ input_sigma: Optional[Union[float, ArrayLike, Callable]] = 0.1,
148
+ popsize: Optional[int] = 32,
149
+ rg: Optional[Generator] = Generator(PCG64DXSM()),
150
+ runid: Optional[int] = 0,
151
+ normalize: Optional[bool] = True,
152
+ lr_decay_steps: Optional[int] = 1000,
153
+ use_ranking: Optional[bool] = False,
154
+ center_learning_rate: Optional[float] = 0.15,
155
+ stdev_learning_rate: Optional[float] = 0.1,
156
+ stdev_max_change: Optional[float] = 0.2,
157
+ b1: Optional[float] = 0.9,
158
+ b2: Optional[float] = 0.999,
159
+ eps: Optional[float] = 1e-8,
160
+ decay_coef: Optional[float] = 1.0,
161
+ ):
162
+
163
+ """Minimization of a scalar function of one or more variables using a
164
+ C++ CR-FM-NES implementation called via ctypes.
165
+
166
+ Parameters
167
+ ----------
168
+ dim : int
169
+ dimension of the argument of the objective function
170
+ bounds : sequence or `Bounds`, optional
171
+ Bounds on variables. There are two ways to specify the bounds:
172
+ 1. Instance of the `scipy.Bounds` class.
173
+ 2. Sequence of ``(min, max)`` pairs for each element in `x`. None
174
+ is used to specify no bound.
175
+ x0 : ndarray, shape (dim,)
176
+ Initial guess. Array of real elements of size (dim,),
177
+ where 'dim' is the number of independent variables.
178
+ input_sigma : float, optional
179
+ Initial step size.
180
+ popsize = int, optional
181
+ CMA-ES population size.
182
+ max_evaluations : int, optional
183
+ Forced termination after ``max_evaluations`` function evaluations.
184
+ workers : int or None, optional
185
+ If workers > 1, function evaluation is performed in parallel for the whole population.
186
+ Useful for costly objective functions but is deactivated for parallel retry.
187
+ stop_fitness : float, optional
188
+ Limit for fitness value. If reached minimize terminates.
189
+ rg = numpy.random.Generator, optional
190
+ Random generator for creating random guesses.
191
+ runid : int, optional
192
+ id used by the is_terminate callback to identify the CMA-ES run.
193
+ normalize : boolean, optional
194
+ if true pheno -> geno transformation maps arguments to interval [-1,1]
195
+ lr_decay_steps int, optional - ADAM optimizer configuration
196
+ use_ranking : boolean, optional - Should we treat the fitness as rankings or not.
197
+ center_learning_rate : float, optional - Learning rate for the Gaussian mean.
198
+ stdev_learning_rate : float, optional - Learning rate for the Gaussian stdev.
199
+ init_stdev : float, optional - Initial stdev for the Gaussian distribution.
200
+ stdev_max_change : float, optional - Maximum allowed change for stdev in abs values.
201
+ b1 : float, optional - ADAM optimizer configuration
202
+ b2 : float, optional - ADAM optimizer configuration
203
+ eps : float, optional - ADAM optimizer configuration
204
+ decay_coef : float, optional - ADAM optimizer configuration"""
205
+
206
+ lower, upper, guess = _get_bounds(dim, bounds, x0, rg)
207
+ if popsize is None:
208
+ popsize = 32
209
+ if popsize % 2 == 1: # requires even popsize
210
+ popsize += 1
211
+ if lower is None:
212
+ lower = [0]*dim
213
+ upper = [0]*dim
214
+ if callable(input_sigma):
215
+ input_sigma=input_sigma()
216
+ if np.ndim(input_sigma) == 0:
217
+ input_sigma = [input_sigma] * dim
218
+ array_type = ct.c_double * dim
219
+ try:
220
+ self.ptr = initPGPE_C(runid, dim, array_type(*guess),
221
+ array_type(*lower), array_type(*upper),
222
+ array_type(*input_sigma), popsize, int(rg.uniform(0, 2**32 - 1)),
223
+ lr_decay_steps, use_ranking, center_learning_rate,
224
+ stdev_learning_rate, stdev_max_change, b1, b2, eps, decay_coef,
225
+ normalize)
226
+ self.popsize = popsize
227
+ self.dim = dim
228
+ except Exception as ex:
229
+ print (ex)
230
+ pass
231
+
232
+ def __del__(self):
233
+ destroyPGPE_C(self.ptr)
234
+
235
+ def ask(self) -> np.array:
236
+ """ask for popsize new argument vectors.
237
+
238
+ Returns
239
+ -------
240
+ xs : popsize sized list of dim sized argument lists."""
241
+
242
+ try:
243
+ lamb = self.popsize
244
+ n = self.dim
245
+ res = np.empty(lamb*n)
246
+ res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
247
+ askPGPE_C(self.ptr, res_p)
248
+ xs = np.empty((lamb, n))
249
+ for p in range(lamb):
250
+ xs[p,:] = res[p*n : (p+1)*n]
251
+ return xs
252
+ except Exception as ex:
253
+ print (ex)
254
+ return None
255
+
256
+ def tell(self,
257
+ ys: np.ndarray) -> int:
258
+ """tell function values for the argument lists retrieved by ask().
259
+
260
+ Parameters
261
+ ----------
262
+ ys : popsize sized list of function values
263
+
264
+ Returns
265
+ -------
266
+ stop : int termination criteria, if != 0 loop should stop."""
267
+
268
+ try:
269
+ array_type_ys = ct.c_double * len(ys)
270
+ return tellPGPE_C(self.ptr, array_type_ys(*ys))
271
+ except Exception as ex:
272
+ print (ex)
273
+ return -1
274
+
275
+ def population(self) -> np.array:
276
+ try:
277
+ lamb = self.popsize
278
+ n = self.dim
279
+ res = np.empty(lamb*n)
280
+ res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
281
+ populationPGPE_C(self.ptr, res_p)
282
+ xs = np.array(lamb, n)
283
+ for p in range(lamb):
284
+ xs[p] = res[p*n : (p+1)*n]
285
+ return xs
286
+ except Exception as ex:
287
+ print (ex)
288
+ return None
289
+
290
+ def result(self) -> OptimizeResult:
291
+ res = np.empty(self.dim+4)
292
+ res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
293
+ try:
294
+ resultPGPE_C(self.ptr, res_p)
295
+ x = res[:self.dim]
296
+ val = res[self.dim]
297
+ evals = int(res[self.dim+1])
298
+ iterations = int(res[self.dim+2])
299
+ stop = int(res[self.dim+3])
300
+ res = OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
301
+ except Exception as ex:
302
+ res = OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
303
+ return res
304
+
305
+ if not libcmalib is None:
306
+
307
+ optimizePGPE_C = libcmalib.optimizePGPE_C
308
+ optimizePGPE_C.argtypes = [ct.c_long, call_back_par, ct.c_int, \
309
+ ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
310
+ ct.POINTER(ct.c_double), ct.c_int, ct.c_double, ct.c_int,
311
+ ct.c_long, ct.c_int,
312
+ ct.c_bool, ct.c_double, ct.c_double, ct.c_double,
313
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double,
314
+ ct.c_bool, ct.POINTER(ct.c_double)]
315
+
316
+ initPGPE_C = libcmalib.initPGPE_C
317
+ initPGPE_C.argtypes = [ct.c_long, ct.c_int, ct.POINTER(ct.c_double),
318
+ ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double),
319
+ ct.c_int, ct.c_long, ct.c_int,
320
+ ct.c_bool, ct.c_double, ct.c_double, ct.c_double,
321
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double,
322
+ ct.c_bool]
323
+
324
+ initPGPE_C.restype = ct.c_void_p
325
+
326
+ destroyPGPE_C = libcmalib.destroyPGPE_C
327
+ destroyPGPE_C.argtypes = [ct.c_void_p]
328
+
329
+ askPGPE_C = libcmalib.askPGPE_C
330
+ askPGPE_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
331
+
332
+ tellPGPE_C = libcmalib.tellPGPE_C
333
+ tellPGPE_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
334
+ tellPGPE_C.restype = ct.c_int
335
+
336
+ populationPGPE_C = libcmalib.populationPGPE_C
337
+ populationPGPE_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
338
+
339
+ resultPGPE_C = libcmalib.resultPGPE_C
340
+ resultPGPE_C.argtypes = [ct.c_void_p, ct.POINTER(ct.c_double)]
fcmaes/pygmoretry.py CHANGED
@@ -6,12 +6,12 @@
6
6
  import math
7
7
  import os
8
8
  import sys
9
- from numpy.random import Generator, MT19937, SeedSequence
9
+ import numpy as np
10
+ from numpy.random import Generator, PCG64DXSM, SeedSequence
10
11
  from scipy.optimize import OptimizeResult, Bounds
11
12
  import multiprocessing as mp
12
13
  from multiprocessing import Process
13
14
  from fcmaes.retry import Store
14
- from fcmaes.optimizer import logger
15
15
 
16
16
  os.environ['MKL_DEBUG_CPU_TYPE'] = '5'
17
17
  os.environ['MKL_NUM_THREADS'] = '1'
@@ -19,12 +19,11 @@ os.environ['OPENBLAS_NUM_THREADS'] = '1'
19
19
 
20
20
  def minimize(prob,
21
21
  algo,
22
- value_limit = math.inf,
22
+ value_limit = np.inf,
23
23
  num_retries = 100*mp.cpu_count(),
24
- logger = None,
25
24
  workers = mp.cpu_count(),
26
25
  popsize = 1,
27
- ):
26
+ ) -> OptimizeResult:
28
27
  """Minimization of a scalar function of one or more variables using parallel retry.
29
28
  Similar to fcmaes.retry but works with pygmo / pagmo problems + algorithms.
30
29
  For problems with equality/inequality contraints or multiple objectives fcmaes.retry cannot
@@ -44,10 +43,6 @@ def minimize(prob,
44
43
  Upper limit for optimized function values to be stored.
45
44
  num_retries : int, optional
46
45
  Number of optimization retries.
47
- logger : logger, optional
48
- logger for log output of the retry mechanism. If None, logging
49
- is switched off. Default is a logger which logs both to stdout and
50
- appends to a file ``optimizer.log``.
51
46
  workers : int, optional
52
47
  number of parallel processes used. Default is mp.cpu_count()
53
48
  popsize = int, optional
@@ -63,30 +58,26 @@ def minimize(prob,
63
58
 
64
59
  lb, ub = prob.get_bounds()
65
60
  bounds = Bounds(lb, ub)
66
- store = Store(bounds, logger = logger)
61
+ store = Store(bounds)
67
62
  return retry(store, prob, algo, num_retries, value_limit, popsize, workers)
68
63
 
69
- def retry(store, prob, algo, num_retries, value_limit = math.inf, popsize=1, workers=mp.cpu_count()):
64
+ def retry(store, prob, algo, num_retries, value_limit = np.inf, popsize=1, workers=mp.cpu_count()):
70
65
  try:
71
66
  import pygmo as pg
72
67
  except ImportError as e:
73
68
  raise ImportError("Please install PYGMO (pip install pygmo) to use PAGMO optimizers") from e
74
69
  sg = SeedSequence()
75
- rgs = [Generator(MT19937(s)) for s in sg.spawn(workers)]
70
+ rgs = [Generator(PCG64DXSM(s)) for s in sg.spawn(workers)]
76
71
  proc=[Process(target=_retry_loop,
77
72
  args=(pid, rgs, store, prob, algo, num_retries, value_limit, popsize, pg)) for pid in range(workers)]
78
73
  [p.start() for p in proc]
79
74
  [p.join() for p in proc]
80
- store.sort()
75
+ store.sort(store.get_xs())
81
76
  store.dump()
82
77
  return OptimizeResult(x=store.get_x_best(), fun=store.get_y_best(),
83
78
  nfev=store.get_count_evals(), success=True)
84
79
 
85
80
  def _retry_loop(pid, rgs, store, prob, algo, num_retries, value_limit, popsize, pg):
86
-
87
- #reinitialize logging config for windows - multi threading fix
88
- if 'win' in sys.platform and not store.logger is None:
89
- store.logger = logger()
90
81
 
91
82
  while store.get_runs_compare_incr(num_retries):
92
83
  try:
@@ -99,7 +90,7 @@ def _retry_loop(pid, rgs, store, prob, algo, num_retries, value_limit, popsize,
99
90
  y = pop.champion_f
100
91
  evals = pop.problem.get_fevals()
101
92
 
102
- feasible = prob.feasibility_x(pop.champion_x)
103
- if feasible:
93
+ _feasible = prob.feasibility_x(pop.champion_x)
94
+ if _feasible:
104
95
  store.add_result(y[0], sol, evals, value_limit)
105
96
  store.dump()