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.
Files changed (22) hide show
  1. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/PKG-INFO +1 -1
  2. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/meson.build +1 -0
  3. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/__init__.py +1 -2
  4. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/_version.py +1 -1
  5. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/coco.c +1 -1
  6. coco_experiment-2.7.2/src/cocoex/noiser.py +282 -0
  7. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/.gitignore +0 -0
  8. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/LICENSE +0 -0
  9. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/README.md +0 -0
  10. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/pyproject.toml +0 -0
  11. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/scripts/run-doctest.py +0 -0
  12. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/bare_problem.pyx +0 -0
  13. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/coco.h +0 -0
  14. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/exceptions.py +0 -0
  15. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/interface.pyx +0 -0
  16. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/solvers.py +0 -0
  17. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/src/cocoex/utilities.py +0 -0
  18. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/bbob2009_testcases.txt +0 -0
  19. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/bbob2009_testcases2.txt +0 -0
  20. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/test_bare_problem.py +0 -0
  21. {coco_experiment-2.7.0a1 → coco_experiment-2.7.2}/tests/test_regression.py +0 -0
  22. {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.0a1
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/
@@ -65,6 +65,7 @@ python_sources = [
65
65
  'src/cocoex/__init__.py',
66
66
  'src/cocoex/_version.py',
67
67
  'src/cocoex/exceptions.py',
68
+ 'src/cocoex/noiser.py',
68
69
  'src/cocoex/solvers.py',
69
70
  'src/cocoex/utilities.py',
70
71
  ]
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  # file generated by fabricate
2
2
  # Do not change, do not track in version control!
3
3
 
4
- __version__ = "2.7.0a1"
4
+ __version__ = "2.7.2"
5
5
  version = __version__
6
6
 
7
7
  if __name__ == "__main__":
@@ -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.0a1";
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