coco-experiment 2.7.0a1__tar.gz → 2.7.2__tar.gz
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.
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/PKG-INFO +1 -1
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/meson.build +1 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/__init__.py +1 -2
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/_version.py +1 -1
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/coco.c +1 -1
- coco_experiment-2.7.2/src/cocoex/noiser.py +282 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/.gitignore +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/LICENSE +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/README.md +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/pyproject.toml +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/scripts/run-doctest.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/bare_problem.pyx +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/coco.h +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/exceptions.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/interface.pyx +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/solvers.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/utilities.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/bbob2009_testcases.txt +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/bbob2009_testcases2.txt +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/test_bare_problem.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/test_regression.py +0 -0
- {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/test_suite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coco-experiment
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.2
|
|
4
4
|
Summary: Benchmarking framework for all types of black-box optimization algorithms.
|
|
5
5
|
Project-URL: Homepage, https://github.com/numbbo/coco-experiment
|
|
6
6
|
Project-URL: Documentation, https://numbbo.github.io/coco-doc/apidocs/cocoex/
|
|
@@ -15,6 +15,7 @@ Help on class Suite...
|
|
|
15
15
|
A more complete example use case can be found in the `example_experiment.py`
|
|
16
16
|
file.
|
|
17
17
|
"""
|
|
18
|
+
from . import noiser # noqa: F401
|
|
18
19
|
from . import solvers # noqa: F401
|
|
19
20
|
from . import utilities # noqa: F401
|
|
20
21
|
from . import exceptions # noqa: F401
|
|
@@ -22,7 +23,6 @@ from .interface import Observer as _Observer
|
|
|
22
23
|
from .interface import Problem as _Problem
|
|
23
24
|
from .interface import Suite as _Suite
|
|
24
25
|
from .interface import known_suite_names
|
|
25
|
-
# from .noiser import Noisifier
|
|
26
26
|
from .utilities import ExperimentRepeater
|
|
27
27
|
from .utilities import BatchScheduler
|
|
28
28
|
from .bare_problem import BareProblem
|
|
@@ -40,7 +40,6 @@ __all__ = [
|
|
|
40
40
|
'BatchScheduler',
|
|
41
41
|
'default_observers',
|
|
42
42
|
'BareProblem',
|
|
43
|
-
# 'Noisifier',
|
|
44
43
|
]
|
|
45
44
|
|
|
46
45
|
|
|
@@ -31526,7 +31526,7 @@ void coco_archive_free(coco_archive_t *archive) {
|
|
|
31526
31526
|
* don't change, don't track in version control!
|
|
31527
31527
|
*/
|
|
31528
31528
|
|
|
31529
|
-
const char *coco_version = "2.7.
|
|
31529
|
+
const char *coco_version = "2.7.2";
|
|
31530
31530
|
#line 1 "coco_runtime_c.c"
|
|
31531
31531
|
/**
|
|
31532
31532
|
* @file coco_runtime_c.c
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""`Noisifier` class and helper code.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
import warnings
|
|
6
|
+
import math
|
|
7
|
+
import json
|
|
8
|
+
import numpy as np # for checking finite values
|
|
9
|
+
|
|
10
|
+
_frozen_noise = True
|
|
11
|
+
math_floor = int
|
|
12
|
+
parameters_file = 'noiser_parameters.json'
|
|
13
|
+
|
|
14
|
+
def rand(x, i=0):
|
|
15
|
+
"""return a uniform random value seeded with ``x[:2]`` and `i`"""
|
|
16
|
+
return _unif(1, _seed(x, i))[0]
|
|
17
|
+
def randn(x, i=0):
|
|
18
|
+
"""return a normal random value seeded with ``x[:2]`` and `i`"""
|
|
19
|
+
return _gauss(1, _seed(x, i))[0]
|
|
20
|
+
def randc(x, i=0):
|
|
21
|
+
"""return a Cauchy random value seeded with ``x[:2]`` and `i`.
|
|
22
|
+
|
|
23
|
+
We observe that median |Z| = 1 and p(pi x Z / 2 > a) = 1/a.
|
|
24
|
+
"""
|
|
25
|
+
return _cauchy(1, _seed(x, i))[0]
|
|
26
|
+
|
|
27
|
+
class Noisifier:
|
|
28
|
+
"""noise wrapper for an observed problem.
|
|
29
|
+
|
|
30
|
+
The noise is applied with different probabilities for
|
|
31
|
+
|
|
32
|
+
- subtracting a value (leading to good outliers)
|
|
33
|
+
- adding a value (leading to bad outliers)
|
|
34
|
+
- adding a small `epsilon` normally distributed noise
|
|
35
|
+
|
|
36
|
+
The noise is frozen in x and y: whether the noise is applied to y and
|
|
37
|
+
its value depend both _deterministically_ on the input x.
|
|
38
|
+
|
|
39
|
+
The outlier noise distribution is a half Cauchy distribution multiplied
|
|
40
|
+
by pi/2, which generates values larger than k with a probability
|
|
41
|
+
awfully close to 1/k when k >= 10.
|
|
42
|
+
|
|
43
|
+
Example::
|
|
44
|
+
|
|
45
|
+
import cocoex # experimentation module
|
|
46
|
+
import scipy # to define the solver to be benchmarked
|
|
47
|
+
|
|
48
|
+
### input
|
|
49
|
+
suite_name = "bbob"
|
|
50
|
+
fmin = scipy.optimize.fmin # optimizer to be benchmarked
|
|
51
|
+
|
|
52
|
+
### prepare
|
|
53
|
+
suite = cocoex.Suite(suite_name, "", "")
|
|
54
|
+
observer = cocoex.Observer(suite_name, "")
|
|
55
|
+
|
|
56
|
+
### go
|
|
57
|
+
for problem in suite: # this loop may take several minutes or more
|
|
58
|
+
problem.observe_with(observer) # generates the data for cocopp
|
|
59
|
+
problem = cocoex.noiser.Noisifier().noisify(problem) # a Noisifier
|
|
60
|
+
fmin(problem, problem.initial_solution, disp=False)
|
|
61
|
+
|
|
62
|
+
The `problem` is now a `Noisifier` but it still abides by the interface
|
|
63
|
+
of the original problem.
|
|
64
|
+
|
|
65
|
+
Details: the random number generators can be passed as argument and
|
|
66
|
+
need to obey the interface of `noiser.rand`. By default, ``rands[0]``
|
|
67
|
+
is uniform in [0,1] and ``rands[1]`` is Gaussian noise,
|
|
68
|
+
``abs(rands[2])`` and ``abs(rands[-1])`` are used to sample added and
|
|
69
|
+
subtracted noise, respectively. Both are by default the same Cauchy
|
|
70
|
+
noise generator and the numbers scaled by pi/2 such that P(number > k)
|
|
71
|
+
equals about 1/k when k >= 5.
|
|
72
|
+
|
|
73
|
+
See also https://github.com/numbbo/coco-experiment/blob/main/build/python/example/example_experiment_complete.py
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
def __init__(self, p_add=0.2, p_subtract=0.0, p_epsilon=0, epsilon=1e-4,
|
|
77
|
+
rands=(rand, randn, randc)):
|
|
78
|
+
"""constructor with 4 optional parameters for the noise model,
|
|
79
|
+
|
|
80
|
+
`p_add`: probability for adding a positive heavy tail random value
|
|
81
|
+
making the solution look worse than it is.
|
|
82
|
+
|
|
83
|
+
`p_subtract`: probability for subtracting a positive heavy tail
|
|
84
|
+
random value making the solution look better than it is.
|
|
85
|
+
|
|
86
|
+
`p_epsilon`: probability for adding a Gaussian random value
|
|
87
|
+
|
|
88
|
+
`epsilon`: standard deviation of the Gaussian random value
|
|
89
|
+
"""
|
|
90
|
+
self._params = {k: v for k, v in locals().items() if k != 'self'}
|
|
91
|
+
if sum(self._params[p] for p in ('p_subtract', 'p_add')) > 1:
|
|
92
|
+
warnings.warn("p_subtract={0} + p_add={1} > 1, hence p_add is interpreted"
|
|
93
|
+
" as ``1-p_subtract``"
|
|
94
|
+
.format(self._params['p_subtract'], self._params['p_add']))
|
|
95
|
+
if p_add < 0 or p_subtract < 0 or p_epsilon < 0 or epsilon < 0:
|
|
96
|
+
raise ValueError("parameters cannot be negative but were {0}"
|
|
97
|
+
.format(self._params))
|
|
98
|
+
if p_epsilon > 0 and epsilon == 0:
|
|
99
|
+
warnings.warn("p_epsilon = {0} > 0 is not effective because epsilon = 0"
|
|
100
|
+
.format(p_epsilon))
|
|
101
|
+
|
|
102
|
+
def noisify(self, problem):
|
|
103
|
+
"""wrap `problem` with frozen noise"""
|
|
104
|
+
self._problem = problem
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def parameters(self):
|
|
109
|
+
return {k: v for (k, v) in self._params.items() if k.startswith(('p_', 'eps'))}
|
|
110
|
+
|
|
111
|
+
def _dump_params(self):
|
|
112
|
+
with open(parameters_file, 'wt') as fp:
|
|
113
|
+
json.dump(self.parameters, fp)
|
|
114
|
+
|
|
115
|
+
def _load_params(self, filename=parameters_file, warn_errors=(FileNotFoundError,), ignore_errors=()):
|
|
116
|
+
try:
|
|
117
|
+
with open(parameters_file, 'rt') as fp:
|
|
118
|
+
return json.load(fp)
|
|
119
|
+
except warn_errors as e:
|
|
120
|
+
warnings.warn('NoisifyProblem._load_params raised the exception "{0}"'.format(e))
|
|
121
|
+
except ignore_errors:
|
|
122
|
+
pass
|
|
123
|
+
return {}
|
|
124
|
+
|
|
125
|
+
def _set_params(self, filename=parameters_file, **kwargs):
|
|
126
|
+
"""set parameters from file `filename`"""
|
|
127
|
+
self._params.update(self._load_params(filename), **kwargs)
|
|
128
|
+
|
|
129
|
+
def __getattr__(self, name):
|
|
130
|
+
"""pretend to be a COCO `problem` as passed in `noisify`"""
|
|
131
|
+
return getattr(self._problem, name)
|
|
132
|
+
|
|
133
|
+
def __call__(self, x):
|
|
134
|
+
fval = self._problem(x)
|
|
135
|
+
try: # as quick as hasattr(fval, '__iter__')
|
|
136
|
+
en = enumerate(fval) # multiobjective
|
|
137
|
+
except TypeError:
|
|
138
|
+
return fval + self._fnoise(x) # single objective
|
|
139
|
+
return np.array([f + self._fnoise([x[0] + 1.001 * i, x[1]]) for i, f in en])
|
|
140
|
+
|
|
141
|
+
def constraint(self, x):
|
|
142
|
+
"""return noisy constraint values"""
|
|
143
|
+
constraint = self._problem.constraint(x)
|
|
144
|
+
for i, c in enumerate(constraint):
|
|
145
|
+
constraint[i] += self._cnoise([x[i % 2], c])
|
|
146
|
+
return constraint
|
|
147
|
+
|
|
148
|
+
def rand0(self, x, fac=1):
|
|
149
|
+
"""uniform by default"""
|
|
150
|
+
return self._params['rands'][0](x, fac)
|
|
151
|
+
def rand1(self, x, fac=1):
|
|
152
|
+
"""Gaussian by default"""
|
|
153
|
+
return self._params['rands'][1](x, fac)
|
|
154
|
+
def rand2(self, x, fac=1):
|
|
155
|
+
"""Cauchy by default"""
|
|
156
|
+
return self._params['rands'][2](x, fac)
|
|
157
|
+
|
|
158
|
+
def _cnoise(self, x):
|
|
159
|
+
return self._fnoise(x) # for the time being
|
|
160
|
+
|
|
161
|
+
def _fnoise(self, x):
|
|
162
|
+
"""x-dependent abs Cauchy noise with median pi/2"""
|
|
163
|
+
n = 0 # no noise by default
|
|
164
|
+
if self._params['p_epsilon'] > 0 and self.rand0(x, 3) < self._params['p_epsilon']:
|
|
165
|
+
n = self._params['epsilon'] * self.rand1(x)
|
|
166
|
+
assert np.isfinite(n)
|
|
167
|
+
if all(self._params[p] <= 0 for p in ('p_add', 'p_subtract')):
|
|
168
|
+
return n
|
|
169
|
+
r = self.rand0(x, 2) # a uniform random number
|
|
170
|
+
assert 0 <= r <= 1, r
|
|
171
|
+
i = 4
|
|
172
|
+
for p, fac, irand in [(self._params['p_add'], 1.5707963267948966, 2),
|
|
173
|
+
(self._params['p_subtract'], -1.5707963267948966, -1)]:
|
|
174
|
+
if r < p:
|
|
175
|
+
assert p > 0, (r, p)
|
|
176
|
+
nn = self._params['rands'][irand](x, i)
|
|
177
|
+
if nn < 0:
|
|
178
|
+
nn *= -1
|
|
179
|
+
return n + fac * nn
|
|
180
|
+
# self.randn(x, 2) / self.randn(x, 12)
|
|
181
|
+
# was: return n + s * 2 * r / self._params[p] / self.rand0(x)
|
|
182
|
+
r = 1 - r # prevent sampling another r
|
|
183
|
+
i += 1 # not clear why we need to change i
|
|
184
|
+
assert np.isfinite(n)
|
|
185
|
+
return n
|
|
186
|
+
|
|
187
|
+
_seed_weights = [1.23468, 2.34579] # otherwise, coordinate search would be noisefree
|
|
188
|
+
_seeds = collections.deque(maxlen=99) # for the record only
|
|
189
|
+
def _seed(x, i=0):
|
|
190
|
+
"""return a scalar seed using the first two entries of vector `x`"""
|
|
191
|
+
if not _frozen_noise:
|
|
192
|
+
import random
|
|
193
|
+
return 1e4 / (random.random() + 1e-22)
|
|
194
|
+
freezer = (i + 1) * (1 + _seed_weights[0] * float(x[0]) + _seed_weights[1] * float(x[1]))
|
|
195
|
+
if freezer < 0:
|
|
196
|
+
freezer = -freezer
|
|
197
|
+
# s = 1 + int(repr(freezer).replace('.', '').split('e')[0][-1:-15:-1])
|
|
198
|
+
s = freezer + 1 / (freezer + 136247e-22)
|
|
199
|
+
while s > 1e21:
|
|
200
|
+
s /= 9
|
|
201
|
+
_seeds.appendleft(s)
|
|
202
|
+
if not np.isfinite(s):
|
|
203
|
+
warnings.warn("seed({0}) = {1} is not finite. Using 1 instead"
|
|
204
|
+
.format(x, s))
|
|
205
|
+
return 1
|
|
206
|
+
return s
|
|
207
|
+
def _unif(N, inseed):
|
|
208
|
+
"""Generates N uniform numbers with starting seed inseed.
|
|
209
|
+
|
|
210
|
+
This is copy-paste from the bbobbenchmarks Python implementation with
|
|
211
|
+
minor adaptation.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# initialization
|
|
215
|
+
if inseed < 0: # np.abs took half the overall time without showing up in the profiler
|
|
216
|
+
inseed = -inseed
|
|
217
|
+
if inseed < 1.:
|
|
218
|
+
inseed += 1.
|
|
219
|
+
|
|
220
|
+
rgrand = 32 * [0.]
|
|
221
|
+
aktseed = inseed
|
|
222
|
+
for i in range(39, -1, -1):
|
|
223
|
+
tmp = math_floor(aktseed / 127773.)
|
|
224
|
+
aktseed = 16807. * (aktseed - tmp * 127773.) - 2836. * tmp
|
|
225
|
+
if aktseed < 0:
|
|
226
|
+
aktseed = aktseed + 2147483647.
|
|
227
|
+
if i < 32:
|
|
228
|
+
rgrand[i] = aktseed
|
|
229
|
+
aktrand = rgrand[0]
|
|
230
|
+
|
|
231
|
+
# sample numbers
|
|
232
|
+
r = N * [0.]
|
|
233
|
+
N0 = 2 # the first is visibly biased to be < 0.05
|
|
234
|
+
for i in range(-N0, N):
|
|
235
|
+
tmp = math_floor(aktseed / 127773.)
|
|
236
|
+
aktseed = 16807. * (aktseed - tmp * 127773.) - 2836. * tmp
|
|
237
|
+
if aktseed < 0:
|
|
238
|
+
aktseed = aktseed + 2147483647.
|
|
239
|
+
tmp = math_floor(aktrand / 67108865.)
|
|
240
|
+
aktrand = rgrand[tmp]
|
|
241
|
+
rgrand[tmp] = aktseed
|
|
242
|
+
if i >= 0:
|
|
243
|
+
r[i] = aktrand / 2.147483647e9
|
|
244
|
+
# r = np.asarray(r)
|
|
245
|
+
if not all(r):
|
|
246
|
+
warnings.warn('zero sampled {0} times, set to 1e-99'.format(sum(ri == 0 for ri in r)))
|
|
247
|
+
for i in range(len(r)):
|
|
248
|
+
if r[i] == 0:
|
|
249
|
+
r[i] = 1e-99
|
|
250
|
+
return r
|
|
251
|
+
def _gauss(N, seed):
|
|
252
|
+
"""Samples N standard normally distributed numbers
|
|
253
|
+
being the same for a given seed
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
r = _unif(2 * N, seed)
|
|
257
|
+
g = [math.sqrt(-2 * math.log(r[i])) * math.cos(2 * math.pi * r[i+N]) for i in range(N)]
|
|
258
|
+
if not all(g):
|
|
259
|
+
for i in range(len(g)):
|
|
260
|
+
if g[i] == 0:
|
|
261
|
+
g[i] = 1e-99
|
|
262
|
+
return g
|
|
263
|
+
def _cauchy(N, seed):
|
|
264
|
+
"""Samples N standard Cauchy distributed numbers
|
|
265
|
+
being the same for a given seed
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
r = _unif(4 * N, seed)
|
|
269
|
+
g = [math.sqrt(-2 * math.log(r[i])) * math.cos(2 * math.pi * r[i+N]) for i in range(N)]
|
|
270
|
+
return [g[i] / max((1e-21,
|
|
271
|
+
math.fabs(math.sqrt(-2 * math.log(r[i+2*N])) * math.cos(2 * math.pi * r[i+3*N]))))
|
|
272
|
+
for i in range(N)]
|
|
273
|
+
# def _gauss(N, seed): # with numpy
|
|
274
|
+
# """Samples N standard normally distributed numbers
|
|
275
|
+
# being the same for a given seed
|
|
276
|
+
|
|
277
|
+
# """
|
|
278
|
+
# r = np.asarray(_unif(2 * N, seed))
|
|
279
|
+
# g = np.sqrt(-2 * np.log(r[:N])) * np.cos(2 * np.pi * r[N:2*N])
|
|
280
|
+
# if not np.all(g):
|
|
281
|
+
# g[g == 0] = 1e-99
|
|
282
|
+
# return g
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|