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/dacpp.py CHANGED
@@ -12,20 +12,22 @@ import sys
12
12
  import os
13
13
  import ctypes as ct
14
14
  import numpy as np
15
- from numpy.random import MT19937, Generator
16
- from scipy.optimize import OptimizeResult
17
- from fcmaes.cmaes import _check_bounds
18
- from fcmaes.cmaescpp import callback
15
+ from numpy.random import PCG64DXSM, Generator
16
+ from scipy.optimize import OptimizeResult, Bounds
17
+ from fcmaes.evaluator import _check_bounds, call_back_type, callback, libcmalib
18
+
19
+ from typing import Optional, Callable, Union
20
+ from numpy.typing import ArrayLike
19
21
 
20
22
  os.environ['MKL_DEBUG_CPU_TYPE'] = '5'
21
23
 
22
- def minimize(fun,
23
- bounds=None,
24
- x0=None,
25
- max_evaluations = 100000,
26
- use_local_search = True,
27
- rg = Generator(MT19937()),
28
- runid=0):
24
+ def minimize(fun: Callable[[ArrayLike], float],
25
+ bounds: Optional[Bounds] = None,
26
+ x0: Optional[ArrayLike] = None,
27
+ max_evaluations: Optional[int] = 100000,
28
+ use_local_search: Optional[bool] = True,
29
+ rg: Optional[Generator] = Generator(PCG64DXSM()),
30
+ runid: Optional[int] = 0) -> OptimizeResult:
29
31
 
30
32
  """Minimization of a scalar function of one or more variables using a
31
33
  C++ Dual Annealing implementation called via ctypes.
@@ -34,18 +36,16 @@ def minimize(fun,
34
36
  ----------
35
37
  fun : callable
36
38
  The objective function to be minimized.
37
- ``fun(x, *args) -> float``
38
- where ``x`` is an 1-D array with shape (n,) and ``args``
39
- is a tuple of the fixed parameters needed to completely
40
- specify the function.
39
+ ``fun(x) -> float``
40
+ where ``x`` is an 1-D array with shape (dim,)
41
41
  bounds : sequence or `Bounds`, optional
42
42
  Bounds on variables. There are two ways to specify the bounds:
43
43
  1. Instance of the `scipy.Bounds` class.
44
44
  2. Sequence of ``(min, max)`` pairs for each element in `x`. None
45
45
  is used to specify no bound.
46
- x0 : ndarray, shape (n,)
47
- Initial guess. Array of real elements of size (n,),
48
- where 'n' is the number of independent variables.
46
+ x0 : ndarray, shape (dim,)
47
+ Initial guess. Array of real elements of size (dim,),
48
+ where 'dim' is the number of independent variables.
49
49
  max_evaluations : int, optional
50
50
  Forced termination after ``max_evaluations`` function evaluations.
51
51
  use_local_search : bool, optional
@@ -53,7 +53,7 @@ def minimize(fun,
53
53
  rg = numpy.random.Generator, optional
54
54
  Random generator for creating random guesses.
55
55
  runid : int, optional
56
- id used to identify the optimization run.
56
+ id used to identify the run for debugging / logging.
57
57
 
58
58
  Returns
59
59
  -------
@@ -66,43 +66,31 @@ def minimize(fun,
66
66
  ``success`` a Boolean flag indicating if the optimizer exited successfully. """
67
67
 
68
68
  lower, upper, guess = _check_bounds(bounds, x0, rg)
69
- n = guess.size
70
- if lower is None:
71
- lower = [0]*n
72
- upper = [0]*n
73
- array_type = ct.c_double * n
69
+ dim = guess.size
70
+ array_type = ct.c_double * dim
74
71
  c_callback = call_back_type(callback(fun))
75
72
  seed = int(rg.uniform(0, 2**32 - 1))
73
+ res = np.empty(dim+4)
74
+ res_p = res.ctypes.data_as(ct.POINTER(ct.c_double))
76
75
  try:
77
- res = optimizeDA_C(runid, c_callback, n, seed,
78
- array_type(*guess), array_type(*lower), array_type(*upper),
79
- max_evaluations, use_local_search)
80
- x = np.array(np.fromiter(res, dtype=np.float64, count=n))
81
- val = res[n]
82
- evals = int(res[n+1])
83
- iterations = int(res[n+2])
84
- stop = int(res[n+3])
85
- freemem(res)
76
+ optimizeDA_C(runid, c_callback, dim, seed,
77
+ array_type(*guess),
78
+ None if lower is None else array_type(*lower),
79
+ None if upper is None else array_type(*upper),
80
+ max_evaluations, use_local_search, res_p)
81
+ x = res[:dim]
82
+ val = res[dim]
83
+ evals = int(res[dim+1])
84
+ iterations = int(res[dim+2])
85
+ stop = int(res[dim+3])
86
86
  return OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop, success=True)
87
87
  except Exception as ex:
88
88
  return OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
89
-
90
- basepath = os.path.dirname(os.path.abspath(__file__))
91
89
 
92
- if sys.platform.startswith('linux'):
93
- libcmalib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.so')
94
- elif 'mac' in sys.platform:
95
- libgtoplib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.dylib')
96
- else:
97
- libcmalib = ct.cdll.LoadLibrary(basepath + '/lib/libacmalib.dll')
98
-
99
- call_back_type = ct.CFUNCTYPE(ct.c_double, ct.c_int, ct.POINTER(ct.c_double))
100
- optimizeDA_C = libcmalib.optimizeDA_C
101
- optimizeDA_C.argtypes = [ct.c_long, call_back_type, ct.c_int, ct.c_int, \
102
- ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
103
- ct.c_int, ct.c_bool]
90
+ if not libcmalib is None:
91
+
92
+ optimizeDA_C = libcmalib.optimizeDA_C
93
+ optimizeDA_C.argtypes = [ct.c_long, call_back_type, ct.c_int, ct.c_int, \
94
+ ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), ct.POINTER(ct.c_double), \
95
+ ct.c_int, ct.c_bool, ct.POINTER(ct.c_double)]
104
96
 
105
- optimizeDA_C.restype = ct.POINTER(ct.c_double)
106
- freemem = libcmalib.free_mem
107
- freemem.argtypes = [ct.POINTER(ct.c_double)]
108
-
fcmaes/de.py ADDED
@@ -0,0 +1,472 @@
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
+ """ Numpy based implementation of Differential Evolution using the DE/best/1 strategy.
7
+ Derived from its C++ counterpart
8
+ https://github.com/dietmarwo/fast-cma-es/blob/master/_fcmaescpp/deoptimizer.cpp
9
+
10
+ Uses three deviations from the standard DE algorithm:
11
+ a) temporal locality introduced in
12
+ https://www.researchgate.net/publication/309179699_Differential_evolution_for_protein_folding_optimization_based_on_a_three-dimensional_AB_off-lattice_model
13
+ b) reinitialization of individuals based on their age.
14
+ c) oscillating CR/F parameters.
15
+
16
+ You may keep parameters F and Cr at their defaults since this implementation works well with the given settings for most problems,
17
+ since the algorithm oscillates between different F and Cr settings.
18
+
19
+ The filter parameter is inspired by "Surrogate-based Optimisation for a Hospital Simulation"
20
+ (https://dl.acm.org/doi/10.1145/3449726.3463283) where a machine learning classifier is used to
21
+ filter candidate solutions for DE. A filter object needs to provide function add(x, y) to enable learning and
22
+ a predicate is_improve(x, x_old, y_old) used to decide if function evaluation of x is worth the effort.
23
+
24
+ The ints parameter is a boolean array indicating which parameters are discrete integer values. This
25
+ parameter was introduced after observing non optimal results for the ESP2 benchmark problem:
26
+ https://github.com/AlgTUDelft/ExpensiveOptimBenchmark/blob/master/expensiveoptimbenchmark/problems/DockerCFDBenchmark.py
27
+ If defined it causes a "special treatment" for discrete variables: They are rounded to the next integer value and
28
+ there is an additional mutation to avoid getting stuck at local minima. This behavior is specified by the internal
29
+ function _modifier which can be overwritten by providing the optional modifier argument. If modifier is defined,
30
+ ints is ignored.
31
+
32
+ Use the C++ implementation combined with parallel retry instead for objective functions which are fast to evaluate.
33
+ For expensive objective functions (e.g. machine learning parameter optimization) use the workers
34
+ parameter to parallelize objective function evaluation. This causes delayed population update.
35
+ It is usually preferrable if popsize > workers and workers = mp.cpu_count() to improve CPU utilization.
36
+ """
37
+
38
+ import numpy as np
39
+ import math, sys
40
+ from time import time
41
+ import ctypes as ct
42
+ from numpy.random import Generator, PCG64DXSM
43
+ from scipy.optimize import OptimizeResult, Bounds
44
+ from fcmaes.evaluator import Evaluator, is_debug_active
45
+ import multiprocessing as mp
46
+ from collections import deque
47
+ from loguru import logger
48
+ from typing import Optional, Callable, Tuple, Union
49
+ from numpy.typing import ArrayLike
50
+
51
+ def minimize(fun: Callable[[ArrayLike], float],
52
+ dim: Optional[int] = None,
53
+ bounds: Optional[Bounds] = None,
54
+ popsize: Optional[int] = 31,
55
+ max_evaluations: Optional[int] = 100000,
56
+ workers: Optional[int] = None,
57
+ stop_fitness: Optional[float] = -np.inf,
58
+ keep: Optional[int] = 200,
59
+ f: Optional[float] = 0.5,
60
+ cr: Optional[float] = 0.9,
61
+ rg: Optional[Generator] = Generator(PCG64DXSM()),
62
+ filter = None,
63
+ ints: Optional[ArrayLike] = None,
64
+ min_mutate: Optional[float] = 0.1,
65
+ max_mutate: Optional[float] = 0.5,
66
+ modifier: Optional[Callable] = None) -> OptimizeResult:
67
+ """Minimization of a scalar function of one or more variables using
68
+ Differential Evolution.
69
+
70
+ Parameters
71
+ ----------
72
+ fun : callable
73
+ The objective function to be minimized.
74
+ ``fun(x) -> float``
75
+ where ``x`` is an 1-D array with shape (n,)
76
+ dim : int
77
+ dimension of the argument of the objective function
78
+ either dim or bounds need to be defined
79
+ bounds : sequence or `Bounds`, optional
80
+ Bounds on variables. There are two ways to specify the bounds:
81
+ 1. Instance of the `scipy.Bounds` class.
82
+ 2. Sequence of ``(min, max)`` pairs for each element in `x`. None
83
+ is used to specify no bound.
84
+ popsize : int, optional
85
+ Population size.
86
+ max_evaluations : int, optional
87
+ Forced termination after ``max_evaluations`` function evaluations.
88
+ workers : int or None, optional
89
+ If not workers is None, function evaluation is performed in parallel for the whole population.
90
+ Useful for costly objective functions but is deactivated for parallel retry.
91
+ stop_fitness : float, optional
92
+ Limit for fitness value. If reached minimize terminates.
93
+ keep = float, optional
94
+ changes the reinitialization probability of individuals based on their age. Higher value
95
+ means lower probablity of reinitialization.
96
+ f = float, optional
97
+ The mutation constant. In the literature this is also known as differential weight,
98
+ being denoted by F. Should be in the range [0, 2].
99
+ cr = float, optional
100
+ The recombination constant. Should be in the range [0, 1].
101
+ In the literature this is also known as the crossover probability.
102
+ rg = numpy.random.Generator, optional
103
+ Random generator for creating random guesses.
104
+ filter = filter object, optional
105
+ needs to provide function add(x, y) and predicate is_improve(x, x_old, y_old).
106
+ used to decide if function evaluation of x is worth the effort.
107
+ Either f(x) < f(x_old) or f(x) < y_old need to be approximated.
108
+ add(x, y) can be used to learn from past results.
109
+ ints = list or array of bool, optional
110
+ indicating which parameters are discrete integer values. If defined these parameters will be
111
+ rounded to the next integer and some additional mutation of discrete parameters are performed.
112
+ min_mutate = float, optional
113
+ Determines the minimal mutation rate for discrete integer parameters.
114
+ max_mutate = float, optional
115
+ Determines the maximal mutation rate for discrete integer parameters.
116
+ modifier = callable, optional
117
+ used to overwrite the default behaviour induced by ints. If defined, the ints parameter is
118
+ ignored. Modifies all generated x vectors.
119
+
120
+ Returns
121
+ -------
122
+ res : scipy.OptimizeResult
123
+ The optimization result is represented as an ``OptimizeResult`` object.
124
+ Important attributes are: ``x`` the solution array,
125
+ ``fun`` the best function value,
126
+ ``nfev`` the number of function evaluations,
127
+ ``nit`` the number of iterations,
128
+ ``success`` a Boolean flag indicating if the optimizer exited successfully. """
129
+
130
+
131
+ de = DE(dim, bounds, popsize, stop_fitness, keep, f, cr, rg, filter, ints,
132
+ min_mutate, max_mutate, modifier)
133
+ try:
134
+ if workers and workers > 1:
135
+ x, val, evals, iterations, stop = de.do_optimize_delayed_update(fun, max_evaluations, workers)
136
+ else:
137
+ x, val, evals, iterations, stop = de.do_optimize(fun, max_evaluations)
138
+ return OptimizeResult(x=x, fun=val, nfev=evals, nit=iterations, status=stop,
139
+ success=True)
140
+ except Exception as ex:
141
+ return OptimizeResult(x=None, fun=sys.float_info.max, nfev=0, nit=0, status=-1, success=False)
142
+
143
+ class DE(object):
144
+
145
+ def __init__(self,
146
+ dim: int,
147
+ bounds: Bounds,
148
+ popsize: Optional[int] = 31,
149
+ stop_fitness: Optional[float] = -np.inf,
150
+ keep: Optional[int] = 200,
151
+ F: Optional[float] = 0.5,
152
+ Cr: Optional[float] = 0.9,
153
+ rg: Optional[Generator] = Generator(PCG64DXSM()),
154
+ filter: Optional = None,
155
+ ints: Optional[ArrayLike] = None,
156
+ min_mutate: Optional[float] = 0.1,
157
+ max_mutate: Optional[float] = 0.5,
158
+ modifier: Optional[Callable] = None):
159
+
160
+ self.dim, self.lower, self.upper = _check_bounds(bounds, dim)
161
+ if popsize is None:
162
+ popsize = 31
163
+ self.popsize = popsize
164
+ self.stop_fitness = stop_fitness
165
+ self.keep = keep
166
+ self.rg = rg
167
+ self.F0 = F
168
+ self.Cr0 = Cr
169
+ self.stop = 0
170
+ self.iterations = 0
171
+ self.evals = 0
172
+ self.p = 0
173
+ self.improves = deque()
174
+ self.filter = filter
175
+ self.ints = np.array(ints)
176
+ self.min_mutate = min_mutate
177
+ self.max_mutate = max_mutate
178
+ # use default variable modifier for int variables if modifier is None
179
+ if modifier is None and not ints is None:
180
+ self.lower = self.lower.astype(float)
181
+ self.upper = self.upper.astype(float)
182
+ self.modifier = self._modifier
183
+ else:
184
+ self.modifier = modifier
185
+ self._init()
186
+ if is_debug_active():
187
+ self.best_y = mp.RawValue(ct.c_double, 1E99)
188
+ self.n_evals = mp.RawValue(ct.c_long, 0)
189
+ self.time_0 = time()
190
+
191
+ def ask(self) -> np.ndarray:
192
+ """ask for popsize new argument vectors.
193
+
194
+ Returns
195
+ -------
196
+ xs : popsize sized array of dim sized argument lists."""
197
+
198
+ xs = [None] * self.popsize
199
+ for _ in range(self.popsize):
200
+ if self.improves:
201
+ p, x = self.improves[0]
202
+ if xs[p] is None:
203
+ xs[p] = x
204
+ self.improves.popleft()
205
+ else:
206
+ break
207
+ else:
208
+ break
209
+ for p in range(self.popsize):
210
+ if xs[p] is None:
211
+ _, _, xs[p] = self._next_x(p)
212
+ self.asked = xs
213
+ return xs
214
+
215
+ def tell(self,
216
+ ys:ArrayLike,
217
+ xs:Optional[ArrayLike] = None) -> int:
218
+
219
+ """tell function values for the argument lists retrieved by ask().
220
+
221
+ Parameters
222
+ ----------
223
+ ys : popsize sized list of function values
224
+ xs : popsize sized list of dim sized argument lists
225
+
226
+ Returns
227
+ -------
228
+ stop : int termination criteria, if != 0 loop should stop."""
229
+
230
+ if xs is None:
231
+ xs = self.asked
232
+ self.evals += len(ys)
233
+ for p in range(len(ys)):
234
+ self.tell_one(p, ys[p], xs[p])
235
+ return self.stop
236
+
237
+ def population(self) -> np.ndarray:
238
+ return self.x
239
+
240
+ def result(self) -> OptimizeResult:
241
+ return OptimizeResult(x=self.best_x, fun=self.best_value,
242
+ nfev=self.iterations*self.popsize,
243
+ nit=self.iterations, status=self.stop, success=True)
244
+
245
+ def ask_one(self) -> Tuple[int, np.ndarray]:
246
+ """ask for one new argument vector.
247
+
248
+ Returns
249
+ -------
250
+ p : int population index
251
+ x : dim sized argument ."""
252
+
253
+ if self.improves:
254
+ p, x = self.improves.popleft()
255
+ else:
256
+ p = self.p
257
+ _, _, x = self._next_x(p)
258
+ self.p = (self.p + 1) % self.popsize
259
+ return p, x
260
+
261
+ def tell_one(self, p: int, y:float , x:ArrayLike) -> int:
262
+ """tell function value for a argument list retrieved by ask_one().
263
+
264
+ Parameters
265
+ ----------
266
+ p : int population index
267
+ y : function value
268
+ x : dim sized argument list
269
+
270
+ Returns
271
+ -------
272
+ stop : int termination criteria, if != 0 loop should stop."""
273
+
274
+ if not self.filter is None:
275
+ self.filter.add(x, y)
276
+
277
+ if (self.y[p] > y):
278
+ # temporal locality
279
+ if self.iterations > 1:
280
+ self.improves.append((p, self._next_improve(self.x[self.best_i], x, self.x0[p])))
281
+ self.x0[p] = self.x[p]
282
+ self.x[p] = x
283
+ self.y[p] = y
284
+ if self.y[self.best_i] > y:
285
+ self.best_i = p
286
+ if self.best_value > y:
287
+ self.best_x = x
288
+ self.best_value = y
289
+ if self.stop_fitness > y:
290
+ self.stop = 1
291
+ self.pop_iter[p] = self.iterations
292
+ else:
293
+ if self.rg.uniform(0, self.keep) < self.iterations - self.pop_iter[p]:
294
+ self.x[p] = self._sample()
295
+ self.y[p] = np.inf
296
+
297
+ if is_debug_active():
298
+ self.n_evals.value += 1
299
+ if y < self.best_y.value or self.n_evals.value % 1000 == 999:
300
+ if y < self.best_y.value: self.best_y.value = y
301
+ t = time() - self.time_0 + 1E-9
302
+ c = self.n_evals.value
303
+ message = '"c/t={0:.2f} c={1:d} t={2:.2f} y={3:.5f} yb={4:.5f} x={5!s}'.format(
304
+ c/t, c, t, y, self.best_y.value, x)
305
+ logger.debug(message)
306
+
307
+ return self.stop
308
+
309
+ def _init(self):
310
+ self.x = np.zeros((self.popsize, self.dim))
311
+ self.x0 = np.zeros((self.popsize, self.dim))
312
+ self.y = np.empty(self.popsize)
313
+ for i in range(self.popsize):
314
+ self.x[i] = self.x0[i] = self._sample()
315
+ self.y[i] = np.inf
316
+ self.best_x = self.x[0]
317
+ self.best_value = np.inf
318
+ self.best_i = 0
319
+ self.pop_iter = np.zeros(self.popsize)
320
+
321
+ def apply_fun(self, x, x_old, y_old):
322
+ if self.filter is None:
323
+ self.evals += 1
324
+ return self.fun(x)
325
+ else:
326
+ if self.filter.is_improve(x, x_old, y_old):
327
+ self.evals += 1
328
+ y = self.fun(x)
329
+ self.filter.add(x, y)
330
+ return y
331
+ else:
332
+ return 1E99
333
+
334
+ def do_optimize(self, fun, max_evals):
335
+ self.fun = fun
336
+ self.max_evals = max_evals
337
+ self.iterations = 0
338
+ self.evals = 0
339
+ while self.evals < self.max_evals:
340
+ for p in range(self.popsize):
341
+ xb, xi, x = self._next_x(p)
342
+ y = self.apply_fun(x, xi, self.y[p])
343
+ if y < self.y[p]:
344
+ # temporal locality
345
+ if self.iterations > 1:
346
+ x2 = self._next_improve(xb, x, xi)
347
+ y2 = self.apply_fun(x2, x, y)
348
+ if y2 < y:
349
+ y = y2
350
+ x = x2
351
+ self.x[p] = x
352
+ self.y[p] = y
353
+ self.pop_iter[p] = self.iterations
354
+ if y < self.y[self.best_i]:
355
+ self.best_i = p;
356
+ if y < self.best_value:
357
+ self.best_value = y;
358
+ self.best_x = x;
359
+ if self.stop_fitness > y:
360
+ self.stop = 1
361
+ else:
362
+ # reinitialize individual
363
+ if self.rg.uniform(0, self.keep) < self.iterations - self.pop_iter[p]:
364
+ self.x[p] = self._sample()
365
+ self.y[p] = np.inf
366
+ if self.evals >= self.max_evals:
367
+ break
368
+
369
+ return self.best_x, self.best_value, self.evals, self.iterations, self.stop
370
+
371
+ def do_optimize_delayed_update(self, fun, max_evals, workers=mp.cpu_count()):
372
+ self.fun = fun
373
+ self.max_evals = max_evals
374
+ evaluator = Evaluator(self.fun)
375
+ evaluator.start(workers)
376
+ evals_x = {}
377
+ self.iterations = 0
378
+ self.evals = 0
379
+ self.p = 0
380
+ self.improves = deque()
381
+ for _ in range(workers): # fill queue with initial population
382
+ p, x = self.ask_one()
383
+ evaluator.pipe[0].send((self.evals, x))
384
+ evals_x[self.evals] = p, x # store x
385
+ self.evals += 1
386
+
387
+ while True: # read from pipe, tell de and create new x
388
+ evals, y = evaluator.pipe[0].recv()
389
+ p, x = evals_x[evals] # retrieve evaluated x
390
+ del evals_x[evals]
391
+ self.tell_one(p, y, x) # tell evaluated x
392
+ if self.stop != 0 or self.evals >= self.max_evals:
393
+ break # shutdown worker if stop criteria met
394
+
395
+ for _ in range(workers):
396
+ p, x = self.ask_one() # create new x
397
+ if self.filter is None or \
398
+ self.filter.is_improve(x, self.x[p], self.y[p]):
399
+ break
400
+ evaluator.pipe[0].send((self.evals, x))
401
+ evals_x[self.evals] = p, x # store x
402
+ self.evals += 1
403
+
404
+ evaluator.stop()
405
+ return self.best_x, self.best_value, self.evals, self.iterations, self.stop
406
+
407
+ def _next_x(self, p):
408
+ if p == 0:
409
+ self.iterations += 1
410
+ self.Cr = 0.5*self.Cr0 if self.iterations % 2 == 0 else self.Cr0
411
+ self.F = 0.5*self.F0 if self.iterations % 2 == 0 else self.F0
412
+ while True:
413
+ r1, r2 = self.rg.integers(0, self.popsize, 2)
414
+ if r1 != p and r1 != self.best_i and r1 != r2 \
415
+ and r2 != p and r2 != self.best_i:
416
+ break
417
+ xp = self.x[p]
418
+ xb = self.x[self.best_i]
419
+ x1 = self.x[r1]
420
+ x2 = self.x[r2]
421
+ x = self._feasible(xb + self.F * (x1 - x2))
422
+ r = self.rg.integers(0, self.dim)
423
+ tr = np.array(
424
+ [i != r and self.rg.random() > self.Cr for i in range(self.dim)])
425
+ x[tr] = xp[tr]
426
+ if not self.modifier is None:
427
+ x = self.modifier(x)
428
+ return xb, xp, x
429
+
430
+ def _next_improve(self, xb, x, xi):
431
+ x = self._feasible(xb + ((x - xi) * 0.5))
432
+ if not self.modifier is None:
433
+ x = self.modifier(x)
434
+ return x
435
+
436
+ def _sample(self):
437
+ if self.upper is None:
438
+ return self.rg.normal()
439
+ else:
440
+ x = self.rg.uniform(self.lower, self.upper)
441
+ if not self.modifier is None:
442
+ x = self.modifier(x)
443
+ return x
444
+
445
+ def _feasible(self, x):
446
+ if self.upper is None:
447
+ return x
448
+ else:
449
+ return np.clip(x, self.lower, self.upper)
450
+
451
+ # default modifier for integer variables
452
+ def _modifier(self, x):
453
+ x_ints = x[self.ints]
454
+ n_ints = len(self.ints)
455
+ lb = self.lower[self.ints]
456
+ ub = self.upper[self.ints]
457
+ to_mutate = self.rg.uniform(self.min_mutate, self.max_mutate)
458
+ # mututate some integer variables
459
+ x[self.ints] = np.array([x if self.rg.random() > to_mutate/n_ints else
460
+ int(self.rg.uniform(lb[i], ub[i]))
461
+ for i, x in enumerate(x_ints)])
462
+ return x
463
+
464
+ def _check_bounds(bounds, dim):
465
+ if bounds is None and dim is None:
466
+ raise ValueError('either dim or bounds need to be defined')
467
+ if bounds is None:
468
+ return dim, None, None
469
+ else:
470
+ return len(bounds.ub), np.asarray(bounds.lb), np.asarray(bounds.ub)
471
+
472
+