1
2 """Module cma implements the CMA-ES (Covariance Matrix Adaptation
3 Evolution Strategy).
4
5 CMA-ES is a stochastic optimizer for robust non-linear non-convex
6 derivative- and function-value-free numerical optimization.
7
8 This implementation can be used with Python versions >= 2.6, namely
9 2.6, 2.7, 3.3, 3.4.
10
11 CMA-ES searches for a minimizer (a solution x in :math:`R^n`) of an
12 objective function f (cost function), such that f(x) is minimal.
13 Regarding f, only a passably reliable ranking of the candidate
14 solutions in each iteration is necessary. Neither the function values
15 itself, nor the gradient of f need to be available or do matter (like
16 in the downhill simplex Nelder-Mead algorithm). Some termination
17 criteria however depend on actual f-values.
18
19 Two interfaces are provided:
20
21 - function `fmin(func, x0, sigma0,...)`
22 runs a complete minimization
23 of the objective function func with CMA-ES.
24
25 - class `CMAEvolutionStrategy`
26 allows for minimization such that the control of the iteration
27 loop remains with the user.
28
29
30 Used packages:
31
32 - unavoidable: `numpy` (see `barecmaes2.py` if `numpy` is not
33 available),
34 - avoidable with small changes: `time`, `sys`
35 - optional: `matplotlib.pyplot` (for `plot` etc., highly
36 recommended), `pprint` (pretty print), `pickle` (in class
37 `Sections`), `doctest`, `inspect`, `pygsl` (never by default)
38
39 Install
40 -------
41 The file ``cma.py`` only needs to be visible in the python path (e.g. in
42 the current working directory).
43
44 The preferred way of (system-wide) installation is calling
45
46 pip install cma
47
48 from the command line.
49
50 The ``cma.py`` file can also be installed from the
51 system shell terminal command line by::
52
53 python cma.py --install
54
55 which solely calls the ``setup`` function from the standard
56 ``distutils.core`` package for installation. If the ``setup.py``
57 file is been provided with ``cma.py``, the standard call is
58
59 python setup.py cma
60
61 Both calls need to see ``cma.py`` in the current working directory and
62 might need to be preceded with ``sudo``.
63
64 To upgrade the currently installed version from the Python Package Index,
65 and also for first time installation, type in the system shell::
66
67 pip install --upgrade cma
68
69 Testing
70 -------
71 From the system shell::
72
73 python cma.py --test
74
75 or from the Python shell ``ipython``::
76
77 run cma.py --test
78
79 or from any python shell
80
81 import cma
82 cma.main('--test')
83
84 runs ``doctest.testmod(cma)`` showing only exceptions (and not the
85 tests that fail due to small differences in the output) and should
86 run without complaints in about between 20 and 100 seconds.
87
88 Example
89 -------
90 From a python shell::
91
92 import cma
93 help(cma) # "this" help message, use cma? in ipython
94 help(cma.fmin)
95 help(cma.CMAEvolutionStrategy)
96 help(cma.CMAOptions)
97 cma.CMAOptions('tol') # display 'tolerance' termination options
98 cma.CMAOptions('verb') # display verbosity options
99 res = cma.fmin(cma.Fcts.tablet, 15 * [1], 1)
100 res[0] # best evaluated solution
101 res[5] # mean solution, presumably better with noise
102
103 :See: `fmin()`, `CMAOptions`, `CMAEvolutionStrategy`
104
105 :Author: Nikolaus Hansen, 2008-2015
106 :Contributor: Petr Baudis, 2014
107
108 :License: BSD 3-Clause, see below.
109
110 """
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 from __future__ import division
308
309 from __future__ import with_statement
310
311 from __future__ import print_function
312
313 from __future__ import absolute_import
314 from __future__ import unicode_literals
315
316
317
318 import sys
319 if not sys.version.startswith('2'):
320 xrange = range
321 raw_input = input
322 basestring = str
323 else:
324 input = raw_input
325
326 import time
327 import collections
328 import numpy as np
329
330
331 from numpy import inf, array, dot, exp, log, sqrt, sum, isscalar, isfinite
332
333
334 try:
335 from matplotlib import pyplot
336 savefig = pyplot.savefig
337 closefig = pyplot.close
339
340 pyplot.ion()
341 pyplot.show()
342
343
344 pyplot.ion()
345 except:
346 pyplot = None
347 savefig = None
348 closefig = None
350 print('pyplot.show() is not available')
351 print('Could not import matplotlib.pyplot, therefore ``cma.plot()``" +'
352 ' etc. is not available')
353
354 __author__ = 'Nikolaus Hansen'
355 __version__ = "1.1.06 $Revision: 4129 $ $Date: 2015-01-23 20:13:51 +0100 (Fri, 23 Jan 2015) $"
356
357
358
359
360 __docformat__ = "reStructuredText"
361 __all__ = (
362 'main',
363 'fmin',
364 'fcts',
365 'Fcts',
366 'felli',
367 'rotate',
368 'pprint',
369 'plot',
370 'disp',
371 'show',
372 'savefig',
373 'closefig',
374 'use_archives',
375 'is_feasible',
376 'unitdoctest',
377 'DerivedDictBase',
378 'SolutionDict',
379 'CMASolutionDict',
380 'BestSolution',
381
382 'BoundNone',
383 'BoundTransform',
384 'BoundPenalty',
385
386
387 'GenoPheno',
388 'OOOptimizer',
389 'CMAEvolutionStrategy',
390 'CMAOptions',
391 'CMASolutionDict',
392 'CMAAdaptSigmaBase',
393 'CMAAdaptSigmaNone',
394 'CMAAdaptSigmaDistanceProportional',
395 'CMAAdaptSigmaCSA',
396 'CMAAdaptSigmaTPA',
397 'CMAAdaptSigmaMedianImprovement',
398 'BaseDataLogger',
399 'CMADataLogger',
400 'NoiseHandler',
401 'Sections',
402 'Misc',
403 'Mh',
404 'ElapsedTime',
405 'Rotation',
406 'fcts',
407 'FFWrappers',
408 )
409 use_archives = True
410 """speed up for very large population size. `use_archives` prevents the
411 need for an inverse gp-transformation, relies on collections module,
412 not sure what happens if set to ``False``. """
498
499
500
501
502
503
504
505
506
507 meta_parameters = MetaParameters()
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525 -def rglen(ar):
526 """shortcut for the iterator ``xrange(len(ar))``"""
527 return xrange(len(ar))
528
530 """default to check feasibility, see also ``cma_default_options``"""
531 return f is not None and f is not np.NaN
532
533 global_verbosity = 1
534 -def _print_warning(msg, method_name=None, class_name=None, iteration=None,
535 verbose=None):
536 if verbose is None:
537 verbose = global_verbosity
538 if verbose > 0:
539 print('WARNING (module=' + __name__ +
540 (', class=' + str(class_name) if class_name else '') +
541 (', method=' + str(method_name) if method_name else '') +
542 (', iteration=' + str(iteration) if iteration else '') +
543 '): ', msg)
544
549 """is used to describe test cases and might in future become helpful
550 as an experimental tutorial as well. The main testing feature at the
551 moment is by doctest with ``cma._test()`` or conveniently by
552 ``python cma.py --test``. With the ``--verbose`` option added, the
553 results will always slightly differ and many "failed" test cases
554 might be reported.
555
556 A simple first overall test:
557 >>> import cma
558 >>> res = cma.fmin(cma.fcts.elli, 3*[1], 1,
559 ... {'CMA_diagonal':2, 'seed':1, 'verb_time':0})
560 (3_w,7)-CMA-ES (mu_w=2.3,w_1=58%) in dimension 3 (seed=1)
561 Covariance matrix is diagonal for 2 iterations (1/ccov=7.0)
562 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
563 1 7 1.453161670768570e+04 1.2e+00 1.08e+00 1e+00 1e+00
564 2 14 3.281197961927601e+04 1.3e+00 1.22e+00 1e+00 2e+00
565 3 21 1.082851071704020e+04 1.3e+00 1.24e+00 1e+00 2e+00
566 100 700 8.544042012075362e+00 1.4e+02 3.18e-01 1e-03 2e-01
567 200 1400 5.691152415221861e-12 1.0e+03 3.82e-05 1e-09 1e-06
568 220 1540 3.890107746209078e-15 9.5e+02 4.56e-06 8e-11 7e-08
569 termination on tolfun : 1e-11
570 final/bestever f-value = 3.89010774621e-15 2.52273602735e-15
571 mean solution: [ -4.63614606e-08 -3.42761465e-10 1.59957987e-11]
572 std deviation: [ 6.96066282e-08 2.28704425e-09 7.63875911e-11]
573
574 Test on the Rosenbrock function with 3 restarts. The first trial only
575 finds the local optimum, which happens in about 20% of the cases.
576
577 >>> import cma
578 >>> res = cma.fmin(cma.fcts.rosen, 4*[-1], 1,
579 ... options={'ftarget':1e-6, 'verb_time':0,
580 ... 'verb_disp':500, 'seed':3},
581 ... restarts=3)
582 (4_w,8)-CMA-ES (mu_w=2.6,w_1=52%) in dimension 4 (seed=3)
583 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
584 1 8 4.875315645656848e+01 1.0e+00 8.43e-01 8e-01 8e-01
585 2 16 1.662319948123120e+02 1.1e+00 7.67e-01 7e-01 8e-01
586 3 24 6.747063604799602e+01 1.2e+00 7.08e-01 6e-01 7e-01
587 184 1472 3.701428610430019e+00 4.3e+01 9.41e-07 3e-08 5e-08
588 termination on tolfun : 1e-11
589 final/bestever f-value = 3.70142861043 3.70142861043
590 mean solution: [-0.77565922 0.61309336 0.38206284 0.14597202]
591 std deviation: [ 2.54211502e-08 3.88803698e-08 4.74481641e-08 3.64398108e-08]
592 (8_w,16)-CMA-ES (mu_w=4.8,w_1=32%) in dimension 4 (seed=4)
593 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
594 1 1489 2.011376859371495e+02 1.0e+00 8.90e-01 8e-01 9e-01
595 2 1505 4.157106647905128e+01 1.1e+00 8.02e-01 7e-01 7e-01
596 3 1521 3.548184889359060e+01 1.1e+00 1.02e+00 8e-01 1e+00
597 111 3249 6.831867555502181e-07 5.1e+01 2.62e-02 2e-04 2e-03
598 termination on ftarget : 1e-06
599 final/bestever f-value = 6.8318675555e-07 1.18576673231e-07
600 mean solution: [ 0.99997004 0.99993938 0.99984868 0.99969505]
601 std deviation: [ 0.00018973 0.00038006 0.00076479 0.00151402]
602 >>> assert res[1] <= 1e-6
603
604 Notice the different termination conditions. Termination on the target
605 function value ftarget prevents further restarts.
606
607 Test of scaling_of_variables option
608
609 >>> import cma
610 >>> opts = cma.CMAOptions()
611 >>> opts['seed'] = 456
612 >>> opts['verb_disp'] = 0
613 >>> opts['CMA_active'] = 1
614 >>> # rescaling of third variable: for searching in roughly
615 >>> # x0 plus/minus 1e3*sigma0 (instead of plus/minus sigma0)
616 >>> opts['scaling_of_variables'] = [1, 1, 1e3, 1]
617 >>> res = cma.fmin(cma.fcts.rosen, 4 * [0.1], 0.1, opts)
618 termination on tolfun : 1e-11
619 final/bestever f-value = 2.68096173031e-14 1.09714829146e-14
620 mean solution: [ 1.00000001 1.00000002 1.00000004 1.00000007]
621 std deviation: [ 3.00466854e-08 5.88400826e-08 1.18482371e-07 2.34837383e-07]
622
623 The printed std deviations reflect the actual value in the parameters
624 of the function (not the one in the internal representation which
625 can be different).
626
627 Test of CMA_stds scaling option.
628
629 >>> import cma
630 >>> opts = cma.CMAOptions()
631 >>> s = 5 * [1]
632 >>> s[0] = 1e3
633 >>> opts.set('CMA_stds', s)
634 >>> opts.set('verb_disp', 0)
635 >>> res = cma.fmin(cma.fcts.cigar, 5 * [0.1], 0.1, opts)
636 >>> assert res[1] < 1800
637
638 :See: cma.main(), cma._test()
639
640 """
641
642 pass
643
645 """blanc container class for having a collection of attributes,
646 that might/should at some point become a more tailored class"""
647
648 if use_archives:
651 """for conveniently adding "features" to a dictionary. The actual
652 dictionary is in ``self.data``. Copy-paste
653 and modify setitem, getitem, and delitem, if necessary.
654
655 Details: This is the clean way to subclass build-in dict.
656
657 """
665 return len(self.data)
669 return iter(self.data)
671 """defines self[key] = value"""
672 self.data[key] = value
674 """defines self[key]"""
675 return self.data[key]
678
680 """dictionary with computation of an hash key.
681
682 The hash key is generated from the inserted solution and a stack of
683 previously inserted same solutions is provided. Each entry is meant
684 to store additional information related to the solution.
685
686 >>> import cma, numpy as np
687 >>> d = cma.SolutionDict()
688 >>> x = np.array([1,2,4])
689 >>> d[x] = {'f': sum(x**2), 'iteration': 1}
690 >>> assert d[x]['iteration'] == 1
691 >>> assert d.get(x) == (d[x] if d.key(x) in d.keys() else None)
692
693 TODO: data_with_same_key behaves like a stack (see setitem and
694 delitem), but rather should behave like a queue?! A queue is less
695 consistent with the operation self[key] = ..., if
696 self.data_with_same_key[key] is not empty.
697
698 TODO: iteration key is used to clean up without error management
699
700 """
702
703 super(SolutionDict, self).__init__(*args, **kwargs)
704 self.data_with_same_key = {}
705 self.last_iteration = 0
707 try:
708 return tuple(x)
709
710 except TypeError:
711 return x
713 """defines self[key] = value"""
714 key = self.key(key)
715 if key in self.data_with_same_key:
716 self.data_with_same_key[key] += [self.data[key]]
717 elif key in self.data:
718 self.data_with_same_key[key] = [self.data[key]]
719 self.data[key] = value
721 """defines self[key]"""
722 return self.data[self.key(key)]
724 """remove only most current key-entry"""
725 key = self.key(key)
726 if key in self.data_with_same_key:
727 if len(self.data_with_same_key[key]) == 1:
728 self.data[key] = self.data_with_same_key.pop(key)[0]
729 else:
730 self.data[key] = self.data_with_same_key[key].pop(-1)
731 else:
732 del self.data[key]
734 if len(self) > max_len:
735 for k in list(self.keys()):
736 if self[k]['iteration'] < min_iter:
737 del self[k]
738
745
746
747
748 - def insert(self, key, geno=None, iteration=None, fitness=None, value=None):
749 """insert an entry with key ``key`` and value
750 ``value if value is not None else {'geno':key}`` and
751 ``self[key]['kwarg'] = kwarg if kwarg is not None`` for the further kwargs.
752
753 """
754
755 if iteration is not None and iteration > self.last_iteration and (iteration % 10) < 1:
756 self.truncate(300, iteration - 3)
757 elif value is not None and value.get('iteration'):
758 iteration = value['iteration']
759 if (iteration % 10) < 1:
760 self.truncate(300, iteration - 3)
761
762 self.last_solution_index += 1
763 if value is not None:
764 try:
765 iteration = value['iteration']
766 except:
767 pass
768 if iteration is not None:
769 if iteration > self.last_iteration:
770 self.last_solution_index = 0
771 self.last_iteration = iteration
772 else:
773 iteration = self.last_iteration + 0.5
774 if value is not None:
775 self[key] = value
776 else:
777 self[key] = {'pheno': key}
778 if geno is not None:
779 self[key]['geno'] = geno
780 if iteration is not None:
781 self[key]['iteration'] = iteration
782 if fitness is not None:
783 self[key]['fitness'] = fitness
784 return self[key]
785
786 if not use_archives:
788 """a hack to get most code examples running"""
789 - def insert(self, *args, **kwargs):
791 - def get(self, key):
797
799 """container to keep track of the best solution seen"""
800 - def __init__(self, x=None, f=np.inf, evals=None):
801 """initialize the best solution with `x`, `f`, and `evals`.
802 Better solutions have smaller `f`-values.
803
804 """
805 self.x = x
806 self.x_geno = None
807 self.f = f if f is not None and f is not np.nan else np.inf
808 self.evals = evals
809 self.evalsall = evals
810 self.last = _BlancClass()
811 self.last.x = x
812 self.last.f = f
813 - def update(self, arx, xarchive=None, arf=None, evals=None):
814 """checks for better solutions in list `arx`.
815
816 Based on the smallest corresponding value in `arf`,
817 alternatively, `update` may be called with a `BestSolution`
818 instance like ``update(another_best_solution)`` in which case
819 the better solution becomes the current best.
820
821 `xarchive` is used to retrieve the genotype of a solution.
822
823 """
824 if isinstance(arx, BestSolution):
825 if self.evalsall is None:
826 self.evalsall = arx.evalsall
827 elif arx.evalsall is not None:
828 self.evalsall = max((self.evalsall, arx.evalsall))
829 if arx.f is not None and arx.f < np.inf:
830 self.update([arx.x], xarchive, [arx.f], arx.evals)
831 return self
832 assert arf is not None
833
834 minidx = np.nanargmin(arf)
835 if minidx is np.nan:
836 return
837 minarf = arf[minidx]
838
839
840 if minarf < np.inf and (minarf < self.f or self.f is None):
841 self.x, self.f = arx[minidx], arf[minidx]
842 if xarchive is not None and xarchive.get(self.x) is not None:
843 self.x_geno = xarchive[self.x].get('geno')
844 else:
845 self.x_geno = None
846 self.evals = None if not evals else evals - len(arf) + minidx + 1
847 self.evalsall = evals
848 elif evals:
849 self.evalsall = evals
850 self.last.x = arx[minidx]
851 self.last.f = minarf
853 """return ``(x, f, evals)`` """
854 return self.x, self.f, self.evals
855
861 """hacked base class """
863 """bounds are not copied, but possibly modified and
864 put into a normalized form: ``bounds`` can be ``None``
865 or ``[lb, ub]`` where ``lb`` and ``ub`` are
866 either None or a vector (which can have ``None`` entries).
867
868 Generally, the last entry is recycled to compute bounds
869 for any dimension.
870
871 """
872 if not bounds:
873 self.bounds = None
874 else:
875 l = [None, None]
876 for i in [0, 1]:
877 try:
878 l[i] = len(bounds[i])
879 except TypeError:
880 bounds[i] = [bounds[i]]
881 l[i] = 1
882 if all([bounds[i][j] is None or not isfinite(bounds[i][j])
883 for j in rglen(bounds[i])]):
884 bounds[i] = None
885 if bounds[i] is not None and any([bounds[i][j] == (-1)**i * np.inf
886 for j in rglen(bounds[i])]):
887 raise ValueError('lower/upper is +inf/-inf and ' +
888 'therefore no finite feasible solution is available')
889 self.bounds = bounds
890
891 - def __call__(self, solutions, *args, **kwargs):
892 """return penalty or list of penalties, by default zero(s).
893
894 This interface seems too specifically tailored to the derived
895 BoundPenalty class, it should maybe change.
896
897 """
898 if isscalar(solutions[0]):
899 return 0.0
900 else:
901 return len(solutions) * [0.0]
902
903 - def update(self, *args, **kwargs):
905
906 - def repair(self, x, copy_if_changed=True, copy_always=False):
907 """projects infeasible values on the domain bound, might be
908 overwritten by derived class """
909 if copy_always:
910 x = array(x, copy=True)
911 copy = False
912 else:
913 copy = copy_if_changed
914 if self.bounds is None:
915 return x
916 for ib in [0, 1]:
917 if self.bounds[ib] is None:
918 continue
919 for i in rglen(x):
920 idx = min([i, len(self.bounds[ib]) - 1])
921 if self.bounds[ib][idx] is not None and \
922 (-1)**ib * x[i] < (-1)**ib * self.bounds[ib][idx]:
923 if copy:
924 x = array(x, copy=True)
925 copy = False
926 x[i] = self.bounds[ib][idx]
927
928 - def inverse(self, y, copy_if_changed=True, copy_always=False):
929 return y if not copy_always else array(y, copy=True)
930
932 """``get_bounds('lower', 8)`` returns the lower bounds in 8-D"""
933 if which == 'lower' or which == 0:
934 return self._get_bounds(0, dimension)
935 elif which == 'upper' or which == 1:
936 return self._get_bounds(1, dimension)
937 else:
938 raise ValueError("argument which must be 'lower' or 'upper'")
939
941 """ib == 0/1 means lower/upper bound, return a vector of length
942 `dimension` """
943 sign_ = 2 * ib - 1
944 assert sign_**2 == 1
945 if self.bounds is None or self.bounds[ib] is None:
946 return array(dimension * [sign_ * np.Inf])
947 res = []
948 for i in xrange(dimension):
949 res.append(self.bounds[ib][min([i, len(self.bounds[ib]) - 1])])
950 if res[-1] is None:
951 res[-1] = sign_ * np.Inf
952 return array(res)
953
955 """return True, if any variable is bounded"""
956 bounds = self.bounds
957 if bounds in (None, [None, None]):
958 return False
959 for ib, bound in enumerate(bounds):
960 if bound is not None:
961 sign_ = 2 * ib - 1
962 for bound_i in bound:
963 if bound_i is not None and sign_ * bound_i < np.inf:
964 return True
965 return False
966
968 """not yet tested"""
969 if self.bounds is None:
970 return True
971 for ib in [0, 1]:
972 if self.bounds[ib] is None:
973 continue
974 for i in rglen(x):
975 idx = min([i, len(self.bounds[ib]) - 1])
976 if self.bounds[ib][idx] is not None and \
977 (-1)**ib * x[i] < (-1)**ib * self.bounds[ib][idx]:
978 return False
979 return True
980
982 """return boundaries in format ``[[lb0, ub0], [lb1, ub1], ...]``,
983 as used by ``BoxConstraints...`` class.
984
985 """
986 if not bounds:
987 b = [[None, None]]
988 else:
989 l = [None, None]
990 for i in [0, 1]:
991 try:
992 l[i] = len(bounds[i])
993 except TypeError:
994 bounds[i] = [bounds[i]]
995 l[i] = 1
996 b = []
997 try:
998 for i in xrange(max(l)):
999 b.append([bounds[0][i] if i < l[0] else None,
1000 bounds[1][i] if i < l[1] else None])
1001 except (TypeError, IndexError):
1002 print("boundaries must be provided in the form " +
1003 "[scalar_of_vector, scalar_or_vector]")
1004 raise
1005 return b
1006
1007
1008
1009
1010 -class BoundNone(BoundaryHandlerBase):
1012 if bounds is not None:
1013 raise ValueError()
1014
1015 super(BoundNone, self).__init__(None)
1018
1093
1098 """Computes the boundary penalty. Must be updated each iteration,
1099 using the `update` method.
1100
1101 Details
1102 -------
1103 The penalty computes like ``sum(w[i] * (x[i]-xfeas[i])**2)``,
1104 where `xfeas` is the closest feasible (in-bounds) solution from `x`.
1105 The weight `w[i]` should be updated during each iteration using
1106 the update method.
1107
1108 Example:
1109
1110 >>> import cma
1111 >>> cma.fmin(cma.felli, 6 * [1], 1,
1112 ... {
1113 ... 'boundary_handling': 'BoundPenalty',
1114 ... 'bounds': [-1, 1],
1115 ... 'fixed_variables': {0: 0.012, 2:0.234}
1116 ... })
1117
1118 Reference: Hansen et al 2009, A Method for Handling Uncertainty...
1119 IEEE TEC, with addendum, see
1120 http://www.lri.fr/~hansen/TEC2009online.pdf
1121
1122 """
1124 """Argument bounds can be `None` or ``bounds[0]`` and ``bounds[1]``
1125 are lower and upper domain boundaries, each is either `None` or
1126 a scalar or a list or array of appropriate size.
1127 """
1128
1129
1130
1131 super(BoundPenalty, self).__init__(bounds)
1132
1133 self.gamma = 1
1134 self.weights_initialized = False
1135 self.hist = []
1136
1137 - def repair(self, x, copy_if_changed=True, copy_always=False):
1138 """sets out-of-bounds components of ``x`` on the bounds.
1139
1140 """
1141
1142
1143 copy = copy_if_changed
1144 if copy_always:
1145 x = array(x, copy=True)
1146 bounds = self.bounds
1147 if bounds not in (None, [None, None], (None, None)):
1148 x = array(x, copy=True) if copy and not copy_always else x
1149 if bounds[0] is not None:
1150 if isscalar(bounds[0]):
1151 for i in rglen(x):
1152 x[i] = max((bounds[0], x[i]))
1153 else:
1154 for i in rglen(x):
1155 j = min([i, len(bounds[0]) - 1])
1156 if bounds[0][j] is not None:
1157 x[i] = max((bounds[0][j], x[i]))
1158 if bounds[1] is not None:
1159 if isscalar(bounds[1]):
1160 for i in rglen(x):
1161 x[i] = min((bounds[1], x[i]))
1162 else:
1163 for i in rglen(x):
1164 j = min((i, len(bounds[1]) - 1))
1165 if bounds[1][j] is not None:
1166 x[i] = min((bounds[1][j], x[i]))
1167 return x
1168
1169
1170
1172 """returns the boundary violation penalty for `x` ,where `x` is a
1173 single solution or a list or array of solutions.
1174
1175 """
1176 if x in (None, (), []):
1177 return x
1178 if self.bounds in (None, [None, None], (None, None)):
1179 return 0.0 if isscalar(x[0]) else [0.0] * len(x)
1180
1181 x_is_single_vector = isscalar(x[0])
1182 x = [x] if x_is_single_vector else x
1183
1184
1185 try:
1186 gamma = list(self.gamma)
1187 for i in sorted(gp.fixed_values):
1188 gamma.insert(i, 0.0)
1189 gamma = array(gamma, copy=False)
1190 except TypeError:
1191 gamma = self.gamma
1192 pen = []
1193 for xi in x:
1194
1195
1196
1197 xpheno = gp.pheno(archive[xi]['geno'])
1198
1199 xinbounds = self.repair(xpheno)
1200
1201 fac = 1
1202 pen.append(sum(gamma * ((xinbounds - xpheno) / fac)**2) / len(xi))
1203 return pen[0] if x_is_single_vector else pen
1204
1205
1206
1208 """counts for each coordinate the number of feasible values in
1209 ``solutions`` and returns an array of length ``len(solutions[0])``
1210 with the ratios.
1211
1212 `solutions` is a list or array of repaired ``Solution``
1213 instances,
1214
1215 """
1216 raise NotImplementedError('Solution class disappeared')
1217 count = np.zeros(len(solutions[0]))
1218 for x in solutions:
1219 count += x.unrepaired == x
1220 return count / float(len(solutions))
1221
1222
1223
1224 - def update(self, function_values, es):
1225 """updates the weights for computing a boundary penalty.
1226
1227 Arguments
1228 ---------
1229 `function_values`
1230 all function values of recent population of solutions
1231 `es`
1232 `CMAEvolutionStrategy` object instance, in particular
1233 mean and variances and the methods from the attribute
1234 `gp` of type `GenoPheno` are used.
1235
1236 """
1237 if self.bounds is None or (self.bounds[0] is None and
1238 self.bounds[1] is None):
1239 return self
1240
1241 N = es.N
1242
1243
1244 varis = es.sigma**2 * array(N * [es.C] if isscalar(es.C) else (
1245 es.C if isscalar(es.C[0]) else
1246 [es.C[i][i] for i in xrange(N)]))
1247
1248
1249 dmean = (es.mean - es.gp.geno(self.repair(es.gp.pheno(es.mean)))) / varis**0.5
1250
1251
1252 fvals = sorted(function_values)
1253 l = 1 + len(fvals)
1254 val = fvals[3 * l // 4] - fvals[l // 4]
1255 val = val / np.mean(varis)
1256
1257 if isfinite(val) and val > 0:
1258 self.hist.insert(0, val)
1259 elif val == inf and len(self.hist) > 1:
1260 self.hist.insert(0, max(self.hist))
1261 else:
1262 pass
1263 if len(self.hist) > 20 + (3 * N) / es.popsize:
1264 self.hist.pop()
1265
1266
1267 dfit = np.median(self.hist)
1268 damp = min(1, es.sp.mueff / 10. / N)
1269
1270
1271
1272 if len(self.hist) == 0:
1273 raise _Error('wrongful initialization, no feasible solution sampled. ' +
1274 'Reasons can be mistakenly set bounds (lower bound not smaller than upper bound) or a too large initial sigma0 or... ' +
1275 'See description of argument func in help(cma.fmin) or an example handling infeasible solutions in help(cma.CMAEvolutionStrategy). ')
1276
1277 if dmean.any() and (not self.weights_initialized or es.countiter == 2):
1278 self.gamma = array(N * [2 * dfit])
1279 self.weights_initialized = True
1280
1281 if self.weights_initialized:
1282 edist = array(abs(dmean) - 3 * max(1, N**0.5 / es.sp.mueff))
1283 if 1 < 3:
1284
1285
1286 self.gamma *= exp((edist > 0) * np.tanh(edist / 3) / 2.)**damp
1287
1288
1289 self.gamma[self.gamma > 5 * dfit] *= exp(-1. / 3)**damp
1290
1291 es.more_to_write += list(self.gamma) if self.weights_initialized else N * [1.0]
1292
1293
1294 return self
1295
1352
1365
1613
1615 """Genotype-phenotype transformation.
1616
1617 Method `pheno` provides the transformation from geno- to phenotype,
1618 that is from the internal representation to the representation used
1619 in the objective function. Method `geno` provides the "inverse" pheno-
1620 to genotype transformation. The geno-phenotype transformation comprises,
1621 in this order:
1622
1623 - insert fixed variables (with the phenotypic and therefore quite
1624 possibly "wrong" values)
1625 - affine linear transformation (first scaling then shift)
1626 - user-defined transformation
1627 - repair (e.g. into feasible domain due to boundaries)
1628 - assign fixed variables their original phenotypic value
1629
1630 By default all transformations are the identity. The repair is only applied,
1631 if the transformation is given as argument to the method `pheno`.
1632
1633 ``geno`` is only necessary, if solutions have been injected.
1634
1635 """
1636 - def __init__(self, dim, scaling=None, typical_x=None,
1637 fixed_values=None, tf=None):
1638 """return `GenoPheno` instance with phenotypic dimension `dim`.
1639
1640 Keyword Arguments
1641 -----------------
1642 `scaling`
1643 the diagonal of a scaling transformation matrix, multipliers
1644 in the genotyp-phenotyp transformation, see `typical_x`
1645 `typical_x`
1646 ``pheno = scaling*geno + typical_x``
1647 `fixed_values`
1648 a dictionary of variable indices and values, like ``{0:2.0, 2:1.1}``,
1649 that are not subject to change, negative indices are ignored
1650 (they act like incommenting the index), values are phenotypic
1651 values.
1652 `tf`
1653 list of two user-defined transformation functions, or `None`.
1654
1655 ``tf[0]`` is a function that transforms the internal representation
1656 as used by the optimizer into a solution as used by the
1657 objective function. ``tf[1]`` does the back-transformation.
1658 For example::
1659
1660 tf_0 = lambda x: [xi**2 for xi in x]
1661 tf_1 = lambda x: [abs(xi)**0.5 fox xi in x]
1662
1663 or "equivalently" without the `lambda` construct::
1664
1665 def tf_0(x):
1666 return [xi**2 for xi in x]
1667 def tf_1(x):
1668 return [abs(xi)**0.5 fox xi in x]
1669
1670 ``tf=[tf_0, tf_1]`` is a reasonable way to guaranty that only positive
1671 values are used in the objective function.
1672
1673 Details
1674 -------
1675 If ``tf_0`` is not the identity and ``tf_1`` is ommitted,
1676 the genotype of ``x0`` cannot be computed consistently and
1677 "injection" of phenotypic solutions is likely to lead to
1678 unexpected results.
1679
1680 """
1681 self.N = dim
1682 self.fixed_values = fixed_values
1683 if tf is not None:
1684 self.tf_pheno = tf[0]
1685 self.tf_geno = tf[1]
1686
1687
1688
1689
1690 _print_warning("in class GenoPheno: user defined transformations have not been tested thoroughly")
1691 else:
1692 self.tf_geno = None
1693 self.tf_pheno = None
1694
1695 if fixed_values:
1696 if not isinstance(fixed_values, dict):
1697 raise _Error("fixed_values must be a dictionary {index:value,...}")
1698 if max(fixed_values.keys()) >= dim:
1699 raise _Error("max(fixed_values.keys()) = " + str(max(fixed_values.keys())) +
1700 " >= dim=N=" + str(dim) + " is not a feasible index")
1701
1702 for k in list(fixed_values.keys()):
1703 if k < 0:
1704 fixed_values.pop(k)
1705
1706 def vec_is_default(vec, default_val=0):
1707 """return True if `vec` has the value `default_val`,
1708 None or [None] are also recognized as default
1709
1710 """
1711
1712
1713 try:
1714 if len(vec) == 1:
1715 vec = vec[0]
1716 except TypeError:
1717 pass
1718
1719 if vec is None or all(vec == default_val):
1720 return True
1721
1722 if all([val is None or val == default_val for val in vec]):
1723 return True
1724
1725 return False
1726
1727 self.scales = array(scaling) if scaling is not None else None
1728 if vec_is_default(self.scales, 1):
1729 self.scales = 1
1730 elif self.scales.shape is not () and len(self.scales) != self.N:
1731 raise _Error('len(scales) == ' + str(len(self.scales)) +
1732 ' does not match dimension N == ' + str(self.N))
1733
1734 self.typical_x = array(typical_x) if typical_x is not None else None
1735 if vec_is_default(self.typical_x, 0):
1736 self.typical_x = 0
1737 elif self.typical_x.shape is not () and len(self.typical_x) != self.N:
1738 raise _Error('len(typical_x) == ' + str(len(self.typical_x)) +
1739 ' does not match dimension N == ' + str(self.N))
1740
1741 if (self.scales is 1 and
1742 self.typical_x is 0 and
1743 self.fixed_values is None and
1744 self.tf_pheno is None):
1745 self.isidentity = True
1746 else:
1747 self.isidentity = False
1748 if self.tf_pheno is None:
1749 self.islinear = True
1750 else:
1751 self.islinear = False
1752
1753 - def pheno(self, x, into_bounds=None, copy=True, copy_always=False,
1754 archive=None, iteration=None):
1755 """maps the genotypic input argument into the phenotypic space,
1756 see help for class `GenoPheno`
1757
1758 Details
1759 -------
1760 If ``copy``, values from ``x`` are copied if changed under the transformation.
1761
1762 """
1763
1764 input_type = type(x)
1765 if into_bounds is None:
1766 into_bounds = (lambda x, copy=False:
1767 x if not copy else array(x, copy=copy))
1768 if copy_always and not copy:
1769 raise ValueError('arguments copy_always=' + str(copy_always) +
1770 ' and copy=' + str(copy) + ' have inconsistent values')
1771 if copy_always:
1772 x = array(x, copy=True)
1773 copy = False
1774
1775 if self.isidentity:
1776 y = into_bounds(x)
1777 else:
1778 if self.fixed_values is None:
1779 y = array(x, copy=copy)
1780 else:
1781 y = list(x)
1782 for i in sorted(self.fixed_values.keys()):
1783 y.insert(i, self.fixed_values[i])
1784 y = array(y, copy=False)
1785 copy = False
1786
1787 if self.scales is not 1:
1788 y *= self.scales
1789
1790 if self.typical_x is not 0:
1791 y += self.typical_x
1792
1793 if self.tf_pheno is not None:
1794 y = array(self.tf_pheno(y), copy=False)
1795
1796 y = into_bounds(y, copy)
1797
1798 if self.fixed_values is not None:
1799 for i, k in list(self.fixed_values.items()):
1800 y[i] = k
1801
1802 if input_type is np.ndarray:
1803 y = array(y, copy=False)
1804 if archive is not None:
1805 archive.insert(y, geno=x, iteration=iteration)
1806 return y
1807
1808 - def geno(self, y, from_bounds=None,
1809 copy_if_changed=True, copy_always=False,
1810 repair=None, archive=None):
1811 """maps the phenotypic input argument into the genotypic space,
1812 that is, computes essentially the inverse of ``pheno``.
1813
1814 By default a copy is made only to prevent to modify ``y``.
1815
1816 The inverse of the user-defined transformation (if any)
1817 is only needed if external solutions are injected, it is not
1818 applied to the initial solution x0.
1819
1820 Details
1821 =======
1822 ``geno`` searches first in ``archive`` for the genotype of
1823 ``y`` and returns the found value, typically unrepaired.
1824 Otherwise, first ``from_bounds`` is applied, to revert a
1825 projection into the bound domain (if necessary) and ``pheno``
1826 is reverted. ``repair`` is applied last, and is usually the
1827 method ``CMAEvolutionStrategy.repair_genotype`` that limits the
1828 Mahalanobis norm of ``geno(y) - mean``.
1829
1830 """
1831 if from_bounds is None:
1832 from_bounds = lambda x, copy=False: x
1833
1834 if archive is not None:
1835 try:
1836 x = archive[y]['geno']
1837 except (KeyError, TypeError):
1838 x = None
1839 if x is not None:
1840 if archive[y]['iteration'] < archive.last_iteration \
1841 and repair is not None:
1842 x = repair(x, copy_if_changed=copy_always)
1843 return x
1844
1845 input_type = type(y)
1846 x = y
1847 if copy_always:
1848 x = array(y, copy=True)
1849 copy = False
1850 else:
1851 copy = copy_if_changed
1852
1853 x = from_bounds(x, copy)
1854
1855 if self.isidentity:
1856 if repair is not None:
1857 x = repair(x, copy)
1858 return x
1859
1860 if copy:
1861 x = array(x, copy=True)
1862 copy = False
1863
1864
1865 if self.tf_geno is not None:
1866 x = array(self.tf_geno(x), copy=False)
1867 elif self.tf_pheno is not None:
1868 raise ValueError('t1 of options transformation was not defined but is needed as being the inverse of t0')
1869
1870
1871 if self.typical_x is not 0:
1872 x -= self.typical_x
1873 if self.scales is not 1:
1874 x /= self.scales
1875
1876
1877 if self.fixed_values is not None:
1878
1879
1880 if 1 < 3:
1881 keys = sorted(self.fixed_values.keys())
1882 x = array([x[i] for i in xrange(len(x)) if i not in keys],
1883 copy=False)
1884
1885 if repair is not None:
1886 x = repair(x, copy)
1887 if input_type is np.ndarray:
1888 x = array(x, copy=False)
1889 return x
1890
1897 """"abstract" base class for an Object Oriented Optimizer interface.
1898
1899 Relevant methods are `__init__`, `ask`, `tell`, `stop`, `result`,
1900 and `optimize`. Only `optimize` is fully implemented in this base
1901 class.
1902
1903 Examples
1904 --------
1905 All examples minimize the function `elli`, the output is not shown.
1906 (A preferred environment to execute all examples is ``ipython`` in
1907 ``%pylab`` mode.)
1908
1909 First we need::
1910
1911 from cma import CMAEvolutionStrategy
1912 # CMAEvolutionStrategy derives from the OOOptimizer class
1913 felli = lambda x: sum(1e3**((i-1.)/(len(x)-1.)*x[i])**2 for i in range(len(x)))
1914
1915 The shortest example uses the inherited method
1916 `OOOptimizer.optimize()`::
1917
1918 es = CMAEvolutionStrategy(8 * [0.1], 0.5).optimize(felli)
1919
1920 The input parameters to `CMAEvolutionStrategy` are specific to this
1921 inherited class. The remaining functionality is based on interface
1922 defined by `OOOptimizer`. We might have a look at the result::
1923
1924 print(es.result()[0]) # best solution and
1925 print(es.result()[1]) # its function value
1926
1927 In order to display more exciting output we do::
1928
1929 es.logger.plot() # if matplotlib is available
1930
1931 Virtually the same example can be written with an explicit loop
1932 instead of using `optimize()`. This gives the necessary insight into
1933 the `OOOptimizer` class interface and entire control over the
1934 iteration loop::
1935
1936 optim = CMAEvolutionStrategy(9 * [0.5], 0.3)
1937 # a new CMAEvolutionStrategy instance
1938
1939 # this loop resembles optimize()
1940 while not optim.stop(): # iterate
1941 X = optim.ask() # get candidate solutions
1942 f = [felli(x) for x in X] # evaluate solutions
1943 # in case do something else that needs to be done
1944 optim.tell(X, f) # do all the real "update" work
1945 optim.disp(20) # display info every 20th iteration
1946 optim.logger.add() # log another "data line"
1947
1948 # final output
1949 print('termination by', optim.stop())
1950 print('best f-value =', optim.result()[1])
1951 print('best solution =', optim.result()[0])
1952 optim.logger.plot() # if matplotlib is available
1953
1954 Details
1955 -------
1956 Most of the work is done in the method `tell(...)`. The method
1957 `result()` returns more useful output.
1958
1959 """
1960 - def __init__(self, xstart, **more_args):
1961 """``xstart`` is a mandatory argument"""
1962 self.xstart = xstart
1963 self.more_args = more_args
1964 self.initialize()
1966 """(re-)set to the initial state"""
1967 self.countiter = 0
1968 self.xcurrent = self.xstart[:]
1969 raise NotImplementedError('method initialize() must be implemented in derived class')
1970 - def ask(self, gradf=None, **more_args):
1971 """abstract method, AKA "get" or "sample_distribution", deliver
1972 new candidate solution(s), a list of "vectors"
1973
1974 """
1975 raise NotImplementedError('method ask() must be implemented in derived class')
1976 - def tell(self, solutions, function_values):
1977 """abstract method, AKA "update", pass f-values and prepare for
1978 next iteration
1979
1980 """
1981 self.countiter += 1
1982 raise NotImplementedError('method tell() must be implemented in derived class')
1984 """abstract method, return satisfied termination conditions in
1985 a dictionary like ``{'termination reason': value, ...}``,
1986 for example ``{'tolfun': 1e-12}``, or the empty dictionary ``{}``.
1987 The implementation of `stop()` should prevent an infinite
1988 loop.
1989
1990 """
1991 raise NotImplementedError('method stop() is not implemented')
1992 - def disp(self, modulo=None):
1993 """abstract method, display some iteration infos if
1994 ``self.iteration_counter % modulo == 0``
1995
1996 """
1997 pass
1999 """abstract method, return ``(x, f(x), ...)``, that is, the
2000 minimizer, its function value, ...
2001
2002 """
2003 raise NotImplementedError('method result() is not implemented')
2004
2005
2006
2007
2008
2009
2010 - def optimize(self, objective_fct, iterations=None, min_iterations=1,
2011 args=(), verb_disp=None, logger=None, call_back=None):
2012 """find minimizer of `objective_fct`.
2013
2014 CAVEAT: the return value for `optimize` has changed to ``self``.
2015
2016 Arguments
2017 ---------
2018
2019 `objective_fct`
2020 function be to minimized
2021 `iterations`
2022 number of (maximal) iterations, while ``not self.stop()``
2023 `min_iterations`
2024 minimal number of iterations, even if ``not self.stop()``
2025 `args`
2026 arguments passed to `objective_fct`
2027 `verb_disp`
2028 print to screen every `verb_disp` iteration, if ``None``
2029 the value from ``self.logger`` is "inherited", if
2030 available.
2031 ``logger``
2032 a `BaseDataLogger` instance, which must be compatible
2033 with the type of ``self``.
2034 ``call_back``
2035 call back function called like ``call_back(self)`` or
2036 a list of call back functions.
2037
2038 ``return self``, that is, the `OOOptimizer` instance.
2039
2040 Example
2041 -------
2042 >>> import cma
2043 >>> es = cma.CMAEvolutionStrategy(7 * [0.1], 0.5
2044 ... ).optimize(cma.fcts.rosen, verb_disp=100)
2045 (4_w,9)-CMA-ES (mu_w=2.8,w_1=49%) in dimension 7 (seed=630721393)
2046 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
2047 1 9 3.163954777181882e+01 1.0e+00 4.12e-01 4e-01 4e-01 0:0.0
2048 2 18 3.299006223906629e+01 1.0e+00 3.60e-01 3e-01 4e-01 0:0.0
2049 3 27 1.389129389866704e+01 1.1e+00 3.18e-01 3e-01 3e-01 0:0.0
2050 100 900 2.494847340045985e+00 8.6e+00 5.03e-02 2e-02 5e-02 0:0.3
2051 200 1800 3.428234862999135e-01 1.7e+01 3.77e-02 6e-03 3e-02 0:0.5
2052 300 2700 3.216640032470860e-04 5.6e+01 6.62e-03 4e-04 9e-03 0:0.8
2053 400 3600 6.155215286199821e-12 6.6e+01 7.44e-06 1e-07 4e-06 0:1.1
2054 438 3942 1.187372505161762e-14 6.0e+01 3.27e-07 4e-09 9e-08 0:1.2
2055 438 3942 1.187372505161762e-14 6.0e+01 3.27e-07 4e-09 9e-08 0:1.2
2056 ('termination by', {'tolfun': 1e-11})
2057 ('best f-value =', 1.1189867885201275e-14)
2058 ('solution =', array([ 1. , 1. , 1. , 0.99999999, 0.99999998,
2059 0.99999996, 0.99999992]))
2060 >>> print(es.result()[0])
2061 array([ 1. 1. 1. 0.99999999 0.99999998 0.99999996
2062 0.99999992])
2063
2064 """
2065 assert iterations is None or min_iterations <= iterations
2066 if not hasattr(self, 'logger'):
2067 self.logger = logger
2068 logger = self.logger = logger or self.logger
2069 if not isinstance(call_back, list):
2070 call_back = [call_back]
2071
2072 citer = 0
2073 while not self.stop() or citer < min_iterations:
2074 if iterations is not None and citer >= iterations:
2075 return self.result()
2076 citer += 1
2077
2078 X = self.ask()
2079 fitvals = [objective_fct(x, *args) for x in X]
2080 self.tell(X, fitvals)
2081 self.disp(verb_disp)
2082 for f in call_back:
2083 f is None or f(self)
2084 logger.add(self) if logger else None
2085
2086
2087
2088
2089 try:
2090 logger.add(self, modulo=bool(logger.modulo)) if logger else None
2091 except TypeError:
2092 print(' suppressing the final call of the logger in ' +
2093 'OOOptimizer.optimize (modulo keyword parameter not ' +
2094 'available)')
2095 except AttributeError:
2096 print(' suppressing the final call of the logger in ' +
2097 'OOOptimizer.optimize (modulo attribute not ' +
2098 'available)')
2099 if verb_disp:
2100 self.disp(1)
2101 if verb_disp in (1, True):
2102 print('termination by', self.stop())
2103 print('best f-value =', self.result()[1])
2104 print('solution =', self.result()[0])
2105
2106 return self
2107
2108
2109 _experimental = False
2112 """step-size adaptation base class, implementing hsig functionality
2113 via an isotropic evolution path.
2114
2115 """
2117 self.is_initialized_base = False
2118 self._ps_updated_iteration = -1
2120 """set parameters and state variable based on dimension,
2121 mueff and possibly further options.
2122
2123 """
2124
2125 b = 1.0
2126
2127 self.cs = 1.0 * (es.sp.mueff + 2)**b / (es.N**b + (es.sp.mueff + 3)**b)
2128 self.ps = np.zeros(es.N)
2129 self.is_initialized_base = True
2130 return self
2132 """update the isotropic evolution path
2133
2134 :type es: CMAEvolutionStrategy
2135 """
2136 if not self.is_initialized_base:
2137 self.initialize_base(es)
2138 if self._ps_updated_iteration == es.countiter:
2139 return
2140 if es.countiter <= es.itereigenupdated:
2141
2142 assert es.countiter >= es.itereigenupdated
2143 _print_warning('distribution transformation (B and D) have been updated before ps could be computed',
2144 '_update_ps', 'CMAAdaptSigmaBase')
2145 z = dot(es.B, (1. / es.D) * dot(es.B.T, (es.mean - es.mean_old) / es.sigma_vec))
2146 z *= es.sp.mueff**0.5 / es.sigma / es.sp.cmean
2147 self.ps = (1 - self.cs) * self.ps + sqrt(self.cs * (2 - self.cs)) * z
2148 self._ps_updated_iteration = es.countiter
2149 - def hsig(self, es):
2150 """return "OK-signal" for rank-one update, `True` (OK) or `False`
2151 (stall rank-one update), based on the length of an evolution path
2152
2153 """
2154 self._update_ps(es)
2155 if self.ps is None:
2156 return True
2157 squared_sum = sum(self.ps**2) / (1 - (1 - self.cs)**(2 * es.countiter))
2158
2159
2160 return squared_sum / es.N - 1 < 1 + 4. / (es.N + 1)
2161 - def update(self, es, **kwargs):
2162 """update ``es.sigma``"""
2163 self._update_ps(es)
2164 raise NotImplementedError('must be implemented in a derived class')
2165
2168 - def update(self, es, **kwargs):
2169 """no update, ``es.sigma`` remains constant.
2170
2171 :param es: ``CMAEvolutionStrategy`` class instance
2172 :param kwargs: whatever else is needed to update ``es.sigma``
2173
2174 """
2175 pass
2176
2179 """artificial setting of ``sigma`` for test purposes, e.g.
2180 to simulate optimal progress rates.
2181
2182 """
2187 - def update(self, es, **kwargs):
2188
2189 es.sigma = self.coefficient * es.sp.mueff * sum(es.mean**2)**0.5 / es.N / es.sp.cmean
2190
2194 """postpone initialization to a method call where dimension and mueff should be known.
2195
2196 """
2197 self.is_initialized = False
2199 """set parameters and state variable based on dimension,
2200 mueff and possibly further options.
2201
2202 """
2203 self.disregard_length_setting = True if es.opts['CSA_disregard_length'] else False
2204 if es.opts['CSA_clip_length_value'] is not None:
2205 try:
2206 if len(es.opts['CSA_clip_length_value']) == 0:
2207 es.opts['CSA_clip_length_value'] = [-np.Inf, np.Inf]
2208 elif len(es.opts['CSA_clip_length_value']) == 1:
2209 es.opts['CSA_clip_length_value'] = [-np.Inf, es.opts['CSA_clip_length_value'][0]]
2210 elif len(es.opts['CSA_clip_length_value']) == 2:
2211 es.opts['CSA_clip_length_value'] = np.sort(es.opts['CSA_clip_length_value'])
2212 else:
2213 raise ValueError('option CSA_clip_length_value should be a number of len(.) in [1,2]')
2214 except TypeError:
2215 es.opts['CSA_clip_length_value'] = [-np.Inf, es.opts['CSA_clip_length_value']]
2216 es.opts['CSA_clip_length_value'] = list(np.sort(es.opts['CSA_clip_length_value']))
2217 if es.opts['CSA_clip_length_value'][0] > 0 or es.opts['CSA_clip_length_value'][1] < 0:
2218 raise ValueError('option CSA_clip_length_value must be a single positive or a negative and a positive number')
2219
2220 b = 1.0
2221
2222 self.cs = 1.0 * (es.sp.mueff + 2)**b / (es.N + (es.sp.mueff + 3)**b)
2223 self.damps = es.opts['CSA_dampfac'] * (0.5 +
2224 0.5 * min([1, (es.sp.lam_mirr / (0.159 * es.sp.popsize) - 1)**2])**1 +
2225 2 * max([0, ((es.sp.mueff - 1) / (es.N + 1))**es.opts['CSA_damp_mueff_exponent'] - 1]) +
2226 self.cs
2227 )
2228 self.max_delta_log_sigma = 1
2229
2230 if self.disregard_length_setting:
2231 es.opts['CSA_clip_length_value'] = [0, 0]
2232
2233 b = 1.0 * 0.5
2234
2235 self.cs = 1.0 * (es.sp.mueff + 1)**b / (es.N**b + 2 * es.sp.mueff**b)
2236 self.damps = es.opts['CSA_dampfac'] * 1
2237 if es.opts['verbose'] > 1 or self.disregard_length_setting or 11 < 3:
2238 print('SigmaCSA Parameters')
2239 for k, v in self.__dict__.items():
2240 print(' ', k, ':', v)
2241 self.ps = np.zeros(es.N)
2242 self._ps_updated_iteration = -1
2243 self.is_initialized = True
2244
2246 if not self.is_initialized:
2247 self.initialize(es)
2248 if self._ps_updated_iteration == es.countiter:
2249 return
2250 z = dot(es.B, (1. / es.D) * dot(es.B.T, (es.mean - es.mean_old) / es.sigma_vec))
2251 z *= es.sp.mueff**0.5 / es.sigma / es.sp.cmean
2252
2253 if es.opts['CSA_clip_length_value'] is not None:
2254 vals = es.opts['CSA_clip_length_value']
2255 min_len = es.N**0.5 + vals[0] * es.N / (es.N + 2)
2256 max_len = es.N**0.5 + vals[1] * es.N / (es.N + 2)
2257 act_len = sum(z**2)**0.5
2258 new_len = Mh.minmax(act_len, min_len, max_len)
2259 if new_len != act_len:
2260 z *= new_len / act_len
2261
2262
2263 self.ps = (1 - self.cs) * self.ps + sqrt(self.cs * (2 - self.cs)) * z
2264 self._ps_updated_iteration = es.countiter
2265 - def update(self, es, **kwargs):
2266 self._update_ps(es)
2267 if es.opts['CSA_squared']:
2268 s = (sum(self.ps**2) / es.N - 1) / 2
2269
2270
2271 else:
2272 s = sum(self.ps**2)**0.5 / es.const.chiN - 1
2273 if es.opts['vv'] == 'pc for ps':
2274 s = sum((es.D**-1 * dot(es.B.T, es.pc))**2)**0.5 / es.const.chiN - 1
2275 s = (sum((es.D**-1 * dot(es.B.T, es.pc))**2) / es.N - 1) / 2
2276 s *= self.cs / self.damps
2277 s_clipped = Mh.minmax(s, -self.max_delta_log_sigma, self.max_delta_log_sigma)
2278 es.sigma *= np.exp(s_clipped)
2279
2280 if s_clipped != s:
2281 _print_warning('sigma change exp(' + str(s) + ') = ' + str(np.exp(s)) +
2282 ' clipped to exp(+-' + str(self.max_delta_log_sigma) + ')',
2283 'update',
2284 'CMAAdaptSigmaCSA',
2285 es.countiter, es.opts['verbose'])
2326 """two point adaptation for step-size sigma. Relies on a specific
2327 sampling of the first two offspring, whose objective function
2328 value ranks are used to decide on the step-size change.
2329
2330 Example
2331 =======
2332
2333 >>> import cma
2334 >>> cma.CMAOptions('adapt').pprint()
2335 >>> es = cma.CMAEvolutionStrategy(10 * [0.2], 0.1, {'AdaptSigma': cma.CMAAdaptSigmaTPA, 'ftarget': 1e-8})
2336 >>> es.optimize(cma.fcts.rosen)
2337 >>> assert 'ftarget' in es.stop()
2338 >>> assert es.result()[1] <= 1e-8
2339 >>> assert es.result()[2] < 6500 # typically < 5500
2340
2341 References: loosely based on Hansen 2008, CMA-ES with Two-Point
2342 Step-Size Adaptation, more tightly based on an upcoming paper by
2343 Hansen et al.
2344
2345 """
2346 - def __init__(self, dimension=None, opts=None):
2347 super(CMAAdaptSigmaTPA, self).__init__()
2348
2349 self.initialized = False
2350 self.dimension = dimension
2351 self.opts = opts
2353 if N is None:
2354 N = self.dimension
2355 if opts is None:
2356 opts = self.opts
2357 try:
2358 damp_fac = opts['CSA_dampfac']
2359 except (TypeError, KeyError):
2360 damp_fac = 1
2361
2362 self.sp = _BlancClass()
2363 try:
2364 self.sp.damp = damp_fac * eval('N')**0.5
2365
2366 except:
2367 self.sp.damp = 4
2368 _print_warning("dimension not known, damping set to 4",
2369 'initialize', 'CMAAdaptSigmaTPA')
2370 try:
2371 if opts['vv'][0] == 'TPA_damp':
2372 self.sp.damp = opts['vv'][1]
2373 print('damp set to %d' % self.sp.damp)
2374 except (TypeError):
2375 pass
2376
2377 self.sp.dampup = 0.5**0.0 * 1.0 * self.sp.damp
2378 self.sp.dampdown = 2.0**0.0 * self.sp.damp
2379 if self.sp.dampup != self.sp.dampdown:
2380 print('TPA damping is asymmetric')
2381 self.sp.c = 0.3
2382 self.sp.z_exponent = 0.5
2383 self.sp.sigma_fac = 1.0
2384 self.sp.relative_to_delta_mean = True
2385 self.s = 0
2386 self.last = None
2387 self.initialized = True
2388 return self
2389 - def update(self, es, function_values, **kwargs):
2390 """the first and second value in ``function_values``
2391 must reflect two mirrored solutions sampled
2392 in direction / in opposite direction of
2393 the previous mean shift, respectively.
2394
2395 """
2396
2397
2398
2399
2400
2401 if not self.initialized:
2402 self.initialize(es.N, es.opts)
2403 if 1 < 3:
2404
2405
2406 z = np.where(es.fit.idx == 1)[0][0] - np.where(es.fit.idx == 0)[0][0]
2407 z /= es.popsize - 1
2408 self.s = (1 - self.sp.c) * self.s + self.sp.c * np.sign(z) * np.abs(z)**self.sp.z_exponent
2409 if self.s > 0:
2410 es.sigma *= exp(self.s / self.sp.dampup)
2411 else:
2412 es.sigma *= exp(self.s / self.sp.dampdown)
2413
2414
2415 new_injections = True
2421 """CMA-ES stochastic optimizer class with ask-and-tell interface.
2422
2423 Calling Sequences
2424 =================
2425
2426 es = CMAEvolutionStrategy(x0, sigma0)
2427
2428 es = CMAEvolutionStrategy(x0, sigma0, opts)
2429
2430 es = CMAEvolutionStrategy(x0, sigma0).optimize(objective_fct)
2431
2432 res = CMAEvolutionStrategy(x0, sigma0,
2433 opts).optimize(objective_fct).result()
2434
2435 Arguments
2436 =========
2437 `x0`
2438 initial solution, starting point. `x0` is given as "phenotype"
2439 which means, if::
2440
2441 opts = {'transformation': [transform, inverse]}
2442
2443 is given and ``inverse is None``, the initial mean is not
2444 consistent with `x0` in that ``transform(mean)`` does not
2445 equal to `x0` unless ``transform(mean)`` equals ``mean``.
2446 `sigma0`
2447 initial standard deviation. The problem variables should
2448 have been scaled, such that a single standard deviation
2449 on all variables is useful and the optimum is expected to
2450 lie within about `x0` +- ``3*sigma0``. See also options
2451 `scaling_of_variables`. Often one wants to check for
2452 solutions close to the initial point. This allows,
2453 for example, for an easier check of consistency of the
2454 objective function and its interfacing with the optimizer.
2455 In this case, a much smaller `sigma0` is advisable.
2456 `opts`
2457 options, a dictionary with optional settings,
2458 see class `CMAOptions`.
2459
2460 Main interface / usage
2461 ======================
2462 The interface is inherited from the generic `OOOptimizer`
2463 class (see also there). An object instance is generated from
2464
2465 es = cma.CMAEvolutionStrategy(8 * [0.5], 0.2)
2466
2467 The least verbose interface is via the optimize method::
2468
2469 es.optimize(objective_func)
2470 res = es.result()
2471
2472 More verbosely, the optimization is done using the
2473 methods ``stop``, ``ask``, and ``tell``::
2474
2475 while not es.stop():
2476 solutions = es.ask()
2477 es.tell(solutions, [cma.fcts.rosen(s) for s in solutions])
2478 es.disp()
2479 es.result_pretty()
2480
2481
2482 where ``ask`` delivers new candidate solutions and ``tell`` updates
2483 the ``optim`` instance by passing the respective function values
2484 (the objective function ``cma.fcts.rosen`` can be replaced by any
2485 properly defined objective function, see ``cma.fcts`` for more
2486 examples).
2487
2488 To change an option, for example a termination condition to
2489 continue the optimization, call
2490
2491 es.opts.set({'tolfacupx': 1e4})
2492
2493 The class `CMAEvolutionStrategy` also provides::
2494
2495 (solutions, func_values) = es.ask_and_eval(objective_func)
2496
2497 and an entire optimization can also be written like::
2498
2499 while not es.stop():
2500 es.tell(*es.ask_and_eval(objective_func))
2501
2502 Besides for termination criteria, in CMA-ES only the ranks of the
2503 `func_values` are relevant.
2504
2505 Attributes and Properties
2506 =========================
2507 - `inputargs` -- passed input arguments
2508 - `inopts` -- passed options
2509 - `opts` -- actually used options, some of them can be changed any
2510 time via ``opts.set``, see class `CMAOptions`
2511 - `popsize` -- population size lambda, number of candidate
2512 solutions returned by `ask()`
2513 - `logger` -- a `CMADataLogger` instance utilized by `optimize`
2514
2515 Examples
2516 ========
2517 Super-short example, with output shown:
2518
2519 >>> import cma
2520 >>> # construct an object instance in 4-D, sigma0=1:
2521 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1, {'seed':234})
2522 (4_w,8)-CMA-ES (mu_w=2.6,w_1=52%) in dimension 4 (seed=234)
2523 >>>
2524 >>> # optimize the ellipsoid function
2525 >>> es.optimize(cma.fcts.elli, verb_disp=1)
2526 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
2527 1 8 2.093015112685775e+04 1.0e+00 9.27e-01 9e-01 9e-01 0:0.0
2528 2 16 4.964814235917688e+04 1.1e+00 9.54e-01 9e-01 1e+00 0:0.0
2529 3 24 2.876682459926845e+05 1.2e+00 1.02e+00 9e-01 1e+00 0:0.0
2530 100 800 6.809045875281943e-01 1.3e+02 1.41e-02 1e-04 1e-02 0:0.2
2531 200 1600 2.473662150861846e-10 8.0e+02 3.08e-05 1e-08 8e-06 0:0.5
2532 233 1864 2.766344961865341e-14 8.6e+02 7.99e-07 8e-11 7e-08 0:0.6
2533 >>>
2534 >>> cma.pprint(es.result())
2535 (array([ -1.98546755e-09, -1.10214235e-09, 6.43822409e-11,
2536 -1.68621326e-11]),
2537 4.5119610261406537e-16,
2538 1666,
2539 1672,
2540 209,
2541 array([ -9.13545269e-09, -1.45520541e-09, -6.47755631e-11,
2542 -1.00643523e-11]),
2543 array([ 3.20258681e-08, 3.15614974e-09, 2.75282215e-10,
2544 3.27482983e-11]))
2545 >>> assert es.result()[1] < 1e-9
2546 >>> help(es.result)
2547 Help on method result in module cma:
2548
2549 result(self) method of cma.CMAEvolutionStrategy instance
2550 return ``(xbest, f(xbest), evaluations_xbest, evaluations, iterations, pheno(xmean), effective_stds)``
2551
2552
2553 The optimization loop can also be written explicitly.
2554
2555 >>> import cma
2556 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1)
2557 >>> while not es.stop():
2558 ... X = es.ask()
2559 ... es.tell(X, [cma.fcts.elli(x) for x in X])
2560 ... es.disp()
2561 <output omitted>
2562
2563 achieving the same result as above.
2564
2565 An example with lower bounds (at zero) and handling infeasible
2566 solutions:
2567
2568 >>> import cma
2569 >>> import numpy as np
2570 >>> es = cma.CMAEvolutionStrategy(10 * [0.2], 0.5, {'bounds': [0, np.inf]})
2571 >>> while not es.stop():
2572 ... fit, X = [], []
2573 ... while len(X) < es.popsize:
2574 ... curr_fit = None
2575 ... while curr_fit in (None, np.NaN):
2576 ... x = es.ask(1)[0]
2577 ... curr_fit = cma.fcts.somenan(x, cma.fcts.elli) # might return np.NaN
2578 ... X.append(x)
2579 ... fit.append(curr_fit)
2580 ... es.tell(X, fit)
2581 ... es.logger.add()
2582 ... es.disp()
2583 <output omitted>
2584 >>>
2585 >>> assert es.result()[1] < 1e-9
2586 >>> assert es.result()[2] < 9000 # by internal termination
2587 >>> # es.logger.plot() # will plot data
2588 >>> # cma.show() # display plot window
2589
2590 An example with user-defined transformation, in this case to realize
2591 a lower bound of 2.
2592
2593 >>> es = cma.CMAEvolutionStrategy(5 * [3], 1,
2594 ... {"transformation": [lambda x: x**2+2, None]})
2595 >>> es.optimize(cma.fcts.rosen)
2596 <output omitted>
2597 >>> assert cma.fcts.rosen(es.result()[0]) < 1e-6 + 5.530760944396627e+02
2598 >>> assert es.result()[2] < 3300
2599
2600 The inverse transformation is (only) necessary if the `BoundPenalty`
2601 boundary handler is used at the same time.
2602
2603 The ``CMAEvolutionStrategy`` class also provides a default logger
2604 (cave: files are overwritten when the logger is used with the same
2605 filename prefix):
2606
2607 >>> import cma
2608 >>> es = cma.CMAEvolutionStrategy(4 * [0.2], 0.5, {'verb_disp': 0})
2609 >>> es.logger.disp_header() # to understand the print of disp
2610 Iterat Nfevals function value axis ratio maxstd minstd
2611 >>> while not es.stop():
2612 ... X = es.ask()
2613 ... es.tell(X, [cma.fcts.sphere(x) for x in X])
2614 ... es.logger.add() # log current iteration
2615 ... es.logger.disp([-1]) # display info for last iteration
2616 1 8 2.72769793021748e+03 1.0e+00 4.05e-01 3.99e-01
2617 2 16 6.58755537926063e+03 1.1e+00 4.00e-01 3.39e-01
2618 <output ommitted>
2619 193 1544 3.15195320957214e-15 1.2e+03 3.70e-08 3.45e-11
2620 >>> es.logger.disp_header()
2621 Iterat Nfevals function value axis ratio maxstd minstd
2622 >>> # es.logger.plot() # will make a plot
2623
2624 Example implementing restarts with increasing popsize (IPOP), output
2625 is not displayed:
2626
2627 >>> import cma, numpy as np
2628 >>>
2629 >>> # restart with increasing population size (IPOP)
2630 >>> bestever = cma.BestSolution()
2631 >>> for lam in 10 * 2**np.arange(8): # 10, 20, 40, 80, ..., 10 * 2**7
2632 ... es = cma.CMAEvolutionStrategy('6 - 8 * np.random.rand(9)', # 9-D
2633 ... 5, # initial std sigma0
2634 ... {'popsize': lam, # options
2635 ... 'verb_append': bestever.evalsall})
2636 ... logger = cma.CMADataLogger().register(es, append=bestever.evalsall)
2637 ... while not es.stop():
2638 ... X = es.ask() # get list of new solutions
2639 ... fit = [cma.fcts.rastrigin(x) for x in X] # evaluate each solution
2640 ... es.tell(X, fit) # besides for termination only the ranking in fit is used
2641 ...
2642 ... # display some output
2643 ... logger.add() # add a "data point" to the log, writing in files
2644 ... es.disp() # uses option verb_disp with default 100
2645 ...
2646 ... print('termination:', es.stop())
2647 ... cma.pprint(es.best.__dict__)
2648 ...
2649 ... bestever.update(es.best)
2650 ...
2651 ... # show a plot
2652 ... # logger.plot();
2653 ... if bestever.f < 1e-8: # global optimum was hit
2654 ... break
2655 <output omitted>
2656 >>> assert es.result()[1] < 1e-8
2657
2658 On the Rastrigin function, usually after five restarts the global
2659 optimum is located.
2660
2661 Using the ``multiprocessing`` module, we can evaluate the function in
2662 parallel with a simple modification of the example (however
2663 multiprocessing seems not always reliable)::
2664
2665 try:
2666 import multiprocessing as mp
2667 import cma
2668 es = cma.CMAEvolutionStrategy(22 * [0.0], 1.0, {'maxiter':10})
2669 pool = mp.Pool(es.popsize)
2670 while not es.stop():
2671 X = es.ask()
2672 f_values = pool.map_async(cma.felli, X).get()
2673 # use chunksize parameter as es.popsize/len(pool)?
2674 es.tell(X, f_values)
2675 es.disp()
2676 es.logger.add()
2677 except ImportError:
2678 pass
2679
2680 The final example shows how to resume:
2681
2682 >>> import cma, pickle
2683 >>>
2684 >>> es = cma.CMAEvolutionStrategy(12 * [0.1], # a new instance, 12-D
2685 ... 0.5) # initial std sigma0
2686 >>> es.optimize(cma.fcts.rosen, iterations=100)
2687 >>> pickle.dump(es, open('saved-cma-object.pkl', 'wb'))
2688 >>> print('saved')
2689 >>> del es # let's start fresh
2690 >>>
2691 >>> es = pickle.load(open('saved-cma-object.pkl', 'rb'))
2692 >>> print('resumed')
2693 >>> es.optimize(cma.fcts.rosen, verb_disp=200)
2694 >>> assert es.result()[2] < 15000
2695 >>> cma.pprint(es.result())
2696
2697 Details
2698 =======
2699 The following two enhancements are implemented, the latter is turned
2700 on by default only for very small population size.
2701
2702 *Active CMA* is implemented with option ``CMA_active`` and
2703 conducts an update of the covariance matrix with negative weights.
2704 The negative update is implemented, such that positive definiteness
2705 is guarantied. The update is applied after the default update and
2706 only before the covariance matrix is decomposed, which limits the
2707 additional computational burden to be at most a factor of three
2708 (typically smaller). A typical speed up factor (number of
2709 f-evaluations) is between 1.1 and two.
2710
2711 References: Jastrebski and Arnold, CEC 2006, Glasmachers et al, GECCO 2010.
2712
2713 *Selective mirroring* is implemented with option ``CMA_mirrors``
2714 in the method ``get_mirror()``. Only the method `ask_and_eval()`
2715 (used by `fmin`) will then sample selectively mirrored vectors. In
2716 selective mirroring, only the worst solutions are mirrored. With
2717 the default small number of mirrors, *pairwise selection* (where at
2718 most one of the two mirrors contribute to the update of the
2719 distribution mean) is implicitly guarantied under selective
2720 mirroring and therefore not explicitly implemented.
2721
2722 References: Brockhoff et al, PPSN 2010, Auger et al, GECCO 2011.
2723
2724 :See: `fmin()`, `OOOptimizer`, `CMAOptions`, `plot()`, `ask()`,
2725 `tell()`, `ask_and_eval()`
2726
2727 """
2728 @property
2730 """number of samples by default returned by` ask()`
2731 """
2732 return self.sp.popsize
2733
2734
2735
2736
2737
2738
2739
2740
2741 - def stop(self, check=True):
2742 """return a dictionary with the termination status.
2743 With ``check==False``, the termination conditions are not checked
2744 and the status might not reflect the current situation.
2745
2746 """
2747 if (check and self.countiter > 0 and self.opts['termination_callback'] and
2748 self.opts['termination_callback'] != str(self.opts['termination_callback'])):
2749 self.callbackstop = self.opts['termination_callback'](self)
2750
2751 return self._stopdict(self, check)
2752
2754 raise NotImplementedError("")
2755
2756 - def __init__(self, x0, sigma0, inopts={}):
2757 """see class `CMAEvolutionStrategy`
2758
2759 """
2760 if isinstance(x0, CMAEvolutionStrategy):
2761 self.copy_constructor(x0)
2762 return
2763 self.inputargs = dict(locals())
2764 del self.inputargs['self']
2765 self.inopts = inopts
2766 opts = CMAOptions(inopts).complement()
2767 global_verbosity = opts.eval('verbose')
2768 if global_verbosity < -8:
2769 opts['verb_disp'] = 0
2770 opts['verb_log'] = 0
2771 opts['verb_plot'] = 0
2772
2773 if 'noise_handling' in opts and opts.eval('noise_handling'):
2774 raise ValueError('noise_handling not available with class CMAEvolutionStrategy, use function fmin')
2775 if 'restarts' in opts and opts.eval('restarts'):
2776 raise ValueError('restarts not available with class CMAEvolutionStrategy, use function fmin')
2777
2778 self._set_x0(x0)
2779 self.N_pheno = len(self.x0)
2780
2781 self.sigma0 = sigma0
2782 if isinstance(sigma0, basestring):
2783
2784 self.sigma0 = eval(sigma0)
2785 if np.size(self.sigma0) != 1 or np.shape(self.sigma0):
2786 raise _Error('input argument sigma0 must be (or evaluate to) a scalar')
2787 self.sigma = self.sigma0
2788
2789
2790 N = self.N_pheno
2791 assert isinstance(opts['fixed_variables'], (basestring, dict)) \
2792 or opts['fixed_variables'] is None
2793
2794 if isinstance(opts['fixed_variables'], dict):
2795 N = self.N_pheno - len(opts['fixed_variables'])
2796 opts.evalall(locals())
2797 self.opts = opts
2798
2799 self.randn = opts['randn']
2800 self.gp = GenoPheno(self.N_pheno, opts['scaling_of_variables'], opts['typical_x'],
2801 opts['fixed_variables'], opts['transformation'])
2802 self.boundary_handler = opts.eval('boundary_handling')(opts.eval('bounds'))
2803 if not self.boundary_handler.has_bounds():
2804 self.boundary_handler = BoundNone()
2805 elif not self.boundary_handler.is_in_bounds(self.x0):
2806 if opts['verbose'] >= 0:
2807 _print_warning('initial solution is out of the domain boundaries:')
2808 print(' x0 = ' + str(self.gp.pheno(self.x0)))
2809 print(' ldom = ' + str(self.boundary_handler.bounds[0]))
2810 print(' udom = ' + str(self.boundary_handler.bounds[1]))
2811
2812
2813 tf_geno_backup = self.gp.tf_geno
2814 if self.gp.tf_pheno and self.gp.tf_geno is None:
2815 self.gp.tf_geno = lambda x: x
2816 _print_warning("""
2817 computed initial point is likely to be wrong, because
2818 no inverse was found of user provided phenotype
2819 transformation""")
2820 self.mean = self.gp.geno(self.x0,
2821 from_bounds=self.boundary_handler.inverse,
2822 copy_always=True)
2823 self.gp.tf_geno = tf_geno_backup
2824
2825
2826 self.N = len(self.mean)
2827 assert N == self.N
2828 self.fmean = np.NaN
2829 self.fmean_noise_free = 0.
2830
2831 self.adapt_sigma = opts['AdaptSigma']
2832 if self.adapt_sigma is False:
2833 self.adapt_sigma = CMAAdaptSigmaNone
2834 self.adapt_sigma = self.adapt_sigma()
2835
2836 self.sp = _CMAParameters(N, opts)
2837 self.sp0 = self.sp
2838
2839
2840 self.countiter = 0
2841 self.countevals = max((0, opts['verb_append'])) \
2842 if not isinstance(opts['verb_append'], bool) else 0
2843 self.pc = np.zeros(N)
2844 self.pc_neg = np.zeros(N)
2845 def eval_scaling_vector(in_):
2846 res = 1
2847 if np.all(in_):
2848 res = array(in_, dtype=float)
2849 if np.size(res) not in (1, N):
2850 raise ValueError("""CMA_stds option must have dimension %d
2851 instead of %d""" %
2852 (str(N), np.size(res)))
2853 return res
2854 self.sigma_vec = eval_scaling_vector(self.opts['CMA_stds'])
2855 if isfinite(self.opts['CMA_dampsvec_fac']):
2856 self.sigma_vec *= np.ones(N)
2857 self.sigma_vec0 = self.sigma_vec if isscalar(self.sigma_vec) \
2858 else self.sigma_vec.copy()
2859 stds = eval_scaling_vector(self.opts['CMA_teststds'])
2860 if self.opts['CMA_diagonal']:
2861
2862 self.B = array(1)
2863 self.C = stds**2 * np.ones(N)
2864 self.dC = self.C
2865 else:
2866 self.B = np.eye(N)
2867
2868
2869 self.C = np.diag(stds**2 * exp((1e-4 / N) * np.arange(N)))
2870 self.dC = np.diag(self.C).copy()
2871 self._Yneg = np.zeros((N, N))
2872
2873 self.D = self.dC**0.5
2874
2875
2876 relative_stds = ((self.gp.pheno(self.mean + self.sigma * self.sigma_vec * self.D)
2877 - self.gp.pheno(self.mean - self.sigma * self.sigma_vec * self.D)) / 2.0
2878 / (self.boundary_handler.get_bounds('upper', self.N_pheno)
2879 - self.boundary_handler.get_bounds('lower', self.N_pheno)))
2880 if np.any(relative_stds > 1):
2881 raise ValueError('initial standard deviations larger than the bounded domain size in variables '
2882 + str(np.where(relative_stds > 1)[0]))
2883 self._flgtelldone = True
2884 self.itereigenupdated = self.countiter
2885 self.count_eigen = 0
2886 self.noiseS = 0
2887 self.hsiglist = []
2888
2889 if not opts['seed']:
2890 np.random.seed()
2891 six_decimals = (time.time() - 1e6 * (time.time() // 1e6))
2892 opts['seed'] = 1e5 * np.random.rand() + six_decimals + 1e5 * (time.time() % 1)
2893 opts['seed'] = int(opts['seed'])
2894 np.random.seed(opts['seed'])
2895
2896 self.sent_solutions = CMASolutionDict()
2897 self.archive = CMASolutionDict()
2898 self.best = BestSolution()
2899
2900 self.const = _BlancClass()
2901 self.const.chiN = N**0.5 * (1 - 1. / (4.*N) + 1. / (21.*N**2))
2902
2903 self.logger = CMADataLogger(opts['verb_filenameprefix'], modulo=opts['verb_log']).register(self)
2904
2905
2906 self._stopdict = _CMAStopDict()
2907 self.callbackstop = 0
2908
2909 self.fit = _BlancClass()
2910 self.fit.fit = []
2911 self.fit.hist = []
2912 self.fit.histbest = []
2913 self.fit.histmedian = []
2914
2915 self.more_to_write = []
2916
2917
2918 if opts['verb_disp'] > 0 and opts['verbose'] >= 0:
2919 sweighted = '_w' if self.sp.mu > 1 else ''
2920 smirr = 'mirr%d' % (self.sp.lam_mirr) if self.sp.lam_mirr else ''
2921 print('(%d' % (self.sp.mu) + sweighted + ',%d' % (self.sp.popsize) + smirr +
2922 ')-' + ('a' if opts['CMA_active'] else '') + 'CMA-ES' +
2923 ' (mu_w=%2.1f,w_1=%d%%)' % (self.sp.mueff, int(100 * self.sp.weights[0])) +
2924 ' in dimension %d (seed=%d, %s)' % (N, opts['seed'], time.asctime()))
2925 if opts['CMA_diagonal'] and self.sp.CMA_on:
2926 s = ''
2927 if opts['CMA_diagonal'] is not True:
2928 s = ' for '
2929 if opts['CMA_diagonal'] < np.inf:
2930 s += str(int(opts['CMA_diagonal']))
2931 else:
2932 s += str(np.floor(opts['CMA_diagonal']))
2933 s += ' iterations'
2934 s += ' (1/ccov=' + str(round(1. / (self.sp.c1 + self.sp.cmu))) + ')'
2935 print(' Covariance matrix is diagonal' + s)
2936
2938 if x0 == str(x0):
2939 x0 = eval(x0)
2940 self.x0 = array(x0)
2941 if self.x0.ndim == 2:
2942 if self.opts.eval('verbose') >= 0:
2943 _print_warning('input x0 should be a list or 1-D array, trying to flatten ' +
2944 str(self.x0.shape) + '-array')
2945 if self.x0.shape[0] == 1:
2946 self.x0 = self.x0[0]
2947 elif self.x0.shape[1] == 1:
2948 self.x0 = array([x[0] for x in self.x0])
2949 if self.x0.ndim != 1:
2950 raise _Error('x0 must be 1-D array')
2951 if len(self.x0) <= 1:
2952 raise _Error('optimization in 1-D is not supported (code was never tested)')
2953 self.x0.resize(self.x0.shape[0])
2954
2955
2956
2957 - def ask(self, number=None, xmean=None, sigma_fac=1,
2958 gradf=None, args=()):
2959 """get new candidate solutions, sampled from a multi-variate
2960 normal distribution and transformed to f-representation
2961 (phenotype) to be evaluated.
2962
2963 Arguments
2964 ---------
2965 `number`
2966 number of returned solutions, by default the
2967 population size ``popsize`` (AKA ``lambda``).
2968 `xmean`
2969 distribution mean, phenotyp?
2970 `sigma_fac`
2971 multiplier for internal sample width (standard
2972 deviation)
2973 `gradf`
2974 gradient, ``len(gradf(x)) == len(x)``, if
2975 ``gradf is not None`` the third solution in the
2976 returned list is "sampled" in supposedly Newton
2977 direction ``dot(C, gradf(xmean, *args))``.
2978 `args`
2979 additional arguments passed to gradf
2980
2981 Return
2982 ------
2983 A list of N-dimensional candidate solutions to be evaluated
2984
2985 Example
2986 -------
2987 >>> import cma
2988 >>> es = cma.CMAEvolutionStrategy([0,0,0,0], 0.3)
2989 >>> while not es.stop() and es.best.f > 1e-6: # my_desired_target_f_value
2990 ... X = es.ask() # get list of new solutions
2991 ... fit = [cma.fcts.rosen(x) for x in X] # call function rosen with each solution
2992 ... es.tell(X, fit) # feed values
2993
2994 :See: `ask_and_eval`, `ask_geno`, `tell`
2995
2996 """
2997 pop_geno = self.ask_geno(number, xmean, sigma_fac)
2998
2999
3000
3001
3002
3003
3004 pop_pheno = [self.gp.pheno(x, copy=True, into_bounds=self.boundary_handler.repair) for x in pop_geno]
3005
3006 if gradf is not None:
3007
3008 if not self.gp.islinear:
3009 _print_warning("""
3010 using the gradient (option ``gradf``) with a non-linear
3011 coordinate-wise transformation (option ``transformation``)
3012 has never been tested.""")
3013
3014 def grad_numerical_of_coordinate_map(x, map, epsilon=None):
3015 """map is a coordinate-wise independent map, return
3016 the estimated diagonal of the Jacobian.
3017 """
3018 eps = 1e-8 * (1 + abs(x)) if epsilon is None else epsilon
3019 return (map(x + eps) - map(x - eps)) / (2 * eps)
3020 def grad_numerical_sym(x, func, epsilon=None):
3021 """return symmetric numerical gradient of func : R^n -> R.
3022 """
3023 eps = 1e-8 * (1 + abs(x)) if epsilon is None else epsilon
3024 grad = np.zeros(len(x))
3025 ei = np.zeros(len(x))
3026 for i in rglen(x):
3027 ei[i] = eps[i]
3028 grad[i] = (func(x + ei) - func(x - ei)) / (2*eps[i])
3029 ei[i] = 0
3030 return grad
3031 try:
3032 if self.last_iteration_with_gradient == self.countiter:
3033 _print_warning('gradient is used several times in ' +
3034 'this iteration', iteration=self.countiter)
3035 self.last_iteration_with_gradient = self.countiter
3036 except AttributeError:
3037 pass
3038 index_for_gradient = min((2, len(pop_pheno)-1))
3039 xmean = self.mean if xmean is None else xmean
3040 xpheno = self.gp.pheno(xmean, copy=True,
3041 into_bounds=self.boundary_handler.repair)
3042 grad_at_mean = gradf(xpheno, *args)
3043
3044 if not self.gp.isidentity or (self.boundary_handler is not None
3045 and self.boundary_handler.has_bounds()):
3046 boundary_repair = None
3047 gradpen = 0
3048 if isinstance(self.boundary_handler, BoundTransform):
3049 boundary_repair = self.boundary_handler.repair
3050 elif isinstance(self.boundary_handler, BoundPenalty):
3051 fpenalty = lambda x: self.boundary_handler.__call__(
3052 x, SolutionDict({tuple(x): {'geno': x}}), self.gp)
3053 gradpen = grad_numerical_sym(
3054 xmean, fpenalty)
3055 elif self.boundary_handler is None or \
3056 isinstance(self.boundary_handler, BoundNone):
3057 pass
3058 else:
3059 raise NotImplementedError(
3060 "unknown boundary handling method" +
3061 str(self.boundary_handler) +
3062 " when using gradf")
3063 gradgp = grad_numerical_of_coordinate_map(
3064 xmean,
3065 lambda x: self.gp.pheno(x, copy=True,
3066 into_bounds=boundary_repair))
3067 grad_at_mean = grad_at_mean * gradgp + gradpen
3068
3069
3070 if len(grad_at_mean) != self.N and self.opts['fixed_variables']:
3071 NotImplementedError("""
3072 gradient with fixed variables is not yet implemented""")
3073 v = self.D * dot(self.B.T, self.sigma_vec * grad_at_mean)
3074
3075
3076 q = sum(v**2)
3077 if q:
3078
3079 pop_geno[index_for_gradient] = xmean - self.sigma \
3080 * (self.N / q)**0.5 \
3081 * (self.sigma_vec * dot(self.B, self.D * v))
3082 else:
3083 pop_geno[index_for_gradient] = xmean
3084 _print_warning('gradient zero observed',
3085 iteration=self.countiter)
3086
3087 pop_pheno[index_for_gradient] = self.gp.pheno(
3088 pop_geno[index_for_gradient], copy=True,
3089 into_bounds=self.boundary_handler.repair)
3090
3091
3092 for i in rglen((pop_geno)):
3093 self.sent_solutions.insert(pop_pheno[i], geno=pop_geno[i], iteration=self.countiter)
3094 return pop_pheno
3095
3096
3097
3098 - def ask_geno(self, number=None, xmean=None, sigma_fac=1):
3099 """get new candidate solutions in genotyp, sampled from a
3100 multi-variate normal distribution.
3101
3102 Arguments are
3103 `number`
3104 number of returned solutions, by default the
3105 population size `popsize` (AKA lambda).
3106 `xmean`
3107 distribution mean
3108 `sigma_fac`
3109 multiplier for internal sample width (standard
3110 deviation)
3111
3112 `ask_geno` returns a list of N-dimensional candidate solutions
3113 in genotyp representation and is called by `ask`.
3114
3115 Details: updates the sample distribution and might change
3116 the geno-pheno transformation during this update.
3117
3118 :See: `ask`, `ask_and_eval`
3119
3120 """
3121
3122 if number is None or number < 1:
3123 number = self.sp.popsize
3124
3125
3126 if self.sp.CMA_on and (
3127 (self.opts['updatecovwait'] is None and
3128 self.countiter >=
3129 self.itereigenupdated + 1. / (self.sp.c1 + self.sp.cmu) / self.N / 10
3130 ) or
3131 (self.opts['updatecovwait'] is not None and
3132 self.countiter > self.itereigenupdated + self.opts['updatecovwait']
3133 ) or
3134 (self.sp.neg.cmuexp * (self.countiter - self.itereigenupdated) > 0.5
3135 )
3136 ):
3137 self.updateBD()
3138 if xmean is None:
3139 xmean = self.mean
3140 else:
3141 try:
3142 xmean = self.archive[xmean]['geno']
3143
3144 except KeyError:
3145 try:
3146 xmean = self.sent_solutions[xmean]['geno']
3147
3148 except KeyError:
3149 pass
3150
3151 if self.countiter == 0:
3152 self.tic = time.clock()
3153 self.elapsed_time = ElapsedTime()
3154
3155 sigma = sigma_fac * self.sigma
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169 if self._flgtelldone:
3170 self._flgtelldone = False
3171 self.ary = []
3172
3173
3174 arinj = []
3175 if hasattr(self, 'pop_injection_directions'):
3176 if self.countiter < 4 and \
3177 len(self.pop_injection_directions) > self.popsize - 2:
3178 _print_warning(' %d special injected samples with popsize %d, '
3179 % (len(self.pop_injection_directions), self.popsize)
3180 + "popsize %d will be used" % (len(self.pop_injection_directions) + 2)
3181 + (" and the warning is suppressed in the following" if self.countiter == 3 else ""))
3182 while self.pop_injection_directions:
3183 y = self.pop_injection_directions.pop(0)
3184 if self.opts['CMA_sample_on_sphere_surface']:
3185 y *= (self.N**0.5 if self.opts['CSA_squared'] else
3186 self.const.chiN) / self.mahalanobis_norm(y)
3187 arinj.append(y)
3188 else:
3189 y *= self.random_rescaling_factor_to_mahalanobis_size(y) / self.sigma
3190 arinj.append(y)
3191
3192
3193 arz = self.randn((max([1, (number - len(arinj))]), self.N))
3194 if self.opts['CMA_sample_on_sphere_surface']:
3195 for i in rglen((arz)):
3196 ss = sum(arz[i]**2)
3197 if 1 < 3 or ss > self.N + 10.1:
3198 arz[i] *= (self.N**0.5 if self.opts['CSA_squared']
3199 else self.const.chiN) / ss**0.5
3200
3201
3202
3203
3204
3205
3206
3207
3208 if len(arz):
3209
3210 if new_injections and self.sp.lam_mirr and self.opts['CMA_mirrormethod'] == 0:
3211 for i in xrange(self.sp.lam_mirr):
3212 if 2 * (i + 1) > len(arz):
3213 if self.countiter < 4:
3214 _print_warning("fewer mirrors generated than given in parameter setting (%d<%d)"
3215 % (i, self.sp.lam_mirr))
3216 break
3217 arz[-1 - 2 * i] = -arz[-2 - 2 * i]
3218 ary = self.sigma_vec * np.dot(self.B, (self.D * arz).T).T
3219 if len(arinj):
3220 ary = np.vstack((arinj, ary))
3221 else:
3222 ary = array(arinj)
3223
3224
3225 if not new_injections and number > 2 and self.countiter > 2:
3226 if (isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) or
3227 self.opts['mean_shift_line_samples'] or
3228 self.opts['pc_line_samples']):
3229 ys = []
3230 if self.opts['pc_line_samples']:
3231 ys.append(self.pc[:])
3232 if self.opts['mean_shift_line_samples']:
3233 ys.append(self.mean - self.mean_old)
3234 if not len(ys):
3235 ys.append(self.mean - self.mean_old)
3236
3237 for i, y in enumerate(ys):
3238 if len(arz) > 2 * i + 1:
3239 assert y is not self.pc
3240
3241 y *= self.random_rescaling_factor_to_mahalanobis_size(y)
3242
3243 ary[2*i] = y / self.sigma
3244 ary[2*i + 1] = y / -self.sigma
3245 else:
3246 _print_warning('line samples omitted due to small popsize',
3247 method_name='ask_geno', iteration=self.countiter)
3248
3249
3250 pop = xmean + sigma * ary
3251 self.evaluations_per_f_value = 1
3252 self.ary = ary
3253 return pop
3254
3256 """change `x` like for injection, all on genotypic level"""
3257 x -= self.mean
3258 if any(x):
3259 x *= sum(self.randn(len(x))**2)**0.5 / self.mahalanobis_norm(x)
3260 x += self.mean
3261 return x
3263 """``self.mean + self.random_rescaling_factor_to_mahalanobis_size(y)``
3264 is guarantied to appear like from the sample distribution.
3265 """
3266 if len(y) != self.N:
3267 raise ValueError('len(y)=%d != %d=dimension' % (len(y), self.N))
3268 if not any(y):
3269 _print_warning("input was all-zeros, which is probably a bug",
3270 "random_rescaling_factor_to_mahalanobis_size",
3271 iteration=self.countiter)
3272 return 1.0
3273 return sum(self.randn(len(y))**2)**0.5 / self.mahalanobis_norm(y)
3274
3275
3277 """return ``pheno(self.mean - (geno(x) - self.mean))``.
3278
3279 >>> import cma
3280 >>> es = cma.CMAEvolutionStrategy(cma.np.random.randn(3), 1)
3281 >>> x = cma.np.random.randn(3)
3282 >>> assert cma.Mh.vequals_approximately(es.mean - (x - es.mean), es.get_mirror(x, preserve_length=True))
3283 >>> x = es.ask(1)[0]
3284 >>> vals = (es.get_mirror(x) - es.mean) / (x - es.mean)
3285 >>> assert cma.Mh.equals_approximately(sum(vals), len(vals) * vals[0])
3286
3287 TODO: this implementation is yet experimental.
3288
3289 TODO: this implementation includes geno-pheno transformation,
3290 however in general GP-transformation should be separated from
3291 specific code.
3292
3293 Selectively mirrored sampling improves to a moderate extend but
3294 overadditively with active CMA for quite understandable reasons.
3295
3296 Optimal number of mirrors are suprisingly small: 1,2,3 for
3297 maxlam=7,13,20 where 3,6,10 are the respective maximal possible
3298 mirrors that must be clearly suboptimal.
3299
3300 """
3301 try:
3302 dx = self.sent_solutions[x]['geno'] - self.mean
3303 except:
3304 dx = self.gp.geno(x, from_bounds=self.boundary_handler.inverse,
3305 copy_if_changed=True) - self.mean
3306
3307 if not preserve_length:
3308
3309 dx *= self.random_rescaling_factor_to_mahalanobis_size(dx)
3310 x = self.mean - dx
3311 y = self.gp.pheno(x, into_bounds=self.boundary_handler.repair)
3312
3313 self.sent_solutions.insert(y, geno=x, iteration=self.countiter)
3314 return y
3315
3317 """obsolete and subject to removal (TODO),
3318 return modified f-values such that for each mirror one becomes worst.
3319
3320 This function is useless when selective mirroring is applied with no
3321 more than (lambda-mu)/2 solutions.
3322
3323 Mirrors are leading and trailing values in ``f_values``.
3324
3325 """
3326 assert len(f_values) >= 2 * len(idx)
3327 m = np.max(np.abs(f_values))
3328 for i in len(idx):
3329 if f_values[idx[i]] > f_values[-1 - i]:
3330 f_values[idx[i]] += m
3331 else:
3332 f_values[-1 - i] += m
3333 return f_values
3334
3336 """obsolete and subject to removal (TODO),
3337 return indices for negative ("active") update of the covariance matrix
3338 assuming that ``f_values[idx1[i]]`` and ``f_values[-1-i]`` are
3339 the corresponding mirrored values
3340
3341 computes the index of the worse solution sorted by the f-value of the
3342 better solution.
3343
3344 TODO: when the actual mirror was rejected, it is better
3345 to return idx1 instead of idx2.
3346
3347 Remark: this function might not be necessary at all: if the worst solution
3348 is the best mirrored, the covariance matrix updates cancel (cave: weights
3349 and learning rates), which seems what is desirable. If the mirror is bad,
3350 as strong negative update is made, again what is desirable.
3351 And the fitness--step-length correlation is in part addressed by
3352 using flat weights.
3353
3354 """
3355 idx2 = np.arange(len(f_values) - 1, len(f_values) - 1 - len(idx1), -1)
3356 f = []
3357 for i in rglen((idx1)):
3358 f.append(min((f_values[idx1[i]], f_values[idx2[i]])))
3359
3360 return idx2[np.argsort(f)][-1::-1]
3361
3363 """evaluate the distribution mean, this is not (yet) effective
3364 in terms of termination or display"""
3365 self.fmean = func(self.mean, *args)
3366 return self.fmean
3367
3368
3369
3370
3371 - def ask_and_eval(self, func, args=(), gradf=None, number=None, xmean=None, sigma_fac=1,
3372 evaluations=1, aggregation=np.median, kappa=1):
3373 """samples `number` solutions and evaluates them on `func`, where
3374 each solution `s` is resampled until ``self.is_feasible(s, func(s)) is True``.
3375
3376 Arguments
3377 ---------
3378 `func`
3379 objective function, ``func(x)`` returns a scalar
3380 `args`
3381 additional parameters for `func`
3382 `gradf`
3383 gradient of objective function, ``g = gradf(x, *args)``
3384 must satisfy ``len(g) == len(x)``
3385 `number`
3386 number of solutions to be sampled, by default
3387 population size ``popsize`` (AKA lambda)
3388 `xmean`
3389 mean for sampling the solutions, by default ``self.mean``.
3390 `sigma_fac`
3391 multiplier for sampling width, standard deviation, for example
3392 to get a small perturbation of solution `xmean`
3393 `evaluations`
3394 number of evaluations for each sampled solution
3395 `aggregation`
3396 function that aggregates `evaluations` values to
3397 as single value.
3398 `kappa`
3399 multiplier used for the evaluation of the solutions, in
3400 that ``func(m + kappa*(x - m))`` is the f-value for x.
3401
3402 Return
3403 ------
3404 ``(X, fit)``, where
3405 X -- list of solutions
3406 fit -- list of respective function values
3407
3408 Details
3409 -------
3410 While ``not self.is_feasible(x, func(x))``new solutions are sampled. By
3411 default ``self.is_feasible == cma.feasible == lambda x, f: f not in (None, np.NaN)``.
3412 The argument to `func` can be freely modified within `func`.
3413
3414 Depending on the ``CMA_mirrors`` option, some solutions are not sampled
3415 independently but as mirrors of other bad solutions. This is a simple
3416 derandomization that can save 10-30% of the evaluations in particular
3417 with small populations, for example on the cigar function.
3418
3419 Example
3420 -------
3421 >>> import cma
3422 >>> x0, sigma0 = 8*[10], 1 # 8-D
3423 >>> es = cma.CMAEvolutionStrategy(x0, sigma0)
3424 >>> while not es.stop():
3425 ... X, fit = es.ask_and_eval(cma.fcts.elli) # handles NaN with resampling
3426 ... es.tell(X, fit) # pass on fitness values
3427 ... es.disp(20) # print every 20-th iteration
3428 >>> print('terminated on ' + str(es.stop()))
3429 <output omitted>
3430
3431 A single iteration step can be expressed in one line, such that
3432 an entire optimization after initialization becomes
3433 ::
3434
3435 while not es.stop():
3436 es.tell(*es.ask_and_eval(cma.fcts.elli))
3437
3438 """
3439
3440 popsize = self.sp.popsize
3441 if number is not None:
3442 popsize = number
3443
3444 selective_mirroring = self.opts['CMA_mirrormethod'] > 0
3445 nmirrors = self.sp.lam_mirr
3446 if popsize != self.sp.popsize:
3447 nmirrors = Mh.sround(popsize * self.sp.lam_mirr / self.sp.popsize)
3448
3449 assert new_injections or self.opts['CMA_mirrormethod'] < 2
3450 if new_injections and self.opts['CMA_mirrormethod'] != 1:
3451 nmirrors = 0
3452 assert nmirrors <= popsize // 2
3453 self.mirrors_idx = np.arange(nmirrors)
3454 self.mirrors_rejected_idx = []
3455 is_feasible = self.opts['is_feasible']
3456
3457
3458 fit = []
3459 X_first = self.ask(popsize, xmean=xmean, gradf=gradf, args=args)
3460 if xmean is None:
3461 xmean = self.mean
3462 X = []
3463 for k in xrange(int(popsize)):
3464 x, f = X_first.pop(0), None
3465 rejected = -1
3466 while rejected < 0 or not is_feasible(x, f):
3467 rejected += 1
3468 if rejected:
3469 x = self.ask(1, xmean, sigma_fac)[0]
3470 elif k >= popsize - nmirrors:
3471 if k == popsize - nmirrors and selective_mirroring:
3472 self.mirrors_idx = np.argsort(fit)[-1:-1 - nmirrors:-1]
3473 x = self.get_mirror(X[self.mirrors_idx[popsize - 1 - k]])
3474 if rejected == 1 and k >= popsize - nmirrors:
3475 self.mirrors_rejected_idx.append(k)
3476
3477
3478 length_normalizer = 1
3479
3480 f = func(x, *args) if kappa == 1 else \
3481 func(xmean + kappa * length_normalizer * (x - xmean),
3482 *args)
3483 if is_feasible(x, f) and evaluations > 1:
3484 f = aggregation([f] + [(func(x, *args) if kappa == 1 else
3485 func(xmean + kappa * length_normalizer * (x - xmean), *args))
3486 for _i in xrange(int(evaluations - 1))])
3487 if rejected + 1 % 1000 == 0:
3488 print(' %d solutions rejected (f-value NaN or None) at iteration %d' %
3489 (rejected, self.countiter))
3490 fit.append(f)
3491 X.append(x)
3492 self.evaluations_per_f_value = int(evaluations)
3493 return X, fit
3494
3496 """provide genotypic directions for TPA and selective mirroring,
3497 with no specific length normalization, to be used in the
3498 coming iteration.
3499
3500 Details:
3501 This method is called in the end of `tell`. The result is
3502 assigned to ``self.pop_injection_directions`` and used in
3503 `ask_geno`.
3504
3505 TODO: should be rather appended?
3506
3507 """
3508
3509 if hasattr(self, 'pop_injection_directions') and self.pop_injection_directions:
3510 ValueError("Looks like a bug in calling order/logics")
3511 ary = []
3512 if (isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) or
3513 self.opts['mean_shift_line_samples']):
3514 ary.append(self.mean - self.mean_old)
3515 ary.append(self.mean_old - self.mean)
3516 if ary[-1][0] == 0.0:
3517 _print_warning('zero mean shift encountered which ',
3518 'prepare_injection_directions',
3519 'CMAEvolutionStrategy', self.countiter)
3520 if self.opts['pc_line_samples']:
3521 ary.append(self.pc.copy())
3522 if self.sp.lam_mirr and self.opts['CMA_mirrormethod'] == 2:
3523 if self.pop_sorted is None:
3524 _print_warning('pop_sorted attribute not found, mirrors obmitted',
3525 'prepare_injection_directions',
3526 iteration=self.countiter)
3527 else:
3528 ary += self.get_selective_mirrors()
3529 self.pop_injection_directions = ary
3530 return ary
3531
3533 """get mirror genotypic directions of the `number` worst
3534 solution, based on ``pop_sorted`` attribute (from last
3535 iteration).
3536
3537 Details:
3538 Takes the last ``number=sp.lam_mirr`` entries in
3539 ``pop_sorted=self.pop_sorted`` as solutions to be mirrored.
3540
3541 """
3542 if pop_sorted is None:
3543 if hasattr(self, 'pop_sorted'):
3544 pop_sorted = self.pop_sorted
3545 else:
3546 return None
3547 if number is None:
3548 number = self.sp.lam_mirr
3549 res = []
3550 for i in xrange(1, number + 1):
3551 res.append(self.mean_old - pop_sorted[-i])
3552 return res
3553
3554
3555 - def tell(self, solutions, function_values, check_points=None,
3556 copy=False):
3557 """pass objective function values to prepare for next
3558 iteration. This core procedure of the CMA-ES algorithm updates
3559 all state variables, in particular the two evolution paths, the
3560 distribution mean, the covariance matrix and a step-size.
3561
3562 Arguments
3563 ---------
3564 `solutions`
3565 list or array of candidate solution points (of
3566 type `numpy.ndarray`), most presumably before
3567 delivered by method `ask()` or `ask_and_eval()`.
3568 `function_values`
3569 list or array of objective function values
3570 corresponding to the respective points. Beside for termination
3571 decisions, only the ranking of values in `function_values`
3572 is used.
3573 `check_points`
3574 If ``check_points is None``, only solutions that are not generated
3575 by `ask()` are possibly clipped (recommended). ``False`` does not clip
3576 any solution (not recommended).
3577 If ``True``, clips solutions that realize long steps (i.e. also
3578 those that are unlikely to be generated with `ask()`). `check_points`
3579 can be a list of indices to be checked in solutions.
3580 `copy`
3581 ``solutions`` can be modified in this routine, if ``copy is False``
3582
3583 Details
3584 -------
3585 `tell()` updates the parameters of the multivariate
3586 normal search distribution, namely covariance matrix and
3587 step-size and updates also the attributes ``countiter`` and
3588 ``countevals``. To check the points for consistency is quadratic
3589 in the dimension (like sampling points).
3590
3591 Bugs
3592 ----
3593 The effect of changing the solutions delivered by `ask()`
3594 depends on whether boundary handling is applied. With boundary
3595 handling, modifications are disregarded. This is necessary to
3596 apply the default boundary handling that uses unrepaired
3597 solutions but might change in future.
3598
3599 Example
3600 -------
3601 ::
3602
3603 import cma
3604 func = cma.fcts.elli # choose objective function
3605 es = cma.CMAEvolutionStrategy(cma.np.random.rand(10), 1)
3606 while not es.stop():
3607 X = es.ask()
3608 es.tell(X, [func(x) for x in X])
3609 es.result() # where the result can be found
3610
3611 :See: class `CMAEvolutionStrategy`, `ask()`, `ask_and_eval()`, `fmin()`
3612
3613 """
3614 if self._flgtelldone:
3615 raise _Error('tell should only be called once per iteration')
3616
3617 lam = len(solutions)
3618 if lam != array(function_values).shape[0]:
3619 raise _Error('for each candidate solution '
3620 + 'a function value must be provided')
3621 if lam + self.sp.lam_mirr < 3:
3622 raise _Error('population size ' + str(lam) + ' is too small when option CMA_mirrors * popsize < 0.5')
3623
3624 if not isscalar(function_values[0]):
3625 if isscalar(function_values[0][0]):
3626 if self.countiter <= 1:
3627 _print_warning('function values are not a list of scalars (further warnings are suppressed)')
3628 function_values = [val[0] for val in function_values]
3629 else:
3630 raise _Error('objective function values must be a list of scalars')
3631
3632
3633
3634 N = self.N
3635 sp = self.sp
3636 if lam < sp.mu:
3637 raise _Error('not enough solutions passed to function tell (mu>lambda)')
3638
3639 self.countiter += 1
3640 self.countevals += sp.popsize * self.evaluations_per_f_value
3641 self.best.update(solutions, self.sent_solutions, function_values, self.countevals)
3642
3643 flg_diagonal = self.opts['CMA_diagonal'] is True \
3644 or self.countiter <= self.opts['CMA_diagonal']
3645 if not flg_diagonal and len(self.C.shape) == 1:
3646
3647 self.C = np.diag(self.C)
3648 if 1 < 3:
3649 self.B = np.eye(N)
3650 idx = np.argsort(self.D)
3651 self.D = self.D[idx]
3652 self.B = self.B[:, idx]
3653 self._Yneg = np.zeros((N, N))
3654
3655
3656 fit = self.fit
3657
3658
3659 fit.bndpen = self.boundary_handler.update(function_values, self)(solutions, self.sent_solutions, self.gp)
3660
3661
3662 fit.idx = np.argsort(array(fit.bndpen) + array(function_values))
3663 fit.fit = array(function_values, copy=False)[fit.idx]
3664
3665
3666
3667
3668
3669
3670
3671 fit.hist.insert(0, fit.fit[0])
3672
3673 if ((self.countiter % 5) == 0):
3674 fit.histbest.insert(0, fit.fit[0])
3675 fit.histmedian.insert(0, np.median(fit.fit) if len(fit.fit) < 21
3676 else fit.fit[self.popsize // 2])
3677 if len(fit.histbest) > 2e4:
3678 fit.histbest.pop()
3679 fit.histmedian.pop()
3680 if len(fit.hist) > 10 + 30 * N / sp.popsize:
3681 fit.hist.pop()
3682
3683
3684
3685 pop = self.pop_sorted = []
3686 for k, s in enumerate(solutions):
3687 if 1 < 3:
3688 pop += [self.gp.geno(s,
3689 from_bounds=self.boundary_handler.inverse,
3690 repair=(self.repair_genotype if check_points not in (False, 0, [], ()) else None),
3691 archive=self.sent_solutions)]
3692 try:
3693 self.archive.insert(s, value=self.sent_solutions.pop(s), fitness=function_values[k])
3694
3695 except KeyError:
3696 pass
3697
3698 if isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) and self.countiter > 3 and not (self.countiter % 3):
3699 dm = self.mean[0] - self.mean_old[0]
3700 dx0 = pop[0][0] - self.mean_old[0]
3701 dx1 = pop[1][0] - self.mean_old[0]
3702 for i in np.random.randint(1, self.N, 1):
3703 try:
3704 if not Mh.equals_approximately(
3705 (self.mean[i] - self.mean_old[i])
3706 / (pop[0][i] - self.mean_old[i]),
3707 dm / dx0, 1e-8) or \
3708 not Mh.equals_approximately(
3709 (self.mean[i] - self.mean_old[i])
3710 / (pop[1][i] - self.mean_old[i]),
3711 dm / dx1, 1e-8):
3712 _print_warning('TPA error with mirrored samples', 'tell',
3713 'CMAEvolutionStrategy', self.countiter)
3714 except ZeroDivisionError:
3715 _print_warning('zero division encountered in TPA check\n which should be very rare and is likely a bug',
3716 'tell', 'CMAEvolutionStrategy', self.countiter)
3717
3718 try:
3719 moldold = self.mean_old
3720 except:
3721 pass
3722 self.mean_old = self.mean
3723 mold = self.mean_old
3724
3725
3726
3727
3728 if check_points not in (None, False, 0, [], ()):
3729 try:
3730 if len(check_points):
3731 idx = check_points
3732 except:
3733 idx = xrange(sp.popsize)
3734
3735 for k in idx:
3736 self.repair_genotype(pop[k])
3737
3738
3739 pop = array(pop, copy=False)
3740
3741
3742 pop = pop[fit.idx]
3743
3744
3745 if self.opts['CMA_elitist'] and self.best.f < fit.fit[0]:
3746 if self.best.x_geno is not None:
3747 xp = [self.best.x_geno]
3748
3749
3750
3751 else:
3752 xp = [self.gp.geno(array(self.best.x, copy=True),
3753 self.boundary_handler.inverse,
3754 copy_if_changed=False)]
3755 print('genotype for elitist not found')
3756 self.clip_or_fit_solutions(xp, [0])
3757 pop = array([xp[0]] + list(pop))
3758 elif self.opts['CMA_elitist'] == 'initial':
3759 self.opts['CMA_elitist'] = False
3760
3761 self.pop_sorted = pop
3762
3763 self.mean = mold + self.sp.cmean * \
3764 (sum(sp.weights * pop[0:sp.mu].T, 1) - mold)
3765
3766
3767
3768
3769
3770
3771
3772 if 1 < 3:
3773 cmean = self.sp.cmean
3774
3775
3776
3777 cc, c1, cmu = sp.cc, sp.c1, sp.cmu
3778 if flg_diagonal:
3779 cc, c1, cmu = sp.cc_sep, sp.c1_sep, sp.cmu_sep
3780
3781
3782
3783 hsig = self.adapt_sigma.hsig(self)
3784
3785
3786
3787
3788
3789 c1a = c1 - (1 - hsig**2) * c1 * cc * (2 - cc)
3790
3791 self.pc = (1 - cc) * self.pc + \
3792 hsig * (sqrt(cc * (2 - cc) * sp.mueff) / self.sigma / cmean) * \
3793 (self.mean - mold) / self.sigma_vec
3794
3795
3796 if sp.CMA_on:
3797
3798 assert c1 + cmu <= 1
3799
3800
3801 if not flg_diagonal:
3802 Y = (pop[0:sp.mu] - mold) / (self.sigma * self.sigma_vec)
3803 Y = dot((cmu * sp.weights) * Y.T, Y)
3804 if self.sp.neg.cmuexp:
3805 tmp = (pop[-sp.neg.mu:] - mold) / (self.sigma * self.sigma_vec)
3806
3807 for i in xrange(tmp.shape[0]):
3808 tmp[i, :] *= N**0.5 / self.mahalanobis_norm(
3809 self.sigma_vec * tmp[i, :]) / self.sigma
3810 self._Yneg *= 1 - self.sp.neg.cmuexp
3811 self._Yneg += dot(sp.neg.weights * tmp.T, tmp) - self.C
3812
3813
3814 self.C *= 1 - c1a - cmu
3815 self.C += np.outer(c1 * self.pc, self.pc) + Y
3816 self.dC[:] = np.diag(self.C)
3817
3818 else:
3819 assert(c1 + cmu <= 1)
3820 Z = np.zeros(N)
3821 for k in xrange(sp.mu):
3822 z = (pop[k] - mold) / (self.sigma * self.sigma_vec)
3823 Z += sp.weights[k] * z * z
3824 self.C = (1 - c1a - cmu) * self.C + c1 * self.pc * self.pc + cmu * Z
3825
3826 self.dC = self.C
3827 self.D = sqrt(self.C)
3828 self.itereigenupdated = self.countiter
3829
3830
3831
3832
3833
3834 self.adapt_sigma.update(self, function_values=function_values)
3835
3836 if self.sigma * min(self.sigma_vec * self.dC**0.5) < self.opts['minstd']:
3837 self.sigma = self.opts['minstd'] / min(self.sigma_vec * self.dC**0.5)
3838 if self.sigma * max(self.sigma_vec * self.dC**0.5) > self.opts['maxstd']:
3839 self.sigma = self.opts['maxstd'] / max(self.sigma_vec * self.dC**0.5)
3840
3841
3842
3843
3844 if self.sigma * min(self.D) < self.opts['mindx']:
3845 self.sigma = self.opts['mindx'] / min(self.D)
3846
3847 if self.sigma > 1e9 * self.sigma0:
3848 alpha = self.sigma / max(self.D)
3849 self.multiplyC(alpha)
3850 self.sigma /= alpha**0.5
3851 self.opts['tolupsigma'] /= alpha**0.5
3852
3853
3854
3855
3856
3857
3858
3859
3860 if new_injections:
3861 self.pop_injection_directions = self.prepare_injection_directions()
3862 self.pop_sorted = []
3863 self._flgtelldone = True
3864
3865
3866 - def inject(self, solutions):
3867 """inject a genotypic solution. The solution is used as direction
3868 relative to the distribution mean to compute a new candidate
3869 solution returned in method `ask_geno` which in turn is used in
3870 method `ask`.
3871
3872 >>> import cma
3873 >>> es = cma.CMAEvolutionStrategy(4 * [1], 2)
3874 >>> while not es.stop():
3875 ... es.inject([4 * [0.0]])
3876 ... X = es.ask()
3877 ... break
3878 >>> assert X[0][0] == X[0][1]
3879
3880 """
3881 if not hasattr(self, 'pop_injection_directions'):
3882 self.pop_injection_directions = []
3883 for solution in solutions:
3884 if len(solution) != self.N:
3885 raise ValueError('method `inject` needs a list or array'
3886 + (' each el with dimension (`len`) %d' % self.N))
3887 self.pop_injection_directions.append(
3888 array(solution, copy=False, dtype=float) - self.mean)
3889
3891 """return::
3892
3893 (xbest, f(xbest), evaluations_xbest, evaluations, iterations,
3894 pheno(xmean), effective_stds)
3895
3896 """
3897
3898 return self.best.get() + (
3899 self.countevals, self.countiter, self.gp.pheno(self.mean),
3900 self.gp.scales * self.sigma * self.sigma_vec * self.dC**0.5)
3901 - def result_pretty(self, number_of_runs=0, time_str=None,
3902 fbestever=None):
3903 """pretty print result.
3904
3905 Returns ``self.result()``
3906
3907 """
3908 if fbestever is None:
3909 fbestever = self.best.f
3910 s = (' after %i restart' + ('s' if number_of_runs > 1 else '')) \
3911 % number_of_runs if number_of_runs else ''
3912 for k, v in self.stop().items():
3913 print('termination on %s=%s%s' % (k, str(v), s +
3914 (' (%s)' % time_str if time_str else '')))
3915
3916 print('final/bestever f-value = %e %e' % (self.best.last.f,
3917 fbestever))
3918 if self.N < 9:
3919 print('incumbent solution: ' + str(list(self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair))))
3920 print('std deviation: ' + str(list(self.sigma * self.sigma_vec * sqrt(self.dC) * self.gp.scales)))
3921 else:
3922 print('incumbent solution: %s ...]' % (str(self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair)[:8])[:-1]))
3923 print('std deviations: %s ...]' % (str((self.sigma * self.sigma_vec * sqrt(self.dC) * self.gp.scales)[:8])[:-1]))
3924 return self.result()
3925
3927 """make sure that solutions fit to sample distribution, this interface will probably change.
3928
3929 In particular the frequency of long vectors appearing in pop[idx] - self.mean is limited.
3930
3931 """
3932 for k in idx:
3933 self.repair_genotype(pop[k])
3934
3936 """make sure that solutions fit to the sample distribution, this interface will probably change.
3937
3938 In particular the frequency of x - self.mean being long is limited.
3939
3940 """
3941 x = array(x, copy=False)
3942 mold = array(self.mean, copy=False)
3943 if 1 < 3:
3944 upper_length = self.N**0.5 + 2 * self.N / (self.N + 2)
3945 fac = self.mahalanobis_norm(x - mold) / upper_length
3946
3947 if fac > 1:
3948 if copy_if_changed:
3949 x = (x - mold) / fac + mold
3950 else:
3951 x -= mold
3952 x /= fac
3953 x += mold
3954
3955
3956 else:
3957 if 'checktail' not in self.__dict__:
3958 raise NotImplementedError
3959
3960
3961
3962 fac = self.checktail.addchin(self.mahalanobis_norm(x - mold))
3963
3964 if fac < 1:
3965 x = fac * (x - mold) + mold
3966
3967 return x
3968
3970 """eigen-decompose self.C and update self.dC, self.C, self.B.
3971
3972 Known bugs: this might give a runtime error with
3973 CMA_diagonal / separable option on.
3974
3975 """
3976 if self.opts['CMA_diagonal']:
3977 _print_warning("this might fail with CMA_diagonal option on",
3978 iteration=self.countiter)
3979 print(self.opts['CMA_diagonal'])
3980
3981
3982 self.C = (self.C + self.C.T) / 2
3983 self.dC = np.diag(self.C).copy()
3984 self.D, self.B = self.opts['CMA_eigenmethod'](self.C)
3985
3986
3987
3988
3989
3990
3991
3992 if any(self.D <= 0):
3993 _print_warning("ERROR", iteration=self.countiter)
3994 raise ValueError("covariance matrix was not positive definite," +
3995 " this must be considered as a bug")
3996 self.D = self.D**0.5
3997 assert all(isfinite(self.D))
3998 idx = np.argsort(self.D)
3999 self.D = self.D[idx]
4000 self.B = self.B[:, idx]
4001 self.count_eigen += 1
4003 """update internal variables for sampling the distribution with the
4004 current covariance matrix C. This method is O(N^3), if C is not diagonal.
4005
4006 """
4007
4008
4009 if self.itereigenupdated == self.countiter:
4010 return
4011 if self.opts['CMA_diagonal'] >= self.countiter:
4012 _print_warning("updateBD called in CMA_diagonal mode, " +
4013 "this should be considered a bug", "updateBD",
4014 iteration=self.countiter)
4015
4016 if self.sp.neg.cmuexp:
4017 C_shrunken = (1 - self.sp.cmu - self.sp.c1)**(self.countiter - self.itereigenupdated)
4018 clip_fac = 0.60
4019 if hasattr(self.opts['vv'], '__getitem__') and self.opts['vv'][0] == 'sweep_ccov_neg':
4020 clip_fac = 0.98
4021 if (self.countiter - self.itereigenupdated) * self.sp.neg.cmuexp * self.N \
4022 < clip_fac * C_shrunken:
4023
4024 self.C -= self.sp.neg.cmuexp * self._Yneg
4025 else:
4026 max_warns = 1
4027 try:
4028 self._updateBD_warnings += 1
4029 except AttributeError:
4030 self._updateBD_warnings = 1
4031 if self.opts['verbose'] > 1 and \
4032 self._updateBD_warnings <= max_warns:
4033 _print_warning('doing two additional eigen' +
4034 'decompositions to guarantee pos.def.',
4035 'updateBD', 'CMAEvolutionStrategy')
4036 if self._updateBD_warnings == max_warns:
4037 _print_warning('further warnings are surpressed',
4038 'updateBD')
4039 self.decompose_C()
4040 _tmp_inverse_root_C = dot(self.B / self.D, self.B.T)
4041 _tmp_inverse_root_C = (_tmp_inverse_root_C + _tmp_inverse_root_C.T) / 2
4042 Zneg = dot(dot(_tmp_inverse_root_C, self._Yneg), _tmp_inverse_root_C)
4043 eigvals, eigvecs = self.opts['CMA_eigenmethod'](Zneg)
4044 self.count_eigen += 1
4045 if max(eigvals) * self.sp.neg.cmuexp <= clip_fac:
4046 self.C -= self.sp.neg.cmuexp * self._Yneg
4047 elif 1 < 3:
4048 self.C -= (clip_fac / max(eigvals)) * self._Yneg
4049 _print_warning(
4050 'clipped learning rate for negative weights, ' +
4051 'maximal eigenvalue = %f, maxeig * ccov = %f > %f'
4052 % (max(eigvals), max(eigvals) * self.sp.neg.cmuexp, clip_fac),
4053 iteration=self.countiter)
4054 if 1 < 3:
4055 eigvals, eigvecs = self.opts['CMA_eigenmethod'](self.C)
4056 self.count_eigen += 1
4057 print('new min eigenval = %e, old = %e'
4058 % (min(eigvals), min(self.D)**2))
4059 if min(eigvals) > 0:
4060 print('new cond = %e, old = %e'
4061 % (max(eigvals) / min(eigvals),
4062 (max(self.D) / min(self.D))**2))
4063 else:
4064 _print_warning('exponential update for negative weights (internally more expensive)',
4065 iteration=self.countiter)
4066 self.update_exponential(self._Yneg, -self.sp.neg.cmuexp)
4067
4068
4069 self._Yneg = np.zeros((self.N, self.N))
4070
4071 if hasattr(self.opts['vv'], '__getitem__') and self.opts['vv'][0].startswith('sweep_ccov'):
4072 self.opts['CMA_const_trace'] = True
4073 if self.opts['CMA_const_trace'] in (True, 1, 2):
4074 if self.opts['CMA_const_trace'] == 2:
4075 s = np.exp(2 * np.mean(np.log(self.D)))
4076 else:
4077 s = np.mean(np.diag(self.C))
4078 self.C /= s
4079
4080 dC = np.diag(self.C)
4081 if max(dC) / min(dC) > 1e8:
4082
4083 self.sigma_vec *= np.diag(self.C)**0.5
4084 self.C = self.correlation_matrix()
4085 _print_warning('condition in coordinate system exceeded 1e8' +
4086 ', rescaled to 1')
4087
4088
4089
4090
4091 self.decompose_C()
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101 if 1 < 3 and max(self.D) / min(self.D) > 1e6 and self.gp.isidentity:
4102
4103
4104 self.gp._tf_matrix = (self.sigma_vec * dot(self.B * self.D, self.B.T).T).T
4105 self.gp._tf_matrix_inv = (dot(self.B / self.D, self.B.T).T / self.sigma_vec).T
4106 self.gp.tf_pheno = lambda x: dot(self.gp._tf_matrix, x)
4107 self.gp.tf_geno = lambda x: dot(self.gp._tf_matrix_inv, x)
4108 self.gp.isidentity = False
4109 assert self.mean is not self.mean_old
4110 self.mean = self.gp.geno(self.mean)
4111 self.mean_old = self.gp.geno(self.mean_old)
4112 self.pc = self.gp.geno(self.pc)
4113 self.D[:] = 1.0
4114 self.B = np.eye(self.N)
4115 self.C = np.eye(self.N)
4116 self.dC[:] = 1.0
4117 self.sigma_vec = 1
4118
4119
4120 _print_warning('\n geno-pheno transformation introduced based on current C,\n injected solutions become "invalid" in this iteration',
4121 'updateBD', 'CMAEvolutionStrategy', self.countiter)
4122
4123 self.itereigenupdated = self.countiter
4124
4126 """multiply C with a scalar and update all related internal variables (dC, D,...)"""
4127 self.C *= alpha
4128 if self.dC is not self.C:
4129 self.dC *= alpha
4130 self.D *= alpha**0.5
4132 """exponential update of C that guarantees positive definiteness, that is,
4133 instead of the assignment ``C = C + eta * Z``,
4134 we have ``C = C**.5 * exp(eta * C**-.5 * Z * C**-.5) * C**.5``.
4135
4136 Parameter `Z` should have expectation zero, e.g. sum(w[i] * z[i] * z[i].T) - C
4137 if E z z.T = C.
4138
4139 Parameter `eta` is the learning rate, for ``eta == 0`` nothing is updated.
4140
4141 This function conducts two eigendecompositions, assuming that
4142 B and D are not up to date, unless `BDpair` is given. Given BDpair,
4143 B is the eigensystem and D is the vector of sqrt(eigenvalues), one
4144 eigendecomposition is omitted.
4145
4146 Reference: Glasmachers et al 2010, Exponential Natural Evolution Strategies
4147
4148 """
4149 if eta == 0:
4150 return
4151 if BDpair:
4152 B, D = BDpair
4153 else:
4154 D, B = self.opts['CMA_eigenmethod'](self.C)
4155 self.count_eigen += 1
4156 D **= 0.5
4157 Cs = dot(B, (B * D).T)
4158 Csi = dot(B, (B / D).T)
4159 self.C = dot(Cs, dot(Mh.expms(eta * dot(Csi, dot(Z, Csi)),
4160 self.opts['CMA_eigenmethod']), Cs))
4161 self.count_eigen += 1
4162
4163
4164
4166 """Given all "previous" candidate solutions and their respective
4167 function values, the state of a `CMAEvolutionStrategy` object
4168 can be reconstructed from this history. This is the purpose of
4169 function `feedForResume`.
4170
4171 Arguments
4172 ---------
4173 `X`
4174 (all) solution points in chronological order, phenotypic
4175 representation. The number of points must be a multiple
4176 of popsize.
4177 `function_values`
4178 respective objective function values
4179
4180 Details
4181 -------
4182 `feedForResume` can be called repeatedly with only parts of
4183 the history. The part must have the length of a multiple
4184 of the population size.
4185 `feedForResume` feeds the history in popsize-chunks into `tell`.
4186 The state of the random number generator might not be
4187 reconstructed, but this would be only relevant for the future.
4188
4189 Example
4190 -------
4191 ::
4192
4193 import cma
4194
4195 # prepare
4196 (x0, sigma0) = ... # initial values from previous trial
4197 X = ... # list of generated solutions from a previous trial
4198 f = ... # respective list of f-values
4199
4200 # resume
4201 es = cma.CMAEvolutionStrategy(x0, sigma0)
4202 es.feedForResume(X, f)
4203
4204 # continue with func as objective function
4205 while not es.stop():
4206 X = es.ask()
4207 es.tell(X, [func(x) for x in X])
4208
4209 Credits to Dirk Bueche and Fabrice Marchal for the feeding idea.
4210
4211 :See: class `CMAEvolutionStrategy` for a simple dump/load to resume
4212
4213 """
4214 if self.countiter > 0:
4215 _print_warning('feed should generally be used with a new object instance')
4216 if len(X) != len(function_values):
4217 raise _Error('number of solutions ' + str(len(X)) +
4218 ' and number function values ' +
4219 str(len(function_values)) + ' must not differ')
4220 popsize = self.sp.popsize
4221 if (len(X) % popsize) != 0:
4222 raise _Error('number of solutions ' + str(len(X)) +
4223 ' must be a multiple of popsize (lambda) ' +
4224 str(popsize))
4225 for i in rglen((X) / popsize):
4226
4227 self.ask()
4228
4229 self.tell(X[i * popsize:(i + 1) * popsize], function_values[i * popsize:(i + 1) * popsize])
4230
4231
4232
4234 """reads dynamic parameters from property file (not implemented)
4235 """
4236 print('not yet implemented')
4237
4238
4239
4241 if len(self.C.shape) <= 1:
4242 return None
4243 c = self.C.copy()
4244 for i in xrange(c.shape[0]):
4245 fac = c[i, i]**0.5
4246 c[:, i] /= fac
4247 c[i, :] /= fac
4248 c = (c + c.T) / 2.0
4249 return c
4251 """compute the Mahalanobis norm that is induced by the adapted
4252 sample distribution, covariance matrix ``C`` times ``sigma**2``,
4253 including ``sigma_vec``. The expected Mahalanobis distance to
4254 the sample mean is about ``sqrt(dimension)``.
4255
4256 Argument
4257 --------
4258 A *genotype* difference `dx`.
4259
4260 Example
4261 -------
4262 >>> import cma, numpy
4263 >>> es = cma.CMAEvolutionStrategy(numpy.ones(10), 1)
4264 >>> xx = numpy.random.randn(2, 10)
4265 >>> d = es.mahalanobis_norm(es.gp.geno(xx[0]-xx[1]))
4266
4267 `d` is the distance "in" the true sample distribution,
4268 sampled points have a typical distance of ``sqrt(2*es.N)``,
4269 where ``es.N`` is the dimension, and an expected distance of
4270 close to ``sqrt(N)`` to the sample mean. In the example,
4271 `d` is the Euclidean distance, because C = I and sigma = 1.
4272
4273 """
4274 return sqrt(sum((self.D**-1. * np.dot(self.B.T, dx / self.sigma_vec))**2)) / self.sigma
4275
4277 """return D^-1 B^T diag(sig) B D as a measure for
4278 C^-1/2 diag(sig) C^1/2
4279
4280 :param sig: a vector "used" as diagonal matrix
4281 :return:
4282
4283 """
4284 return dot((self.B * self.D**-1.).T * sig, self.B * self.D)
4285
4287 """print annotation for `disp()`"""
4288 print('Iterat #Fevals function value axis ratio sigma min&max std t[m:s]')
4289 sys.stdout.flush()
4290
4291 - def disp(self, modulo=None):
4292 """prints some single-line infos according to `disp_annotation()`,
4293 if ``iteration_counter % modulo == 0``
4294
4295 """
4296 if modulo is None:
4297 modulo = self.opts['verb_disp']
4298
4299
4300 if modulo:
4301 if (self.countiter - 1) % (10 * modulo) < 1:
4302 self.disp_annotation()
4303 if self.countiter > 0 and (self.stop() or self.countiter < 4
4304 or self.countiter % modulo < 1):
4305 if self.opts['verb_time']:
4306 toc = self.elapsed_time()
4307 stime = str(int(toc // 60)) + ':' + str(round(toc % 60, 1))
4308 else:
4309 stime = ''
4310 print(' '.join((repr(self.countiter).rjust(5),
4311 repr(self.countevals).rjust(6),
4312 '%.15e' % (min(self.fit.fit)),
4313 '%4.1e' % (self.D.max() / self.D.min()),
4314 '%6.2e' % self.sigma,
4315 '%6.0e' % (self.sigma * min(self.sigma_vec * sqrt(self.dC))),
4316 '%6.0e' % (self.sigma * max(self.sigma_vec * sqrt(self.dC))),
4317 stime)))
4318
4319 sys.stdout.flush()
4320 return self
4322 try:
4323 self.logger.plot()
4324 except AttributeError:
4325 _print_warning('plotting failed, no logger attribute found')
4326 except:
4327 _print_warning(('plotting failed with:', sys.exc_info()[0]),
4328 'plot', 'CMAEvolutionStrategy')
4329 return self
4330
4331 cma_default_options = {
4332
4333 'AdaptSigma': 'CMAAdaptSigmaCSA # or any other CMAAdaptSigmaBase class e.g. CMAAdaptSigmaTPA',
4334 'CMA_active': 'True # negative update, conducted after the original update',
4335
4336 'CMA_cmean': '1 # learning rate for the mean value',
4337 'CMA_const_trace': 'False # normalize trace, value CMA_const_trace=2 normalizes sum log eigenvalues to zero',
4338 'CMA_diagonal': '0*100*N/sqrt(popsize) # nb of iterations with diagonal covariance matrix, True for always',
4339 'CMA_eigenmethod': 'np.linalg.eigh # 0=numpy-s eigh, -1=pygsl, otherwise cma.Misc.eig (slower)',
4340 'CMA_elitist': 'False #v or "initial" or True, elitism likely impairs global search performance',
4341 'CMA_mirrors': 'popsize < 6 # values <0.5 are interpreted as fraction, values >1 as numbers (rounded), otherwise about 0.16 is used',
4342 'CMA_mirrormethod': '1 # 0=unconditional, 1=selective, 2==experimental',
4343 'CMA_mu': 'None # parents selection parameter, default is popsize // 2',
4344 'CMA_on': 'True # False or 0 for no adaptation of the covariance matrix',
4345 'CMA_sample_on_sphere_surface': 'False #v all mutation vectors have the same length',
4346 'CMA_rankmu': 'True # False or 0 for omitting rank-mu update of covariance matrix',
4347 'CMA_rankmualpha': '0.3 # factor of rank-mu update if mu=1, subject to removal, default might change to 0.0',
4348 'CMA_dampsvec_fac': 'np.Inf # tentative and subject to changes, 0.5 would be a "default" damping for sigma vector update',
4349 'CMA_dampsvec_fade': '0.1 # tentative fading out parameter for sigma vector update',
4350 'CMA_teststds': 'None # factors for non-isotropic initial distr. of C, mainly for test purpose, see CMA_stds for production',
4351 'CMA_stds': 'None # multipliers for sigma0 in each coordinate, not represented in C, makes scaling_of_variables obsolete',
4352
4353 'CSA_dampfac': '1 #v positive multiplier for step-size damping, 0.3 is close to optimal on the sphere',
4354 'CSA_damp_mueff_exponent': '0.5 # zero would mean no dependency of damping on mueff, useful with CSA_disregard_length option',
4355 'CSA_disregard_length': 'False #v True is untested',
4356 'CSA_clip_length_value': 'None #v untested, [0, 0] means disregarding length completely',
4357 'CSA_squared': 'False #v use squared length for sigma-adaptation ',
4358 'boundary_handling': 'BoundTransform # or BoundPenalty, unused when ``bounds in (None, [None, None])``',
4359 'bounds': '[None, None] # lower (=bounds[0]) and upper domain boundaries, each a scalar or a list/vector',
4360
4361 'fixed_variables': 'None # dictionary with index-value pairs like {0:1.1, 2:0.1} that are not optimized',
4362 'ftarget': '-inf #v target function value, minimization',
4363 'is_feasible': 'is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.NaN)',
4364 'maxfevals': 'inf #v maximum number of function evaluations',
4365 'maxiter': '100 + 50 * (N+3)**2 // popsize**0.5 #v maximum number of iterations',
4366 'mean_shift_line_samples': 'False #v sample two new solutions colinear to previous mean shift',
4367 'mindx': '0 #v minimal std in any direction, cave interference with tol*',
4368 'minstd': '0 #v minimal std in any coordinate direction, cave interference with tol*',
4369 'maxstd': 'inf #v maximal std in any coordinate direction',
4370 'pc_line_samples': 'False #v two line samples along the evolution path pc',
4371 'popsize': '4+int(3*log(N)) # population size, AKA lambda, number of new solution per iteration',
4372 'randn': 'np.random.standard_normal #v randn((lam, N)) must return an np.array of shape (lam, N)',
4373 'scaling_of_variables': 'None # (rather use CMA_stds) scale for each variable, sigma0 is interpreted w.r.t. this scale, in that effective_sigma0 = sigma0*scaling. Internally the variables are divided by scaling_of_variables and sigma is unchanged, default is np.ones(N)',
4374 'seed': 'None # random number seed',
4375 'signals_filename': 'cmaes_signals.par # read from this file, e.g. "stop now"',
4376 'termination_callback': 'None #v a function returning True for termination, called after each iteration step and could be abused for side effects',
4377 'tolfacupx': '1e3 #v termination when step-size increases by tolfacupx (diverges). That is, the initial step-size was chosen far too small and better solutions were found far away from the initial solution x0',
4378 'tolupsigma': '1e20 #v sigma/sigma0 > tolupsigma * max(sqrt(eivenvals(C))) indicates "creeping behavior" with usually minor improvements',
4379 'tolfun': '1e-11 #v termination criterion: tolerance in function value, quite useful',
4380 'tolfunhist': '1e-12 #v termination criterion: tolerance in function value history',
4381 'tolstagnation': 'int(100 + 100 * N**1.5 / popsize) #v termination if no improvement over tolstagnation iterations',
4382 'tolx': '1e-11 #v termination criterion: tolerance in x-changes',
4383 'transformation': 'None # [t0, t1] are two mappings, t0 transforms solutions from CMA-representation to f-representation (tf_pheno), t1 is the (optional) back transformation, see class GenoPheno',
4384 'typical_x': 'None # used with scaling_of_variables',
4385 'updatecovwait': 'None #v number of iterations without distribution update, name is subject to future changes',
4386 'verbose': '1 #v verbosity e.v. of initial/final message, -1 is very quiet, -9 maximally quiet, not yet fully implemented',
4387 'verb_append': '0 # initial evaluation counter, if append, do not overwrite output files',
4388 'verb_disp': '100 #v verbosity: display console output every verb_disp iteration',
4389 'verb_filenameprefix': 'outcmaes # output filenames prefix',
4390 'verb_log': '1 #v verbosity: write data to files every verb_log iteration, writing can be time critical on fast to evaluate functions',
4391 'verb_plot': '0 #v in fmin(): plot() is called every verb_plot iteration',
4392 'verb_time': 'True #v output timings on console',
4393 'vv': '0 #? versatile variable for hacking purposes, value found in self.opts["vv"]'
4394 }
4396 """``CMAOptions()`` returns a dictionary with the available options
4397 and their default values for class ``CMAEvolutionStrategy``.
4398
4399 ``CMAOptions('pop')`` returns a subset of recognized options that
4400 contain 'pop' in there keyword name or (default) value or description.
4401
4402 ``CMAOptions(opts)`` returns the subset of recognized options in
4403 ``dict(opts)``.
4404
4405 Option values can be "written" in a string and, when passed to fmin
4406 or CMAEvolutionStrategy, are evaluated using "N" and "popsize" as
4407 known values for dimension and population size (sample size, number
4408 of new solutions per iteration). All default option values are such
4409 a string.
4410
4411 Details
4412 -------
4413 ``CMAOptions`` entries starting with ``tol`` are termination
4414 "tolerances".
4415
4416 For `tolstagnation`, the median over the first and the second half
4417 of at least `tolstagnation` iterations are compared for both, the
4418 per-iteration best and per-iteration median function value.
4419
4420 Example
4421 -------
4422 ::
4423
4424 import cma
4425 cma.CMAOptions('tol')
4426
4427 is a shortcut for cma.CMAOptions().match('tol') that returns all options
4428 that contain 'tol' in their name or description.
4429
4430 To set an option::
4431
4432 import cma
4433 opts = cma.CMAOptions()
4434 opts.set('tolfun', 1e-12)
4435 opts['tolx'] = 1e-11
4436
4437 :See: `fmin`(), `CMAEvolutionStrategy`, `_CMAParameters`
4438
4439 """
4440
4441
4442
4443
4444
4445
4446
4447 @staticmethod
4449 """return a dictionary with default option values and description"""
4450 return dict((str(k), str(v)) for k, v in cma_default_options.items())
4451
4452
4453
4454 @staticmethod
4456 """return list of options that can be changed at any time (not
4457 only be initialized), however the list might not be entirely up
4458 to date.
4459
4460 The string ' #v ' in the default value indicates a 'versatile'
4461 option that can be changed any time.
4462
4463 """
4464 return tuple(sorted(i[0] for i in list(CMAOptions.defaults().items()) if i[1].find(' #v ') > 0))
4465 - def check(self, options=None):
4472 corrected_key = CMAOptions().corrected_key
4473 validated_keys = []
4474 original_keys = []
4475 if options is None:
4476 options = self
4477 for key in options:
4478 correct_key = corrected_key(key)
4479 if correct_key is None:
4480 raise ValueError("""%s is not a valid option""" % key)
4481 if correct_key in validated_keys:
4482 if key == correct_key:
4483 key = original_keys[validated_keys.index(key)]
4484 raise ValueError("%s was not a unique key for %s option"
4485 % (key, correct_key))
4486 validated_keys.append(correct_key)
4487 original_keys.append(key)
4488 return options
4490 """check for attributes and moves them into the dictionary"""
4491 if opts is None:
4492 opts = self
4493 if 1 < 3:
4494
4495
4496
4497 if hasattr(opts, '__dict__'):
4498 for key in list(opts.__dict__):
4499 if key in self._attributes:
4500 continue
4501 _print_warning(
4502 """
4503 An option attribute has been merged into the dictionary,
4504 thereby possibly overwriting the dictionary value, and the
4505 attribute has been removed. Assign options with
4506
4507 ``opts['%s'] = value`` # dictionary assignment
4508
4509 or use
4510
4511 ``opts.set('%s', value) # here isinstance(opts, CMAOptions)
4512
4513 instead of
4514
4515 ``opts.%s = value`` # attribute assignment
4516 """ % (key, key, key), 'check', 'CMAOptions')
4517
4518 opts[key] = opts.__dict__[key]
4519 delattr(opts, key)
4520
4521
4522 return opts
4523
4524 @staticmethod
4525 - def merge(self, dict_=None):
4526 """not is use so far, see check()"""
4527 if dict_ is None and hasattr(self, '__dict__'):
4528 dict_ = self.__dict__
4529
4530 if dict_ is None:
4531 return self
4532 self.update(dict_)
4533 return self
4534
4535 - def __init__(self, s=None, unchecked=False):
4536 """return an `CMAOptions` instance, either with the default
4537 options, if ``s is None``, or with all options whose name or
4538 description contains `s`, if `s` is a string (case is
4539 disregarded), or with entries from dictionary `s` as options,
4540 not complemented with default options or settings
4541
4542 Returns: see above.
4543
4544 """
4545
4546
4547 if s is None:
4548 super(CMAOptions, self).__init__(CMAOptions.defaults())
4549
4550 elif isinstance(s, basestring):
4551 super(CMAOptions, self).__init__(CMAOptions().match(s))
4552
4553 else:
4554 super(CMAOptions, self).__init__(s)
4555
4556 if not unchecked and s is not None:
4557 self.check()
4558 for key in list(self.keys()):
4559 correct_key = self.corrected_key(key)
4560 if correct_key not in CMAOptions.defaults():
4561 _print_warning('invalid key ``' + str(key) +
4562 '`` removed', '__init__', 'CMAOptions')
4563 self.pop(key)
4564 elif key != correct_key:
4565 self[correct_key] = self.pop(key)
4566
4567 self._lock_setting = False
4568 self._attributes = self.__dict__.copy()
4569 self._attributes['_attributes'] = len(self._attributes)
4570
4571 - def init(self, dict_or_str, val=None, warn=True):
4572 """initialize one or several options.
4573
4574 Arguments
4575 ---------
4576 `dict_or_str`
4577 a dictionary if ``val is None``, otherwise a key.
4578 If `val` is provided `dict_or_str` must be a valid key.
4579 `val`
4580 value for key
4581
4582 Details
4583 -------
4584 Only known keys are accepted. Known keys are in `CMAOptions.defaults()`
4585
4586 """
4587
4588 self.check(dict_or_str)
4589 dic = dict_or_str
4590 if val is not None:
4591 dic = {dict_or_str:val}
4592
4593 for key, val in dic.items():
4594 key = self.corrected_key(key)
4595 if key not in CMAOptions.defaults():
4596
4597 if warn:
4598 print('Warning in cma.CMAOptions.init(): key ' +
4599 str(key) + ' ignored')
4600 else:
4601 self[key] = val
4602
4603 return self
4604
4605 - def set(self, dic, val=None, force=False):
4606 """set can assign versatile options from
4607 `CMAOptions.versatile_options()` with a new value, use `init()`
4608 for the others.
4609
4610 Arguments
4611 ---------
4612 `dic`
4613 either a dictionary or a key. In the latter
4614 case, `val` must be provided
4615 `val`
4616 value for `key`, approximate match is sufficient
4617 `force`
4618 force setting of non-versatile options, use with caution
4619
4620 This method will be most probably used with the ``opts`` attribute of
4621 a `CMAEvolutionStrategy` instance.
4622
4623 """
4624 if val is not None:
4625 dic = {dic:val}
4626 for key_original, val in list(dict(dic).items()):
4627 key = self.corrected_key(key_original)
4628 if not self._lock_setting or \
4629 key in CMAOptions.versatile_options():
4630 self[key] = val
4631 else:
4632 _print_warning('key ' + str(key_original) +
4633 ' ignored (not recognized as versatile)',
4634 'set', 'CMAOptions')
4635 return self
4636
4646
4648 """return the subset of those options that are settable at any
4649 time.
4650
4651 Settable options are in `versatile_options()`, but the
4652 list might be incomplete.
4653
4654 """
4655 return CMAOptions([i for i in list(self.items())
4656 if i[0] in CMAOptions.versatile_options()])
4657
4658 - def __call__(self, key, default=None, loc=None):
4659 """evaluate and return the value of option `key` on the fly, or
4660 returns those options whose name or description contains `key`,
4661 case disregarded.
4662
4663 Details
4664 -------
4665 Keys that contain `filename` are not evaluated.
4666 For ``loc==None``, `self` is used as environment
4667 but this does not define ``N``.
4668
4669 :See: `eval()`, `evalall()`
4670
4671 """
4672 try:
4673 val = self[key]
4674 except:
4675 return self.match(key)
4676
4677 if loc is None:
4678 loc = self
4679 try:
4680 if isinstance(val, basestring):
4681 val = val.split('#')[0].strip()
4682 if isinstance(val, basestring) and \
4683 key.find('filename') < 0:
4684
4685 val = eval(val, globals(), loc)
4686
4687
4688
4689 elif val is None and default is not None:
4690 val = eval(str(default), globals(), loc)
4691 except:
4692 pass
4693 return val
4694
4696 """return the matching valid key, if ``key.lower()`` is a unique
4697 starting sequence to identify the valid key, ``else None``
4698
4699 """
4700 matching_keys = []
4701 for allowed_key in CMAOptions.defaults():
4702 if allowed_key.lower() == key.lower():
4703 return allowed_key
4704 if allowed_key.lower().startswith(key.lower()):
4705 matching_keys.append(allowed_key)
4706 return matching_keys[0] if len(matching_keys) == 1 else None
4707
4708 - def eval(self, key, default=None, loc=None, correct_key=True):
4709 """Evaluates and sets the specified option value in
4710 environment `loc`. Many options need ``N`` to be defined in
4711 `loc`, some need `popsize`.
4712
4713 Details
4714 -------
4715 Keys that contain 'filename' are not evaluated.
4716 For `loc` is None, the self-dict is used as environment
4717
4718 :See: `evalall()`, `__call__`
4719
4720 """
4721
4722 if correct_key:
4723
4724 key = self.corrected_key(key)
4725 self[key] = self(key, default, loc)
4726 return self[key]
4727
4728 - def evalall(self, loc=None, defaults=None):
4729 """Evaluates all option values in environment `loc`.
4730
4731 :See: `eval()`
4732
4733 """
4734 self.check()
4735 if defaults is None:
4736 defaults = cma_default_options
4737
4738 if 'N' in loc:
4739 popsize = self('popsize', defaults['popsize'], loc)
4740 for k in list(self.keys()):
4741 k = self.corrected_key(k)
4742 self.eval(k, defaults[k],
4743 {'N':loc['N'], 'popsize':popsize})
4744 self._lock_setting = True
4745 return self
4746
4748 """return all options that match, in the name or the description,
4749 with string `s`, case is disregarded.
4750
4751 Example: ``cma.CMAOptions().match('verb')`` returns the verbosity
4752 options.
4753
4754 """
4755 match = s.lower()
4756 res = {}
4757 for k in sorted(self):
4758 s = str(k) + '=\'' + str(self[k]) + '\''
4759 if match in s.lower():
4760 res[k] = self[k]
4761 return CMAOptions(res, unchecked=True)
4762
4765
4766 - def pprint(self, linebreak=80):
4767 for i in sorted(self.items()):
4768 s = str(i[0]) + "='" + str(i[1]) + "'"
4769 a = s.split(' ')
4770
4771
4772 l = ''
4773 while a:
4774 while a and len(l) + len(a[0]) < linebreak:
4775 l += ' ' + a.pop(0)
4776 print(l)
4777 l = ' '
4778 print_ = pprint
4779 printme = pprint
4780
4784 """keep and update a termination condition dictionary, which is
4785 "usually" empty and returned by `CMAEvolutionStrategy.stop()`.
4786 The class methods entirely depend on `CMAEvolutionStrategy` class
4787 attributes.
4788
4789 Details
4790 -------
4791 This class is not relevant for the end-user and could be a nested
4792 class, but nested classes cannot be serialized.
4793
4794 Example
4795 -------
4796 >>> import cma
4797 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1, {'verbose':-1})
4798 >>> print(es.stop())
4799 {}
4800 >>> es.optimize(cma.fcts.sphere, verb_disp=0)
4801 >>> print(es.stop())
4802 {'tolfun': 1e-11}
4803
4804 :See: `OOOptimizer.stop()`, `CMAEvolutionStrategy.stop()`
4805
4806 """
4817
4818 - def __call__(self, es=None, check=True):
4819 """update and return the termination conditions dictionary
4820
4821 """
4822 if not check:
4823 return self
4824 if es is None and self.es is None:
4825 raise ValueError('termination conditions need an optimizer to act upon')
4826 self._update(es)
4827 return self
4828
4830 """Test termination criteria and update dictionary
4831
4832 """
4833 if es is None:
4834 es = self.es
4835 assert es is not None
4836
4837 if es.countiter == 0:
4838 self.__init__()
4839 return self
4840
4841 self.lastiter = es.countiter
4842 self.es = es
4843
4844 self.clear()
4845
4846 N = es.N
4847 opts = es.opts
4848 self.opts = opts
4849
4850
4851 self._addstop('ftarget',
4852 es.best.f < opts['ftarget'])
4853
4854 self._addstop('maxfevals',
4855 es.countevals - 1 >= opts['maxfevals'])
4856 self._addstop('maxiter',
4857
4858 es.countiter >= 1.0 * opts['maxiter'])
4859
4860
4861 self._addstop('tolx',
4862 all([es.sigma * xi < opts['tolx'] for xi in es.sigma_vec * es.pc]) and
4863 all([es.sigma * xi < opts['tolx'] for xi in es.sigma_vec * sqrt(es.dC)]))
4864 self._addstop('tolfacupx',
4865 any(es.sigma * es.sigma_vec * sqrt(es.dC) >
4866 es.sigma0 * es.sigma_vec0 * opts['tolfacupx']))
4867 self._addstop('tolfun',
4868 es.fit.fit[-1] - es.fit.fit[0] < opts['tolfun'] and
4869 max(es.fit.hist) - min(es.fit.hist) < opts['tolfun'])
4870 self._addstop('tolfunhist',
4871 len(es.fit.hist) > 9 and
4872 max(es.fit.hist) - min(es.fit.hist) < opts['tolfunhist'])
4873
4874
4875
4876
4877
4878
4879 l = int(max(( 1.0 * opts['tolstagnation'] / 5. / 2, len(es.fit.histbest) / 10)))
4880
4881
4882
4883 self._addstop('tolstagnation',
4884 1 < 3 and opts['tolstagnation'] and es.countiter > N * (5 + 100 / es.popsize) and
4885 len(es.fit.histbest) > 100 and 2 * l < len(es.fit.histbest) and
4886 np.median(es.fit.histmedian[:l]) >= np.median(es.fit.histmedian[l:2 * l]) and
4887 np.median(es.fit.histbest[:l]) >= np.median(es.fit.histbest[l:2 * l]))
4888
4889
4890 self._addstop('tolupsigma', opts['tolupsigma'] and
4891 es.sigma / np.max(es.D) > es.sigma0 * opts['tolupsigma'])
4892
4893 if 1 < 3:
4894
4895
4896 idx = np.where(es.mean == es.mean + 0.2 * es.sigma *
4897 es.sigma_vec * es.dC**0.5)[0]
4898 self._addstop('noeffectcoord', any(idx), idx)
4899
4900
4901
4902
4903
4904 if opts['CMA_diagonal'] is not True and es.countiter > opts['CMA_diagonal']:
4905 i = es.countiter % N
4906 self._addstop('noeffectaxis',
4907 sum(es.mean == es.mean + 0.1 * es.sigma * es.D[i] * es.B[:, i]) == N)
4908 self._addstop('conditioncov',
4909 es.D[-1] > 1e7 * es.D[0], 1e14)
4910
4911 self._addstop('callback', es.callbackstop)
4912 try:
4913 with open(self.opts['signals_filename'], 'r') as f:
4914 for line in f.readlines():
4915 words = line.split()
4916 if len(words) < 2 or words[0].startswith(('#', '%')):
4917 continue
4918 if words[0] == 'stop' and words[1] == 'now':
4919 if len(words) > 2 and not words[2].startswith(
4920 self.opts['verb_filenameprefix']):
4921 continue
4922 self._addstop('file_signal', True, "stop now")
4923 break
4924 except IOError:
4925 pass
4926
4927 if len(self):
4928 self._addstop('flat fitness: please (re)consider how to compute the fitness more elaborate',
4929 len(es.fit.hist) > 9 and
4930 max(es.fit.hist) == min(es.fit.hist))
4931 return self
4932
4933 - def _addstop(self, key, cond, val=None):
4934 if cond:
4935 self.stoplist.append(key)
4936 self[key] = val if val is not None \
4937 else self.opts.get(key, None)
4938
4940 for k in list(self):
4941 self.pop(k)
4942 self.stoplist = []
4943
4947 """strategy parameters like population size and learning rates.
4948
4949 Note:
4950 contrary to `CMAOptions`, `_CMAParameters` is not (yet) part of the
4951 "user-interface" and subject to future changes (it might become
4952 a `collections.namedtuple`)
4953
4954 Example
4955 -------
4956 >>> import cma
4957 >>> es = cma.CMAEvolutionStrategy(20 * [0.1], 1)
4958 (6_w,12)-CMA-ES (mu_w=3.7,w_1=40%) in dimension 20 (seed=504519190) # the seed is "random" by default
4959 >>>
4960 >>> type(es.sp) # sp contains the strategy parameters
4961 <class 'cma._CMAParameters'>
4962 >>>
4963 >>> es.sp.disp()
4964 {'CMA_on': True,
4965 'N': 20,
4966 'c1': 0.004181139918745593,
4967 'c1_sep': 0.034327992810300939,
4968 'cc': 0.17176721127681213,
4969 'cc_sep': 0.25259494835857677,
4970 'cmean': 1.0,
4971 'cmu': 0.0085149624979034746,
4972 'cmu_sep': 0.057796356229390715,
4973 'cs': 0.21434997799189287,
4974 'damps': 1.2143499779918929,
4975 'mu': 6,
4976 'mu_f': 6.0,
4977 'mueff': 3.7294589343030671,
4978 'popsize': 12,
4979 'rankmualpha': 0.3,
4980 'weights': array([ 0.40240294, 0.25338908, 0.16622156, 0.10437523, 0.05640348,
4981 0.01720771])}
4982 >>>
4983 >> es.sp == cma._CMAParameters(20, 12, cma.CMAOptions().evalall({'N': 20}))
4984 True
4985
4986 :See: `CMAOptions`, `CMAEvolutionStrategy`
4987
4988 """
4989 - def __init__(self, N, opts, ccovfac=1, verbose=True):
4990 """Compute strategy parameters, mainly depending on
4991 dimension and population size, by calling `set`
4992
4993 """
4994 self.N = N
4995 if ccovfac == 1:
4996 ccovfac = opts['CMA_on']
4997 self.popsize = None
4998 self.set(opts, ccovfac=ccovfac, verbose=verbose)
4999
5000 - def set(self, opts, popsize=None, ccovfac=1, verbose=True):
5001 """Compute strategy parameters as a function
5002 of dimension and population size """
5003
5004 alpha_cc = 1.0
5005
5006 def conedf(df, mu, N):
5007 """used for computing separable learning rate"""
5008 return 1. / (df + 2.*sqrt(df) + float(mu) / N)
5009
5010 def cmudf(df, mu, alphamu):
5011 """used for computing separable learning rate"""
5012 return (alphamu + mu - 2. + 1. / mu) / (df + 4.*sqrt(df) + mu / 2.)
5013
5014 sp = self
5015 N = sp.N
5016 if popsize:
5017 opts.evalall({'N':N, 'popsize':popsize})
5018 else:
5019 popsize = opts.evalall({'N':N})['popsize']
5020
5021 popsize = int(popsize + N** 0.0 - 1)
5022 sp.popsize = popsize
5023 if opts['CMA_mirrors'] < 0.5:
5024 sp.lam_mirr = int(0.5 + opts['CMA_mirrors'] * popsize)
5025 elif opts['CMA_mirrors'] > 1:
5026 sp.lam_mirr = int(0.5 + opts['CMA_mirrors'])
5027 else:
5028 sp.lam_mirr = int(0.5 + 0.16 * min((popsize, 2 * N + 2)) + 0.29)
5029
5030
5031
5032
5033
5034
5035 sp.mu_f = 0.5 * sp.popsize
5036 if opts['CMA_mu'] is not None:
5037 sp.mu_f = opts['CMA_mu']
5038 sp.mu = int(sp.mu_f + 0.499999)
5039 sp.mu = max((sp.mu, 1))
5040
5041
5042 if sp.mu > sp.popsize - 2 * sp.lam_mirr + 1:
5043 _print_warning("pairwise selection is not implemented, therefore " +
5044 " mu = %d > %d = %d - 2*%d + 1 = popsize - 2*mirr + 1 can produce a bias" % (
5045 sp.mu, sp.popsize - 2 * sp.lam_mirr + 1, sp.popsize, sp.lam_mirr))
5046 if sp.lam_mirr > sp.popsize // 2:
5047 raise _Error("fraction of mirrors in the population as read from option CMA_mirrors cannot be larger 0.5, " +
5048 "theoretically optimal is 0.159")
5049 sp.weights = log(max([sp.mu, sp.popsize / 2.0]) + 0.5) - log(1 + np.arange(sp.mu))
5050 sp.weights /= sum(sp.weights)
5051 sp.mueff = 1 / sum(sp.weights**2)
5052
5053
5054 b = 1.0
5055
5056 sp.cs = 1.0 * (sp.mueff + 2)**b / (N + (sp.mueff + 3)**b)
5057
5058
5059 b = 1.0
5060
5061 sp.cc = 1.0 * \
5062 (4 + alpha_cc * sp.mueff / N)**b / \
5063 (N**b + (4 + alpha_cc * 2 * sp.mueff / N)**b)
5064 sp.cc_sep = (1 + 1 / N + alpha_cc * sp.mueff / N) / (N**0.5 + 1 / N + alpha_cc * 2 * sp.mueff / N)
5065 if hasattr(opts['vv'], '__getitem__') and opts['vv'][0] == 'sweep_ccov1':
5066
5067 sp.cc = 1.0 * (4 + sp.mueff / N)**0.5 / ((N + 4)**0.5 + (2 * sp.mueff / N)**0.5)
5068 sp.rankmualpha = opts['CMA_rankmualpha']
5069
5070
5071 sp.c1 = ( 1.0 * ccovfac * min(1, sp.popsize / 6) *
5072
5073 2 / ((N + 1.3)** 2.0 + sp.mueff))
5074
5075 sp.c1_sep = ccovfac * conedf(N, sp.mueff, N)
5076 if opts['CMA_rankmu'] != 0:
5077
5078 alphacov, mu = 2.0 , sp.mueff
5079 sp.cmu = min(1 - sp.c1, ccovfac * alphacov *
5080
5081 (sp.rankmualpha + mu - 2 + 1 / mu) / ((N + 2)** 2.0 + alphacov * mu / 2))
5082 if hasattr(opts['vv'], '__getitem__') and opts['vv'][0] == 'sweep_ccov':
5083 sp.cmu = opts['vv'][1]
5084 sp.cmu_sep = min(1 - sp.c1_sep, ccovfac * cmudf(N, sp.mueff, sp.rankmualpha))
5085 else:
5086 sp.cmu = sp.cmu_sep = 0
5087 if hasattr(opts['vv'], '__getitem__') and opts['vv'][0] == 'sweep_ccov1':
5088 sp.c1 = opts['vv'][1]
5089
5090 sp.neg = _BlancClass()
5091 if opts['CMA_active'] and opts['CMA_on']:
5092
5093
5094 if 1 < 3:
5095 sp.neg.mu_f = popsize // 2
5096 sp.neg.weights = array([log(k) - log(popsize/2 + 1/2) for k in np.arange(np.ceil(popsize/2 + 1.1/2), popsize + .1)])
5097 sp.neg.mu = len(sp.neg.weights)
5098 sp.neg.weights /= sum(sp.neg.weights)
5099 sp.neg.mueff = 1 / sum(sp.neg.weights**2)
5100
5101 sp.neg.cmuexp = opts['CMA_active'] * 0.3 * sp.neg.mueff / ((N + 2)** 1.5 + 1.0 * sp.neg.mueff)
5102 if hasattr(opts['vv'], '__getitem__') and opts['vv'][0] == 'sweep_ccov_neg':
5103 sp.neg.cmuexp = opts['vv'][1]
5104
5105
5106
5107 assert sp.neg.mu >= sp.lam_mirr
5108
5109 else:
5110 sp.neg.cmuexp = 0
5111
5112 sp.CMA_on = sp.c1 + sp.cmu > 0
5113
5114
5115 if not opts['CMA_on'] and opts['CMA_on'] not in (None, [], (), ''):
5116 sp.CMA_on = False
5117
5118 mueff_exponent = 0.5
5119 if 1 < 3:
5120 mueff_exponent = opts['CSA_damp_mueff_exponent']
5121
5122 sp.damps = opts['CSA_dampfac'] * (0.5 +
5123 0.5 * min([1, (sp.lam_mirr / (0.159 * sp.popsize) - 1)**2])**1 +
5124 2 * max([0, ((sp.mueff - 1) / (N + 1))**mueff_exponent - 1]) + sp.cs
5125 )
5126 sp.cmean = float(opts['CMA_cmean'])
5127
5128
5129 if verbose:
5130 if not sp.CMA_on:
5131 print('covariance matrix adaptation turned off')
5132 if opts['CMA_mu'] != None:
5133 print('mu = %f' % (sp.mu_f))
5134
5135
5136
5139
5140 -def fmin(objective_function, x0, sigma0,
5141 options=None,
5142 args=(),
5143 gradf=None,
5144 restarts=0,
5145 restart_from_best='False',
5146 incpopsize=2,
5147 eval_initial_x=False,
5148 noise_handler=None,
5149 noise_change_sigma_exponent=1,
5150 noise_kappa_exponent=0,
5151 bipop=False):
5152 """functional interface to the stochastic optimizer CMA-ES
5153 for non-convex function minimization.
5154
5155 Calling Sequences
5156 =================
5157 ``fmin(objective_function, x0, sigma0)``
5158 minimizes `objective_function` starting at `x0` and with standard deviation
5159 `sigma0` (step-size)
5160 ``fmin(objective_function, x0, sigma0, options={'ftarget': 1e-5})``
5161 minimizes `objective_function` up to target function value 1e-5, which
5162 is typically useful for benchmarking.
5163 ``fmin(objective_function, x0, sigma0, args=('f',))``
5164 minimizes `objective_function` called with an additional argument ``'f'``.
5165 ``fmin(objective_function, x0, sigma0, options={'ftarget':1e-5, 'popsize':40})``
5166 uses additional options ``ftarget`` and ``popsize``
5167 ``fmin(objective_function, esobj, None, options={'maxfevals': 1e5})``
5168 uses the `CMAEvolutionStrategy` object instance `esobj` to optimize
5169 `objective_function`, similar to `esobj.optimize()`.
5170
5171 Arguments
5172 =========
5173 `objective_function`
5174 function to be minimized. Called as ``objective_function(x,
5175 *args)``. `x` is a one-dimensional `numpy.ndarray`.
5176 `objective_function` can return `numpy.NaN`,
5177 which is interpreted as outright rejection of solution `x`
5178 and invokes an immediate resampling and (re-)evaluation
5179 of a new solution not counting as function evaluation.
5180 `x0`
5181 list or `numpy.ndarray`, initial guess of minimum solution
5182 before the application of the geno-phenotype transformation
5183 according to the ``transformation`` option. It can also be
5184 a string holding a Python expression that is evaluated
5185 to yield the initial guess - this is important in case
5186 restarts are performed so that they start from different
5187 places. Otherwise `x0` can also be a `cma.CMAEvolutionStrategy`
5188 object instance, in that case `sigma0` can be ``None``.
5189 `sigma0`
5190 scalar, initial standard deviation in each coordinate.
5191 `sigma0` should be about 1/4th of the search domain width
5192 (where the optimum is to be expected). The variables in
5193 `objective_function` should be scaled such that they
5194 presumably have similar sensitivity.
5195 See also option `scaling_of_variables`.
5196 `options`
5197 a dictionary with additional options passed to the constructor
5198 of class ``CMAEvolutionStrategy``, see ``cma.CMAOptions()``
5199 for a list of available options.
5200 ``args=()``
5201 arguments to be used to call the `objective_function`
5202 ``gradf``
5203 gradient of f, where ``len(gradf(x, *args)) == len(x)``.
5204 `gradf` is called once in each iteration if
5205 ``gradf is not None``.
5206 ``restarts=0``
5207 number of restarts with increasing population size, see also
5208 parameter `incpopsize`, implementing the IPOP-CMA-ES restart
5209 strategy, see also parameter `bipop`; to restart from
5210 different points (recommended), pass `x0` as a string.
5211 ``restart_from_best=False``
5212 which point to restart from
5213 ``incpopsize=2``
5214 multiplier for increasing the population size `popsize` before
5215 each restart
5216 ``eval_initial_x=None``
5217 evaluate initial solution, for `None` only with elitist option
5218 ``noise_handler=None``
5219 a ``NoiseHandler`` instance or ``None``, a simple usecase is
5220 ``cma.fmin(f, 6 * [1], 1, noise_handler=cma.NoiseHandler(6))``
5221 see ``help(cma.NoiseHandler)``.
5222 ``noise_change_sigma_exponent=1``
5223 exponent for sigma increment for additional noise treatment
5224 ``noise_evaluations_as_kappa``
5225 instead of applying reevaluations, the "number of evaluations"
5226 is (ab)used as scaling factor kappa (experimental).
5227 ``bipop``
5228 if True, run as BIPOP-CMA-ES; BIPOP is a special restart
5229 strategy switching between two population sizings - small
5230 (like the default CMA, but with more focused search) and
5231 large (progressively increased as in IPOP). This makes the
5232 algorithm perform well both on functions with many regularly
5233 or irregularly arranged local optima (the latter by frequently
5234 restarting with small populations). For the `bipop` parameter
5235 to actually take effect, also select non-zero number of
5236 (IPOP) restarts; the recommended setting is ``restarts<=9``
5237 and `x0` passed as a string. Note that small-population
5238 restarts do not count into the total restart count.
5239
5240 Optional Arguments
5241 ==================
5242 All values in the `options` dictionary are evaluated if they are of
5243 type `str`, besides `verb_filenameprefix`, see class `CMAOptions` for
5244 details. The full list is available via ``cma.CMAOptions()``.
5245
5246 >>> import cma
5247 >>> cma.CMAOptions()
5248
5249 Subsets of options can be displayed, for example like
5250 ``cma.CMAOptions('tol')``, or ``cma.CMAOptions('bound')``,
5251 see also class `CMAOptions`.
5252
5253 Return
5254 ======
5255 Return the list provided by `CMAEvolutionStrategy.result()` appended
5256 with termination conditions, an `OOOptimizer` and a `BaseDataLogger`::
5257
5258 res = es.result() + (es.stop(), es, logger)
5259
5260 where
5261 - ``res[0]`` (``xopt``) -- best evaluated solution
5262 - ``res[1]`` (``fopt``) -- respective function value
5263 - ``res[2]`` (``evalsopt``) -- respective number of function evaluations
5264 - ``res[3]`` (``evals``) -- number of overall conducted objective function evaluations
5265 - ``res[4]`` (``iterations``) -- number of overall conducted iterations
5266 - ``res[5]`` (``xmean``) -- mean of the final sample distribution
5267 - ``res[6]`` (``stds``) -- effective stds of the final sample distribution
5268 - ``res[-3]`` (``stop``) -- termination condition(s) in a dictionary
5269 - ``res[-2]`` (``cmaes``) -- class `CMAEvolutionStrategy` instance
5270 - ``res[-1]`` (``logger``) -- class `CMADataLogger` instance
5271
5272 Details
5273 =======
5274 This function is an interface to the class `CMAEvolutionStrategy`. The
5275 latter class should be used when full control over the iteration loop
5276 of the optimizer is desired.
5277
5278 Examples
5279 ========
5280 The following example calls `fmin` optimizing the Rosenbrock function
5281 in 10-D with initial solution 0.1 and initial step-size 0.5. The
5282 options are specified for the usage with the `doctest` module.
5283
5284 >>> import cma
5285 >>> # cma.CMAOptions() # returns all possible options
5286 >>> options = {'CMA_diagonal':100, 'seed':1234, 'verb_time':0}
5287 >>>
5288 >>> res = cma.fmin(cma.fcts.rosen, [0.1] * 10, 0.5, options)
5289 (5_w,10)-CMA-ES (mu_w=3.2,w_1=45%) in dimension 10 (seed=1234)
5290 Covariance matrix is diagonal for 10 iterations (1/ccov=29.0)
5291 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
5292 1 10 1.264232686260072e+02 1.1e+00 4.40e-01 4e-01 4e-01
5293 2 20 1.023929748193649e+02 1.1e+00 4.00e-01 4e-01 4e-01
5294 3 30 1.214724267489674e+02 1.2e+00 3.70e-01 3e-01 4e-01
5295 100 1000 6.366683525319511e+00 6.2e+00 2.49e-02 9e-03 3e-02
5296 200 2000 3.347312410388666e+00 1.2e+01 4.52e-02 8e-03 4e-02
5297 300 3000 1.027509686232270e+00 1.3e+01 2.85e-02 5e-03 2e-02
5298 400 4000 1.279649321170636e-01 2.3e+01 3.53e-02 3e-03 3e-02
5299 500 5000 4.302636076186532e-04 4.6e+01 4.78e-03 3e-04 5e-03
5300 600 6000 6.943669235595049e-11 5.1e+01 5.41e-06 1e-07 4e-06
5301 650 6500 5.557961334063003e-14 5.4e+01 1.88e-07 4e-09 1e-07
5302 termination on tolfun : 1e-11
5303 final/bestever f-value = 5.55796133406e-14 2.62435631419e-14
5304 mean solution: [ 1. 1.00000001 1. 1.
5305 1. 1.00000001 1.00000002 1.00000003 ...]
5306 std deviation: [ 3.9193387e-09 3.7792732e-09 4.0062285e-09 4.6605925e-09
5307 5.4966188e-09 7.4377745e-09 1.3797207e-08 2.6020765e-08 ...]
5308 >>>
5309 >>> print('best solutions fitness = %f' % (res[1]))
5310 best solutions fitness = 2.62435631419e-14
5311 >>> assert res[1] < 1e-12
5312
5313 The above call is pretty much equivalent with the slightly more
5314 verbose call::
5315
5316 es = cma.CMAEvolutionStrategy([0.1] * 10, 0.5,
5317 options=options).optimize(cma.fcts.rosen)
5318
5319 The following example calls `fmin` optimizing the Rastrigin function
5320 in 3-D with random initial solution in [-2,2], initial step-size 0.5
5321 and the BIPOP restart strategy (that progressively increases population).
5322 The options are specified for the usage with the `doctest` module.
5323
5324 >>> import cma
5325 >>> # cma.CMAOptions() # returns all possible options
5326 >>> options = {'seed':12345, 'verb_time':0, 'ftarget': 1e-8}
5327 >>>
5328 >>> res = cma.fmin(cma.fcts.rastrigin, '2. * np.random.rand(3) - 1', 0.5,
5329 ... options, restarts=9, bipop=True)
5330 (3_w,7)-aCMA-ES (mu_w=2.3,w_1=58%) in dimension 3 (seed=12345)
5331 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
5332 1 7 1.633489455763566e+01 1.0e+00 4.35e-01 4e-01 4e-01
5333 2 14 9.762462950258016e+00 1.2e+00 4.12e-01 4e-01 4e-01
5334 3 21 2.461107851413725e+01 1.4e+00 3.78e-01 3e-01 4e-01
5335 100 700 9.949590571272680e-01 1.7e+00 5.07e-05 3e-07 5e-07
5336 123 861 9.949590570932969e-01 1.3e+00 3.93e-06 9e-09 1e-08
5337 termination on tolfun=1e-11
5338 final/bestever f-value = 9.949591e-01 9.949591e-01
5339 mean solution: [ 9.94958638e-01 -7.19265205e-10 2.09294450e-10]
5340 std deviation: [ 8.71497860e-09 8.58994807e-09 9.85585654e-09]
5341 [...]
5342 (4_w,9)-aCMA-ES (mu_w=2.8,w_1=49%) in dimension 3 (seed=12349)
5343 Iterat #Fevals function value axis ratio sigma minstd maxstd min:sec
5344 1 5342.0 2.114883315350800e+01 1.0e+00 3.42e-02 3e-02 4e-02
5345 2 5351.0 1.810102940125502e+01 1.4e+00 3.79e-02 3e-02 4e-02
5346 3 5360.0 1.340222457448063e+01 1.4e+00 4.58e-02 4e-02 6e-02
5347 50 5783.0 8.631491965616078e-09 1.6e+00 2.01e-04 8e-06 1e-05
5348 termination on ftarget=1e-08 after 4 restarts
5349 final/bestever f-value = 8.316963e-09 8.316963e-09
5350 mean solution: [ -3.10652459e-06 2.77935436e-06 -4.95444519e-06]
5351 std deviation: [ 1.02825265e-05 8.08348144e-06 8.47256408e-06]
5352
5353 In either case, the method::
5354
5355 cma.plot();
5356
5357 (based on `matplotlib.pyplot`) produces a plot of the run and, if
5358 necessary::
5359
5360 cma.show()
5361
5362 shows the plot in a window. Finally::
5363
5364 cma.savefig('myfirstrun') # savefig from matplotlib.pyplot
5365
5366 will save the figure in a png.
5367
5368 We can use the gradient like
5369
5370 >>> import cma
5371 >>> res = cma.fmin(cma.fcts.rosen, np.zeros(10), 0.1,
5372 ... options = {'ftarget':1e-8,},
5373 ... gradf=cma.fcts.grad_rosen,
5374 ... )
5375 >>> assert cma.fcts.rosen(res[0]) < 1e-8
5376 >>> assert res[2] < 3600 # 1% are > 3300
5377 >>> assert res[3] < 3600 # 1% are > 3300
5378
5379 :See: `CMAEvolutionStrategy`, `OOOptimizer.optimize(), `plot()`,
5380 `CMAOptions`, `scipy.optimize.fmin()`
5381
5382 """
5383 if 1 < 3:
5384 if not objective_function:
5385 return CMAOptions()
5386
5387 fmin_options = locals().copy()
5388 del fmin_options['objective_function']
5389 del fmin_options['x0']
5390 del fmin_options['sigma0']
5391 del fmin_options['options']
5392 del fmin_options['args']
5393
5394 if options is None:
5395 options = cma_default_options
5396 CMAOptions().check_attributes(options)
5397
5398 opts = CMAOptions(options.copy()).complement()
5399
5400
5401 runs_with_small = 0
5402 small_i = []
5403 large_i = []
5404 popsize0 = None
5405 maxiter0 = None
5406 base_evals = 0
5407
5408 irun = 0
5409 best = BestSolution()
5410 while True:
5411 sigma_factor = 1
5412
5413
5414 if not bipop:
5415
5416
5417 if irun > 0:
5418 popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
5419 opts['popsize'] = popsize0 * popsize_multiplier
5420
5421 elif irun == 0:
5422
5423
5424
5425
5426 poptype = 'small'
5427
5428 elif sum(small_i) < sum(large_i):
5429
5430 poptype = 'small'
5431 runs_with_small += 1
5432
5433 sigma_factor = 0.01 ** np.random.uniform()
5434 popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
5435 opts['popsize'] = np.floor(popsize0 * popsize_multiplier ** (np.random.uniform() ** 2))
5436 opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize'])
5437
5438
5439 else:
5440
5441
5442 poptype = 'large'
5443
5444 popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
5445 opts['popsize'] = popsize0 * popsize_multiplier
5446 opts['maxiter'] = maxiter0
5447
5448
5449
5450 if irun == 0 and isinstance(x0, CMAEvolutionStrategy):
5451 es = x0
5452 x0 = es.inputargs['x0']
5453 if isscalar(sigma0) and isfinite(sigma0) and sigma0 > 0:
5454 es.sigma = sigma0
5455
5456 sigma0 = es.inputargs['sigma0']
5457 if options is not None:
5458 es.opts.set(options)
5459
5460 else:
5461 if irun and eval(str(fmin_options['restart_from_best'])):
5462 print_warning('CAVE: restart_from_best is often not useful',
5463 verbose=opts['verbose'])
5464 es = CMAEvolutionStrategy(best.x, sigma_factor * sigma0, opts)
5465 else:
5466 es = CMAEvolutionStrategy(x0, sigma_factor * sigma0, opts)
5467 if eval_initial_x or es.opts['CMA_elitist'] == 'initial' \
5468 or (es.opts['CMA_elitist'] and eval_initial_x is None):
5469 x = es.gp.pheno(es.mean,
5470 into_bounds=es.boundary_handler.repair,
5471 archive=es.sent_solutions)
5472 es.best.update([x], es.sent_solutions,
5473 [objective_function(x, *args)], 1)
5474 es.countevals += 1
5475
5476 opts = es.opts
5477
5478 fmin_opts = CMAOptions(fmin_options.copy(), unchecked=True)
5479 for k in fmin_opts:
5480
5481
5482 fmin_opts.eval(k, loc={'N': es.N,
5483 'popsize': opts['popsize']},
5484 correct_key=False)
5485
5486 append = opts['verb_append'] or es.countiter > 0 or irun > 0
5487
5488
5489 logger = CMADataLogger(opts['verb_filenameprefix'],
5490 opts['verb_log'])
5491 logger.register(es, append).add()
5492 es.logger = logger
5493
5494 if noise_handler:
5495 noisehandler = noise_handler
5496 noise_handling = True
5497 if fmin_opts['noise_change_sigma_exponent'] > 0:
5498 es.opts['tolfacupx'] = inf
5499 else:
5500 noisehandler = NoiseHandler(es.N, 0)
5501 noise_handling = False
5502 es.noise_handler = noisehandler
5503
5504
5505
5506 if 1 < 3:
5507 while not es.stop():
5508
5509 X, fit = es.ask_and_eval(objective_function, args, gradf=gradf,
5510 evaluations=noisehandler.evaluations,
5511 aggregation=np.median)
5512
5513
5514 es.tell(X, fit)
5515 if noise_handling:
5516 es.sigma *= noisehandler(X, fit, objective_function, es.ask,
5517 args=args)**fmin_opts['noise_change_sigma_exponent']
5518
5519 es.countevals += noisehandler.evaluations_just_done
5520
5521 if noisehandler.maxevals > noisehandler.minevals:
5522 es.more_to_write.append(noisehandler.get_evaluations())
5523 if 1 < 3:
5524 es.sp.cmean *= exp(-noise_kappa_exponent * np.tanh(noisehandler.noiseS))
5525 if es.sp.cmean > 1:
5526 es.sp.cmean = 1
5527
5528 es.disp()
5529 logger.add(
5530 modulo=1 if es.stop() and logger.modulo else None)
5531 if (opts['verb_log'] and opts['verb_plot'] and
5532 (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())):
5533 logger.plot(324)
5534
5535
5536 mean_pheno = es.gp.pheno(es.mean, into_bounds=es.boundary_handler.repair, archive=es.sent_solutions)
5537 fmean = objective_function(mean_pheno, *args)
5538 es.countevals += 1
5539
5540 es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals)
5541 best.update(es.best, es.sent_solutions)
5542
5543
5544 this_evals = es.countevals - base_evals
5545 base_evals = es.countevals
5546
5547
5548
5549 if irun == 0:
5550 popsize0 = opts['popsize']
5551 maxiter0 = opts['maxiter']
5552
5553
5554
5555 if bipop:
5556 if poptype == 'small':
5557 small_i.append(this_evals)
5558 else:
5559 large_i.append(this_evals)
5560
5561
5562 if opts['verb_disp']:
5563 es.result_pretty(irun, time.asctime(time.localtime()),
5564 best.f)
5565
5566 irun += 1
5567
5568
5569 if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \
5570 or 'maxfevals' in es.stop(check=False):
5571 break
5572 opts['verb_append'] = es.countevals
5573 opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize
5574 opts['seed'] += 1
5575
5576
5577
5578
5579 if 1 < 3:
5580 if irun:
5581 es.best.update(best)
5582
5583 return es.result() + (es.stop(), es, logger)
5584
5585 else:
5586 return (best.x.copy(), best.f, es.countevals,
5587 dict((('stopdict', _CMAStopDict(es._stopdict))
5588 , ('mean', es.gp.pheno(es.mean))
5589 , ('std', es.sigma * es.sigma_vec * sqrt(es.dC) * es.gp.scales)
5590 , ('out', es.out)
5591 , ('opts', es.opts)
5592 , ('cma', es)
5593 , ('inputargs', es.inputargs)
5594 ))
5595 )
5596
5597
5598 else:
5599 if eval(str(options['verb_disp'])) > 0:
5600 print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception')
5601 raise
5602
5607 """"abstract" base class for a data logger that can be used with an `OOOptimizer`
5608
5609 Details: attribute `modulo` is used in ``OOOptimizer.optimize``
5610
5611 """
5612 - def add(self, optim=None, more_data=[]):
5613 """abstract method, add a "data point" from the state of `optim` into the
5614 logger, the argument `optim` can be omitted if it was `register()`-ed before,
5615 acts like an event handler"""
5616 raise NotImplementedError
5618 """abstract method, register an optimizer `optim`, only needed if `add()` is
5619 called without a value for the `optim` argument"""
5620 self.optim = optim
5622 """display some data trace (not implemented)"""
5623 print('method BaseDataLogger.disp() not implemented, to be done in subclass ' + str(type(self)))
5625 """plot data (not implemented)"""
5626 print('method BaseDataLogger.plot() is not implemented, to be done in subclass ' + str(type(self)))
5628 """return logged data in a dictionary (not implemented)"""
5629 print('method BaseDataLogger.data() is not implemented, to be done in subclass ' + str(type(self)))
5630
5635 """data logger for class `CMAEvolutionStrategy`. The logger is
5636 identified by its name prefix and (over-)writes or reads according
5637 data files. Therefore, the logger must be considered as *global* variable
5638 with unpredictable side effects, if two loggers with the same name
5639 and on the same working folder are used at the same time.
5640
5641 Examples
5642 ========
5643 ::
5644
5645 import cma
5646 es = cma.CMAEvolutionStrategy(...)
5647 logger = cma.CMADataLogger().register(es)
5648 while not es.stop():
5649 ...
5650 logger.add() # add can also take an argument
5651
5652 logger.plot() # or a short cut can be used:
5653 cma.plot() # plot data from logger with default name
5654
5655
5656 logger2 = cma.CMADataLogger('just_another_filename_prefix').load()
5657 logger2.plot()
5658 logger2.disp()
5659
5660 ::
5661
5662 import cma
5663 from matplotlib.pylab import *
5664 res = cma.fmin(cma.Fcts.sphere, rand(10), 1e-0)
5665 logger = res[-1] # the CMADataLogger
5666 logger.load() # by "default" data are on disk
5667 semilogy(logger.f[:,0], logger.f[:,5]) # plot f versus iteration, see file header
5668 show()
5669
5670 Details
5671 =======
5672 After loading data, the logger has the attributes `xmean`, `xrecent`,
5673 `std`, `f`, `D` and `corrspec` corresponding to ``xmean``,
5674 ``xrecentbest``, ``stddev``, ``fit``, ``axlen`` and ``axlencorr``
5675 filename trails.
5676
5677 :See: `disp()`, `plot()`
5678
5679 """
5680 default_prefix = 'outcmaes'
5681
5682
5683
5685 """initialize logging of data from a `CMAEvolutionStrategy`
5686 instance, default ``modulo=1`` means logging with each call
5687
5688 """
5689
5690
5691
5692 self.name_prefix = name_prefix if name_prefix \
5693 else CMADataLogger.default_prefix
5694 if isinstance(self.name_prefix, CMAEvolutionStrategy):
5695 self.name_prefix = self.name_prefix.opts.eval(
5696 'verb_filenameprefix')
5697 self.file_names = ('axlen', 'axlencorr', 'fit', 'stddev', 'xmean',
5698 'xrecentbest')
5699 """used in load, however hard-coded in add"""
5700 self.key_names = ('D', 'corrspec', 'f', 'std', 'xmean', 'xrecent')
5701 """used in load, however hard-coded in plot"""
5702 self._key_names_with_annotation = ('std', 'xmean', 'xrecent')
5703 """used in load to add one data row to be modified in plot"""
5704 self.modulo = modulo
5705 """how often to record data, allows calling `add` without args"""
5706 self.append = append
5707 """append to previous data"""
5708 self.counter = 0
5709 """number of calls to `add`"""
5710 self.last_iteration = 0
5711 self.registered = False
5712 self.last_correlation_spectrum = None
5713 self._eigen_counter = 1
5715 """return dictionary with data.
5716
5717 If data entries are None or incomplete, consider calling
5718 ``.load().data()`` to (re-)load the data from files first.
5719
5720 """
5721 d = {}
5722 for name in self.key_names:
5723 d[name] = self.__dict__.get(name, None)
5724 return d
5725 - def register(self, es, append=None, modulo=None):
5726 """register a `CMAEvolutionStrategy` instance for logging,
5727 ``append=True`` appends to previous data logged under the same name,
5728 by default previous data are overwritten.
5729
5730 """
5731 if not isinstance(es, CMAEvolutionStrategy):
5732 raise TypeError("only class CMAEvolutionStrategy can be " +
5733 "registered for logging")
5734 self.es = es
5735 if append is not None:
5736 self.append = append
5737 if modulo is not None:
5738 self.modulo = modulo
5739 self.registered = True
5740 return self
5741
5743 """reset logger, overwrite original files, `modulo`: log only every modulo call"""
5744 if modulo is not None:
5745 self.modulo = modulo
5746 try:
5747 es = self.es
5748 except AttributeError:
5749 pass
5750 raise _Error('call register() before initialize()')
5751
5752 self.counter = 0
5753 self.last_iteration = 0
5754
5755
5756 fn = self.name_prefix + 'fit.dat'
5757 strseedtime = 'seed=%d, %s' % (es.opts['seed'], time.asctime())
5758
5759 try:
5760 with open(fn, 'w') as f:
5761 f.write('% # columns="iteration, evaluation, sigma, axis ratio, ' +
5762 'bestever, best, median, worst objective function value, ' +
5763 'further objective values of best", ' +
5764 strseedtime +
5765
5766 '\n')
5767 except (IOError, OSError):
5768 print('could not open file ' + fn)
5769
5770 fn = self.name_prefix + 'axlen.dat'
5771 try:
5772 with open(fn, 'w') as f:
5773 f.write('% columns="iteration, evaluation, sigma, ' +
5774 'max axis length, ' +
5775 ' min axis length, all principle axes lengths ' +
5776 ' (sorted square roots of eigenvalues of C)", ' +
5777 strseedtime +
5778 '\n')
5779 except (IOError, OSError):
5780 print('could not open/write file ' + fn)
5781 fn = self.name_prefix + 'axlencorr.dat'
5782 try:
5783 with open(fn, 'w') as f:
5784 f.write('% columns="iteration, evaluation, min max(neg(.)) min(pos(.))' +
5785 ' max correlation, correlation matrix principle axes lengths ' +
5786 ' (sorted square roots of eigenvalues of correlation matrix)", ' +
5787 strseedtime +
5788 '\n')
5789 except (IOError, OSError):
5790 print('could not open file ' + fn)
5791 fn = self.name_prefix + 'stddev.dat'
5792 try:
5793 with open(fn, 'w') as f:
5794 f.write('% # columns=["iteration, evaluation, sigma, void, void, ' +
5795 ' stds==sigma*sqrt(diag(C))", ' +
5796 strseedtime +
5797 '\n')
5798 except (IOError, OSError):
5799 print('could not open file ' + fn)
5800
5801 fn = self.name_prefix + 'xmean.dat'
5802 try:
5803 with open(fn, 'w') as f:
5804 f.write('% # columns="iteration, evaluation, void, void, void, xmean", ' +
5805 strseedtime)
5806 f.write(' # scaling_of_variables: ')
5807 if np.size(es.gp.scales) > 1:
5808 f.write(' '.join(map(str, es.gp.scales)))
5809 else:
5810 f.write(str(es.gp.scales))
5811 f.write(', typical_x: ')
5812 if np.size(es.gp.typical_x) > 1:
5813 f.write(' '.join(map(str, es.gp.typical_x)))
5814 else:
5815 f.write(str(es.gp.typical_x))
5816 f.write('\n')
5817 except (IOError, OSError):
5818 print('could not open/write file ' + fn)
5819
5820 fn = self.name_prefix + 'xrecentbest.dat'
5821 try:
5822 with open(fn, 'w') as f:
5823 f.write('% # iter+eval+sigma+0+fitness+xbest, ' +
5824 strseedtime +
5825 '\n')
5826 except (IOError, OSError):
5827 print('could not open/write file ' + fn)
5828
5829 return self
5830
5831
5832 - def load(self, filenameprefix=None):
5833 """load (or reload) data from output files, `load()` is called in
5834 `plot()` and `disp()`.
5835
5836 Argument `filenameprefix` is the filename prefix of data to be
5837 loaded (six files), by default ``'outcmaes'``.
5838
5839 Return self with (added) attributes `xrecent`, `xmean`,
5840 `f`, `D`, `std`, 'corrspec'
5841
5842 """
5843 if not filenameprefix:
5844 filenameprefix = self.name_prefix
5845 assert len(self.file_names) == len(self.key_names)
5846 for i in rglen((self.file_names)):
5847 fn = filenameprefix + self.file_names[i] + '.dat'
5848 try:
5849 self.__dict__[self.key_names[i]] = _fileToMatrix(fn)
5850 except:
5851 _print_warning('reading from file "' + fn + '" failed',
5852 'load', 'CMADataLogger')
5853 try:
5854 if self.key_names[i] in self._key_names_with_annotation:
5855
5856 self.__dict__[self.key_names[i]].append(
5857 self.__dict__[self.key_names[i]][-1])
5858 self.__dict__[self.key_names[i]] = \
5859 array(self.__dict__[self.key_names[i]], copy=False)
5860 except:
5861 _print_warning('no data for %s' % fn, 'load',
5862 'CMADataLogger')
5863 return self
5864
5865 - def add(self, es=None, more_data=[], modulo=None):
5866 """append some logging data from `CMAEvolutionStrategy` class instance `es`,
5867 if ``number_of_times_called % modulo`` equals to zero, never if ``modulo==0``.
5868
5869 The sequence ``more_data`` must always have the same length.
5870
5871 When used for a different optimizer class, this function can be
5872 (easily?) adapted by changing the assignments under INTERFACE
5873 in the implemention.
5874
5875 """
5876 mod = modulo if modulo is not None else self.modulo
5877 self.counter += 1
5878 if mod == 0 or (self.counter > 3 and (self.counter - 1) % mod):
5879 return
5880 if es is None:
5881 try:
5882 es = self.es
5883 except AttributeError :
5884 raise _Error('call `add` with argument `es` or ``register(es)`` before ``add()``')
5885 elif not self.registered:
5886 self.register(es)
5887
5888 if 1 < 3:
5889 if self.counter == 1 and not self.append and self.modulo != 0:
5890 self.initialize()
5891 self.counter = 1
5892
5893
5894 if not isinstance(es, CMAEvolutionStrategy):
5895 _print_warning('type CMAEvolutionStrategy expected, found '
5896 + str(type(es)), 'add', 'CMADataLogger')
5897 evals = es.countevals
5898 iteration = es.countiter
5899 eigen_decompositions = es.count_eigen
5900 sigma = es.sigma
5901 axratio = es.D.max() / es.D.min()
5902 xmean = es.mean
5903 fmean_noise_free = es.fmean_noise_free
5904 fmean = es.fmean
5905
5906 try:
5907 besteverf = es.best.f
5908 bestf = es.fit.fit[0]
5909 worstf = es.fit.fit[-1]
5910 medianf = es.fit.fit[es.sp.popsize // 2]
5911 except:
5912 if iteration > 0:
5913 raise
5914 try:
5915 xrecent = es.best.last.x
5916 except:
5917 xrecent = None
5918 maxD = es.D.max()
5919 minD = es.D.min()
5920 diagD = es.D
5921 diagC = es.sigma * es.sigma_vec * sqrt(es.dC)
5922 more_to_write = es.more_to_write
5923 es.more_to_write = []
5924
5925
5926 try:
5927
5928 if iteration > self.last_iteration:
5929 fn = self.name_prefix + 'fit.dat'
5930 with open(fn, 'a') as f:
5931 f.write(str(iteration) + ' '
5932 + str(evals) + ' '
5933 + str(sigma) + ' '
5934 + str(axratio) + ' '
5935 + str(besteverf) + ' '
5936 + '%.16e' % bestf + ' '
5937 + str(medianf) + ' '
5938 + str(worstf) + ' '
5939
5940
5941
5942 + ' '.join(str(i) for i in more_to_write) + ' '
5943 + ' '.join(str(i) for i in more_data) + ' '
5944 + '\n')
5945
5946 fn = self.name_prefix + 'axlen.dat'
5947 if 1 < 3:
5948 with open(fn, 'a') as f:
5949 f.write(str(iteration) + ' '
5950 + str(evals) + ' '
5951 + str(sigma) + ' '
5952 + str(maxD) + ' '
5953 + str(minD) + ' '
5954 + ' '.join(map(str, diagD))
5955 + '\n')
5956
5957 if 1 < 3:
5958 fn = self.name_prefix + 'axlencorr.dat'
5959 c = es.correlation_matrix()
5960 if c is not None:
5961
5962 if self._eigen_counter < eigen_decompositions / 2:
5963 self.last_correlation_spectrum = \
5964 sorted(es.opts['CMA_eigenmethod'](c)[0]**0.5)
5965 self._eigen_counter += 1
5966 if self.last_correlation_spectrum is None:
5967 self.last_correlation_spectrum = len(diagD) * [1]
5968 c = c[c < 1 - 1e-14]
5969 c[c > 1 - 1e-14] = 1 - 1e-14
5970 c[c < -1 + 1e-14] = -1 + 1e-14
5971 c_min = np.min(c)
5972 c_max = np.max(c)
5973 if np.min(abs(c)) == 0:
5974 c_medminus = 0
5975 c_medplus = 0
5976 else:
5977 c_medminus = c[np.argmin(1/c)]
5978 c_medplus = c[np.argmax(1/c)]
5979
5980 with open(fn, 'a') as f:
5981 f.write(str(iteration) + ' '
5982 + str(evals) + ' '
5983 + str(c_min) + ' '
5984 + str(c_medminus) + ' '
5985 + str(c_medplus) + ' '
5986 + str(c_max) + ' '
5987 + ' '.join(map(str,
5988 self.last_correlation_spectrum))
5989 + '\n')
5990
5991
5992 fn = self.name_prefix + 'stddev.dat'
5993 with open(fn, 'a') as f:
5994 f.write(str(iteration) + ' '
5995 + str(evals) + ' '
5996 + str(sigma) + ' '
5997 + '0 0 '
5998 + ' '.join(map(str, diagC))
5999 + '\n')
6000
6001 fn = self.name_prefix + 'xmean.dat'
6002 with open(fn, 'a') as f:
6003 f.write(str(iteration) + ' '
6004 + str(evals) + ' '
6005
6006 + '0 '
6007 + str(fmean_noise_free) + ' '
6008 + str(fmean) + ' '
6009
6010 + ' '.join(map(str, xmean))
6011 + '\n')
6012
6013 fn = self.name_prefix + 'xrecentbest.dat'
6014 if iteration > 0 and xrecent is not None:
6015 with open(fn, 'a') as f:
6016 f.write(str(iteration) + ' '
6017 + str(evals) + ' '
6018 + str(sigma) + ' '
6019 + '0 '
6020 + str(bestf) + ' '
6021 + ' '.join(map(str, xrecent))
6022 + '\n')
6023 except (IOError, OSError):
6024 if iteration <= 1:
6025 _print_warning(('could not open/write file %s: ' % fn,
6026 sys.exc_info()))
6027 self.last_iteration = iteration
6028
6030 pyplot.close(self.fighandle)
6031
6032 - def save_to(self, nameprefix, switch=False):
6033 """saves logger data to a different set of files, for
6034 ``switch=True`` also the loggers name prefix is switched to
6035 the new value
6036
6037 """
6038 if not nameprefix or not isinstance(nameprefix, basestring):
6039 raise _Error('filename prefix must be a nonempty string')
6040
6041 if nameprefix == self.default_prefix:
6042 raise _Error('cannot save to default name "' + nameprefix + '...", chose another name')
6043
6044 if nameprefix == self.name_prefix:
6045 return
6046
6047 for name in self.file_names:
6048 open(nameprefix + name + '.dat', 'w').write(open(self.name_prefix + name + '.dat').read())
6049
6050 if switch:
6051 self.name_prefix = nameprefix
6053 """keep only data of `iteration_indices`"""
6054 dat = self
6055 iteridx = iteration_indices
6056 dat.f = dat.f[np.where([x in iteridx for x in dat.f[:, 0]])[0], :]
6057 dat.D = dat.D[np.where([x in iteridx for x in dat.D[:, 0]])[0], :]
6058 try:
6059 iteridx = list(iteridx)
6060 iteridx.append(iteridx[-1])
6061 except:
6062 pass
6063 dat.std = dat.std[np.where([x in iteridx
6064 for x in dat.std[:, 0]])[0], :]
6065 dat.xmean = dat.xmean[np.where([x in iteridx
6066 for x in dat.xmean[:, 0]])[0], :]
6067 try:
6068 dat.xrecent = dat.x[np.where([x in iteridx for x in
6069 dat.xrecent[:, 0]])[0], :]
6070 except AttributeError:
6071 pass
6072 try:
6073 dat.corrspec = dat.x[np.where([x in iteridx for x in
6074 dat.corrspec[:, 0]])[0], :]
6075 except AttributeError:
6076 pass
6077 - def plot(self, fig=None, iabscissa=1, iteridx=None,
6078 plot_mean=False,
6079 foffset=1e-19, x_opt=None, fontsize=9):
6080 """plot data from a `CMADataLogger` (using the files written
6081 by the logger).
6082
6083 Arguments
6084 ---------
6085 `fig`
6086 figure number, by default 325
6087 `iabscissa`
6088 ``0==plot`` versus iteration count,
6089 ``1==plot`` versus function evaluation number
6090 `iteridx`
6091 iteration indices to plot
6092
6093 Return `CMADataLogger` itself.
6094
6095 Examples
6096 --------
6097 ::
6098
6099 import cma
6100 logger = cma.CMADataLogger() # with default name
6101 # try to plot the "default logging" data (e.g.
6102 # from previous fmin calls, which is essentially what
6103 # also cma.plot() does)
6104 logger.plot()
6105 cma.savefig('fig325.png') # save current figure
6106 logger.closefig()
6107
6108 Dependencies: matlabplotlib/pyplot.
6109
6110 """
6111 try:
6112
6113 from matplotlib.pyplot import figure, subplot, hold, gcf
6114 except ImportError:
6115 ImportError('could not find matplotlib.pyplot module, function plot() is not available')
6116 return
6117
6118 if fig is None:
6119 fig = 325
6120 if iabscissa not in (0, 1):
6121 iabscissa = 1
6122
6123 self.load()
6124 dat = self
6125 dat.x = dat.xmean
6126 if not plot_mean:
6127 if len(dat.x) < 2:
6128 print('not enough data to plot recent x')
6129 else:
6130 dat.x = dat.xrecent
6131
6132
6133 if iteridx is not None:
6134 self.select_data(iteridx)
6135
6136 if len(dat.f) <= 1:
6137 print('nothing to plot')
6138 return
6139
6140
6141
6142
6143
6144
6145
6146
6147
6148 figure(fig)
6149 self._enter_plotting(fontsize)
6150 self.fighandle = gcf()
6151
6152 subplot(2, 2, 1)
6153 self.plot_divers(iabscissa, foffset)
6154 pyplot.xlabel('')
6155
6156
6157 subplot(2, 2, 3)
6158 self.plot_axes_scaling(iabscissa)
6159
6160
6161 figure(fig)
6162
6163 subplot(2, 2, 2)
6164 if plot_mean:
6165 self.plot_mean(iabscissa, x_opt)
6166 else:
6167 self.plot_xrecent(iabscissa, x_opt)
6168 pyplot.xlabel('')
6169
6170
6171
6172 subplot(2, 2, 4)
6173 self.plot_stds(iabscissa)
6174 self._finalize_plotting()
6175 return self
6176 - def plot_all(self, fig=None, iabscissa=1, iteridx=None,
6177 foffset=1e-19, x_opt=None, fontsize=9):
6178 """
6179 plot data from a `CMADataLogger` (using the files written by the logger).
6180
6181 Arguments
6182 ---------
6183 `fig`
6184 figure number, by default 425
6185 `iabscissa`
6186 ``0==plot`` versus iteration count,
6187 ``1==plot`` versus function evaluation number
6188 `iteridx`
6189 iteration indices to plot
6190
6191 Return `CMADataLogger` itself.
6192
6193 Examples
6194 --------
6195 ::
6196
6197 import cma
6198 logger = cma.CMADataLogger() # with default name
6199 # try to plot the "default logging" data (e.g.
6200 # from previous fmin calls, which is essentially what
6201 # also cma.plot() does)
6202 logger.plot_all()
6203 cma.savefig('fig425.png') # save current figure
6204 logger.closefig()
6205
6206 Dependencies: matlabplotlib/pyplot.
6207
6208 """
6209 try:
6210
6211 from matplotlib.pyplot import figure, subplot, gcf
6212 except ImportError:
6213 ImportError('could not find matplotlib.pyplot module, function plot() is not available')
6214 return
6215
6216 if fig is None:
6217 fig = 426
6218 if iabscissa not in (0, 1):
6219 iabscissa = 1
6220
6221 self.load()
6222 dat = self
6223
6224
6225 if iteridx is not None:
6226 self.select_data(iteridx)
6227
6228 if len(dat.f) == 0:
6229 print('nothing to plot')
6230 return
6231
6232
6233
6234
6235
6236
6237
6238
6239 figure(fig)
6240 self._enter_plotting(fontsize)
6241 self.fighandle = gcf()
6242
6243 if 1 < 3:
6244 subplot(2, 3, 1)
6245 self.plot_divers(iabscissa, foffset)
6246 pyplot.xlabel('')
6247
6248
6249 subplot(2, 3, 4)
6250 self.plot_stds(iabscissa)
6251
6252
6253 subplot(2, 3, 2)
6254 self.plot_axes_scaling(iabscissa)
6255 pyplot.xlabel('')
6256
6257
6258 subplot(2, 3, 5)
6259 self.plot_correlations(iabscissa)
6260
6261
6262 subplot(2, 3, 3)
6263 self.plot_xrecent(iabscissa, x_opt)
6264 pyplot.xlabel('')
6265
6266 subplot(2, 3, 6)
6267 self.plot_mean(iabscissa, x_opt)
6268
6269 self._finalize_plotting()
6270 return self
6272 if not hasattr(self, 'D'):
6273 self.load()
6274 dat = self
6275 self._enter_plotting()
6276 pyplot.semilogy(dat.D[:, iabscissa], dat.D[:, 5:], '-b')
6277 pyplot.hold(True)
6278 pyplot.grid(True)
6279 ax = array(pyplot.axis())
6280
6281 pyplot.axis(ax)
6282 pyplot.title('Principle Axes Lengths')
6283
6284 self._xlabel(iabscissa)
6285 self._finalize_plotting()
6286 return self
6288 if not hasattr(self, 'std'):
6289 self.load()
6290 dat = self
6291 self._enter_plotting()
6292
6293 dat.std[:, 5:] = np.transpose(dat.std[:, 5:].T / dat.std[:, 2].T)
6294
6295
6296
6297 if 1 < 2 and dat.std.shape[1] < 100:
6298
6299 minxend = int(1.06 * dat.std[-2, iabscissa])
6300
6301 dat.std[-1, iabscissa] = minxend
6302 idx = np.argsort(dat.std[-2, 5:])
6303 idx2 = np.argsort(idx)
6304 dat.std[-1, 5 + idx] = np.logspace(np.log10(np.min(dat.std[:, 5:])),
6305 np.log10(np.max(dat.std[:, 5:])), dat.std.shape[1] - 5)
6306
6307 dat.std[-1, iabscissa] = minxend
6308 pyplot.semilogy(dat.std[:, iabscissa], dat.std[:, 5:], '-')
6309 pyplot.hold(True)
6310 ax = array(pyplot.axis())
6311
6312 yy = np.logspace(np.log10(ax[2]), np.log10(ax[3]), dat.std.shape[1] - 5)
6313
6314 idx = np.argsort(dat.std[-1, 5:])
6315 idx2 = np.argsort(idx)
6316
6317
6318 pyplot.plot(np.dot(dat.std[-2, iabscissa], [1, 1]),
6319 array([ax[2] + 1e-6, ax[3] - 1e-6]),
6320
6321 'k-')
6322 pyplot.hold(True)
6323
6324 for i in rglen((idx)):
6325
6326 pyplot.text(dat.std[-1, iabscissa], dat.std[-1, 5 + i], ' ' + str(i))
6327 else:
6328 pyplot.semilogy(dat.std[:, iabscissa], dat.std[:, 5:], '-')
6329 pyplot.hold(True)
6330 pyplot.grid(True)
6331 pyplot.title(r'Standard Deviations $\times$ $\sigma^{-1}$ in All Coordinates')
6332
6333 self._xlabel(iabscissa)
6334 self._finalize_plotting()
6335 return self
6336 - def plot_mean(self, iabscissa=1, x_opt=None, annotations=None):
6337 if not hasattr(self, 'xmean'):
6338 self.load()
6339 self.x = self.xmean
6340 self._plot_x(iabscissa, x_opt, 'mean', annotations=annotations)
6341 self._xlabel(iabscissa)
6342 return self
6343 - def plot_xrecent(self, iabscissa=1, x_opt=None, annotations=None):
6344 if not hasattr(self, 'xrecent'):
6345 self.load()
6346 self.x = self.xrecent
6347 self._plot_x(iabscissa, x_opt, 'curr best', annotations=annotations)
6348 self._xlabel(iabscissa)
6349 return self
6351 """spectrum of correlation matrix and largest correlation"""
6352 if not hasattr(self, 'corrspec'):
6353 self.load()
6354 if len(self.corrspec) < 2:
6355 return self
6356 x = self.corrspec[:, iabscissa]
6357 y = self.corrspec[:, 6:]
6358 ys = self.corrspec[:, :6]
6359
6360 from matplotlib.pyplot import semilogy, hold, text, grid, axis, title
6361 self._enter_plotting()
6362 semilogy(x, y, '-c')
6363 hold(True)
6364 semilogy(x[:], np.max(y, 1) / np.min(y, 1), '-r')
6365 text(x[-1], np.max(y[-1, :]) / np.min(y[-1, :]), 'axis ratio')
6366 if ys is not None:
6367 semilogy(x, 1 + ys[:, 2], '-b')
6368 text(x[-1], 1 + ys[-1, 2], '1 + min(corr)')
6369 semilogy(x, 1 - ys[:, 5], '-b')
6370 text(x[-1], 1 - ys[-1, 5], '1 - max(corr)')
6371 semilogy(x[:], 1 + ys[:, 3], '-k')
6372 text(x[-1], 1 + ys[-1, 3], '1 + max(neg corr)')
6373 semilogy(x[:], 1 - ys[:, 4], '-k')
6374 text(x[-1], 1 - ys[-1, 4], '1 - min(pos corr)')
6375 grid(True)
6376 ax = array(axis())
6377
6378 axis(ax)
6379 title('Spectrum (roots) of correlation matrix')
6380
6381 self._xlabel(iabscissa)
6382 self._finalize_plotting()
6383 return self
6385 """plot fitness, sigma, axis ratio...
6386
6387 :param iabscissa: 0 means vs evaluations, 1 means vs iterations
6388 :param foffset: added to f-value
6389
6390 :See: `plot()`
6391
6392 """
6393 from matplotlib.pyplot import semilogy, hold, grid, \
6394 axis, title, text
6395 fontsize = pyplot.rcParams['font.size']
6396
6397 if not hasattr(self, 'f'):
6398 self.load()
6399 dat = self
6400
6401 minfit = min(dat.f[:, 5])
6402 dfit = dat.f[:, 5] - minfit
6403 dfit[dfit < 1e-98] = np.NaN
6404
6405 self._enter_plotting()
6406 if dat.f.shape[1] > 7:
6407
6408 semilogy(dat.f[:, iabscissa], abs(dat.f[:, [6, 7]]) + foffset, '-k')
6409 hold(True)
6410
6411
6412 if dat.f.shape[1] > 8:
6413
6414
6415 semilogy(dat.f[:, iabscissa], np.abs(dat.f[:, 8:]) + 10 * foffset, 'y')
6416 hold(True)
6417
6418 idx = np.where(dat.f[:, 5] > 1e-98)[0]
6419 semilogy(dat.f[idx, iabscissa], dat.f[idx, 5] + foffset, '.b')
6420 hold(True)
6421 grid(True)
6422
6423
6424 semilogy(dat.f[:, iabscissa], abs(dat.f[:, 5]) + foffset, '-b')
6425 text(dat.f[-1, iabscissa], abs(dat.f[-1, 5]) + foffset,
6426 r'$|f_\mathsf{best}|$', fontsize=fontsize + 2)
6427
6428
6429 sgn = np.sign(dat.f[:, 5])
6430 sgn[np.abs(dat.f[:, 5]) < 1e-98] = 0
6431 idx = np.where(sgn < 0)[0]
6432 semilogy(dat.f[idx, iabscissa], abs(dat.f[idx, 5]) + foffset,
6433 '.m')
6434
6435
6436 dsgn = np.diff(sgn)
6437 start_idx = 1 + np.where((dsgn < 0) * (sgn[1:] < 0))[0]
6438 stop_idx = 1 + np.where(dsgn > 0)[0]
6439 if sgn[0] < 0:
6440 start_idx = np.concatenate(([0], start_idx))
6441 for istart in start_idx:
6442 istop = stop_idx[stop_idx > istart]
6443 istop = istop[0] if len(istop) else 0
6444 idx = xrange(istart, istop if istop else dat.f.shape[0])
6445 if len(idx) > 1:
6446 semilogy(dat.f[idx, iabscissa], abs(dat.f[idx, 5]) + foffset,
6447 'm')
6448
6449
6450 if istart > 0:
6451 semilogy(dat.f[istart-1:istart+1, iabscissa],
6452 abs(dat.f[istart-1:istart+1, 5]) +
6453 foffset, '--m')
6454 if istop:
6455 semilogy(dat.f[istop-1:istop+1, iabscissa],
6456 abs(dat.f[istop-1:istop+1, 5]) +
6457 foffset, '--m')
6458
6459 semilogy(dat.f[istop, iabscissa], abs(dat.f[istop, 5]) +
6460 foffset, '.b', markersize=7)
6461
6462 semilogy(dat.f[istart, iabscissa], abs(dat.f[istart, 5]) +
6463 foffset, '.r', markersize=7)
6464
6465
6466 semilogy(dat.std[:-1, iabscissa],
6467 np.vstack([list(map(max, dat.std[:-1, 5:])),
6468 list(map(min, dat.std[:-1, 5:]))]).T,
6469 '-m', linewidth=2)
6470 text(dat.std[-2, iabscissa], max(dat.std[-2, 5:]), 'max std',
6471 fontsize=fontsize)
6472 text(dat.std[-2, iabscissa], min(dat.std[-2, 5:]), 'min std',
6473 fontsize=fontsize)
6474
6475
6476 idx = isfinite(dfit)
6477 if 1 < 3:
6478 idx_nan = np.where(idx == False)[0]
6479 if not len(idx_nan):
6480 semilogy(dat.f[:, iabscissa][idx], dfit[idx], '-c')
6481 else:
6482 i_start = 0
6483 for i_end in idx_nan:
6484 if i_end > i_start:
6485 semilogy(dat.f[:, iabscissa][i_start:i_end],
6486 dfit[i_start:i_end], '-c')
6487 i_start = i_end + 1
6488 if len(dfit) > idx_nan[-1] + 1:
6489 semilogy(dat.f[:, iabscissa][idx_nan[-1]+1:],
6490 dfit[idx_nan[-1]+1:], '-c')
6491 text(dat.f[idx, iabscissa][-1], dfit[idx][-1],
6492 r'$f_\mathsf{best} - \min(f)$', fontsize=fontsize + 2)
6493
6494
6495 i = np.argmin(dat.f[:, 5])
6496 semilogy(dat.f[i, iabscissa], np.abs(dat.f[i, 5]), 'ro',
6497 markersize=9)
6498 semilogy(dat.f[i, iabscissa], dfit[idx][np.argmin(dfit[idx])]
6499 + 1e-98, 'ro', markersize=9)
6500
6501
6502
6503 semilogy(dat.f[:, iabscissa], dat.f[:, 3], '-r')
6504 semilogy(dat.f[:, iabscissa], dat.f[:, 2], '-g')
6505 text(dat.f[-1, iabscissa], dat.f[-1, 3], r'axis ratio',
6506 fontsize=fontsize)
6507 text(dat.f[-1, iabscissa], dat.f[-1, 2] / 1.5, r'$\sigma$',
6508 fontsize=fontsize+3)
6509 ax = array(axis())
6510
6511 axis(ax)
6512 text(ax[0] + 0.01, ax[2],
6513 '.min($f$)=' + repr(minfit))
6514
6515
6516
6517
6518
6519 title(r'$|f_{\mathrm{best},\mathrm{med},\mathrm{worst}}|$, $f - \min(f)$, $\sigma$, axis ratio')
6520
6521
6522 self._xlabel(iabscissa)
6523 self._finalize_plotting()
6524 return self
6526 """assumes that a figure is open """
6527
6528 self.original_fontsize = pyplot.rcParams['font.size']
6529 pyplot.rcParams['font.size'] = fontsize
6530 pyplot.hold(False)
6531 pyplot.ioff()
6533 pyplot.ion()
6534 pyplot.draw()
6535 pyplot.show()
6536
6537 pyplot.rcParams['font.size'] = self.original_fontsize
6539 pyplot.xlabel('iterations' if iabscissa == 0
6540 else 'function evaluations')
6541 - def _plot_x(self, iabscissa=1, x_opt=None, remark=None,
6542 annotations=None):
6543 """If ``x_opt is not None`` the difference to x_opt is plotted
6544 in log scale
6545
6546 """
6547 if not hasattr(self, 'x'):
6548 _print_warning('no x-attributed found, use methods ' +
6549 'plot_xrecent or plot_mean', 'plot_x',
6550 'CMADataLogger')
6551 return
6552 from matplotlib.pyplot import plot, semilogy, hold, text, grid, axis, title
6553 dat = self
6554
6555 if dat.x.shape[1] < 100:
6556 minxend = int(1.06 * dat.x[-2, iabscissa])
6557
6558 dat.x[-1, iabscissa] = minxend
6559 if x_opt is None:
6560 idx = np.argsort(dat.x[-2, 5:])
6561 idx2 = np.argsort(idx)
6562 dat.x[-1, 5 + idx] = np.linspace(np.min(dat.x[:, 5:]),
6563 np.max(dat.x[:, 5:]), dat.x.shape[1] - 5)
6564 else:
6565 xdat = np.abs(dat.x[:, 5:] - np.array(x_opt, copy=False))
6566 idx = np.argsort(xdat[-2, :])
6567 idx2 = np.argsort(idx)
6568 xdat[-1, idx] = np.logspace(np.log10(np.min(abs(xdat[xdat!=0]))),
6569 np.log10(np.max(np.abs(xdat))),
6570 dat.x.shape[1] - 5)
6571 else:
6572 minxend = 0
6573 self._enter_plotting()
6574 if x_opt is not None:
6575 semilogy(dat.x[:, iabscissa], abs(xdat), '-')
6576 else:
6577 plot(dat.x[:, iabscissa], dat.x[:, 5:], '-')
6578 hold(True)
6579 grid(True)
6580 ax = array(axis())
6581
6582 axis(ax)
6583 ax[1] -= 1e-6
6584 if dat.x.shape[1] < 100:
6585 yy = np.linspace(ax[2] + 1e-6, ax[3] - 1e-6, dat.x.shape[1] - 5)
6586
6587 if x_opt is not None:
6588
6589 semilogy(np.dot(dat.x[-2, iabscissa], [1, 1]),
6590 array([ax[2] * (1+1e-6), ax[3] / (1+1e-6)]), 'k-')
6591 else:
6592
6593 plot(np.dot(dat.x[-2, iabscissa], [1, 1]),
6594 array([ax[2] + 1e-6, ax[3] - 1e-6]), 'k-')
6595
6596
6597 for i in rglen(idx):
6598
6599
6600
6601 text(dat.x[-1, iabscissa], dat.x[-1, 5 + i]
6602 if x_opt is None else np.abs(xdat[-1, i]),
6603 ('x(' + str(i) + ')=' if annotations is None
6604 else str(i) + ':' + annotations[i] + "=")
6605 + str(dat.x[-2, 5 + i]))
6606 i = 2
6607 while i < len(dat.f) and dat.f[-i][0] == dat.f[-1][0]:
6608 i += 1
6609 title('Object Variables (' +
6610 (remark + ', ' if remark is not None else '') +
6611 str(dat.x.shape[1] - 5) + '-D, popsize~' +
6612 (str(int((dat.f[-1][1] - dat.f[-i][1]) / (dat.f[-1][0] - dat.f[-i][0])))
6613 if len(dat.f.T[0]) > 1 and dat.f[-1][0] > dat.f[-i][0] else 'NA')
6614 + ')')
6615 self._finalize_plotting()
6616 - def downsampling(self, factor=10, first=3, switch=True, verbose=True):
6617 """
6618 rude downsampling of a `CMADataLogger` data file by `factor`,
6619 keeping also the first `first` entries. This function is a
6620 stump and subject to future changes. Return self.
6621
6622 Arguments
6623 ---------
6624 - `factor` -- downsampling factor
6625 - `first` -- keep first `first` entries
6626 - `switch` -- switch the new logger to the downsampled logger
6627 original_name+'down'
6628
6629 Details
6630 -------
6631 ``self.name_prefix+'down'`` files are written
6632
6633 Example
6634 -------
6635 ::
6636
6637 import cma
6638 cma.downsampling() # takes outcmaes* files
6639 cma.plot('outcmaesdown')
6640
6641 """
6642 newprefix = self.name_prefix + 'down'
6643 for name in self.file_names:
6644 f = open(newprefix + name + '.dat', 'w')
6645 iline = 0
6646 cwritten = 0
6647 for line in open(self.name_prefix + name + '.dat'):
6648 if iline < first or iline % factor == 0:
6649 f.write(line)
6650 cwritten += 1
6651 iline += 1
6652 f.close()
6653 if verbose and iline > first:
6654 print('%d' % (cwritten) + ' lines written in ' + newprefix + name + '.dat')
6655 if switch:
6656 self.name_prefix += 'down'
6657 return self
6658
6659
6660
6661
6662 - def disp(self, idx=100):
6663 """displays selected data from (files written by) the class `CMADataLogger`.
6664
6665 Arguments
6666 ---------
6667 `idx`
6668 indices corresponding to rows in the data file;
6669 if idx is a scalar (int), the first two, then every idx-th,
6670 and the last three rows are displayed. Too large index values are removed.
6671
6672 Example
6673 -------
6674 >>> import cma, numpy as np
6675 >>> res = cma.fmin(cma.fcts.elli, 7 * [0.1], 1, {'verb_disp':1e9}) # generate data
6676 >>> assert res[1] < 1e-9
6677 >>> assert res[2] < 4400
6678 >>> l = cma.CMADataLogger() # == res[-1], logger with default name, "points to" above data
6679 >>> l.disp([0,-1]) # first and last
6680 >>> l.disp(20) # some first/last and every 20-th line
6681 >>> l.disp(np.r_[0:999999:100, -1]) # every 100-th and last
6682 >>> l.disp(np.r_[0, -10:0]) # first and ten last
6683 >>> cma.disp(l.name_prefix, np.r_[0::100, -10:]) # the same as l.disp(...)
6684
6685 Details
6686 -------
6687 The data line with the best f-value is displayed as last line.
6688
6689 :See: `disp()`
6690
6691 """
6692
6693 filenameprefix = self.name_prefix
6694
6695 def printdatarow(dat, iteration):
6696 """print data of iteration i"""
6697 i = np.where(dat.f[:, 0] == iteration)[0][0]
6698 j = np.where(dat.std[:, 0] == iteration)[0][0]
6699 print('%5d' % (int(dat.f[i, 0])) + ' %6d' % (int(dat.f[i, 1])) + ' %.14e' % (dat.f[i, 5]) +
6700 ' %5.1e' % (dat.f[i, 3]) +
6701 ' %6.2e' % (max(dat.std[j, 5:])) + ' %6.2e' % min(dat.std[j, 5:]))
6702
6703 dat = CMADataLogger(filenameprefix).load()
6704 ndata = dat.f.shape[0]
6705
6706
6707
6708
6709 if idx is None:
6710 idx = 100
6711 if isscalar(idx):
6712
6713 if idx:
6714 idx = np.r_[0, 1, idx:ndata - 3:idx, -3:0]
6715 else:
6716 idx = np.r_[0, 1, -3:0]
6717
6718 idx = array(idx)
6719 idx = idx[idx < ndata]
6720 idx = idx[-idx <= ndata]
6721 iters = dat.f[idx, 0]
6722 idxbest = np.argmin(dat.f[:, 5])
6723 iterbest = dat.f[idxbest, 0]
6724
6725 if len(iters) == 1:
6726 printdatarow(dat, iters[0])
6727 else:
6728 self.disp_header()
6729 for i in iters:
6730 printdatarow(dat, i)
6731 self.disp_header()
6732 printdatarow(dat, iterbest)
6733 sys.stdout.flush()
6735 heading = 'Iterat Nfevals function value axis ratio maxstd minstd'
6736 print(heading)
6737
6738
6739
6740
6741
6742
6743
6744 last_figure_number = 324
6745 -def plot(name=None, fig=None, abscissa=1, iteridx=None,
6746 plot_mean=False,
6747 foffset=1e-19, x_opt=None, fontsize=9):
6748 """
6749 plot data from files written by a `CMADataLogger`,
6750 the call ``cma.plot(name, **argsdict)`` is a shortcut for
6751 ``cma.CMADataLogger(name).plot(**argsdict)``
6752
6753 Arguments
6754 ---------
6755 `name`
6756 name of the logger, filename prefix, None evaluates to
6757 the default 'outcmaes'
6758 `fig`
6759 filename or figure number, or both as a tuple (any order)
6760 `abscissa`
6761 0==plot versus iteration count,
6762 1==plot versus function evaluation number
6763 `iteridx`
6764 iteration indices to plot
6765
6766 Return `None`
6767
6768 Examples
6769 --------
6770 ::
6771
6772 cma.plot(); # the optimization might be still
6773 # running in a different shell
6774 cma.savefig('fig325.png')
6775 cma.closefig()
6776
6777 cdl = cma.CMADataLogger().downsampling().plot()
6778 # in case the file sizes are large
6779
6780 Details
6781 -------
6782 Data from codes in other languages (C, Java, Matlab, Scilab) have the same
6783 format and can be plotted just the same.
6784
6785 :See: `CMADataLogger`, `CMADataLogger.plot()`
6786
6787 """
6788 global last_figure_number
6789 if not fig:
6790 last_figure_number += 1
6791 fig = last_figure_number
6792 if isinstance(fig, (int, float)):
6793 last_figure_number = fig
6794 CMADataLogger(name).plot(fig, abscissa, iteridx, plot_mean, foffset,
6795 x_opt, fontsize)
6796
6797 -def disp(name=None, idx=None):
6798 """displays selected data from (files written by) the class `CMADataLogger`.
6799
6800 The call ``cma.disp(name, idx)`` is a shortcut for ``cma.CMADataLogger(name).disp(idx)``.
6801
6802 Arguments
6803 ---------
6804 `name`
6805 name of the logger, filename prefix, `None` evaluates to
6806 the default ``'outcmaes'``
6807 `idx`
6808 indices corresponding to rows in the data file; by
6809 default the first five, then every 100-th, and the last
6810 10 rows. Too large index values are removed.
6811
6812 Examples
6813 --------
6814 ::
6815
6816 import cma, numpy
6817 # assume some data are available from previous runs
6818 cma.disp(None,numpy.r_[0,-1]) # first and last
6819 cma.disp(None,numpy.r_[0:1e9:100,-1]) # every 100-th and last
6820 cma.disp(idx=numpy.r_[0,-10:0]) # first and ten last
6821 cma.disp(idx=numpy.r_[0:1e9:1e3,-10:0])
6822
6823 :See: `CMADataLogger.disp()`
6824
6825 """
6826 return CMADataLogger(name if name else CMADataLogger.default_prefix
6827 ).disp(idx)
6828
6831 """rudimentary method to read in data from a file"""
6832
6833
6834 if 1 < 3:
6835 lres = []
6836 for line in open(file_name, 'r').readlines():
6837 if len(line) > 0 and line[0] not in ('%', '#'):
6838 lres.append(list(map(float, line.split())))
6839 res = lres
6840 while res != [] and res[0] == []:
6841 del res[0]
6842 return res
6843
6844 print('could not read file ' + file_name)
6845
6849 """Noise handling according to [Hansen et al 2009, A Method for
6850 Handling Uncertainty in Evolutionary Optimization...]
6851
6852 The interface of this class is yet versatile and subject to changes.
6853
6854 The noise handling follows closely [Hansen et al 2009] in the
6855 measurement part, but the implemented treatment is slightly
6856 different: for ``noiseS > 0``, ``evaluations`` (time) and sigma are
6857 increased by ``alpha``. For ``noiseS < 0``, ``evaluations`` (time)
6858 is decreased by ``alpha**(1/4)``.
6859
6860 The (second) parameter ``evaluations`` defines the maximal number
6861 of evaluations for a single fitness computation. If it is a list,
6862 the smallest element defines the minimal number and if the list has
6863 three elements, the median value is the start value for
6864 ``evaluations``.
6865
6866 ``NoiseHandler`` serves to control the noise via steps-size
6867 increase and number of re-evaluations, for example via ``fmin`` or
6868 with ``ask_and_eval()``.
6869
6870 Examples
6871 --------
6872 Minimal example together with `fmin` on a non-noisy function:
6873
6874 >>> import cma
6875 >>> cma.fmin(cma.felli, 7 * [1], 1, noise_handler=cma.NoiseHandler(7))
6876
6877 in dimension 7 (which needs to be given tice). More verbose example
6878 in the optimization loop with a noisy function defined in ``func``:
6879
6880 >>> import cma, numpy as np
6881 >>> func = lambda x: cma.fcts.sphere(x) * (1 + 4 * np.random.randn() / len(x)) # cma.Fcts.noisysphere
6882 >>> es = cma.CMAEvolutionStrategy(np.ones(10), 1)
6883 >>> nh = cma.NoiseHandler(es.N, maxevals=[1, 1, 30])
6884 >>> while not es.stop():
6885 ... X, fit_vals = es.ask_and_eval(func, evaluations=nh.evaluations)
6886 ... es.tell(X, fit_vals) # prepare for next iteration
6887 ... es.sigma *= nh(X, fit_vals, func, es.ask) # see method __call__
6888 ... es.countevals += nh.evaluations_just_done # this is a hack, not important though
6889 ... es.logger.add(more_data = [nh.evaluations, nh.noiseS]) # add a data point
6890 ... es.disp()
6891 ... # nh.maxevals = ... it might be useful to start with smaller values and then increase
6892 >>> print(es.stop())
6893 >>> print(es.result()[-2]) # take mean value, the best solution is totally off
6894 >>> assert sum(es.result()[-2]**2) < 1e-9
6895 >>> print(X[np.argmin(fit_vals)]) # not bad, but probably worse than the mean
6896 >>> # es.logger.plot()
6897
6898
6899 The command ``logger.plot()`` will plot the logged data.
6900
6901 The noise options of `fmin()` control a `NoiseHandler` instance
6902 similar to this example. The command ``cma.CMAOptions('noise')``
6903 lists in effect the parameters of `__init__` apart from
6904 ``aggregate``.
6905
6906 Details
6907 -------
6908 The parameters reevals, theta, c_s, and alpha_t are set differently
6909 than in the original publication, see method `__init__()`. For a
6910 very small population size, say popsize <= 5, the measurement
6911 technique based on rank changes is likely to fail.
6912
6913 Missing Features
6914 ----------------
6915 In case no noise is found, ``self.lam_reeval`` should be adaptive
6916 and get at least as low as 1 (however the possible savings from this
6917 are rather limited). Another option might be to decide during the
6918 first call by a quantitative analysis of fitness values whether
6919 ``lam_reeval`` is set to zero. More generally, an automatic noise
6920 mode detection might also set the covariance matrix learning rates
6921 to smaller values.
6922
6923 :See: `fmin()`, `CMAEvolutionStrategy.ask_and_eval()`
6924
6925 """
6926
6927
6928
6929 - def __init__(self, N, maxevals=[1, 1, 1], aggregate=np.median,
6930 reevals=None, epsilon=1e-7, parallel=False):
6931 """parameters are
6932
6933 `N`
6934 dimension, (only) necessary to adjust the internal
6935 "alpha"-parameters
6936 `maxevals`
6937 maximal value for ``self.evaluations``, where
6938 ``self.evaluations`` function calls are aggregated for
6939 noise treatment. With ``maxevals == 0`` the noise
6940 handler is (temporarily) "switched off". If `maxevals`
6941 is a list, min value and (for >2 elements) median are
6942 used to define minimal and initial value of
6943 ``self.evaluations``. Choosing ``maxevals > 1`` is only
6944 reasonable, if also the original ``fit`` values (that
6945 are passed to `__call__`) are computed by aggregation of
6946 ``self.evaluations`` values (otherwise the values are
6947 not comparable), as it is done within `fmin()`.
6948 `aggregate`
6949 function to aggregate single f-values to a 'fitness', e.g.
6950 ``np.median``.
6951 `reevals`
6952 number of solutions to be reevaluated for noise
6953 measurement, can be a float, by default set to ``2 +
6954 popsize/20``, where ``popsize = len(fit)`` in
6955 ``__call__``. zero switches noise handling off.
6956 `epsilon`
6957 multiplier for perturbation of the reevaluated solutions
6958 `parallel`
6959 a single f-call with all resampled solutions
6960
6961 :See: `fmin()`, `CMAOptions`, `CMAEvolutionStrategy.ask_and_eval()`
6962
6963 """
6964 self.lam_reeval = reevals
6965 self.epsilon = epsilon
6966 self.parallel = parallel
6967
6968 self.theta = 0.5
6969 self.cum = 0.3
6970
6971 self.alphasigma = 1 + 2.0 / (N + 10)
6972
6973 self.alphaevals = 1 + 2.0 / (N + 10)
6974
6975 self.alphaevalsdown = self.alphaevals** -0.25
6976
6977 self.evaluations = 1
6978 self.minevals = 1
6979 self.maxevals = int(np.max(maxevals))
6980 if hasattr(maxevals, '__contains__'):
6981 if len(maxevals) > 1:
6982 self.minevals = min(maxevals)
6983 self.evaluations = self.minevals
6984 if len(maxevals) > 2:
6985 self.evaluations = np.median(maxevals)
6986
6987 self.f_aggregate = aggregate if not None else {1: np.median, 2: np.mean}[ None ]
6988 self.evaluations_just_done = 0
6989 self.noiseS = 0
6990
6991 - def __call__(self, X, fit, func, ask=None, args=()):
6992 """proceed with noise measurement, set anew attributes ``evaluations``
6993 (proposed number of evaluations to "treat" noise) and ``evaluations_just_done``
6994 and return a factor for increasing sigma.
6995
6996 Parameters
6997 ----------
6998 `X`
6999 a list/sequence/vector of solutions
7000 `fit`
7001 the respective list of function values
7002 `func`
7003 the objective function, ``fit[i]`` corresponds to ``func(X[i], *args)``
7004 `ask`
7005 a method to generate a new, slightly disturbed solution. The argument
7006 is (only) mandatory if ``epsilon`` is not zero, see `__init__()`.
7007 `args`
7008 optional additional arguments to `func`
7009
7010 Details
7011 -------
7012 Calls the methods ``reeval()``, ``update_measure()`` and ``treat()`` in this order.
7013 ``self.evaluations`` is adapted within the method `treat()`.
7014
7015 """
7016 self.evaluations_just_done = 0
7017 if not self.maxevals or self.lam_reeval == 0:
7018 return 1.0
7019 res = self.reeval(X, fit, func, ask, args)
7020 if not len(res):
7021 return 1.0
7022 self.update_measure()
7023 return self.treat()
7024
7026 """return ``self.evaluations``, the number of evalutions to get a single fitness measurement"""
7027 return self.evaluations
7028
7030 """adapt self.evaluations depending on the current measurement value
7031 and return ``sigma_fac in (1.0, self.alphasigma)``
7032
7033 """
7034 if self.noiseS > 0:
7035 self.evaluations = min((self.evaluations * self.alphaevals, self.maxevals))
7036 return self.alphasigma
7037 else:
7038 self.evaluations = max((self.evaluations * self.alphaevalsdown, self.minevals))
7039 return 1.0
7040
7041 - def reeval(self, X, fit, func, ask, args=()):
7042 """store two fitness lists, `fit` and ``fitre`` reevaluating some
7043 solutions in `X`.
7044 ``self.evaluations`` evaluations are done for each reevaluated
7045 fitness value.
7046 See `__call__()`, where `reeval()` is called.
7047
7048 """
7049 self.fit = list(fit)
7050 self.fitre = list(fit)
7051 self.idx = self.indices(fit)
7052 if not len(self.idx):
7053 return self.idx
7054 evals = int(self.evaluations) if self.f_aggregate else 1
7055 fagg = np.median if self.f_aggregate is None else self.f_aggregate
7056 for i in self.idx:
7057 X_i = X[i]
7058 if self.epsilon:
7059 if self.parallel:
7060 self.fitre[i] = fagg(func(ask(evals, X_i, self.epsilon), *args))
7061 else:
7062 self.fitre[i] = fagg([func(ask(1, X_i, self.epsilon)[0], *args)
7063 for _k in xrange(evals)])
7064 else:
7065 self.fitre[i] = fagg([func(X_i, *args) for _k in xrange(evals)])
7066 self.evaluations_just_done = evals * len(self.idx)
7067 return self.fit, self.fitre, self.idx
7068
7070 """updated noise level measure using two fitness lists ``self.fit`` and
7071 ``self.fitre``, return ``self.noiseS, all_individual_measures``.
7072
7073 Assumes that `self.idx` contains the indices where the fitness
7074 lists differ
7075
7076 """
7077 lam = len(self.fit)
7078 idx = np.argsort(self.fit + self.fitre)
7079 ranks = np.argsort(idx).reshape((2, lam))
7080 rankDelta = ranks[0] - ranks[1] - np.sign(ranks[0] - ranks[1])
7081
7082
7083 r = np.arange(1, 2 * lam)
7084 limits = [0.5 * (Mh.prctile(np.abs(r - (ranks[0, i] + 1 - (ranks[0, i] > ranks[1, i]))),
7085 self.theta * 50) +
7086 Mh.prctile(np.abs(r - (ranks[1, i] + 1 - (ranks[1, i] > ranks[0, i]))),
7087 self.theta * 50))
7088 for i in self.idx]
7089
7090
7091 s = np.abs(rankDelta[self.idx]) - Mh.amax(limits, 1)
7092 self.noiseS += self.cum * (np.mean(s) - self.noiseS)
7093 return self.noiseS, s
7094
7096 """return the set of indices to be reevaluated for noise
7097 measurement.
7098
7099 Given the first values are the earliest, this is a useful policy also
7100 with a time changing objective.
7101
7102 """
7103
7104 lam_reev = 1.0 * (self.lam_reeval if self.lam_reeval
7105 else 2 + len(fit) / 20)
7106 lam_reev = int(lam_reev) + ((lam_reev % 1) > np.random.rand())
7107
7108 choice = 1
7109 if choice == 1:
7110
7111 n_first = lam_reev - lam_reev // 2
7112 sort_idx = np.argsort(array(fit, copy=False)[n_first:]) + n_first
7113 return np.array(list(range(0, n_first)) +
7114 list(sort_idx[0:lam_reev - n_first]), copy=False)
7115 elif choice == 2:
7116 idx_sorted = np.argsort(array(fit, copy=False))
7117
7118 linsp = np.linspace(0, len(fit) - len(fit) / lam_reev, lam_reev)
7119 return idx_sorted[[int(i) for i in linsp]]
7120
7121 elif choice == 3:
7122 return np.argsort(array(fit, copy=False)[:2 * (lam_reev + 1)])[:lam_reev]
7123 else:
7124 raise ValueError('unrecognized choice value %d for noise reev'
7125 % choice)
7126
7130 """plot sections through an objective function.
7131
7132 A first rational thing to do, when facing an (expensive)
7133 application. By default 6 points in each coordinate are evaluated.
7134 This class is still experimental.
7135
7136 Examples
7137 --------
7138
7139 >>> import cma, numpy as np
7140 >>> s = cma.Sections(cma.Fcts.rosen, np.zeros(3)).do(plot=False)
7141 >>> s.do(plot=False) # evaluate the same points again, i.e. check for noise
7142 >> try:
7143 ... s.plot()
7144 ... except:
7145 ... print('plotting failed: matplotlib.pyplot package missing?')
7146
7147 Details
7148 -------
7149 Data are saved after each function call during `do()`. The filename
7150 is attribute ``name`` and by default ``str(func)``, see `__init__()`.
7151
7152 A random (orthogonal) basis can be generated with
7153 ``cma.Rotation()(np.eye(3))``.
7154
7155 CAVEAT: The default name is unique in the function name, but it
7156 should be unique in all parameters of `__init__()` but `plot_cmd`
7157 and `load`. If, for example, a different basis is chosen, either
7158 the name must be changed or the ``.pkl`` file containing the
7159 previous data must first be renamed or deleted.
7160
7161 ``s.res`` is a dictionary with an entry for each "coordinate" ``i``
7162 and with an entry ``'x'``, the middle point. Each entry ``i`` is
7163 again a dictionary with keys being different dx values and the
7164 value being a sequence of f-values. For example ``s.res[2][0.1] ==
7165 [0.01, 0.01]``, which is generated using the difference vector ``s
7166 .basis[2]`` like
7167
7168 ``s.res[2][dx] += func(s.res['x'] + dx * s.basis[2])``.
7169
7170 :See: `__init__()`
7171
7172 """
7173 - def __init__(self, func, x, args=(), basis=None, name=None,
7174 plot_cmd=pyplot.plot if pyplot else None, load=True):
7175 """
7176 Parameters
7177 ----------
7178 `func`
7179 objective function
7180 `x`
7181 point in search space, middle point of the sections
7182 `args`
7183 arguments passed to `func`
7184 `basis`
7185 evaluated points are ``func(x + locations[j] * basis[i])
7186 for i in len(basis) for j in len(locations)``,
7187 see `do()`
7188 `name`
7189 filename where to save the result
7190 `plot_cmd`
7191 command used to plot the data, typically matplotlib pyplots `plot` or `semilogy`
7192 `load`
7193 load previous data from file ``str(func) + '.pkl'``
7194
7195 """
7196 self.func = func
7197 self.args = args
7198 self.x = x
7199 self.name = name if name else str(func).replace(' ', '_').replace('>', '').replace('<', '')
7200 self.plot_cmd = plot_cmd
7201 self.basis = np.eye(len(x)) if basis is None else basis
7202
7203 try:
7204 self.load()
7205 if any(self.res['x'] != x):
7206 self.res = {}
7207 self.res['x'] = x
7208 else:
7209 print(self.name + ' loaded')
7210 except:
7211 self.res = {}
7212 self.res['x'] = x
7213
7214 - def do(self, repetitions=1, locations=np.arange(-0.5, 0.6, 0.2), plot=True):
7215 """generates, plots and saves function values ``func(y)``,
7216 where ``y`` is 'close' to `x` (see `__init__()`). The data are stored in
7217 the ``res`` attribute and the class instance is saved in a file
7218 with (the weired) name ``str(func)``.
7219
7220 Parameters
7221 ----------
7222 `repetitions`
7223 for each point, only for noisy functions is >1 useful. For
7224 ``repetitions==0`` only already generated data are plotted.
7225 `locations`
7226 coordinated wise deviations from the middle point given in `__init__`
7227
7228 """
7229 if not repetitions:
7230 self.plot()
7231 return
7232
7233 res = self.res
7234 for i in xrange(len(self.basis)):
7235 if i not in res:
7236 res[i] = {}
7237
7238
7239 for dx in locations:
7240 xx = self.x + dx * self.basis[i]
7241 xkey = dx
7242 if xkey not in res[i]:
7243 res[i][xkey] = []
7244 n = repetitions
7245 while n > 0:
7246 n -= 1
7247 res[i][xkey].append(self.func(xx, *self.args))
7248 if plot:
7249 self.plot()
7250 self.save()
7251 return self
7252
7253 - def plot(self, plot_cmd=None, tf=lambda y: y):
7254 """plot the data we have, return ``self``"""
7255 if not plot_cmd:
7256 plot_cmd = self.plot_cmd
7257 colors = 'bgrcmyk'
7258 pyplot.hold(False)
7259 res = self.res
7260
7261 flatx, flatf = self.flattened()
7262 minf = np.inf
7263 for i in flatf:
7264 minf = min((minf, min(flatf[i])))
7265 addf = 1e-9 - minf if minf <= 1e-9 else 0
7266 for i in sorted(res.keys()):
7267 if isinstance(i, int):
7268 color = colors[i % len(colors)]
7269 arx = sorted(res[i].keys())
7270 plot_cmd(arx, [tf(np.median(res[i][x]) + addf) for x in arx], color + '-')
7271 pyplot.text(arx[-1], tf(np.median(res[i][arx[-1]])), i)
7272 pyplot.hold(True)
7273 plot_cmd(flatx[i], tf(np.array(flatf[i]) + addf), color + 'o')
7274 pyplot.ylabel('f + ' + str(addf))
7275 pyplot.draw()
7276 pyplot.ion()
7277 pyplot.show()
7278
7279 return self
7280
7282 """return flattened data ``(x, f)`` such that for the sweep through
7283 coordinate ``i`` we have for data point ``j`` that ``f[i][j] == func(x[i][j])``
7284
7285 """
7286 flatx = {}
7287 flatf = {}
7288 for i in self.res:
7289 if isinstance(i, int):
7290 flatx[i] = []
7291 flatf[i] = []
7292 for x in sorted(self.res[i]):
7293 for d in sorted(self.res[i][x]):
7294 flatx[i].append(x)
7295 flatf[i].append(d)
7296 return flatx, flatf
7297
7298 - def save(self, name=None):
7299 """save to file"""
7300 import pickle
7301 name = name if name else self.name
7302 fun = self.func
7303 del self.func
7304 pickle.dump(self, open(name + '.pkl', "wb"))
7305 self.func = fun
7306 return self
7307
7308 - def load(self, name=None):
7309 """load from file"""
7310 import pickle
7311 name = name if name else self.name
7312 s = pickle.load(open(name + '.pkl', 'rb'))
7313 self.res = s.res
7314 return self
7315
7316
7317
7318 -class _Error(Exception):
7319 """generic exception of cma module"""
7320 pass
7321
7326 """using ``time.clock`` with overflow handling to measure CPU time.
7327
7328 Example:
7329
7330 >>> clock = ElapsedTime() # clock starts here
7331 >>> t1 = clock() # get elapsed CPU time
7332
7333 Details: 32-bit C overflows after int(2**32/1e6) == 4294s about 72 min
7334
7335 """
7337 self.tic0 = time.clock()
7338 self.tic = self.tic0
7339 self.lasttoc = time.clock()
7340 self.lastdiff = time.clock() - self.lasttoc
7341 self.time_to_add = 0
7342 self.messages = 0
7343 reset = __init__
7345 toc = time.clock()
7346 if toc - self.tic >= self.lasttoc - self.tic:
7347 self.lastdiff = toc - self.lasttoc
7348 self.lasttoc = toc
7349 else:
7350 if self.messages < 3:
7351 self.messages += 1
7352 print(' in cma.ElapsedTime: time measure overflow, last difference estimated from',
7353 self.tic0, self.tic, self.lasttoc, toc, toc - self.lasttoc, self.lastdiff)
7354
7355 self.time_to_add += self.lastdiff + self.lasttoc - self.tic
7356 self.tic = toc
7357 self.lasttoc = toc
7358 self.elapsedtime = toc - self.tic + self.time_to_add
7359 return self.elapsedtime
7360
7361 -class Misc(object):
7362
7363
7364
7366 """static convenience math helper functions, if the function name
7367 is preceded with an "a", a numpy array is returned
7368
7369 """
7370 @staticmethod
7373 @staticmethod
7375 if a < 0:
7376 a, b = -1 * a, -1 * b
7377 return (a - eps < b < a + eps) or ((1 - eps) * a < b < (1 + eps) * a)
7378 @staticmethod
7380 a, b = array(a), array(b)
7381 idx = np.where(a < 0)[0]
7382 if len(idx):
7383 a[idx], b[idx] = -1 * a[idx], -1 * b[idx]
7384 return (np.all(a - eps < b) and np.all(b < a + eps)
7385 ) or (np.all((1 - eps) * a < b) and np.all(b < (1 + eps) * a))
7386 @staticmethod
7387 - def expms(A, eig=np.linalg.eigh):
7388 """matrix exponential for a symmetric matrix"""
7389
7390
7391 D, B = eig(A)
7392 return np.dot(B, (np.exp(D) * B).T)
7393 @staticmethod
7394 - def amax(vec, vec_or_scalar):
7396 @staticmethod
7397 - def max(vec, vec_or_scalar):
7398 b = vec_or_scalar
7399 if isscalar(b):
7400 m = [max(x, b) for x in vec]
7401 else:
7402 m = [max(vec[i], b[i]) for i in rglen((vec))]
7403 return m
7404 @staticmethod
7405 - def minmax(val, min_val, max_val):
7406 assert min_val <= max_val
7407 return min((max_val, max((val, min_val))))
7408 @staticmethod
7409 - def aminmax(val, min_val, max_val):
7410 return array([min((max_val, max((v, min_val)))) for v in val])
7411 @staticmethod
7412 - def amin(vec_or_scalar, vec_or_scalar2):
7414 @staticmethod
7416 iss = isscalar
7417 if iss(a) and iss(b):
7418 return min(a, b)
7419 if iss(a):
7420 a, b = b, a
7421
7422 if iss(b):
7423 return [min(x, b) for x in a]
7424 else:
7425 return [min(a[i], b[i]) for i in rglen((a))]
7426 @staticmethod
7427 - def norm(vec, expo=2):
7428 return sum(vec**expo)**(1 / expo)
7429 @staticmethod
7430 - def apos(x, lower=0):
7431 """clips argument (scalar or array) from below at lower"""
7432 if lower == 0:
7433 return (x > 0) * x
7434 else:
7435 return lower + (x > lower) * (x - lower)
7436 @staticmethod
7437 - def prctile(data, p_vals=[0, 25, 50, 75, 100], sorted_=False):
7438 """``prctile(data, 50)`` returns the median, but p_vals can
7439 also be a sequence.
7440
7441 Provides for small samples better values than matplotlib.mlab.prctile,
7442 however also slower.
7443
7444 """
7445 ps = [p_vals] if isscalar(p_vals) else p_vals
7446
7447 if not sorted_:
7448 data = sorted(data)
7449 n = len(data)
7450 d = []
7451 for p in ps:
7452 fi = p * n / 100 - 0.5
7453 if fi <= 0:
7454 d.append(data[0])
7455 elif fi >= n - 1:
7456 d.append(data[-1])
7457 else:
7458 i = int(fi)
7459 d.append((i + 1 - fi) * data[i] + (fi - i) * data[i + 1])
7460 return d[0] if isscalar(p_vals) else d
7461 @staticmethod
7463 """return stochastic round: floor(nb) + (rand()<remainder(nb))"""
7464 return nb // 1 + (np.random.rand(1)[0] < (nb % 1))
7465
7466 @staticmethod
7468 n = np.random.randn() / np.random.randn()
7469 while abs(n) > 1000:
7470 n = np.random.randn() / np.random.randn()
7471 return n / 25
7472 @staticmethod
7488
7489
7490 @staticmethod
7491 - def likelihood(x, m=None, Cinv=None, sigma=1, detC=None):
7492 """return likelihood of x for the normal density N(m, sigma**2 * Cinv**-1)"""
7493
7494
7495 if m is None:
7496 dx = x
7497 else:
7498 dx = x - m
7499 n = len(x)
7500 s2pi = (2 * np.pi)**(n / 2.)
7501 if Cinv is None:
7502 return exp(-sum(dx**2) / sigma**2 / 2) / s2pi / sigma**n
7503 if detC is None:
7504 detC = 1. / np.linalg.linalg.det(Cinv)
7505 return exp(-np.dot(dx, np.dot(Cinv, dx)) / sigma**2 / 2) / s2pi / abs(detC)**0.5 / sigma**n
7506
7507 @staticmethod
7509 """return log-likelihood of `x` regarding the current sample distribution"""
7510
7511
7512
7513
7514
7515
7516 if previous and hasattr(self, 'lastiter'):
7517 sigma = self.lastiter.sigma
7518 Crootinv = self.lastiter._Crootinv
7519 xmean = self.lastiter.mean
7520 D = self.lastiter.D
7521 elif previous and self.countiter > 1:
7522 raise _Error('no previous distribution parameters stored, check options importance_mixing')
7523 else:
7524 sigma = self.sigma
7525 Crootinv = self._Crootinv
7526 xmean = self.mean
7527 D = self.D
7528
7529 dx = array(x) - xmean
7530 n = self.N
7531 logs2pi = n * log(2 * np.pi) / 2.
7532 logdetC = 2 * sum(log(D))
7533 dx = np.dot(Crootinv, dx)
7534 res = -sum(dx**2) / sigma**2 / 2 - logs2pi - logdetC / 2 - n * log(sigma)
7535 if 1 < 3:
7536 s2pi = (2 * np.pi)**(n / 2.)
7537 detC = np.prod(D)**2
7538 res2 = -sum(dx**2) / sigma**2 / 2 - log(s2pi * abs(detC)**0.5 * sigma**n)
7539 assert res2 < res + 1e-8 or res2 > res - 1e-8
7540 return res
7541
7542
7543
7544
7545
7546
7547
7548
7549
7550
7551
7552
7553 @staticmethod
7555 """eigendecomposition of a symmetric matrix, much slower than
7556 `numpy.linalg.eigh`, return ``(EVals, Basis)``, the eigenvalues
7557 and an orthonormal basis of the corresponding eigenvectors, where
7558
7559 ``Basis[i]``
7560 the i-th row of ``Basis``
7561 columns of ``Basis``, ``[Basis[j][i] for j in range(len(Basis))]``
7562 the i-th eigenvector with eigenvalue ``EVals[i]``
7563
7564 """
7565
7566
7567
7568
7569
7570
7571
7572
7573
7574
7575
7576
7577
7578
7579
7580
7581
7582
7583
7584
7585
7586
7587
7588
7589
7590
7591
7592 def tred2 (n, V, d, e):
7593
7594
7595
7596
7597
7598 num_opt = False
7599
7600 for j in xrange(n):
7601 d[j] = V[n - 1][j]
7602
7603
7604
7605 for i in xrange(n - 1, 0, -1):
7606
7607 h = 0.0
7608 if not num_opt:
7609 scale = 0.0
7610 for k in xrange(i):
7611 scale = scale + abs(d[k])
7612 else:
7613 scale = sum(abs(d[0:i]))
7614
7615 if scale == 0.0:
7616 e[i] = d[i - 1]
7617 for j in xrange(i):
7618 d[j] = V[i - 1][j]
7619 V[i][j] = 0.0
7620 V[j][i] = 0.0
7621 else:
7622
7623
7624 if not num_opt:
7625 for k in xrange(i):
7626 d[k] /= scale
7627 h += d[k] * d[k]
7628 else:
7629 d[:i] /= scale
7630 h = np.dot(d[:i], d[:i])
7631
7632 f = d[i - 1]
7633 g = h**0.5
7634
7635 if f > 0:
7636 g = -g
7637
7638 e[i] = scale * g
7639 h = h - f * g
7640 d[i - 1] = f - g
7641 if not num_opt:
7642 for j in xrange(i):
7643 e[j] = 0.0
7644 else:
7645 e[:i] = 0.0
7646
7647
7648
7649 for j in xrange(i):
7650 f = d[j]
7651 V[j][i] = f
7652 g = e[j] + V[j][j] * f
7653 if not num_opt:
7654 for k in xrange(j + 1, i):
7655 g += V[k][j] * d[k]
7656 e[k] += V[k][j] * f
7657 e[j] = g
7658 else:
7659 e[j + 1:i] += V.T[j][j + 1:i] * f
7660 e[j] = g + np.dot(V.T[j][j + 1:i], d[j + 1:i])
7661
7662 f = 0.0
7663 if not num_opt:
7664 for j in xrange(i):
7665 e[j] /= h
7666 f += e[j] * d[j]
7667 else:
7668 e[:i] /= h
7669 f += np.dot(e[:i], d[:i])
7670
7671 hh = f / (h + h)
7672 if not num_opt:
7673 for j in xrange(i):
7674 e[j] -= hh * d[j]
7675 else:
7676 e[:i] -= hh * d[:i]
7677
7678 for j in xrange(i):
7679 f = d[j]
7680 g = e[j]
7681 if not num_opt:
7682 for k in xrange(j, i):
7683 V[k][j] -= (f * e[k] + g * d[k])
7684 else:
7685 V.T[j][j:i] -= (f * e[j:i] + g * d[j:i])
7686
7687 d[j] = V[i - 1][j]
7688 V[i][j] = 0.0
7689
7690 d[i] = h
7691
7692
7693
7694
7695 for i in xrange(n - 1):
7696 V[n - 1][i] = V[i][i]
7697 V[i][i] = 1.0
7698 h = d[i + 1]
7699 if h != 0.0:
7700 if not num_opt:
7701 for k in xrange(i + 1):
7702 d[k] = V[k][i + 1] / h
7703 else:
7704 d[:i + 1] = V.T[i + 1][:i + 1] / h
7705
7706 for j in xrange(i + 1):
7707 if not num_opt:
7708 g = 0.0
7709 for k in xrange(i + 1):
7710 g += V[k][i + 1] * V[k][j]
7711 for k in xrange(i + 1):
7712 V[k][j] -= g * d[k]
7713 else:
7714 g = np.dot(V.T[i + 1][0:i + 1], V.T[j][0:i + 1])
7715 V.T[j][:i + 1] -= g * d[:i + 1]
7716
7717 if not num_opt:
7718 for k in xrange(i + 1):
7719 V[k][i + 1] = 0.0
7720 else:
7721 V.T[i + 1][:i + 1] = 0.0
7722
7723
7724 if not num_opt:
7725 for j in xrange(n):
7726 d[j] = V[n - 1][j]
7727 V[n - 1][j] = 0.0
7728 else:
7729 d[:n] = V[n - 1][:n]
7730 V[n - 1][:n] = 0.0
7731
7732 V[n - 1][n - 1] = 1.0
7733 e[0] = 0.0
7734
7735
7736
7737
7738
7739 def tql2 (n, d, e, V):
7740
7741
7742
7743
7744
7745
7746 num_opt = False
7747
7748 if not num_opt:
7749 for i in xrange(1, n):
7750 e[i - 1] = e[i]
7751 else:
7752 e[0:n - 1] = e[1:n]
7753 e[n - 1] = 0.0
7754
7755 f = 0.0
7756 tst1 = 0.0
7757 eps = 2.0**-52.0
7758 for l in xrange(n):
7759
7760
7761
7762 tst1 = max(tst1, abs(d[l]) + abs(e[l]))
7763 m = l
7764 while m < n:
7765 if abs(e[m]) <= eps * tst1:
7766 break
7767 m += 1
7768
7769
7770
7771
7772 if m > l:
7773 iiter = 0
7774 while 1:
7775 iiter += 1
7776
7777
7778
7779 g = d[l]
7780 p = (d[l + 1] - g) / (2.0 * e[l])
7781 r = (p**2 + 1)**0.5
7782 if p < 0:
7783 r = -r
7784
7785 d[l] = e[l] / (p + r)
7786 d[l + 1] = e[l] * (p + r)
7787 dl1 = d[l + 1]
7788 h = g - d[l]
7789 if not num_opt:
7790 for i in xrange(l + 2, n):
7791 d[i] -= h
7792 else:
7793 d[l + 2:n] -= h
7794
7795 f = f + h
7796
7797
7798
7799 p = d[m]
7800 c = 1.0
7801 c2 = c
7802 c3 = c
7803 el1 = e[l + 1]
7804 s = 0.0
7805 s2 = 0.0
7806
7807
7808 for i in xrange(m - 1, l - 1, -1):
7809 c3 = c2
7810 c2 = c
7811 s2 = s
7812 g = c * e[i]
7813 h = c * p
7814 r = (p**2 + e[i]**2)**0.5
7815 e[i + 1] = s * r
7816 s = e[i] / r
7817 c = p / r
7818 p = c * d[i] - s * g
7819 d[i + 1] = h + s * (c * g + s * d[i])
7820
7821
7822
7823 if not num_opt:
7824 for k in xrange(n):
7825 h = V[k][i + 1]
7826 V[k][i + 1] = s * V[k][i] + c * h
7827 V[k][i] = c * V[k][i] - s * h
7828 else:
7829 hh = V.T[i + 1].copy()
7830
7831 V.T[i + 1] = s * V.T[i] + c * hh
7832 V.T[i] = c * V.T[i] - s * hh
7833
7834
7835
7836 p = -s * s2 * c3 * el1 * e[l] / dl1
7837 e[l] = s * p
7838 d[l] = c * p
7839
7840
7841 if abs(e[l]) <= eps * tst1:
7842 break
7843
7844
7845 d[l] = d[l] + f
7846 e[l] = 0.0
7847
7848
7849
7850
7851
7852 N = len(C[0])
7853 if 1 < 3:
7854 V = [[x[i] for i in xrange(N)] for x in C]
7855 d = N * [0.]
7856 e = N * [0.]
7857
7858 tred2(N, V, d, e)
7859 tql2(N, d, e, V)
7860 return (array(d), array(V))
7861 Mh = Misc.MathHelperFunctions
7862
7863
7864
7865
7866 -def pprint(to_be_printed):
7867 """nicely formated print"""
7868 try:
7869 import pprint as pp
7870
7871
7872 pp.pprint(to_be_printed)
7873 except ImportError:
7874 if isinstance(to_be_printed, dict):
7875 print('{')
7876 for k, v in to_be_printed.items():
7877 print("'" + k + "'" if isinstance(k, basestring) else k,
7878 ': ',
7879 "'" + v + "'" if isinstance(k, basestring) else v,
7880 sep="")
7881 print('}')
7882 else:
7883 print('could not import pprint module, appling regular print')
7884 print(to_be_printed)
7885
7886 pp = pprint
7889 """``ConstRandnShift()(x)`` adds a fixed realization of
7890 ``stddev * randn(len(x))`` to the vector x.
7891
7892 By default, the realized shift is the same for each instance of
7893 ``ConstRandnShift``, see ``seed`` argument. This class is used in
7894 class ``FFWrapper.ShiftedFitness`` as default transformation.
7895
7896 See: class ``FFWrapper.ShiftedFitness``
7897
7898 """
7900 """with ``seed=None`` each instance realizes a different shift"""
7901 self.seed = seed
7902 self.stddev = stddev
7903 self._xopt = {}
7905 """return "shifted" ``x - shift``
7906
7907 """
7908 try:
7909 x_opt = self._xopt[len(x)]
7910 except KeyError:
7911 if self.seed is None:
7912 shift = np.random.randn(len(x))
7913 else:
7914 rstate = np.random.get_state()
7915 np.random.seed(self.seed)
7916 shift = np.random.randn(len(x))
7917 np.random.set_state(rstate)
7918 x_opt = self._xopt.setdefault(len(x), self.stddev * shift)
7919 return array(x, copy=False) - x_opt
7920 - def get(self, dimension):
7921 """return shift applied to ``zeros(dimension)``
7922
7923 >>> import numpy as np, cma
7924 >>> s = cma.ConstRandnShift()
7925 >>> assert all(s(-s.get(3)) == np.zeros(3))
7926 >>> assert all(s.get(3) == s(np.zeros(3)))
7927
7928 """
7929 return self.__call__(np.zeros(dimension))
7930
7932 """Rotation class that implements an orthogonal linear transformation,
7933 one for each dimension.
7934
7935 By default reach ``Rotation`` instance provides a different "random"
7936 but fixed rotation. This class is used to implement non-separable
7937 test functions, most conveniently via `FFWrapper.RotatedFitness`.
7938
7939 Example:
7940
7941 >>> import cma, numpy as np
7942 >>> R = cma.Rotation()
7943 >>> R2 = cma.Rotation() # another rotation
7944 >>> x = np.array((1,2,3))
7945 >>> print(R(R(x), inverse=1))
7946 [ 1. 2. 3.]
7947
7948 See: `FFWrapper.RotatedFitness`
7949
7950 """
7951 dicMatrices = {}
7953 """by default a random but fixed rotation, different for each instance"""
7954 self.seed = seed
7955 self.dicMatrices = {}
7957 """Rotates the input array `x` with a fixed rotation matrix
7958 (``self.dicMatrices['str(len(x))']``)
7959 """
7960 x = np.array(x, copy=False)
7961 N = x.shape[0]
7962 if str(N) not in self.dicMatrices:
7963 rstate = np.random.get_state()
7964 np.random.seed(self.seed) if self.seed else np.random.seed()
7965 B = np.random.randn(N, N)
7966 for i in xrange(N):
7967 for j in xrange(0, i):
7968 B[i] -= np.dot(B[i], B[j]) * B[j]
7969 B[i] /= sum(B[i]**2)**0.5
7970 self.dicMatrices[str(N)] = B
7971 np.random.set_state(rstate)
7972 if inverse:
7973 return np.dot(self.dicMatrices[str(N)].T, x)
7974 else:
7975 return np.dot(self.dicMatrices[str(N)], x)
7976
7977 rotate = Rotation()
7978
7979
7980
7981
7982
7983 -class FFWrapper(object):
7984 """
7985 A collection of (yet experimental) classes to implement fitness
7986 transformations and wrappers. Aliased to `FF2` below.
7987
7988 """
8013 """a stump for experimenting with use-cases and possible
8014 extensions of book keeping
8015
8016 use-case:
8017
8018 f = BookKeeping(f)
8019 print(f.count_evaluations)
8020
8021 """
8023 self.count_evaluations = 0
8024 self.inner_fitness = callable
8026
8027 self.count_evaluations += 1
8028 return self.inner_fitness(*args)
8061
8063 """define a scaling of each variable
8064 """
8065 - def __init__(self, fitness_function, multipliers=None):
8066 """
8067 :param fitness_function: a callable object
8068 :param multipliers: recycling is not implemented, i.e.
8069 the dimension must fit to the `fitness_function` argument
8070 when called
8071 """
8072 super(FFWrapper.ScaleCoordinates, self).__init__(
8073 fitness_function, self.transformation)
8074
8075
8076 self.multiplier = multipliers
8077 if self.multiplier is not None and hasattr(self.multiplier, 'len'):
8078 self.multiplier = array(self.multiplier, copy=True)
8083
8085 """``f = cma.ShiftedFitness(cma.fcts.sphere)`` constructs a
8086 shifted sphere function, by default the shift is computed
8087 from class ``ConstRandnShift`` with std dev 3.
8088
8089 """
8091 """``shift(x)`` must return a (stable) shift of x.
8092
8093 Details: this class solely provides as default second
8094 argument to TransformSearchSpace a shift in search space.
8095 ``shift=lambda x: x`` would provide "no shift", ``None``
8096 expands to ``cma.ConstRandnShift()``.
8097
8098 """
8099 self.inner_fitness = f
8100 self.x_transformation = shift if shift else ConstRandnShift()
8101
8103 """``f = cma.RotatedFitness(cma.fcts.elli)`` constructs a
8104 rotated ellipsoid function
8105
8106 """
8108 """``rotate(x)`` must return a (stable) rotation of x.
8109
8110 Details: this class solely provides a default second
8111 argument to TransformSearchSpace, namely a search space
8112 rotation.
8113
8114 """
8115 super(FFWrapper.RotatedFitness, self).__init__(f, rotate)
8116
8118 """fix variables to given values, thereby reducing the
8119 dimensionality of the preimage.
8120
8121 The constructor takes ``index_value_pairs`` as dict or list of
8122 pairs as input and returns a function with smaller preimage space
8123 than `f`.
8124
8125 Details: this might replace the fixed_variables option in
8126 CMAOptions in future, but hasn't been tested yet.
8127
8128 """
8129 - def __init__(self, f, index_value_pairs):
8135 y = np.zeros(len(x) + len(self.index_value_pairs))
8136 assert len(y) > max(self.index_value_pairs)
8137 j = 0
8138 for i in xrange(len(y)):
8139 if i in self.index_value_pairs:
8140 y[i] = self.index_value_pairs[i]
8141 else:
8142 y[i] = x[j]
8143 j += 1
8144 return y
8146 - def __init__(self, fitness_function, probability_of_nan=0.1):
8147 self.p = probability_of_nan
8148 self.inner_fitness = fitness_function
8150 if np.random.rand(1) <= self.p:
8151 return np.NaN
8152 else:
8153 return self.inner_fitness(x, *args)
8155 """apply noise via f += rel_noise(dim) * f + abs_noise()"""
8156 - def __init__(self, fitness_function,
8157 rel_noise=lambda dim: 1.1 * np.random.randn() / dim,
8158 abs_noise=lambda: 1.1 * np.random.randn()):
8159 self.rel_noise = rel_noise
8160 self.abs_noise = abs_noise
8161 self.inner_fitness = fitness_function
8163 f = self.inner_fitness(x, *args)
8164 if self.rel_noise:
8165 f += f * self.rel_noise(len(x))
8166 assert isscalar(f)
8167 if self.abs_noise:
8168 f += self.abs_noise()
8169 return f
8171 """``f = cma.FF2.GlueArguments(cma.fcts.elli, cond=1e4)``
8172
8173 >>> import cma
8174 >>> f = cma.FF2.GlueArguments(cma.fcts.elli, cond=1e1)
8175 >>> f([1, 2]) # == 1**2 + 1e1 * 2**2
8176 41.0
8177
8178 """
8179 - def __init__(self, fitness_function, *args, **kwargs):
8180 self.inner_fitness = fitness_function
8181 self.args = args
8182 self.kwargs = kwargs
8184 return self.inner_fitness(array(x, copy=False),
8185 *(args + self.args), **self.kwargs)
8187 """search in [-10, 10] for the unknown (optimum)"""
8189 self.seed = seed
8190 self._x_opt_ = {}
8191 self.rotate = Rotation(seed)
8192 self.count_evaluations = 0
8194 rstate = np.random.get_state()
8195 np.random.seed(self.seed)
8196 x = self._x_opt_.setdefault(dim,
8197 0 * 3 * np.random.randn(dim))
8198 np.random.set_state(rstate)
8199 return x
8201 off = self.rotate(np.floor(np.arange(0, 3, 3. / dim)) /
8202 np.logspace(0, 1, dim), inverse=True)
8203 off[np.s_[3:]] += 0.005
8204 off[-1] *= 1e2
8205 off[0] /= 2.0e3 if off[0] > 0 else 1e3
8206 off[2] /= 3.01e4 if off[2] < 0 else 2e4
8207 return self._x_opt(dim) + off
8209 self.count_evaluations += 1
8210 N = len(x)
8211 x = x - self._x_opt(N)
8212 x[-1] /= 1e2
8213 x[0] *= 2.0e3 if x[0] > 0 else 1e3
8214 x[2] *= 3.01e4 if x[2] < 0 else 2e4
8215 x = np.logspace(0, 1, N) * self.rotate(x)
8216 return 10 * N - np.e**2 + \
8217 sum(x**2 - 10 * np.cos(2 * np.pi * x))
8218
8219 FF2 = FFWrapper
8222 """ versatile container for test objective functions """
8223
8226 - def rot(self, x, fun, rot=1, args=()):
8227 """returns ``fun(rotation(x), *args)``, ie. `fun` applied to a rotated argument"""
8228 if len(np.shape(array(x))) > 1:
8229 res = []
8230 for x in x:
8231 res.append(self.rot(x, fun, rot, args))
8232 return res
8233
8234 if rot:
8235 return fun(rotate(x, *args))
8236 else:
8237 return fun(x)
8238 - def somenan(self, x, fun, p=0.1):
8239 """returns sometimes np.NaN, otherwise fun(x)"""
8240 if np.random.rand(1) < p:
8241 return np.NaN
8242 else:
8243 return fun(x)
8244 - def rand(self, x):
8245 """Random test objective function"""
8246 return np.random.random(1)[0]
8250 if 1 < 3 and any(array(x) < 0):
8251 return np.nan
8252 if 1 < 3 and sum([ (10 + i) * x[i] for i in rglen(x)]) > 50e3:
8253 return np.nan
8254 return -sum(x)
8256 """Sphere (squared norm) test objective function"""
8257
8258 return sum((x + 0)**2)
8260 return 2*array(x, copy=False)
8262 return array(x, copy=False) - 1
8264 """Sphere (squared norm) test objective function"""
8265
8266 c = 0.0
8267 if x[0] < c:
8268 return np.nan
8269 return -c**2 + sum((x + 0)**2)
8271 return sum((x + 0)**2) if x[0] > 1 else np.nan
8273 return self.ellirot(x) if all(array(x)[idx] > 1) else np.nan
8274
8276 return sum((x + 0)**2) if all(array(x) > 1) else np.nan
8277
8278 - def noisysphere(self, x, noise=2.10e-9, cond=1.0, noise_offset=0.10):
8279 """noise=10 does not work with default popsize, noise handling does not help """
8280 return self.elli(x, cond=cond) * (1 + noise * np.random.randn() / len(x)) + noise_offset * np.random.rand()
8282 """Sphere (squared norm) with sum x_i = 1 test objective function"""
8283
8284
8285
8286
8287 return -0.01 * x[0] + abs(x[0])**-2 * sum(x[1:]**2)
8289 """Sphere (squared norm) test objective function"""
8290 self.counter += 1
8291
8292 dim = len(x)
8293 x = array([x[i % dim] for i in xrange(2 * dim)])
8294 N = 8
8295 i = self.counter % dim
8296
8297 f = sum(x[np.random.randint(dim, size=N)]**2)
8298 return f
8300 """asymmetric Sphere (squared norm) test objective function"""
8301 return sum(x**2) + (1e6 - 1) * sum(x[x < 0]**2)
8303 """Sphere (squared norm) test objective function constraint to the corner"""
8304 nconstr = len(x) - 0
8305 if any(x[:nconstr] < 1):
8306 return np.NaN
8307 return sum(x**2) - nconstr
8309 """ """
8310 if any(x < 1):
8311 return np.NaN
8312 return self.elli(x) - self.elli(np.ones(len(x)))
8314 """ """
8315 if any(x < 1):
8316 return np.NaN
8317 return self.ellirot(x)
8319 N = np.random.randn(1)[0]**2
8320 if N < 1:
8321 N = f * N
8322 return N
8324 f = func(self, x)
8325 N = np.random.randn(1)[0] / np.random.randn(1)[0]
8326 return max(1e-19, f + (float(fac) / len(x)) * f**expon * N)
8328 f = func(self, x)
8329
8330 R = np.log10(f) + expon * abs(10 - np.log10(f)) * np.random.rand(1)[0]
8331
8332
8333
8334
8335
8336 return f + 10**R
8337 - def cigar(self, x, rot=0, cond=1e6, noise=0):
8338 """Cigar test objective function"""
8339 if rot:
8340 x = rotate(x)
8341 x = [x] if isscalar(x[0]) else x
8342 f = [(x[0]**2 + cond * sum(x[1:]**2)) * np.exp(noise * np.random.randn(1)[0] / len(x)) for x in x]
8343 return f if len(f) > 1 else f[0]
8345 grad = 2 * 1e6 * np.array(x)
8346 grad[0] /= 1e6
8347 return grad
8349 axis = np.ones(len(x)) / len(x)**0.5
8350 proj = dot(axis, x) * axis
8351 s = sum(proj**2)
8352 s += cond * sum((x - proj)**2)
8353 return s
8355 """Tablet test objective function"""
8356 if rot and rot is not fcts.tablet:
8357 x = rotate(x)
8358 x = [x] if isscalar(x[0]) else x
8359 f = [1e6 * x[0]**2 + sum(x[1:]**2) for x in x]
8360 return f if len(f) > 1 else f[0]
8362 grad = 2 * np.array(x)
8363 grad[0] *= 1e6
8364 return grad
8366 """Cigtab test objective function"""
8367 X = [y] if isscalar(y[0]) else y
8368 f = [1e-4 * x[0]**2 + 1e4 * x[1]**2 + sum(x[2:]**2) for x in X]
8369 return f if len(f) > 1 else f[0]
8371 """Cigtab test objective function"""
8372 X = [y] if isscalar(y[0]) else y
8373 N2 = len(X[0]) // 2
8374 f = [1e6 * sum(x[0:N2]**2) + sum(x[N2:]**2) for x in X]
8375 return f if len(f) > 1 else f[0]
8379 N = len(x)
8380 return sum((np.arange(1, N + 1) * x)**2)
8382 l = len(x) // 2
8383 felli = self.elli(x[:l])
8384 return felli + 1e-8 * sum(x[l:]**2)
8385 - def elli(self, x, rot=0, xoffset=0, cond=1e6, actuator_noise=0.0, both=False):
8386 """Ellipsoid test objective function"""
8387 if not isscalar(x[0]):
8388 return [self.elli(xi, rot) for xi in x]
8389 if rot:
8390 x = rotate(x)
8391 N = len(x)
8392 if actuator_noise:
8393 x = x + actuator_noise * np.random.randn(N)
8394
8395 ftrue = sum(cond**(np.arange(N) / (N - 1.)) * (x + xoffset)**2)
8396
8397 alpha = 0.49 + 1. / N
8398 beta = 1
8399 felli = np.random.rand(1)[0]**beta * ftrue * \
8400 max(1, (10.**9 / (ftrue + 1e-99))**(alpha * np.random.rand(1)[0]))
8401
8402
8403 if both:
8404 return (felli, ftrue)
8405 else:
8406
8407 return ftrue
8409 cond = 1e6
8410 N = len(x)
8411 return 2 * cond**(np.arange(N) / (N - 1.)) * array(x, copy=False)
8413 """``fun_as_arg(x, fun, *more_args)`` calls ``fun(x, *more_args)``.
8414
8415 Use case::
8416
8417 fmin(cma.fun_as_arg, args=(fun,), gradf=grad_numerical)
8418
8419 calls fun_as_args(x, args) and grad_numerical(x, fun, args=args)
8420
8421 """
8422 fun = args[0]
8423 more_args = args[1:] if len(args) > 1 else ()
8424 return fun(x, *more_args)
8426 """symmetric gradient"""
8427 eps = 1e-8 * (1 + abs(x)) if epsilon is None else epsilon
8428 grad = np.zeros(len(x))
8429 ei = np.zeros(len(x))
8430 for i in rglen(x):
8431 ei[i] = eps[i]
8432 grad[i] = (func(x + ei) - func(x - ei)) / (2*eps[i])
8433 ei[i] = 0
8434 return grad
8436 """ellipsoid test objective function with "constraints" """
8437 N = len(x)
8438 f = sum(cond**(np.arange(N)[-1::-1] / (N - 1)) * x**2)
8439 cvals = (x[0] + 1,
8440 x[0] + 1 + 100 * x[1],
8441 x[0] + 1 - 100 * x[1])
8442 if tough:
8443 f += cfac * sum(max(0, c) for c in cvals)
8444 else:
8445 f += cfac * sum(max(0, c + 1e-3)**2 for c in cvals)
8446 return f
8447 - def rosen(self, x, alpha=1e2):
8448 """Rosenbrock test objective function"""
8449 x = [x] if isscalar(x[0]) else x
8450 f = [sum(alpha * (x[:-1]**2 - x[1:])**2 + (1. - x[:-1])**2) for x in x]
8451 return f if len(f) > 1 else f[0]
8453 N = len(x)
8454 grad = np.zeros(N)
8455 grad[0] = 2 * (x[0] - 1) + 200 * (x[1] - x[0]**2) * -2 * x[0]
8456 i = np.arange(1, N - 1)
8457 grad[i] = 2 * (x[i] - 1) - 400 * (x[i+1] - x[i]**2) * x[i] + 200 * (x[i] - x[i-1]**2)
8458 grad[N-1] = 200 * (x[N-1] - x[N-2]**2)
8459 return grad
8461 """Diffpow test objective function"""
8462 N = len(x)
8463 if rot:
8464 x = rotate(x)
8465 return sum(np.abs(x)**(2. + 4.*np.arange(N) / (N - 1.)))**0.5
8467 N = len(x)
8468 return self.rosen(x[:N / 2]) + self.elli(x[N / 2:], cond=1)
8469 - def ridge(self, x, expo=2):
8470 x = [x] if isscalar(x[0]) else x
8471 f = [x[0] + 100 * np.sum(x[1:]**2)**(expo / 2.) for x in x]
8472 return f if len(f) > 1 else f[0]
8474 """happy cat by HG Beyer"""
8475 a = len(x)
8476 s = sum(x**2)
8477 return ((s - a)**2)**(expo / 2) + s / a + sum(x) / a
8479 s = sum(x**2)
8480 return ((s - len(x))**2)**alpha + (s / 2 + sum(x)) / len(x) + 0.5
8481 - def flat(self, x):
8482 return 1
8483 return 1 if np.random.rand(1) < 0.9 else 1.1
8484 return np.random.randint(1, 30)
8486
8487 y = x[1]
8488 x = x[0] + 5
8489 return (y - 5.1 * x**2 / 4 / np.pi**2 + 5 * x / np.pi - 6)**2 + 10 * (1 - 1 / 8 / np.pi) * np.cos(x) + 10 - 0.397887357729738160000
8491 x1 = x[0]
8492 x2 = x[1]
8493 return (1 + (x1 + x2 + 1)**2 * (19 - 14 * x1 + 3 * x1**2 - 14 * x2 + 6 * x1 * x2 + 3 * x2**2)) * (
8494 30 + (2 * x1 - 3 * x2)**2 * (18 - 32 * x1 + 12 * x1**2 + 48 * x2 - 36 * x1 * x2 + 27 * x2**2)) - 3
8496
8497 x = (600. / 5) * x
8498 return 1 - np.prod(np.cos(x / sqrt(1. + np.arange(len(x))))) + sum(x**2) / 4e3
8500 """Rastrigin test objective function"""
8501 if not isscalar(x[0]):
8502 N = len(x[0])
8503 return [10 * N + sum(xi**2 - 10 * np.cos(2 * np.pi * xi)) for xi in x]
8504
8505 N = len(x)
8506 return 10 * N + sum(x**2 - 10 * np.cos(2 * np.pi * x))
8508 """ Schaffer function x0 in [-100..100]"""
8509 N = len(x)
8510 s = x[0:N - 1]**2 + x[1:N]**2
8511 return sum(s**0.25 * (np.sin(50 * s**0.1)**2 + 1))
8512
8514 s = 0
8515 f = 0
8516 for i in rglen(x):
8517 s += x[i]
8518 f += s**2
8519 return f
8521 """multimodal Schwefel function with domain -500..500"""
8522 y = [x] if isscalar(x[0]) else x
8523 N = len(y[0])
8524 f = array([418.9829 * N - 1.27275661e-5 * N - sum(x * np.sin(np.abs(x)**0.5))
8525 + pen_fac * sum((abs(x) > 500) * (abs(x) - 500)**2) for x in y])
8526 return f if len(f) > 1 else f[0]
8528 n = np.arange(len(x)) + 1
8529 f = n * x * (1 - x)**(n - 1)
8530 return sum(1 - f)
8531 - def lincon(self, x, theta=0.01):
8532 """ridge like linear function with one linear constraint"""
8533 if x[0] < 0:
8534 return np.NaN
8535 return theta * x[1] + x[0]
8537 """needs exponential number of steps in a non-increasing f-sequence.
8538
8539 x_0 = (-1,1,...,1)
8540 See Jarre (2011) "On Nesterov's Smooth Chebyshev-Rosenbrock Function"
8541
8542 """
8543 f = 0.25 * (x[0] - 1)**2
8544 f += rho * sum((x[1:] - 2 * x[:-1]**2 + 1)**2)
8545 return f
8547
8548 res = np.sum((x[i - 1] + 10 * x[i])**2 + 5 * (x[i + 1] - x[i + 2])**2 +
8549 (x[i] - 2 * x[i + 1])**4 + 10 * (x[i - 1] - x[i + 2])**4
8550 for i in xrange(1, len(x) - 2))
8551 return 1 + res
8553 """in [-5, 5]
8554 """
8555
8556
8557 return (39.1661657037714171054273576010019 * len(x))**1 + \
8558 sum(x**4 - 16*x**2 + 5*x) / 2
8559
8560 - def trid(self, x):
8561 return sum((x-1)**2) - sum(x[:-1] * x[1:])
8562
8564 """Bukin function from Wikipedia, generalized simplistically from 2-D.
8565
8566 http://en.wikipedia.org/wiki/Test_functions_for_optimization"""
8567 s = 0
8568 for k in xrange((1+len(x)) // 2):
8569 z = x[2 * k]
8570 y = x[min((2*k + 1, len(x)-1))]
8571 s += 100 * np.abs(y - 0.01 * z**2)**0.5 + 0.01 * np.abs(z + 10)
8572 return s
8573
8574 fcts = FitnessFunctions()
8575 Fcts = fcts
8576 FF = fcts
8579 """unbound test function, needed to test multiprocessor"""
8580 return sum(1e6**(np.arange(len(x)) / (len(x) - 1)) * (np.array(x, copy=False))**2)
8581
8582
8583
8584
8585 -def _test(module=None):
8586 import doctest
8587 print(doctest.testmod(module))
8589 """ """
8590 import fileinput
8591 s1 = ""
8592 s2 = ""
8593 s3 = ""
8594 state = 0
8595 for line in fileinput.input(stream):
8596 if 1 < 3:
8597 s3 += line
8598 if state < -1 and line.startswith('***'):
8599 print(s3)
8600 if line.startswith('***'):
8601 s3 = ""
8602
8603 if state == -1:
8604 s1 += '\n\n*** Failed Example:' + line
8605 s2 += '\n\n\n'
8606
8607
8608 if line.startswith('Expected:'):
8609 state = 1
8610 continue
8611 elif line.startswith('Got:'):
8612 state = 2
8613 continue
8614 elif line.startswith('***'):
8615 state = 0
8616 elif line.startswith('Failed example:'):
8617 state = -1
8618 elif line.startswith('Exception raised'):
8619 state = -2
8620
8621
8622 if state == 1:
8623 s1 += line + ''
8624 if state == 2:
8625 s2 += line + ''
8626
8627
8628
8629
8630 -def main(argv=None):
8631 """to install and/or test from the command line use::
8632
8633 python cma.py [options | func dim sig0 [optkey optval][optkey optval]...]
8634
8635 with options being
8636
8637 ``--test`` (or ``-t``) to run the doctest, ``--test -v`` to get (much) verbosity.
8638
8639 ``install`` to install cma.py (uses setup from distutils.core).
8640
8641 ``--doc`` for more infos.
8642
8643 Or start Python or (even better) ``ipython`` and::
8644
8645 import cma
8646 cma.main('--test')
8647 help(cma)
8648 help(cma.fmin)
8649 res = fmin(cma.fcts.rosen, 10 * [0], 1)
8650 cma.plot()
8651
8652 Examples
8653 ========
8654 Testing with the local python distribution from a command line
8655 in a folder where ``cma.py`` can be found::
8656
8657 python cma.py --test
8658
8659 And a single run on the Rosenbrock function::
8660
8661 python cma.py rosen 10 1 # dimension initial_sigma
8662 python cma.py plot
8663
8664 In the python shell::
8665
8666 import cma
8667 cma.main('--test')
8668
8669 """
8670 if argv is None:
8671 argv = sys.argv
8672 else:
8673 if isinstance(argv, list):
8674 argv = ['python'] + argv
8675 else:
8676 argv = ['python'] + [argv]
8677
8678
8679
8680
8681 if len(argv) >= 1:
8682 if len(argv) == 1 or argv[1].startswith('-h') or argv[1].startswith('--help'):
8683 print(main.__doc__)
8684 fun = None
8685 elif argv[1].startswith('-t') or argv[1].startswith('--test'):
8686 import doctest
8687 if len(argv) > 2 and (argv[2].startswith('--v') or argv[2].startswith('-v')):
8688 print('doctest for cma.py: due to different platforms and python versions')
8689 print('and in some cases due to a missing unique random seed')
8690 print('many examples will "fail". This is OK, if they give a similar')
8691 print('to the expected result and if no exception occurs. ')
8692
8693 doctest.testmod(sys.modules[__name__], report=True)
8694 else:
8695 print('doctest for cma.py: launching...')
8696 fn = '_cma_doctest_.txt'
8697 stdout = sys.stdout
8698 try:
8699 with open(fn, 'w') as f:
8700 sys.stdout = f
8701 clock = ElapsedTime()
8702 doctest.testmod(sys.modules[__name__], report=True)
8703 t_elapsed = clock()
8704 finally:
8705 sys.stdout = stdout
8706 process_doctest_output(fn)
8707
8708 try:
8709 import os
8710 for name in os.listdir('.'):
8711 if (name.startswith('bound_method_FitnessFunctions.rosen_of_cma.FitnessFunctions_object_at_')
8712 and name.endswith('.pkl')):
8713 os.remove(name)
8714 except:
8715 pass
8716 print('doctest for cma.py: finished (no other output should be seen after launching, more in file _cma_doctest_.txt)')
8717 print(' elapsed time [s]:', t_elapsed)
8718 return
8719 elif argv[1] == '--doc':
8720 print(__doc__)
8721 print(CMAEvolutionStrategy.__doc__)
8722 print(fmin.__doc__)
8723 fun = None
8724 elif argv[1] == '--fcts':
8725 print('List of valid function names:')
8726 print([d for d in dir(fcts) if not d.startswith('_')])
8727 fun = None
8728 elif argv[1] in ('install', '--install'):
8729 from distutils.core import setup
8730 setup(name="cma",
8731 long_description=__doc__,
8732 version=__version__.split()[0],
8733 description="CMA-ES, Covariance Matrix Adaptation Evolution Strategy for non-linear numerical optimization in Python",
8734 author="Nikolaus Hansen",
8735 author_email="hansen at lri.fr",
8736 maintainer="Nikolaus Hansen",
8737 maintainer_email="hansen at lri.fr",
8738 url="https://www.lri.fr/~hansen/cmaes_inmatlab.html#python",
8739 license="BSD",
8740 classifiers = [
8741 "Intended Audience :: Science/Research",
8742 "Intended Audience :: Education",
8743 "Intended Audience :: Other Audience",
8744 "Topic :: Scientific/Engineering",
8745 "Topic :: Scientific/Engineering :: Mathematics",
8746 "Topic :: Scientific/Engineering :: Artificial Intelligence",
8747 "Operating System :: OS Independent",
8748 "Programming Language :: Python :: 2.6",
8749 "Programming Language :: Python :: 2.7",
8750 "Programming Language :: Python :: 3",
8751 "Development Status :: 4 - Beta",
8752 "Environment :: Console",
8753 "License :: OSI Approved :: BSD License",
8754
8755 ],
8756 keywords=["optimization", "CMA-ES", "cmaes"],
8757 py_modules=["cma"],
8758 requires=["numpy"],
8759 )
8760 fun = None
8761 elif argv[1] in ('plot',):
8762 plot(name=argv[2] if len(argv) > 2 else None)
8763 raw_input('press return')
8764 fun = None
8765 elif len(argv) > 3:
8766 fun = eval('fcts.' + argv[1])
8767 else:
8768 print('try -h option')
8769 fun = None
8770
8771 if fun is not None:
8772
8773 if len(argv) > 2:
8774 x0 = np.ones(eval(argv[2]))
8775 if len(argv) > 3:
8776 sig0 = eval(argv[3])
8777
8778 opts = {}
8779 for i in xrange(5, len(argv), 2):
8780 opts[argv[i - 1]] = eval(argv[i])
8781
8782
8783 if fun is not None:
8784 tic = time.time()
8785 fmin(fun, x0, sig0, opts)
8786
8787
8788 print('elapsed time [s]: + %.2f', round(time.time() - tic, 2))
8789
8790 elif not len(argv):
8791 fmin(fcts.elli, np.ones(6) * 0.1, 0.1, {'ftarget':1e-9})
8792
8793
8794
8795
8796
8797
8798
8799 if __name__ == "__main__":
8800
8801
8802 main()
8803