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/__init__.py +12 -2
- fcmaes/advretry.py +217 -159
- fcmaes/astro.py +143 -27
- fcmaes/bitecpp.py +107 -0
- fcmaes/cmaes.py +204 -173
- fcmaes/cmaescpp.py +253 -87
- fcmaes/crfmnes.py +339 -0
- fcmaes/crfmnescpp.py +273 -0
- fcmaes/dacpp.py +39 -51
- fcmaes/de.py +472 -0
- fcmaes/decpp.py +222 -64
- fcmaes/diversifier.py +357 -0
- fcmaes/evaluator.py +297 -14
- fcmaes/lib/libacmalib.dll +0 -0
- fcmaes/lib/libacmalib.dylib +0 -0
- fcmaes/lib/libacmalib.so +0 -0
- fcmaes/lib/libhbv.so +0 -0
- fcmaes/lib/liblrgv.so +0 -0
- fcmaes/lib/librw_top_trumps.dll +0 -0
- fcmaes/lib/librw_top_trumps.so +0 -0
- fcmaes/mapelites.py +737 -0
- fcmaes/mode.py +719 -0
- fcmaes/modecpp.py +470 -0
- fcmaes/moretry.py +270 -0
- fcmaes/multiretry.py +195 -0
- fcmaes/optimizer.py +883 -112
- fcmaes/pgpecpp.py +340 -0
- fcmaes/pygmoretry.py +10 -19
- fcmaes/retry.py +248 -121
- fcmaes/test_cma.py +207 -30
- fcmaes/testfun.py +38 -1
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/METADATA +22 -12
- fcmaes-1.6.9.dist-info/RECORD +36 -0
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/WHEEL +1 -1
- fcmaes/hhcpp.py +0 -114
- fcmaes/lib/libgtoplib.dll +0 -0
- fcmaes/lib/libgtoplib.so +0 -0
- fcmaes-1.1.3.dist-info/RECORD +0 -23
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/LICENSE +0 -0
- {fcmaes-1.1.3.dist-info → fcmaes-1.6.9.dist-info}/top_level.txt +0 -0
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
|
|
16
|
-
from scipy.optimize import OptimizeResult
|
|
17
|
-
from fcmaes.
|
|
18
|
-
|
|
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(
|
|
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
|
|
38
|
-
where ``x`` is an 1-D array with shape (
|
|
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 (
|
|
47
|
-
Initial guess. Array of real elements of size (
|
|
48
|
-
where '
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
|