Module cma
[hide private]
[frames] | no frames]

Source Code for Module cma

   1  #!/usr/bin/env python 
   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  # The BSD 3-Clause License 
 113  # Copyright (c) 2014 Inria 
 114  # Author: Nikolaus Hansen, 2008-2015 
 115  # 
 116  # Redistribution and use in source and binary forms, with or without 
 117  # modification, are permitted provided that the following conditions 
 118  # are met: 
 119  # 
 120  # 1. Redistributions of source code must retain the above copyright and 
 121  #    authors notice, this list of conditions and the following disclaimer. 
 122  # 
 123  # 2. Redistributions in binary form must reproduce the above copyright 
 124  #    and authors notice, this list of conditions and the following 
 125  #    disclaimer in the documentation and/or other materials provided with 
 126  #    the distribution. 
 127  # 
 128  # 3. Neither the name of the copyright holder nor the names of its 
 129  #    contributors nor the authors names may be used to endorse or promote 
 130  #    products derived from this software without specific prior written 
 131  #    permission. 
 132  # 
 133  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 134  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 135  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 136  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 137  # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 138  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 139  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 140  # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 141  # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 142  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
 143  # WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 144  # POSSIBILITY OF SUCH DAMAGE. 
 145   
 146   
 147  # (note to self) for testing: 
 148  #   pyflakes cma.py   # finds bugs by static analysis 
 149  #   pychecker --limit 60 cma.py  # also executes, all 60 warnings checked 
 150  #   or python ~/Downloads/pychecker-0.8.19/pychecker/checker.py cma.py 
 151  #   python cma.py -t -quiet # executes implemented tests based on doctest 
 152  #   python -3 cma.py --test  2> out2to3warnings.txt # 
 153   
 154  # to create a html documentation file: 
 155  #    pydoc -w cma  # edit the header (remove local pointers) 
 156  #    epydoc cma.py  # comes close to javadoc but does not find the 
 157  #                   # links of function references etc 
 158  #    doxygen needs @package cma as first line in the module docstring 
 159  #       some things like class attributes are not interpreted correctly 
 160  #    sphinx: doc style of doc.python.org, could not make it work (yet) 
 161   
 162  # TODO: implement a (deep enough) copy-constructor for class 
 163  #       CMAEvolutionStrategy to repeat the same step in different 
 164  #       configurations for online-adaptation of meta parameters 
 165  # TODO: reconsider geno-pheno transformation. Can it be a completely 
 166  #       separate module that operates inbetween optimizer and objective? 
 167  #       Can we still propagate a repair of solutions to the optimizer? 
 168  #       How about gradients (should be fine)? 
 169  # TODO: implement bipop in a separate algorithm as meta portfolio 
 170  #       algorithm of IPOP and a local restart option to be implemented 
 171  #       in fmin (e.g. option restart_mode in [IPOP, local]) 
 172  # TODO: self.opts['mindx'] is checked without sigma_vec, which is wrong, 
 173  #       TODO: project sigma_vec on the smallest eigenvector? 
 174  # TODO: class _CMAStopDict implementation looks way too complicated 
 175  # TODO: separate display and logging options, those CMAEvolutionStrategy 
 176  #       instances don't use themselves (probably all?) 
 177  # TODO: disp method is implemented in CMAEvolutionStrategy and in 
 178  #       CMADataLogger separately, OOOptimizer.disp_str should return a str 
 179  #       which can be used uniformly? Only logger can disp a history. 
 180  # TODO: check scitools.easyviz and how big the adaptation would be 
 181  # TODO: split tell into a variable transformation part and the "pure" 
 182  #       functionality 
 183  #       usecase: es.tell_geno(X, [func(es.pheno(x)) for x in X]) 
 184  #       genotypic repair is not part of tell_geno 
 185  # TODO: copy_always optional parameter does not make much sense, 
 186  #       as one can always copy the input argument first, 
 187  #       however some calls are simpler 
 188  # TODO: generalize input logger in optimize() as after_iteration_handler 
 189  #       (which is logger.add by default)? One difficulty is that 
 190  #       the logger object is returned (not anymore when return of optimize 
 191  #       is change). Another difficulty is the obscure usage of modulo 
 192  #       for writing a final data line in optimize. 
 193  # TODO: separate initialize==reset_state from __init__ 
 194  # TODO: introduce Ypos == diffC which makes the code more consistent and 
 195  #       the active update "exact"? 
 196  # TODO: dynamically read "signals" from a file, see import ConfigParser 
 197  #       or myproperties.py (to be called after tell()) 
 198  # 
 199  # typical parameters in scipy.optimize: disp, xtol, ftol, maxiter, maxfun, 
 200  #         callback=None 
 201  #         maxfev, diag (A sequency of N positive entries that serve as 
 202  #                 scale factors for the variables.) 
 203  #           full_output -- non-zero to return all optional outputs. 
 204  #   If xtol < 0.0, xtol is set to sqrt(machine_precision) 
 205  #    'infot -- a dictionary of optional outputs with the keys: 
 206  #                      'nfev': the number of function calls... 
 207  # 
 208  #    see eg fmin_powell 
 209  # typical returns 
 210  #        x, f, dictionary d 
 211  #        (xopt, {fopt, gopt, Hopt, func_calls, grad_calls, warnflag}, 
 212  #         <allvecs>) 
 213  # 
 214  # TODO: keep best ten solutions 
 215  # TODO: implement constraints handling 
 216  # TODO: extend function unitdoctest, or use unittest? 
 217  # TODO: apply style guide 
 218  # TODO: eigh(): thorough testing would not hurt 
 219   
 220  # changes: 
 221  # 15/01/20: larger condition numbers for C realized by using tf_pheno 
 222  #           of GenoPheno attribute gp. 
 223  # 15/01/19: injection method, first implementation, short injections 
 224  #           and long injections with good fitness need to be addressed yet 
 225  # 15/01/xx: prepare_injection_directions to simplify/centralize injected 
 226  #           solutions from mirroring and TPA 
 227  # 14/12/26: bug fix in correlation_matrix computation if np.diag is a view 
 228  # 14/12/06: meta_parameters now only as annotations in ## comments 
 229  # 14/12/03: unified use of base class constructor call, now always 
 230  #         super(ThisClass, self).__init__(args_for_base_class_constructor) 
 231  # 14/11/29: termination via "stop now" in file cmaes_signals.par 
 232  # 14/11/28: bug fix initialization of C took place before setting the 
 233  #           seed. Now in some dimensions (e.g. 10) results are (still) not 
 234  #           determistic due to np.linalg.eigh, in some dimensions (<9, 12) 
 235  #           they seem to be deterministic. 
 236  # 14/11/23: bipop option integration, contributed by Petr Baudis 
 237  # 14/09/30: initial_elitism option added to fmin 
 238  # 14/08/1x: developing fitness wrappers in FFWrappers class 
 239  # 14/08/xx: return value of OOOptimizer.optimize changed to self. 
 240  #           CMAOptions now need to uniquely match an *initial substring* 
 241  #           only (via method corrected_key). 
 242  #           Bug fix in CMAEvolutionStrategy.stop: termination conditions 
 243  #           are now recomputed iff check and self.countiter > 0. 
 244  #           Doc corrected that self.gp.geno _is_ applied to x0 
 245  #           Vaste reorganization/modularization/improvements of plotting 
 246  # 14/08/01: bug fix to guaranty pos. def. in active CMA 
 247  # 14/06/04: gradient of f can now be used with fmin and/or ask 
 248  # 14/05/11: global rcParams['font.size'] not permanently changed anymore, 
 249  #           a little nicer annotations for the plots 
 250  # 14/05/07: added method result_pretty to pretty print optimization result 
 251  # 14/05/06: associated show() everywhere with ion() which should solve the 
 252  #           blocked terminal problem 
 253  # 14/05/05: all instances of "unicode" removed (was incompatible to 3.x) 
 254  # 14/05/05: replaced type(x) == y with isinstance(x, y), reorganized the 
 255  #           comments before the code starts 
 256  # 14/05/xx: change the order of kwargs of OOOptimizer.optimize, 
 257  #           remove prepare method in AdaptSigma classes, various changes/cleaning 
 258  # 14/03/01: bug fix BoundaryHandlerBase.has_bounds didn't check lower bounds correctly 
 259  #           bug fix in BoundPenalty.repair len(bounds[0]) was used instead of len(bounds[1]) 
 260  #           bug fix in GenoPheno.pheno, where x was not copied when only boundary-repair was applied 
 261  # 14/02/27: bug fixed when BoundPenalty was combined with fixed variables. 
 262  # 13/xx/xx: step-size adaptation becomes a class derived from CMAAdaptSigmaBase, 
 263  #           to make testing different adaptation rules (much) easier 
 264  # 12/12/14: separated CMAOptions and arguments to fmin 
 265  # 12/10/25: removed useless check_points from fmin interface 
 266  # 12/10/17: bug fix printing number of infeasible samples, moved not-in-use methods 
 267  #           timesCroot and divCroot to the right class 
 268  # 12/10/16 (0.92.00): various changes commit: bug bound[0] -> bounds[0], more_to_write fixed, 
 269  #   sigma_vec introduced, restart from elitist, trace normalization, max(mu,popsize/2) 
 270  #   is used for weight calculation. 
 271  # 12/07/23: (bug:) BoundPenalty.update respects now genotype-phenotype transformation 
 272  # 12/07/21: convert value True for noisehandling into 1 making the output compatible 
 273  # 12/01/30: class Solution and more old stuff removed r3101 
 274  # 12/01/29: class Solution is depreciated, GenoPheno and SolutionDict do the job (v0.91.00, r3100) 
 275  # 12/01/06: CMA_eigenmethod option now takes a function (integer still works) 
 276  # 11/09/30: flat fitness termination checks also history length 
 277  # 11/09/30: elitist option (using method clip_or_fit_solutions) 
 278  # 11/09/xx: method clip_or_fit_solutions for check_points option for all sorts of 
 279  #           injected or modified solutions and even reliable adaptive encoding 
 280  # 11/08/19: fixed: scaling and typical_x type clashes 1 vs array(1) vs ones(dim) vs dim * [1] 
 281  # 11/07/25: fixed: fmin wrote first and last line even with verb_log==0 
 282  #           fixed: method settableOptionsList, also renamed to versatileOptions 
 283  #           default seed depends on time now 
 284  # 11/07/xx (0.9.92): added: active CMA, selective mirrored sampling, noise/uncertainty handling 
 285  #           fixed: output argument ordering in fmin, print now only used as function 
 286  #           removed: parallel option in fmin 
 287  # 11/07/01: another try to get rid of the memory leak by replacing self.unrepaired = self[:] 
 288  # 11/07/01: major clean-up and reworking of abstract base classes and of the documentation, 
 289  #           also the return value of fmin changed and attribute stop is now a method. 
 290  # 11/04/22: bug-fix: option fixed_variables in combination with scaling 
 291  # 11/04/21: stopdict is not a copy anymore 
 292  # 11/04/15: option fixed_variables implemented 
 293  # 11/03/23: bug-fix boundary update was computed even without boundaries 
 294  # 11/03/12: bug-fix of variable annotation in plots 
 295  # 11/02/05: work around a memory leak in numpy 
 296  # 11/02/05: plotting routines improved 
 297  # 10/10/17: cleaning up, now version 0.9.30 
 298  # 10/10/17: bug-fix: return values of fmin now use phenotyp (relevant 
 299  #           if input scaling_of_variables is given) 
 300  # 08/10/01: option evalparallel introduced, 
 301  #           bug-fix for scaling being a vector 
 302  # 08/09/26: option CMAseparable becomes CMA_diagonal 
 303  # 08/10/18: some names change, test functions go into a class 
 304  # 08/10/24: more refactorizing 
 305  # 10/03/09: upper bound exp(min(1,...)) for step-size control 
 306   
 307  from __future__ import division 
 308  # future is >= 3.0, this code has mainly been used with 2.6 & 2.7 
 309  from __future__ import with_statement 
 310  # only necessary for python 2.5 and not in heavy use 
 311  from __future__ import print_function 
 312  # available from python 2.6, code should also work without 
 313  from __future__ import absolute_import 
 314  from __future__ import unicode_literals 
 315  # from __future__ import collections.MutableMapping 
 316  # does not exist in future, otherwise Python 2.5 would work, since 0.91.01 
 317   
 318  import sys 
 319  if not sys.version.startswith('2'):  # in python 3 
 320      xrange = range 
 321      raw_input = input 
 322      basestring = str 
 323  else: 
 324      input = raw_input  # in py2, input(x) == eval(raw_input(x)) 
 325   
 326  import time  # not really essential 
 327  import collections 
 328  import numpy as np 
 329  # arange, cos, size, eye, inf, dot, floor, outer, zeros, linalg.eigh, 
 330  # sort, argsort, random, ones,... 
 331  from numpy import inf, array, dot, exp, log, sqrt, sum, isscalar, isfinite 
 332  # to access the built-in sum fct:  ``__builtins__.sum`` or ``del sum`` 
 333  # removes the imported sum and recovers the shadowed build-in 
 334  try: 
 335      from matplotlib import pyplot 
 336      savefig = pyplot.savefig  # now we can use cma.savefig() etc 
 337      closefig = pyplot.close 
338 - def show():
339 # is_interactive = matplotlib.is_interactive() 340 pyplot.ion() 341 pyplot.show()
342 # if we call now matplotlib.interactive(True), the console is 343 # blocked 344 pyplot.ion() # prevents that execution stops after plotting 345 except: 346 pyplot = None 347 savefig = None 348 closefig = None
349 - def show():
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 # $Source$ # according to PEP 8 style guides, but what is it good for? 357 # $Id: cma.py 4129 2015-01-23 19:13:51Z hansen $ 358 # bash $: svn propset svn:keywords 'Date Revision Id' cma.py 359 360 __docformat__ = "reStructuredText" # this hides some comments entirely? 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 # 'BoundaryHandlerBase', 382 'BoundNone', 383 'BoundTransform', 384 'BoundPenalty', 385 # 'BoxConstraintsTransformationBase', 386 # 'BoxConstraintsLinQuadTransformation', 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 # on False some unit tests fail 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``. """
413 414 -class MetaParameters(object):
415 """meta parameters are either "modifiable constants" or refer to 416 options from ``CMAOptions`` or are arguments to ``fmin`` or to the 417 ``NoiseHandler`` class constructor. 418 419 Details 420 ------- 421 This code contains a single class instance `meta_parameters` 422 423 Some interfaces rely on parameters being either `int` or 424 `float` only. More sophisticated choices are implemented via 425 ``choice_value = {1: 'this', 2: 'or that'}[int_param_value]`` here. 426 427 CAVEAT 428 ------ 429 ``meta_parameters`` should not be used to determine default 430 arguments, because these are assigned only once and for all during 431 module import. 432 433 """
434 - def __init__(self):
435 self.sigma0 = None ## [~0.01, ~10] # no default available 436 437 # learning rates and back-ward time horizons 438 self.CMA_cmean = 1.0 ## [~0.1, ~10] # 439 self.c1_multiplier = 1.0 ## [~1e-4, ~20] l 440 self.cmu_multiplier = 2.0 ## [~1e-4, ~30] l # zero means off 441 self.CMA_active = 1.0 ## [~1e-4, ~10] l # 0 means off, was CMA_activefac 442 self.cc_multiplier = 1.0 ## [~0.01, ~20] l 443 self.cs_multiplier = 1.0 ## [~0.01, ~10] l # learning rate for cs 444 self.CSA_dampfac = 1.0 ## [~0.01, ~10] 445 self.CMA_dampsvec_fac = None ## [~0.01, ~100] # def=np.Inf or 0.5, not clear whether this is a log parameter 446 self.CMA_dampsvec_fade = 0.1 ## [0, ~2] 447 448 # exponents for learning rates 449 self.c1_exponent = 2.0 ## [~1.25, 2] 450 self.cmu_exponent = 2.0 ## [~1.25, 2] 451 self.cact_exponent = 1.5 ## [~1.25, 2] 452 self.cc_exponent = 1.0 ## [~0.25, ~1.25] 453 self.cs_exponent = 1.0 ## [~0.25, ~1.75] # upper bound depends on CSA_clip_length_value 454 455 # selection related parameters 456 self.lambda_exponent = 0.0 ## [0, ~2.5] # usually <= 2, used by adding N**lambda_exponent to popsize-1 457 self.parent_fraction = 0.5 ## [0, 1] # default is weighted recombination 458 self.CMA_elitist = 0 ## [0, 2] i # a choice variable 459 self.CMA_mirrors = 0.0 ## [0, 0.5) # values <0.5 are interpreted as fraction, values >1 as numbers (rounded), otherwise about 0.16 is used', 460 461 # sampling strategies 462 self.CMA_sample_on_sphere_surface = 0 ## [0, 1] i # boolean 463 self.mean_shift_line_samples = 0 ## [0, 1] i # boolean 464 self.pc_line_samples = 0 ## [0, 1] i # boolean 465 466 # step-size adapation related parameters 467 self.CSA_damp_mueff_exponent = 0.5 ## [~0.25, ~1.5] # zero would mean no dependency of damping on mueff, useful with CSA_disregard_length option', 468 self.CSA_disregard_length = 0 ## [0, 1] i 469 self.CSA_squared = 0 ## [0, 1] i 470 self.CSA_clip_length_value = None ## [0, ~20] # None reflects inf 471 472 # noise handling 473 self.noise_reeval_multiplier = 1.0 ## [0.2, 4] # usually 2 offspring are reevaluated 474 self.noise_choose_reeval = 1 ## [1, 3] i # which ones to reevaluate 475 self.noise_theta = 0.5 ## [~0.05, ~0.9] 476 self.noise_alphasigma = 2.0 ## [0, 10] 477 self.noise_alphaevals = 2.0 ## [0, 10] 478 self.noise_alphaevalsdown_exponent = -0.25 ## [-1.5, 0] 479 self.noise_aggregate = None ## [1, 2] i # None and 0 == default or user option choice, 1 == median, 2 == mean 480 # TODO: more noise handling options (maxreevals...) 481 482 # restarts 483 self.restarts = 0 ## [0, ~30] # but depends on popsize inc 484 self.restart_from_best = 0 ## [0, 1] i # bool 485 self.incpopsize = 2.0 ## [~1, ~5] 486 487 # termination conditions (for restarts) 488 self.maxiter_multiplier = 1.0 ## [~0.01, ~100] l 489 self.mindx = 0.0 ## [1e-17, ~1e-3] l #v minimal std in any direction, cave interference with tol*', 490 self.minstd = 0.0 ## [1e-17, ~1e-3] l #v minimal std in any coordinate direction, cave interference with tol*', 491 self.maxstd = None ## [~1, ~1e9] l #v maximal std in any coordinate direction, default is inf', 492 self.tolfacupx = 1e3 ## [~10, ~1e9] l #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', 493 self.tolupsigma = 1e20 ## [~100, ~1e99] l #v sigma/sigma0 > tolupsigma * max(sqrt(eivenvals(C))) indicates "creeping behavior" with usually minor improvements', 494 self.tolx = 1e-11 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in x-changes', 495 self.tolfun = 1e-11 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in function value, quite useful', 496 self.tolfunhist = 1e-12 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in function value history', 497 self.tolstagnation_multiplier = 1.0 ## [0.01, ~100] # ': 'int(100 + 100 * N**1.5 / popsize) #v termination if no improvement over tolstagnation iterations',
498 499 # abandoned: 500 # self.noise_change_sigma_exponent = 1.0 ## [0, 2] 501 # self.noise_epsilon = 1e-7 ## [0, ~1e-2] l # 502 # self.maxfevals = None ## [1, ~1e11] l # is not a performance parameter 503 # self.cc_mu_multiplier = 1 ## [0, ~10] # AKA alpha_cc 504 # self.lambda_log_multiplier = 3 ## [0, ~10] 505 # self.lambda_multiplier = 0 ## (0, ~10] 506 507 meta_parameters = MetaParameters()
508 509 # emptysets = ('', (), [], {}) 510 # array([]) does not work but np.size(.) == 0 511 # here is the problem: 512 # bool(array([0])) is False 513 # bool(list(array([0]))) is True 514 # bool(list(array([0, 1]))) is True 515 # bool(array([0, 1])) raises ValueError 516 # 517 # "x in emptysets" cannot be well replaced by "not x" 518 # which is also True for array([]) and None, but also for 0 and False, 519 # and False for NaN, and an exception for array([0,1]), see also 520 # http://google-styleguide.googlecode.com/svn/trunk/pyguide.html#True/False_evaluations 521 522 # ____________________________________________________________ 523 # ____________________________________________________________ 524 # 525 -def rglen(ar):
526 """shortcut for the iterator ``xrange(len(ar))``""" 527 return xrange(len(ar))
528
529 -def is_feasible(x, f):
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
545 # ____________________________________________________________ 546 # ____________________________________________________________ 547 # 548 -def unitdoctest():
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
644 -class _BlancClass(object):
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:
649 650 - class DerivedDictBase(collections.MutableMapping):
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 """
658 - def __init__(self, *args, **kwargs):
659 # collections.MutableMapping.__init__(self) 660 super(DerivedDictBase, self).__init__() 661 # super(SolutionDict, self).__init__() # the same 662 self.data = dict() 663 self.data.update(dict(*args, **kwargs))
664 - def __len__(self):
665 return len(self.data)
666 - def __contains__(self, key):
667 return key in self.data
668 - def __iter__(self):
669 return iter(self.data)
670 - def __setitem__(self, key, value):
671 """defines self[key] = value""" 672 self.data[key] = value
673 - def __getitem__(self, key):
674 """defines self[key]""" 675 return self.data[key]
676 - def __delitem__(self, key):
677 del self.data[key]
678
679 - class SolutionDict(DerivedDictBase):
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 """
701 - def __init__(self, *args, **kwargs):
702 # DerivedDictBase.__init__(self, *args, **kwargs) 703 super(SolutionDict, self).__init__(*args, **kwargs) 704 self.data_with_same_key = {} 705 self.last_iteration = 0
706 - def key(self, x):
707 try: 708 return tuple(x) 709 # using sum(x) is slower, using x[0] is slightly faster 710 except TypeError: 711 return x
712 - def __setitem__(self, key, value):
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
720 - def __getitem__(self, key): # 50% of time of
721 """defines self[key]""" 722 return self.data[self.key(key)]
723 - def __delitem__(self, 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]
733 - def truncate(self, max_len, min_iter):
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 # deletes one item with k as key, better delete all?
739 740 - class CMASolutionDict(SolutionDict):
741 - def __init__(self, *args, **kwargs):
742 # SolutionDict.__init__(self, *args, **kwargs) 743 super(CMASolutionDict, self).__init__(*args, **kwargs) 744 self.last_solution_index = 0
745 746 # TODO: insert takes 30% of the overall CPU time, mostly in def key() 747 # with about 15% of the overall CPU time
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 # archive returned solutions, first clean up archive 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 # a hack to get a somewhat reasonable value 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:
787 - class CMASolutionDict(dict):
788 """a hack to get most code examples running"""
789 - def insert(self, *args, **kwargs):
790 pass
791 - def get(self, key):
792 return None
793 - def __getitem__(self, key):
794 return None
795 - def __setitem__(self, key, value):
796 pass
797
798 -class BestSolution(object):
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 # find failsave minimum 834 minidx = np.nanargmin(arf) 835 if minidx is np.nan: 836 return 837 minarf = arf[minidx] 838 # minarf = reduce(lambda x, y: y if y and y is not np.nan 839 # and y < x else x, arf, np.inf) 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
852 - def get(self):
853 """return ``(x, f, evals)`` """ 854 return self.x, self.f, self.evals # , self.x_geno
855
856 857 # ____________________________________________________________ 858 # ____________________________________________________________ 859 # 860 -class BoundaryHandlerBase(object):
861 """hacked base class """
862 - def __init__(self, bounds):
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] # figure out lenths 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):
904 return self
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
931 - def get_bounds(self, which, dimension):
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
940 - def _get_bounds(self, ib, dimension):
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
954 - def has_bounds(self):
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
967 - def is_in_bounds(self, x):
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
981 - def to_dim_times_two(self, bounds):
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] # figure out lenths 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 = [] # bounds in different format 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):
1011 - def __init__(self, bounds=None):
1012 if bounds is not None: 1013 raise ValueError() 1014 # BoundaryHandlerBase.__init__(self, None) 1015 super(BoundNone, self).__init__(None)
1016 - def is_in_bounds(self, x):
1017 return True
1018
1019 # ____________________________________________________________ 1020 # ____________________________________________________________ 1021 # 1022 -class BoundTransform(BoundaryHandlerBase):
1023 """Handles boundary by a smooth, piecewise linear and quadratic 1024 transformation into the feasible domain. 1025 1026 >>> import cma 1027 >>> veq = cma.Mh.vequals_approximately 1028 >>> b = cma.BoundTransform([None, 1]) 1029 >>> assert b.bounds == [[None], [1]] 1030 >>> assert veq(b.repair([0, 1, 1.2]), array([ 0., 0.975, 0.975])) 1031 >>> assert b.is_in_bounds([0, 0.5, 1]) 1032 >>> assert veq(b.transform([0, 1, 2]), [ 0. , 0.975, 0.2 ]) 1033 >>> o=cma.fmin(cma.fcts.sphere, 6 * [-2], 0.5, options={ 1034 ... 'boundary_handling': 'BoundTransform ', 1035 ... 'bounds': [[], 5 * [-1] + [inf]] }) 1036 >>> assert o[1] < 5 + 1e-8 1037 >>> import numpy as np 1038 >>> b = cma.BoundTransform([-np.random.rand(120), np.random.rand(120)]) 1039 >>> for i in range(100): 1040 ... x = (-i-1) * np.random.rand(120) + i * np.random.randn(120) 1041 ... x_to_b = b.repair(x) 1042 ... x2 = b.inverse(x_to_b) 1043 ... x2_to_b = b.repair(x2) 1044 ... x3 = b.inverse(x2_to_b) 1045 ... x3_to_b = b.repair(x3) 1046 ... assert veq(x_to_b, x2_to_b) 1047 ... assert veq(x2, x3) 1048 ... assert veq(x2_to_b, x3_to_b) 1049 1050 Details: this class uses ``class BoxConstraintsLinQuadTransformation`` 1051 1052 """
1053 - def __init__(self, bounds=None):
1054 """Argument bounds can be `None` or ``bounds[0]`` and ``bounds[1]`` 1055 are lower and upper domain boundaries, each is either `None` or 1056 a scalar or a list or array of appropriate size. 1057 1058 """ 1059 # BoundaryHandlerBase.__init__(self, bounds) 1060 super(BoundTransform, self).__init__(bounds) 1061 self.bounds_tf = BoxConstraintsLinQuadTransformation(self.to_dim_times_two(bounds))
1062
1063 - def repair(self, x, copy_if_changed=True, copy_always=False):
1064 """transforms ``x`` into the bounded domain. 1065 1066 ``copy_always`` option might disappear. 1067 1068 """ 1069 copy = copy_if_changed 1070 if copy_always: 1071 x = array(x, copy=True) 1072 copy = False 1073 if self.bounds is None or (self.bounds[0] is None and 1074 self.bounds[1] is None): 1075 return x 1076 return self.bounds_tf(x, copy)
1077
1078 - def transform(self, x):
1079 return self.repair(x)
1080
1081 - def inverse(self, x, copy_if_changed=True, copy_always=False):
1082 """inverse transform of ``x`` from the bounded domain. 1083 1084 """ 1085 copy = copy_if_changed 1086 if copy_always: 1087 x = array(x, copy=True) 1088 copy = False 1089 if self.bounds is None or (self.bounds[0] is None and 1090 self.bounds[1] is None): 1091 return x 1092 return self.bounds_tf.inverse(x, copy) # this doesn't exist
1093
1094 # ____________________________________________________________ 1095 # ____________________________________________________________ 1096 # 1097 -class BoundPenalty(BoundaryHandlerBase):
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 """
1123 - def __init__(self, bounds=None):
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 # bounds attribute reminds the domain boundary values 1130 # BoundaryHandlerBase.__init__(self, bounds) 1131 super(BoundPenalty, self).__init__(bounds) 1132 1133 self.gamma = 1 # a very crude assumption 1134 self.weights_initialized = False # gamma becomes a vector after initialization 1135 self.hist = [] # delta-f history
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 # TODO (old data): CPU(N,lam,iter=20,200,100): 3.3s of 8s for two bounds, 1.8s of 6.5s for one bound 1142 # remark: np.max([bounds[0], x]) is about 40 times slower than max((bounds[0], x)) 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)): # solely for effiency 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 #
1171 - def __call__(self, x, archive, gp):
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) # no penalty 1180 1181 x_is_single_vector = isscalar(x[0]) 1182 x = [x] if x_is_single_vector else x 1183 1184 # add fixed variables to self.gamma 1185 try: 1186 gamma = list(self.gamma) # fails if self.gamma is a scalar 1187 for i in sorted(gp.fixed_values): # fails if fixed_values is None 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 # CAVE: this does not work with already repaired values!! 1195 # CPU(N,lam,iter=20,200,100)?: 3s of 10s, array(xi): 1s 1196 # remark: one deep copy can be prevented by xold = xi first 1197 xpheno = gp.pheno(archive[xi]['geno']) 1198 # necessary, because xi was repaired to be in bounds 1199 xinbounds = self.repair(xpheno) 1200 # could be omitted (with unpredictable effect in case of external repair) 1201 fac = 1 # exp(0.1 * (log(self.scal) - np.mean(self.scal))) 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 #
1207 - def feasible_ratio(self, solutions):
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 # ## prepare 1243 # compute varis = sigma**2 * C_ii 1244 varis = es.sigma**2 * array(N * [es.C] if isscalar(es.C) else (# scalar case 1245 es.C if isscalar(es.C[0]) else # diagonal matrix case 1246 [es.C[i][i] for i in xrange(N)])) # full matrix case 1247 1248 # relative violation in geno-space 1249 dmean = (es.mean - es.gp.geno(self.repair(es.gp.pheno(es.mean)))) / varis**0.5 1250 1251 # ## Store/update a history of delta fitness value 1252 fvals = sorted(function_values) 1253 l = 1 + len(fvals) 1254 val = fvals[3 * l // 4] - fvals[l // 4] # exact interquartile range apart interpolation 1255 val = val / np.mean(varis) # new: val is normalized with sigma of the same iteration 1256 # insert val in history 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 # ignore 0 or nan values 1263 if len(self.hist) > 20 + (3 * N) / es.popsize: 1264 self.hist.pop() 1265 1266 # ## prepare 1267 dfit = np.median(self.hist) # median interquartile range 1268 damp = min(1, es.sp.mueff / 10. / N) 1269 1270 # ## set/update weights 1271 # Throw initialization error 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 # initialize weights 1277 if dmean.any() and (not self.weights_initialized or es.countiter == 2): # TODO 1278 self.gamma = array(N * [2 * dfit]) ## BUGBUGzzzz: N should be phenotypic (bounds are in phenotype), but is genotypic 1279 self.weights_initialized = True 1280 # update weights gamma 1281 if self.weights_initialized: 1282 edist = array(abs(dmean) - 3 * max(1, N**0.5 / es.sp.mueff)) 1283 if 1 < 3: # this is better, around a factor of two 1284 # increase single weights possibly with a faster rate than they can decrease 1285 # value unit of edst is std dev, 3==random walk of 9 steps 1286 self.gamma *= exp((edist > 0) * np.tanh(edist / 3) / 2.)**damp 1287 # decrease all weights up to the same level to avoid single extremely small weights 1288 # use a constant factor for pseudo-keeping invariance 1289 self.gamma[self.gamma > 5 * dfit] *= exp(-1. / 3)**damp 1290 # self.gamma[idx] *= exp(5*dfit/self.gamma[idx] - 1)**(damp/3) 1291 es.more_to_write += list(self.gamma) if self.weights_initialized else N * [1.0] 1292 # ## return penalty 1293 # es.more_to_write = self.gamma if not isscalar(self.gamma) else N*[1] 1294 return self # bound penalty values
1295
1296 # ____________________________________________________________ 1297 # ____________________________________________________________ 1298 # 1299 -class BoxConstraintsTransformationBase(object):
1300 """Implements a transformation into boundaries and is used for 1301 boundary handling:: 1302 1303 tf = BoxConstraintsTransformationAnyDerivedClass([[1, 4]]) 1304 x = [3, 2, 4.4] 1305 y = tf(x) # "repaired" solution 1306 print(tf([2.5])) # middle value is never changed 1307 [2.5] 1308 1309 :See: ``BoundaryHandler`` 1310 1311 """
1312 - def __init__(self, bounds):
1313 try: 1314 if len(bounds[0]) != 2: 1315 raise ValueError 1316 except: 1317 raise ValueError(' bounds must be either [[lb0, ub0]] or [[lb0, ub0], [lb1, ub1],...], \n where in both cases the last entry is reused for all remaining dimensions') 1318 self.bounds = bounds 1319 self.initialize()
1320
1321 - def initialize(self):
1322 """initialize in base class""" 1323 self._lb = [b[0] for b in self.bounds] # can be done more efficiently? 1324 self._ub = [b[1] for b in self.bounds]
1325
1326 - def _lowerupperval(self, a, b, c):
1327 return np.max([np.max(a), np.min([np.min(b), c])])
1328 - def bounds_i(self, i):
1329 """return ``[ith_lower_bound, ith_upper_bound]``""" 1330 return self.bounds[self._index(i)]
1331 - def __call__(self, solution_in_genotype):
1332 res = [self._transform_i(x, i) for i, x in enumerate(solution_in_genotype)] 1333 return res
1334 transform = __call__
1335 - def inverse(self, solution_in_phenotype, copy_if_changed=True, copy_always=True):
1336 return [self._inverse_i(y, i) for i, y in enumerate(solution_in_phenotype)]
1337 - def _index(self, i):
1338 return min((i, len(self.bounds) - 1))
1339 - def _transform_i(self, x, i):
1340 raise NotImplementedError('this is an abstract method that should be implemented in the derived class')
1341 - def _inverse_i(self, y, i):
1342 raise NotImplementedError('this is an abstract method that should be implemented in the derived class')
1343 - def shift_or_mirror_into_invertible_domain(self, solution_genotype):
1344 """return the reference solution that has the same ``box_constraints_transformation(solution)`` 1345 value, i.e. ``tf.shift_or_mirror_into_invertible_domain(x) = tf.inverse(tf.transform(x))``. 1346 This is an idempotent mapping (leading to the same result independent how often it is 1347 repeatedly applied). 1348 1349 """ 1350 return self.inverse(self(solution_genotype)) 1351 raise NotImplementedError('this is an abstract method that should be implemented in the derived class')
1352
1353 -class _BoxConstraintsTransformationTemplate(BoxConstraintsTransformationBase):
1354 """copy/paste this template to implement a new boundary handling transformation"""
1355 - def __init__(self, bounds):
1356 # BoxConstraintsTransformationBase.__init__(self, bounds) 1357 super(_BoxConstraintsTransformationTemplate, self).__init__(bounds)
1358 - def initialize(self):
1359 BoxConstraintsTransformationBase.initialize(self) # likely to be removed
1360 - def _transform_i(self, x, i):
1361 raise NotImplementedError('this is an abstract method that should be implemented in the derived class')
1362 - def _inverse_i(self, y, i):
1363 raise NotImplementedError('this is an abstract method that should be implemented in the derived class')
1364 __doc__ = BoxConstraintsTransformationBase.__doc__ + __doc__
1365
1366 -class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase):
1367 """implements a bijective, monotonous transformation between [lb - al, ub + au] 1368 and [lb, ub] which is the identity (and therefore linear) in [lb + al, ub - au] 1369 (typically about 90% of the interval) and quadratic in [lb - 3*al, lb + al] 1370 and in [ub - au, ub + 3*au]. The transformation is periodically 1371 expanded beyond the limits (somewhat resembling the shape sin(x-pi/2)) 1372 with a period of ``2 * (ub - lb + al + au)``. 1373 1374 Details 1375 ======= 1376 Partly due to numerical considerations depend the values ``al`` and ``au`` 1377 on ``abs(lb)`` and ``abs(ub)`` which makes the transformation non-translation 1378 invariant. In contrast to sin(.), the transformation is robust to "arbitrary" 1379 values for boundaries, e.g. a lower bound of ``-1e99`` or ``np.Inf`` or 1380 ``None``. 1381 1382 Examples 1383 ======== 1384 Example to use with cma: 1385 1386 >>> import cma 1387 >>> # only the first variable has an upper bound 1388 >>> tf = cma.BoxConstraintsLinQuadTransformation([[1,2], [1,None]]) # second==last pair is re-cycled 1389 >>> cma.fmin(cma.felli, 9 * [2], 1, {'transformation': [tf.transform, tf.inverse], 'verb_disp': 0}) 1390 >>> # ...or... 1391 >>> es = cma.CMAEvolutionStrategy(9 * [2], 1) 1392 >>> while not es.stop(): 1393 ... X = es.ask() 1394 ... f = [cma.felli(tf(x)) for x in X] # tf(x) == tf.transform(x) 1395 ... es.tell(X, f) 1396 1397 Example of the internal workings: 1398 1399 >>> import cma 1400 >>> tf = cma.BoxConstraintsLinQuadTransformation([[1,2], [1,11], [1,11]]) 1401 >>> tf.bounds 1402 [[1, 2], [1, 11], [1, 11]] 1403 >>> tf([1.5, 1.5, 1.5]) 1404 [1.5, 1.5, 1.5] 1405 >>> tf([1.52, -2.2, -0.2, 2, 4, 10.4]) 1406 [1.52, 4.0, 2.0, 2.0, 4.0, 10.4] 1407 >>> res = np.round(tf._au, 2) 1408 >>> assert list(res[:4]) == [ 0.15, 0.6, 0.6, 0.6] 1409 >>> res = [round(x, 2) for x in tf.shift_or_mirror_into_invertible_domain([1.52, -12.2, -0.2, 2, 4, 10.4])] 1410 >>> assert res == [1.52, 9.2, 2.0, 2.0, 4.0, 10.4] 1411 >>> tmp = tf([1]) # call with lower dimension 1412 1413 """
1414 - def __init__(self, bounds):
1415 """``x`` is defined in ``[lb - 3*al, ub + au + r - 2*al]`` with ``r = ub - lb + al + au``, 1416 and ``x == transformation(x)`` in ``[lb + al, ub - au]``. 1417 ``beta*x - alphal = beta*x - alphau`` is then defined in ``[lb, ub]``, 1418 1419 ``alphal`` and ``alphau`` represent the same value, but respectively numerically 1420 better suited for values close to lb and ub. 1421 1422 """ 1423 # BoxConstraintsTransformationBase.__init__(self, bounds) 1424 super(BoxConstraintsLinQuadTransformation, self).__init__(bounds)
1425 # super().__init__(bounds) # only available since Python 3.x 1426 # super(BB, self).__init__(bounds) # is supposed to call initialize 1427
1428 - def initialize(self, length=None):
1429 """see ``__init__``""" 1430 if length is None: 1431 length = len(self.bounds) 1432 max_i = min((len(self.bounds) - 1, length - 1)) 1433 self._lb = array([self.bounds[min((i, max_i))][0] 1434 if self.bounds[min((i, max_i))][0] is not None 1435 else -np.Inf 1436 for i in xrange(length)], copy=False) 1437 self._ub = array([self.bounds[min((i, max_i))][1] 1438 if self.bounds[min((i, max_i))][1] is not None 1439 else np.Inf 1440 for i in xrange(length)], copy=False) 1441 lb = self._lb 1442 ub = self._ub 1443 # define added values for lower and upper bound 1444 self._al = array([min([(ub[i] - lb[i]) / 2, (1 + np.abs(lb[i])) / 20]) 1445 if isfinite(lb[i]) else 1 for i in rglen(lb)], copy=False) 1446 self._au = array([min([(ub[i] - lb[i]) / 2, (1 + np.abs(ub[i])) / 20]) 1447 if isfinite(ub[i]) else 1 for i in rglen(ub)], copy=False)
1448
1449 - def __call__(self, solution_genotype, copy_if_changed=True, copy_always=False):
1450 # about four times faster version of array([self._transform_i(x, i) for i, x in enumerate(solution_genotype)]) 1451 # still, this makes a typical run on a test function two times slower, but there might be one too many copies 1452 # during the transformations in gp 1453 if len(self._lb) != len(solution_genotype): 1454 self.initialize(len(solution_genotype)) 1455 lb = self._lb 1456 ub = self._ub 1457 al = self._al 1458 au = self._au 1459 1460 if copy_always or not isinstance(solution_genotype[0], float): 1461 # transformed value is likely to be a float 1462 y = np.array(solution_genotype, copy=True, dtype=float) 1463 # if solution_genotype is not a float, copy value is disregarded 1464 copy = False 1465 else: 1466 y = solution_genotype 1467 copy = copy_if_changed 1468 idx = (y < lb - 2 * al - (ub - lb) / 2.0) | (y > ub + 2 * au + (ub - lb) / 2.0) 1469 if idx.any(): 1470 r = 2 * (ub[idx] - lb[idx] + al[idx] + au[idx]) # period 1471 s = lb[idx] - 2 * al[idx] - (ub[idx] - lb[idx]) / 2.0 # start 1472 if copy: 1473 y = np.array(y, copy=True) 1474 copy = False 1475 y[idx] -= r * ((y[idx] - s) // r) # shift 1476 idx = y > ub + au 1477 if idx.any(): 1478 if copy: 1479 y = np.array(y, copy=True) 1480 copy = False 1481 y[idx] -= 2 * (y[idx] - ub[idx] - au[idx]) 1482 idx = y < lb - al 1483 if idx.any(): 1484 if copy: 1485 y = np.array(y, copy=True) 1486 copy = False 1487 y[idx] += 2 * (lb[idx] - al[idx] - y[idx]) 1488 idx = y < lb + al 1489 if idx.any(): 1490 if copy: 1491 y = np.array(y, copy=True) 1492 copy = False 1493 y[idx] = lb[idx] + (y[idx] - (lb[idx] - al[idx]))**2 / 4 / al[idx] 1494 idx = y > ub - au 1495 if idx.any(): 1496 if copy: 1497 y = np.array(y, copy=True) 1498 copy = False 1499 y[idx] = ub[idx] - (y[idx] - (ub[idx] + au[idx]))**2 / 4 / au[idx] 1500 # assert Mh.vequals_approximately(y, BoxConstraintsTransformationBase.__call__(self, solution_genotype)) 1501 return y
1502 __call__.doc = BoxConstraintsTransformationBase.__doc__ 1503 transform = __call__
1504 - def idx_infeasible(self, solution_genotype):
1505 """return indices of "infeasible" variables, that is, 1506 variables that do not directly map into the feasible domain such that 1507 ``tf.inverse(tf(x)) == x``. 1508 1509 """ 1510 res = [i for i, x in enumerate(solution_genotype) 1511 if not self.is_feasible_i(x, i)] 1512 return res
1513 - def is_feasible_i(self, x, i):
1514 """return True if value ``x`` is in the invertible domain of 1515 variable ``i`` 1516 1517 """ 1518 lb = self._lb[self._index(i)] 1519 ub = self._ub[self._index(i)] 1520 al = self._al[self._index(i)] 1521 au = self._au[self._index(i)] 1522 return lb - al < x < ub + au
1523 - def is_loosely_feasible_i(self, x, i):
1524 """never used""" 1525 lb = self._lb[self._index(i)] 1526 ub = self._ub[self._index(i)] 1527 al = self._al[self._index(i)] 1528 au = self._au[self._index(i)] 1529 return lb - 2 * al - (ub - lb) / 2.0 <= x <= ub + 2 * au + (ub - lb) / 2.0
1530
1531 - def shift_or_mirror_into_invertible_domain(self, solution_genotype, 1532 copy=False):
1533 """Details: input ``solution_genotype`` is changed. The domain is 1534 [lb - al, ub + au] and in [lb - 2*al - (ub - lb) / 2, lb - al] 1535 mirroring is applied. 1536 1537 """ 1538 assert solution_genotype is not None 1539 if copy: 1540 y = [val for val in solution_genotype] 1541 else: 1542 y = solution_genotype 1543 if isinstance(y, np.ndarray) and not isinstance(y[0], float): 1544 y = array(y, dtype=float) 1545 for i in rglen(y): 1546 lb = self._lb[self._index(i)] 1547 ub = self._ub[self._index(i)] 1548 al = self._al[self._index(i)] 1549 au = self._au[self._index(i)] 1550 # x is far from the boundary, compared to ub - lb 1551 if y[i] < lb - 2 * al - (ub - lb) / 2.0 or y[i] > ub + 2 * au + (ub - lb) / 2.0: 1552 r = 2 * (ub - lb + al + au) # period 1553 s = lb - 2 * al - (ub - lb) / 2.0 # start 1554 y[i] -= r * ((y[i] - s) // r) # shift 1555 if y[i] > ub + au: 1556 y[i] -= 2 * (y[i] - ub - au) 1557 if y[i] < lb - al: 1558 y[i] += 2 * (lb - al - y[i]) 1559 return y
1560 shift_or_mirror_into_invertible_domain.__doc__ = BoxConstraintsTransformationBase.shift_or_mirror_into_invertible_domain.__doc__ + shift_or_mirror_into_invertible_domain.__doc__ 1561
1562 - def _shift_or_mirror_into_invertible_i(self, x, i):
1563 """shift into the invertible domain [lb - ab, ub + au], mirror close to 1564 boundaries in order to get a smooth transformation everywhere 1565 1566 """ 1567 assert x is not None 1568 lb = self._lb[self._index(i)] 1569 ub = self._ub[self._index(i)] 1570 al = self._al[self._index(i)] 1571 au = self._au[self._index(i)] 1572 # x is far from the boundary, compared to ub - lb 1573 if x < lb - 2 * al - (ub - lb) / 2.0 or x > ub + 2 * au + (ub - lb) / 2.0: 1574 r = 2 * (ub - lb + al + au) # period 1575 s = lb - 2 * al - (ub - lb) / 2.0 # start 1576 x -= r * ((x - s) // r) # shift 1577 if x > ub + au: 1578 x -= 2 * (x - ub - au) 1579 if x < lb - al: 1580 x += 2 * (lb - al - x) 1581 return x
1582 - def _transform_i(self, x, i):
1583 """return transform of x in component i""" 1584 x = self._shift_or_mirror_into_invertible_i(x, i) 1585 lb = self._lb[self._index(i)] 1586 ub = self._ub[self._index(i)] 1587 al = self._al[self._index(i)] 1588 au = self._au[self._index(i)] 1589 if x < lb + al: 1590 return lb + (x - (lb - al))**2 / 4 / al 1591 elif x < ub - au: 1592 return x 1593 elif x < ub + 3 * au: 1594 return ub - (x - (ub + au))**2 / 4 / au 1595 else: 1596 assert False # shift removes this case 1597 return ub + au - (x - (ub + au))
1598 - def _inverse_i(self, y, i):
1599 """return inverse of y in component i""" 1600 lb = self._lb[self._index(i)] 1601 ub = self._ub[self._index(i)] 1602 al = self._al[self._index(i)] 1603 au = self._au[self._index(i)] 1604 if 1 < 3: 1605 if not lb <= y <= ub: 1606 raise ValueError('argument of inverse must be within the given bounds') 1607 if y < lb + al: 1608 return (lb - al) + 2 * (al * (y - lb))**0.5 1609 elif y < ub - au: 1610 return y 1611 else: 1612 return (ub + au) - 2 * (au * (ub - y))**0.5
1613
1614 -class GenoPheno(object):
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] # TODO: should not necessarily be needed 1686 # r = np.random.randn(dim) 1687 # assert all(tf[0](tf[1](r)) - r < 1e-7) 1688 # r = np.random.randn(dim) 1689 # assert all(tf[0](tf[1](r)) - r > -1e-7) 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 # convenience commenting functionality: drop negative keys 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 # TODO: rather let default_val be a list of default values, 1712 # cave comparison of arrays 1713 try: 1714 if len(vec) == 1: 1715 vec = vec[0] # [None] becomes None and is always default 1716 except TypeError: 1717 pass # vec is a scalar 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 # CAVE: 1 is not array(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 # TODO: copy_always seems superfluous, as it could be done in the calling code 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) # was into_bounds(x, False) before (bug before v0.96.22) 1777 else: 1778 if self.fixed_values is None: 1779 y = array(x, copy=copy) # make a copy, in case 1780 else: # expand with fixed values 1781 y = list(x) # is a copy 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: # just for efficiency 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) # copy is False 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 # not change, no copy 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: # could be improved? 1861 x = array(x, copy=True) 1862 copy = False 1863 1864 # user-defined transformation 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 # affine-linear transformation: shift and scaling 1871 if self.typical_x is not 0: 1872 x -= self.typical_x 1873 if self.scales is not 1: # just for efficiency 1874 x /= self.scales 1875 1876 # kick out fixed_values 1877 if self.fixed_values is not None: 1878 # keeping the transformed values does not help much 1879 # therefore it is omitted 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 # repair injected solutions 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
1891 # ____________________________________________________________ 1892 # ____________________________________________________________ 1893 # check out built-in package abc: class ABCMeta, abstractmethod, abstractproperty... 1894 # see http://docs.python.org/whatsnew/2.6.html PEP 3119 abstract base classes 1895 # 1896 -class OOOptimizer(object):
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()
1965 - def initialize(self):
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')
1983 - def stop(self):
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 # raise NotImplementedError('method disp() is not implemented')
1998 - def result(self):
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 # previous ordering: 2006 # def optimize(self, objectivefct, 2007 # logger=None, verb_disp=20, 2008 # iterations=None, min_iterations=1, 2009 # call_back=None):
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() # deliver candidate solutions 2079 fitvals = [objective_fct(x, *args) for x in X] 2080 self.tell(X, fitvals) # all the work is done here 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 # signal logger that we left the loop 2087 # TODO: this is very ugly, because it assumes modulo keyword 2088 # argument *and* modulo attribute to be available 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 # was: return self.result() + (self.stop(), self, logger) 2108 2109 _experimental = False
2110 2111 -class CMAAdaptSigmaBase(object):
2112 """step-size adaptation base class, implementing hsig functionality 2113 via an isotropic evolution path. 2114 2115 """
2116 - def __init__(self, *args, **kwargs):
2117 self.is_initialized_base = False 2118 self._ps_updated_iteration = -1
2119 - def initialize_base(self, es):
2120 """set parameters and state variable based on dimension, 2121 mueff and possibly further options. 2122 2123 """ 2124 ## meta_parameters.cs_exponent == 1.0 2125 b = 1.0 2126 ## meta_parameters.cs_multiplier == 1.0 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
2131 - def _update_ps(self, es):
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 # es.B and es.D must/should be those from the last iteration 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 # correction with self.countiter seems not necessary, 2159 # as pc also starts with zero 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
2166 2167 -class CMAAdaptSigmaNone(CMAAdaptSigmaBase):
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
2177 2178 -class CMAAdaptSigmaDistanceProportional(CMAAdaptSigmaBase):
2179 """artificial setting of ``sigma`` for test purposes, e.g. 2180 to simulate optimal progress rates. 2181 2182 """
2183 - def __init__(self, coefficient=1.2):
2184 super(CMAAdaptSigmaDistanceProportional, self).__init__() # base class provides method hsig() 2185 self.coefficient = coefficient 2186 self.is_initialized = True
2187 - def update(self, es, **kwargs):
2188 # optimal step-size is 2189 es.sigma = self.coefficient * es.sp.mueff * sum(es.mean**2)**0.5 / es.N / es.sp.cmean
2190
2191 2192 -class CMAAdaptSigmaCSA(CMAAdaptSigmaBase):
2193 - def __init__(self):
2194 """postpone initialization to a method call where dimension and mueff should be known. 2195 2196 """ 2197 self.is_initialized = False
2198 - def initialize(self, es):
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: # len(...) failed 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 ## meta_parameters.cs_exponent == 1.0 2220 b = 1.0 2221 ## meta_parameters.cs_multiplier == 1.0 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 # in symmetric use (strict lower bound is -cs/damps anyway) 2229 2230 if self.disregard_length_setting: 2231 es.opts['CSA_clip_length_value'] = [0, 0] 2232 ## meta_parameters.cs_exponent == 1.0 2233 b = 1.0 * 0.5 2234 ## meta_parameters.cs_multiplier == 1.0 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 # * (1.1 - 1/(es.N+1)**0.5) 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
2245 - def _update_ps(self, es):
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 # zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 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 # z *= (es.N / sum(z**2))**0.5 # ==> sum(z**2) == es.N 2262 # z *= es.const.chiN / sum(z**2)**0.5 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) # caveat: if es.B or es.D are already updated and ps is not, this goes wrong! 2267 if es.opts['CSA_squared']: 2268 s = (sum(self.ps**2) / es.N - 1) / 2 2269 # sum(self.ps**2) / es.N has mean 1 and std sqrt(2/N) and is skewed 2270 # divided by 2 to have the derivative d/dx (x**2 / N - 1) for x**2=N equal to 1 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 # "error" handling 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'])
2286 -class CMAAdaptSigmaMedianImprovement(CMAAdaptSigmaBase):
2287 """Compares median fitness against a fitness percentile of the previous iteration, 2288 see Ait ElHara et al, GECCO 2013. 2289 2290 """
2291 - def __init__(self):
2292 # CMAAdaptSigmaBase.__init__(self) 2293 super(CMAAdaptSigmaMedianImprovement, self).__init__() # base class provides method hsig()
2294 - def initialize(self, es):
2295 r = es.sp.mueff / es.popsize 2296 self.index_to_compare = 0.5 * (r**0.5 + 2.0 * (1 - r**0.5) / log(es.N + 9)**2) * (es.popsize) # TODO 2297 self.index_to_compare = (0.30 if not es.opts['vv'] 2298 else es.opts['vv']) * es.popsize # TODO 2299 self.damp = 2 - 2 / es.N # sign-rule: 2 2300 self.c = 0.3 # sign-rule needs <= 0.3 2301 self.s = 0 # averaged statistics, usually between -1 and +1
2302 - def update(self, es, **kwargs):
2303 if es.countiter < 2: 2304 self.initialize(es) 2305 self.fit = es.fit.fit 2306 else: 2307 ft1, ft2 = self.fit[int(self.index_to_compare)], self.fit[int(np.ceil(self.index_to_compare))] 2308 ftt1, ftt2 = es.fit.fit[(es.popsize - 1) // 2], es.fit.fit[int(np.ceil((es.popsize - 1) / 2))] 2309 pt2 = self.index_to_compare - int(self.index_to_compare) 2310 # ptt2 = (es.popsize - 1) / 2 - (es.popsize - 1) // 2 # not in use 2311 s = 0 2312 if 1 < 3: 2313 s += pt2 * sum(es.fit.fit <= self.fit[int(np.ceil(self.index_to_compare))]) 2314 s += (1 - pt2) * sum(es.fit.fit < self.fit[int(self.index_to_compare)]) 2315 s -= es.popsize / 2. 2316 s *= 2. / es.popsize # the range was popsize, is 2 2317 self.s = (1 - self.c) * self.s + self.c * s 2318 es.sigma *= exp(self.s / self.damp) 2319 # es.more_to_write.append(10**(self.s)) 2320 2321 #es.more_to_write.append(10**((2 / es.popsize) * (sum(es.fit.fit < self.fit[int(self.index_to_compare)]) - (es.popsize + 1) / 2))) 2322 # # es.more_to_write.append(10**(self.index_to_compare - sum(self.fit <= es.fit.fit[es.popsize // 2]))) 2323 # # es.more_to_write.append(10**(np.sign(self.fit[int(self.index_to_compare)] - es.fit.fit[es.popsize // 2]))) 2324 self.fit = es.fit.fit
2325 -class CMAAdaptSigmaTPA(CMAAdaptSigmaBase):
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__() # base class provides method hsig() 2348 # CMAAdaptSigmaBase.__init__(self) 2349 self.initialized = False 2350 self.dimension = dimension 2351 self.opts = opts
2352 - def initialize(self, N=None, opts=None):
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'] # should be renamed to sigma_adapt_dampfac or something 2359 except (TypeError, KeyError): 2360 damp_fac = 1 2361 2362 self.sp = _BlancClass() # just a container to have sp.name instead of sp['name'] to access parameters 2363 try: 2364 self.sp.damp = damp_fac * eval('N')**0.5 # why do we need 10 <-> exp(1/10) == 1.1? 2 should be fine!? 2365 # self.sp.damp = damp_fac * (4 - 3.6/eval('N')**0.5) 2366 except: 2367 self.sp.damp = 4 # - 3.6 / N**0.5 # should become new default 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 # 0.5 fails to converge on the Rastrigin function 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 # rank difference is asymetric and therefore the switch from increase to decrease takes too long 2382 self.sp.z_exponent = 0.5 # sign(z) * abs(z)**z_exponent, 0.5 seems better with larger popsize, 1 was default 2383 self.sp.sigma_fac = 1.0 # (obsolete) 0.5 feels better, but no evidence whether it is 2384 self.sp.relative_to_delta_mean = True # (obsolete) 2385 self.s = 0 # the state variable 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 # TODO: on the linear function, the two mirrored samples lead 2397 # to a sharp increase of condition of the covariance matrix. 2398 # They should not be used to update the covariance matrix, 2399 # if the step-size inreases quickly. This should be fine with 2400 # negative updates though. 2401 if not self.initialized: 2402 self.initialize(es.N, es.opts) 2403 if 1 < 3: 2404 # use the ranking difference of the mirrors for adaptation 2405 # damp = 5 should be fine 2406 z = np.where(es.fit.idx == 1)[0][0] - np.where(es.fit.idx == 0)[0][0] 2407 z /= es.popsize - 1 # z in [-1, 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 #es.more_to_write.append(10**z) 2414 2415 new_injections = True
2416 2417 # ____________________________________________________________ 2418 # ____________________________________________________________ 2419 # 2420 -class CMAEvolutionStrategy(OOOptimizer):
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 # read only attribute decorator for a method
2729 - def popsize(self):
2730 """number of samples by default returned by` ask()` 2731 """ 2732 return self.sp.popsize
2733 2734 # this is not compatible with python2.5: 2735 # @popsize.setter 2736 # def popsize(self, p): 2737 # """popsize cannot be set (this might change in future) 2738 # """ 2739 # raise _Error("popsize cannot be changed") 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) # update the stopdict and return a Dict
2752
2753 - def copy_constructor(self, es):
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()) # for the record 2764 del self.inputargs['self'] # otherwise the instance self has a cyclic reference 2765 self.inopts = inopts 2766 opts = CMAOptions(inopts).complement() # CMAOptions() == fmin([],[]) == defaultOptions() 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) # manage weird shapes, set self.x0 2779 self.N_pheno = len(self.x0) 2780 2781 self.sigma0 = sigma0 2782 if isinstance(sigma0, basestring): 2783 # TODO: no real need here (do rather in fmin) 2784 self.sigma0 = eval(sigma0) # like '1./N' or 'np.random.rand(1)[0]+1e-2' 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 # goes to inialize 2788 2789 # extract/expand options 2790 N = self.N_pheno 2791 assert isinstance(opts['fixed_variables'], (basestring, dict)) \ 2792 or opts['fixed_variables'] is None 2793 # TODO: in case of a string we need to eval the fixed_variables 2794 if isinstance(opts['fixed_variables'], dict): 2795 N = self.N_pheno - len(opts['fixed_variables']) 2796 opts.evalall(locals()) # using only N 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() # just a little faster and well defined 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 # set self.mean to geno(x0) 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 # a hack to avoid an exception 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 # without copy_always interface: 2825 # self.mean = self.gp.geno(array(self.x0, copy=True), copy_if_changed=False) 2826 self.N = len(self.mean) 2827 assert N == self.N 2828 self.fmean = np.NaN # TODO name should change? prints nan in output files (OK with matlab&octave) 2829 self.fmean_noise_free = 0. # for output only 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() # class instance 2835 2836 self.sp = _CMAParameters(N, opts) 2837 self.sp0 = self.sp # looks useless, as it is not a copy 2838 2839 # initialization of state variables 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) # make sure to get a vector 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']: # is True or > 0 2861 # linear time and space complexity 2862 self.B = array(1) # fine for np.dot(self.B, .) and self.B.T 2863 self.C = stds**2 * np.ones(N) # in case stds == 1 2864 self.dC = self.C 2865 else: 2866 self.B = np.eye(N) # identity(N) 2867 # prevent equal eigenvals, a hack for np.linalg: 2868 # self.C = np.diag(stds**2 * exp(1e-4 * np.random.rand(N))) 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 # we assume that C is diagonal 2874 2875 # self.gp.pheno adds fixed variables 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 # noise "signal" 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']) # CAVEAT: this only seeds np.random 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)) # expectation of norm(randn(N,1)) 2902 2903 self.logger = CMADataLogger(opts['verb_filenameprefix'], modulo=opts['verb_log']).register(self) 2904 2905 # attribute for stopping criteria in function stop 2906 self._stopdict = _CMAStopDict() 2907 self.callbackstop = 0 2908 2909 self.fit = _BlancClass() 2910 self.fit.fit = [] # not really necessary 2911 self.fit.hist = [] # short history of best 2912 self.fit.histbest = [] # long history of best 2913 self.fit.histmedian = [] # long history of median 2914 2915 self.more_to_write = [] # [1, 1, 1, 1] # N*[1] # needed when writing takes place before setting 2916 2917 # say hello 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())) # + func.__name__ 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
2937 - def _set_x0(self, x0):
2938 if x0 == str(x0): 2939 x0 = eval(x0) 2940 self.x0 = array(x0) # should not have column or row, is just 1-D 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]) # 1-D array, not really necessary?!
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 # N,lambda=20,200: overall CPU 7s vs 5s == 40% overhead, even without bounds! 3000 # new data: 11.5s vs 9.5s == 20% 3001 # TODO: check here, whether this is necessary? 3002 # return [self.gp.pheno(x, copy=False, into_bounds=self.boundary_handler.repair) for x in pop] # probably fine 3003 # return [Solution(self.gp.pheno(x, copy=False), copy=False) for x in pop] # here comes the memory leak, now solved 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 # see Hansen (2011), Injecting external solutions into CMA-ES 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 # TODO: check this out 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)) # float is 1.6 times faster than int 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 # lift gradient into geno-space 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 # TODO: frozen variables brake the code (e.g. at grad of map) 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 # newton_direction = sv * B * D * D * B^T * sv * gradient = sv * B * D * v 3075 # v = D^-1 * B^T * sv^-1 * newton_direction = D * B^T * sv * gradient 3076 q = sum(v**2) 3077 if q: 3078 # Newton direction 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 # insert solutions, this could also (better?) be done in self.gp.pheno 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 # update distribution, might change self.mean 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 ) # TODO (minor): not sure whether this is "the right" criterion 3136 ): 3137 self.updateBD() 3138 if xmean is None: 3139 xmean = self.mean 3140 else: 3141 try: 3142 xmean = self.archive[xmean]['geno'] 3143 # noise handling after call of tell 3144 except KeyError: 3145 try: 3146 xmean = self.sent_solutions[xmean]['geno'] 3147 # noise handling before calling tell 3148 except KeyError: 3149 pass 3150 3151 if self.countiter == 0: 3152 self.tic = time.clock() # backward compatible 3153 self.elapsed_time = ElapsedTime() 3154 3155 sigma = sigma_fac * self.sigma 3156 3157 # update parameters for sampling the distribution 3158 # fac 0 1 10 3159 # 150-D cigar: 3160 # 50749 50464 50787 3161 # 200-D elli: == 6.9 3162 # 99900 101160 3163 # 100995 103275 == 2% loss 3164 # 100-D elli: == 6.9 3165 # 363052 369325 < 2% loss 3166 # 365075 365755 3167 3168 # sample distribution 3169 if self._flgtelldone: # could be done in tell()!? 3170 self._flgtelldone = False 3171 self.ary = [] 3172 3173 # check injections from pop_injection_directions 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 # each row is a solution 3192 # the 1 is a small safeguard which needs to be removed to implement "pure" adaptive encoding 3193 arz = self.randn((max([1, (number - len(arinj))]), self.N)) 3194 if self.opts['CMA_sample_on_sphere_surface']: # normalize the length to chiN 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 # or to average 3201 # arz *= 1 * self.const.chiN / np.mean([sum(z**2)**0.5 for z in arz]) 3202 3203 # fac = np.mean(sum(arz**2, 1)**0.5) 3204 # print fac 3205 # arz *= self.const.chiN / fac 3206 3207 # compute ary from arz 3208 if len(arz): # should always be true 3209 # apply unconditional mirroring, is pretty obsolete 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 # TODO: subject to removal in future 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[:]) # now TPA is with pc_line_samples 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 # assign a mirrored pair from each element of ys into ary 3237 for i, y in enumerate(ys): 3238 if len(arz) > 2 * i + 1: # at least two more samples 3239 assert y is not self.pc 3240 # y *= sum(self.randn(self.N)**2)**0.5 / self.mahalanobis_norm(y) 3241 y *= self.random_rescaling_factor_to_mahalanobis_size(y) 3242 # TODO: rescale y depending on some parameter? 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 # print(xmean[0]) 3250 pop = xmean + sigma * ary 3251 self.evaluations_per_f_value = 1 3252 self.ary = ary 3253 return pop
3254
3255 - def random_rescale_to_mahalanobis(self, x):
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
3262 - def random_rescaling_factor_to_mahalanobis_size(self, y):
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
3276 - def get_mirror(self, x, preserve_length=False):
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: # can only happen with injected solutions?! 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 # dx *= sum(self.randn(self.N)**2)**0.5 / self.mahalanobis_norm(dx) 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 # old measure: costs 25% in CPU performance with N,lambda=20,200 3313 self.sent_solutions.insert(y, geno=x, iteration=self.countiter) 3314 return y
3315
3316 - def _mirror_penalized(self, f_values, idx):
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
3335 - def _mirror_idx_cov(self, f_values, idx1): # will most likely be removed
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 # idx.append(idx1[i] if f_values[idx1[i]] > f_values[idx2[i]] else idx2[i]) 3360 return idx2[np.argsort(f)][-1::-1] 3361
3362 - def eval_mean(self, func, args=()):
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 # initialize 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 # TODO: now selective mirroring might be impaired 3449 assert new_injections or self.opts['CMA_mirrormethod'] < 2 3450 if new_injections and self.opts['CMA_mirrormethod'] != 1: # otherwise mirrors are done elsewhere 3451 nmirrors = 0 3452 assert nmirrors <= popsize // 2 3453 self.mirrors_idx = np.arange(nmirrors) # might never be used 3454 self.mirrors_rejected_idx = [] # might never be used 3455 is_feasible = self.opts['is_feasible'] 3456 3457 # do the work 3458 fit = [] # or np.NaN * np.empty(number) 3459 X_first = self.ask(popsize, xmean=xmean, gradf=gradf, args=args) 3460 if xmean is None: 3461 xmean = self.mean # might have changed in self.ask 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): # rejection sampling 3467 rejected += 1 3468 if rejected: # resample 3469 x = self.ask(1, xmean, sigma_fac)[0] 3470 elif k >= popsize - nmirrors: # mirrored sample 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 # contraints handling test hardwired ccccccccccc 3478 length_normalizer = 1 3479 # zzzzzzzzzzzzzzzzzzzzzzzzz 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
3495 - def prepare_injection_directions(self):
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 # self.pop_injection_directions is supposed to be empty here 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) # another copy! 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']: # caveat: before, two samples were used 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
3532 - def get_selective_mirrors(self, number=None, pop_sorted=None):
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 # ## prepare 3634 N = self.N 3635 sp = self.sp 3636 if lam < sp.mu: # rather decrease cmean instead of having mu > lambda//2 3637 raise _Error('not enough solutions passed to function tell (mu>lambda)') 3638 3639 self.countiter += 1 # >= 1 now 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: # C was diagonal ie 1-D 3646 # enter non-separable phase (no easy return from here) 3647 self.C = np.diag(self.C) 3648 if 1 < 3: 3649 self.B = np.eye(N) # identity(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 # ## manage fitness 3656 fit = self.fit # make short cut 3657 3658 # CPU for N,lam=20,200: this takes 10s vs 7s 3659 fit.bndpen = self.boundary_handler.update(function_values, self)(solutions, self.sent_solutions, self.gp) 3660 # for testing: 3661 # fit.bndpen = self.boundary_handler.update(function_values, self)([s.unrepaired for s in solutions]) 3662 fit.idx = np.argsort(array(fit.bndpen) + array(function_values)) 3663 fit.fit = array(function_values, copy=False)[fit.idx] 3664 3665 # update output data TODO: this is obsolete!? However: need communicate current best x-value? 3666 # old: out['recent_x'] = self.gp.pheno(pop[0]) 3667 # self.out['recent_x'] = array(solutions[fit.idx[0]]) # TODO: change in a data structure(?) and use current as identify 3668 # self.out['recent_f'] = fit.fit[0] 3669 3670 # fitness histories 3671 fit.hist.insert(0, fit.fit[0]) 3672 # if len(self.fit.histbest) < 120+30*N/sp.popsize or # does not help, as tablet in the beginning is the critical counter-case 3673 if ((self.countiter % 5) == 0): # 20 percent of 1e5 gen. 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: # 10 + 30*N/sp.popsize: 3678 fit.histbest.pop() 3679 fit.histmedian.pop() 3680 if len(fit.hist) > 10 + 30 * N / sp.popsize: 3681 fit.hist.pop() 3682 3683 # TODO: clean up inconsistency when an unrepaired solution is available and used 3684 # now get the genotypes 3685 pop = self.pop_sorted = [] # create pop from input argument solutions 3686 for k, s in enumerate(solutions): # use phenotype before Solution.repair() 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)] # takes genotype from sent_solutions, if available 3692 try: 3693 self.archive.insert(s, value=self.sent_solutions.pop(s), fitness=function_values[k]) 3694 # self.sent_solutions.pop(s) 3695 except KeyError: 3696 pass 3697 # check that TPA mirrors are available, TODO: move to TPA class? 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 # just an alias 3724 3725 # check and normalize each x - m 3726 # check_points is a flag (None is default: check non-known solutions) or an index list 3727 # should also a number possible (first check_points points)? 3728 if check_points not in (None, False, 0, [], ()): # useful in case of injected solutions and/or adaptive encoding, however is automatic with use_sent_solutions 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 # only arrays can be multiple indexed 3739 pop = array(pop, copy=False) 3740 3741 # sort pop 3742 pop = pop[fit.idx] 3743 3744 # prepend best-ever solution to population, in case 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 # xp = [self.best.xdict['geno']] 3749 # xp = [self.gp.geno(self.best.x[:])] # TODO: remove 3750 # print self.mahalanobis_norm(xp[0]-self.mean) 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': # current solution was better 3759 self.opts['CMA_elitist'] = False 3760 3761 self.pop_sorted = pop 3762 # compute new mean 3763 self.mean = mold + self.sp.cmean * \ 3764 (sum(sp.weights * pop[0:sp.mu].T, 1) - mold) 3765 3766 3767 # check Delta m (this is not default, but could become at some point) 3768 # CAVE: upper_length=sqrt(2)+2 is too restrictive, test upper_length = sqrt(2*N) thoroughly. 3769 # replaced by repair_geno? 3770 # simple test case injecting self.mean: 3771 # self.mean = 1e-4 * self.sigma * np.random.randn(N) 3772 if 1 < 3: 3773 cmean = self.sp.cmean 3774 3775 # zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 3776 # get learning rate constants 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 # now the real work can start 3782 3783 hsig = self.adapt_sigma.hsig(self) # ps update must be done here in separable case 3784 3785 # hsig = sum(self.ps**2) / self.N < 2 + 4./(N+1) 3786 # adjust missing variance due to hsig, in 4-D with damps=1e99 and sig0 small 3787 # hsig leads to premature convergence of C otherwise 3788 # hsiga = (1-hsig**2) * c1 * cc * (2-cc) # to be removed in future 3789 c1a = c1 - (1 - hsig**2) * c1 * cc * (2 - cc) # adjust for variance loss 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 # covariance matrix adaptation/udpate 3796 if sp.CMA_on: 3797 # assert sp.c1 + sp.cmu < sp.mueff / N # ?? 3798 assert c1 + cmu <= 1 3799 3800 # default full matrix case 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) # learning rate integrated 3804 if self.sp.neg.cmuexp: 3805 tmp = (pop[-sp.neg.mu:] - mold) / (self.sigma * self.sigma_vec) 3806 # normalize to constant length (seems preferable in several aspects) 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 # for some reason necessary? 3811 self._Yneg += dot(sp.neg.weights * tmp.T, tmp) - self.C 3812 # self.update_exponential(dot(sp.neg.weights * tmp.T, tmp) - 1 * self.C, -1*self.sp.neg.cmuexp) 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) # for output and termination checking 3817 3818 else: # separable/diagonal linear case 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) # TODO see above 3823 Z += sp.weights[k] * z * z # is 1-D 3824 self.C = (1 - c1a - cmu) * self.C + c1 * self.pc * self.pc + cmu * Z 3825 # TODO: self.C *= exp(cmuneg * (N - dot(sp.neg.weights, **2) 3826 self.dC = self.C 3827 self.D = sqrt(self.C) # C is a 1-D array, this is why adapt_sigma needs to prepare before 3828 self.itereigenupdated = self.countiter 3829 3830 # idx = self._mirror_idx_cov() # take half of mirrored vectors for negative update 3831 3832 # step-size adaptation, adapt sigma 3833 # in case of TPA, function_values[0] and [1] must reflect samples colinear to xmean - xmean_old 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 # g = self.countiter 3841 # N = self.N 3842 # mindx = eval(self.opts['mindx']) 3843 # if isinstance(self.opts['mindx'], basestring) else self.opts['mindx'] 3844 if self.sigma * min(self.D) < self.opts['mindx']: # TODO: sigma_vec is missing here 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 # to be compared with sigma 3852 3853 # TODO increase sigma in case of a plateau? 3854 3855 # Uncertainty noise measurement is done on an upper level 3856 3857 # move mean into "feasible preimage", leads to weird behavior on 3858 # 40-D tablet with bound 0.1, not quite explained (constant 3859 # dragging is problematic, but why doesn't it settle), still a bug? 3860 if new_injections: 3861 self.pop_injection_directions = self.prepare_injection_directions() 3862 self.pop_sorted = [] # remove this in case pop is still needed 3863 self._flgtelldone = True
3864 # end tell() 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
3890 - def result(self):
3891 """return:: 3892 3893 (xbest, f(xbest), evaluations_xbest, evaluations, iterations, 3894 pheno(xmean), effective_stds) 3895 3896 """ 3897 # TODO: how about xcurrent? 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
3926 - def clip_or_fit_solutions(self, pop, idx):
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
3935 - def repair_genotype(self, x, copy_if_changed=False):
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: # hard clip at upper_length 3944 upper_length = self.N**0.5 + 2 * self.N / (self.N + 2) # should become an Option, but how? e.g. [0, 2, 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: # should be 25% faster: 3951 x -= mold 3952 x /= fac 3953 x += mold 3954 # print self.countiter, k, fac, self.mahalanobis_norm(pop[k] - mold) 3955 # adapt also sigma: which are the trust-worthy/injected solutions? 3956 else: 3957 if 'checktail' not in self.__dict__: # hasattr(self, 'checktail') 3958 raise NotImplementedError 3959 # from check_tail_smooth import CheckTail # for the time being 3960 # self.checktail = CheckTail() 3961 # print('untested feature checktail is on') 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
3969 - def decompose_C(self):
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 # print(' %.19e' % self.C[0][0]) 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 # self.B = np.round(self.B, 10) 3986 # for i in rglen(self.D): 3987 # d = self.D[i] 3988 # oom = np.round(np.log10(d)) 3989 # self.D[i] = 10**oom * np.round(d / 10**oom, 10) 3990 # print(' %.19e' % self.C[0][0]) 3991 # print(' %.19e' % self.D[0]) 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] # self.B[i] is a row, columns self.B[:,i] are eigenvectors 4001 self.count_eigen += 1
4002 - def updateBD(self):
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 # itereigenupdated is always up-to-date in the diagonal case 4008 # just double check here 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 # C has already positive updates, here come the additional negative updates 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 # 0.9 is sufficient to prevent degeneration in small dimension 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 # pos.def. guarantied, because vectors are normalized 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: # let's check 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: # guaranties pos.def. unconditionally 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 # self.C = self.Ypos + Cs * Mh.expms(-self.sp.neg.cmuexp*Csi*self.Yneg*Csi) * Cs 4068 # Yneg = self.Yneg # for temporary debugging, can be removed 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): # normalize trace of C 4074 if self.opts['CMA_const_trace'] == 2: 4075 s = np.exp(2 * np.mean(np.log(self.D))) # or geom average of dC? 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 # allows for much larger condition numbers, if axis-parallel 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 # self.C = np.triu(self.C) + np.triu(self.C,1).T # should work as well 4089 # self.D, self.B = eigh(self.C) # hermitian, ie symmetric C is assumed 4090 4091 self.decompose_C() 4092 # assert(sum(self.D-DD) < 1e-6) 4093 # assert(sum(sum(np.dot(BB, BB.T)-np.eye(self.N))) < 1e-6) 4094 # assert(sum(sum(np.dot(BB * DD, BB.T) - self.C)) < 1e-6) 4095 # assert(all(self.B[self.countiter % self.N] == self.B[self.countiter % self.N,:])) 4096 4097 # qqqqqqqqqq 4098 # is O(N^3) 4099 # assert(sum(abs(self.C - np.dot(self.D * self.B, self.B.T))) < N**2*1e-11) 4100 4101 if 1 < 3 and max(self.D) / min(self.D) > 1e6 and self.gp.isidentity: 4102 # TODO: allow to do this again 4103 # dmean_prev = dot(self.B, (1. / self.D) * dot(self.B.T, (self.mean - 0*self.mean_old) / self.sigma_vec)) 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) # not really necessary 4108 self.gp.isidentity = False 4109 assert self.mean is not self.mean_old 4110 self.mean = self.gp.geno(self.mean) # same as tf_geno 4111 self.mean_old = self.gp.geno(self.mean_old) # not needed? 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 # dmean_now = dot(self.B, (1. / self.D) * dot(self.B.T, (self.mean - 0*self.mean_old) / self.sigma_vec)) 4119 # assert Mh.vequals_approximately(dmean_now, dmean_prev) 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
4125 - def multiplyC(self, alpha):
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
4131 - def update_exponential(self, Z, eta, BDpair=None):
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) # square root of C 4158 Csi = dot(B, (B / D).T) # square root of inverse of C 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 # ____________________________________________________________
4165 - def feedForResume(self, X, function_values):
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 # feed in chunks of size popsize 4227 self.ask() # a fake ask, mainly for a conditioned calling of updateBD 4228 # and secondary to get possibly the same random state 4229 self.tell(X[i * popsize:(i + 1) * popsize], function_values[i * popsize:(i + 1) * popsize])
4230 4231 # ____________________________________________________________ 4232 # ____________________________________________________________
4233 - def readProperties(self):
4234 """reads dynamic parameters from property file (not implemented) 4235 """ 4236 print('not yet implemented')
4237 4238 # ____________________________________________________________ 4239 # ____________________________________________________________
4240 - def correlation_matrix(self):
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
4250 - def mahalanobis_norm(self, dx):
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
4276 - def _metric_when_multiplied_with_sig_vec(self, sig):
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
4286 - def disp_annotation(self):
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): # TODO: rather assign opt['verb_disp'] as default?
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 # console display 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 # if self.countiter < 4: 4319 sys.stdout.flush() 4320 return self
4321 - def plot(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 # the follow string arguments are evaluated if they do not contain "filename" 4333 'AdaptSigma': 'CMAAdaptSigmaCSA # or any other CMAAdaptSigmaBase class e.g. CMAAdaptSigmaTPA', 4334 'CMA_active': 'True # negative update, conducted after the original update', 4335 # 'CMA_activefac': '1 # learning rate multiplier for active update', 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', # TODO 4/ccov_separable? 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 # 'CMA_AII': 'False # not yet tested', 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 # , eval_parallel2': 'not in use {"processes": None, "timeout": 12, "is_feasible": lambda x: True} # distributes function calls to processes processes' 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', # TODO: rename: iterwaitupdatedistribution? 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 }
4395 -class CMAOptions(dict):
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 # @classmethod # self is the class, not the instance 4442 # @property 4443 # def default(self): 4444 # """returns all options with defaults""" 4445 # return fmin([],[]) 4446 4447 @staticmethod
4448 - def defaults():
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 # getting rid of the u of u"name" by str(u"name") 4452 # return dict(cma_default_options) 4453 4454 @staticmethod
4455 - def versatile_options():
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):
4466 """check for ambiguous keys and move attributes into dict""" 4467 self.check_values(options) 4468 self.check_attributes(options) 4469 self.check_values(options) 4470 return self
4471 - def check_values(self, options=None):
4472 corrected_key = CMAOptions().corrected_key # caveat: infinite recursion 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
4489 - def check_attributes(self, opts=None):
4490 """check for attributes and moves them into the dictionary""" 4491 if opts is None: 4492 opts = self 4493 if 1 < 3: 4494 # the problem with merge is that ``opts['ftarget'] = new_value`` 4495 # would be overwritten by the old ``opts.ftarget``. 4496 # The solution here is to empty opts.__dict__ after the merge 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] # getattr(opts, key) 4519 delattr(opts, key) # is that cosher? 4520 # delattr is necessary to prevent that the attribute 4521 # overwrites the dict entry later again 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 # doesn't work anymore as we have _lock attribute 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 # if not CMAOptions.defaults: # this is different from self.defaults!!! 4546 # CMAOptions.defaults = fmin([],[]) 4547 if s is None: 4548 super(CMAOptions, self).__init__(CMAOptions.defaults()) # dict.__init__(self, CMAOptions.defaults()) should be the same 4549 # self = CMAOptions.defaults() 4550 elif isinstance(s, basestring): 4551 super(CMAOptions, self).__init__(CMAOptions().match(s)) 4552 # we could return here 4553 else: 4554 super(CMAOptions, self).__init__(s) 4555 4556 if not unchecked and s is not None: 4557 self.check() # caveat: infinite recursion 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 # self.evaluated = False # would become an option entry 4567 self._lock_setting = False 4568 self._attributes = self.__dict__.copy() # are not valid keys 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 # dic = dict_or_key if val is None else {dict_or_key:val} 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 # TODO: find a better solution? 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: # dic is a key in this case 4625 dic = {dic:val} # compose a dictionary 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 # to allow o = CMAOptions(o).set(new)
4636
4637 - def complement(self):
4638 """add all missing options with their default values""" 4639 4640 # add meta-parameters, given options have priority 4641 self.check() 4642 for key in CMAOptions.defaults(): 4643 if key not in self: 4644 self[key] = CMAOptions.defaults()[key] 4645 return self
4646
4647 - def settable(self):
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 # TODO: this hack is not so useful: popsize could be there, but N is missing 4679 try: 4680 if isinstance(val, basestring): 4681 val = val.split('#')[0].strip() # remove comments 4682 if isinstance(val, basestring) and \ 4683 key.find('filename') < 0: 4684 # and key.find('mindx') < 0: 4685 val = eval(val, globals(), loc) 4686 # invoke default 4687 # TODO: val in ... fails with array type, because it is applied element wise! 4688 # elif val in (None,(),[],{}) and default is not None: 4689 elif val is None and default is not None: 4690 val = eval(str(default), globals(), loc) 4691 except: 4692 pass # slighly optimistic: the previous is bug-free 4693 return val
4694
4695 - def corrected_key(self, key):
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 # TODO: try: loc['dim'] = loc['N'] etc 4722 if correct_key: 4723 # in_key = key # for debugging only 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 # TODO: this needs rather the parameter N instead of loc 4738 if 'N' in loc: # TODO: __init__ of CMA can be simplified 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
4747 - def match(self, s=''):
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
4763 - def pp(self):
4764 pprint(self)
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 # print s in chunks 4772 l = '' # start entire to the left 4773 while a: 4774 while a and len(l) + len(a[0]) < linebreak: 4775 l += ' ' + a.pop(0) 4776 print(l) 4777 l = ' ' # tab for subsequent lines
4778 print_ = pprint # Python style to prevent clash with keywords 4779 printme = pprint
4780
4781 # ____________________________________________________________ 4782 # ____________________________________________________________ 4783 -class _CMAStopDict(dict):
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 """
4807 - def __init__(self, d={}):
4808 update = isinstance(d, CMAEvolutionStrategy) 4809 super(_CMAStopDict, self).__init__({} if update else d) 4810 self._stoplist = [] # to keep multiple entries 4811 self.lastiter = 0 # probably not necessary 4812 if isinstance(d, _CMAStopDict): # inherit 4813 self._stoplist = d._stoplist # multiple entries 4814 self.lastiter = d.lastiter # probably not necessary 4815 if update: 4816 self._update(d)
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
4829 - def _update(self, es):
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: # in this case termination tests fail 4838 self.__init__() 4839 return self 4840 4841 self.lastiter = es.countiter 4842 self.es = es 4843 4844 self.clear() # compute conditions from scratch 4845 4846 N = es.N 4847 opts = es.opts 4848 self.opts = opts # a hack to get _addstop going 4849 4850 # fitness: generic criterion, user defined w/o default 4851 self._addstop('ftarget', 4852 es.best.f < opts['ftarget']) 4853 # maxiter, maxfevals: generic criteria 4854 self._addstop('maxfevals', 4855 es.countevals - 1 >= opts['maxfevals']) 4856 self._addstop('maxiter', 4857 ## meta_parameters.maxiter_multiplier == 1.0 4858 es.countiter >= 1.0 * opts['maxiter']) 4859 # tolx, tolfacupx: generic criteria 4860 # tolfun, tolfunhist (CEC:tolfun includes hist) 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 # worst seen false positive: table N=80,lam=80, getting worse for fevals=35e3 \approx 50 * N**1.5 4875 # but the median is not so much getting worse 4876 # / 5 reflects the sparsity of histbest/median 4877 # / 2 reflects the left and right part to be compared 4878 ## meta_parameters.tolstagnation_multiplier == 1.0 4879 l = int(max(( 1.0 * opts['tolstagnation'] / 5. / 2, len(es.fit.histbest) / 10))) 4880 # TODO: why max(..., len(histbest)/10) ??? 4881 # TODO: the problem in the beginning is only with best ==> ??? 4882 # equality should handle flat fitness 4883 self._addstop('tolstagnation', # leads sometimes early stop on ftablet, fcigtab, N>=50? 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 # iiinteger: stagnation termination can prevent to find the optimum 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 # non-user defined, method specific 4895 # noeffectaxis (CEC: 0.1sigma), noeffectcoord (CEC:0.2sigma), conditioncov 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 # any([es.mean[i] == es.mean[i] + 0.2 * es.sigma * 4900 # (es.sigma_vec if isscalar(es.sigma_vec) else es.sigma_vec[i]) * 4901 # sqrt(es.dC[i]) 4902 # for i in xrange(N)]) 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) # TODO 4910 4911 self._addstop('callback', es.callbackstop) # termination_callback 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) # can have the same key twice 4936 self[key] = val if val is not None \ 4937 else self.opts.get(key, None)
4938
4939 - def clear(self):
4940 for k in list(self): 4941 self.pop(k) 4942 self.stoplist = []
4943
4944 # ____________________________________________________________ 4945 # ____________________________________________________________ 4946 -class _CMAParameters(object):
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'] # that's a hack 4997 self.popsize = None # declaring the attribute, not necessary though 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 # cc-correction for mueff, was zero before 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'] # the default popsize is computed in CMAOptions() 5020 ## meta_parameters.lambda_exponent == 0.0 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) # 0.158650... * popsize is optimal 5029 # lam = arange(2,22) 5030 # mirr = 0.16 + 0.29/lam 5031 # print(lam); print([int(0.5 + l) for l in mirr*lam]) 5032 # [ 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21] 5033 # [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4] 5034 ## meta_parameters.parent_fraction == 0.5 5035 sp.mu_f = 0.5 * sp.popsize # float value of mu 5036 if opts['CMA_mu'] is not None: 5037 sp.mu_f = opts['CMA_mu'] 5038 sp.mu = int(sp.mu_f + 0.499999) # round down for x.5 5039 sp.mu = max((sp.mu, 1)) 5040 # in principle we have mu_opt = popsize/2 + lam_mirr/2, 5041 # which means in particular weights should only be negative for q > 0.5+mirr_frac/2 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 # TODO: this will disappear, as it is done in class CMAAdaptSigmaCSA 5053 ## meta_parameters.cs_exponent == 1.0 5054 b = 1.0 5055 ## meta_parameters.cs_multiplier == 1.0 5056 sp.cs = 1.0 * (sp.mueff + 2)**b / (N + (sp.mueff + 3)**b) # TODO: this doesn't change dependency of dimension 5057 # sp.cs = (sp.mueff + 2) / (N + 1.5*sp.mueff + 1) 5058 ## meta_parameters.cc_exponent == 1.0 5059 b = 1.0 5060 ## meta_parameters.cc_multiplier == 1.0 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) # \not\gg\cc 5065 if hasattr(opts['vv'], '__getitem__') and opts['vv'][0] == 'sweep_ccov1': 5066 ## meta_parameters.cc_multiplier == 1.0 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 # sp.rankmualpha = _evalOption(opts['CMA_rankmualpha'], 0.3) 5070 ## meta_parameters.c1_multiplier == 1.0 5071 sp.c1 = ( 1.0 * ccovfac * min(1, sp.popsize / 6) * 5072 ## meta_parameters.c1_exponent == 2.0 5073 2 / ((N + 1.3)** 2.0 + sp.mueff)) 5074 # 1/0 5075 sp.c1_sep = ccovfac * conedf(N, sp.mueff, N) 5076 if opts['CMA_rankmu'] != 0: # also empty 5077 ## meta_parameters.cmu_multiplier == 2.0 5078 alphacov, mu = 2.0 , sp.mueff 5079 sp.cmu = min(1 - sp.c1, ccovfac * alphacov * 5080 ## meta_parameters.cmu_exponent == 2.0 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 # in principle we have mu_opt = popsize/2 + lam_mirr/2, 5093 # which means in particular weights should only be negative for q > 0.5+mirr_frac/2 5094 if 1 < 3: # seems most natural: continuation of log(lambda/2) - log(k) qqqqqqqqqqqqqqqqqqqqqqqqqq 5095 sp.neg.mu_f = popsize // 2 # not sure anymore what this is good for 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 ## meta_parameters.cact_exponent == 1.5 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 # reasoning on learning rate cmuexp: with sum |w| == 1 and 5105 # length-normalized vectors in the update, the residual 5106 # variance in any direction exceeds exp(-N*cmuexp) 5107 assert sp.neg.mu >= sp.lam_mirr # not really necessary 5108 # sp.neg.minresidualvariance = 0.66 # not it use, keep at least 0.66 in all directions, small popsize is most critical 5109 else: 5110 sp.neg.cmuexp = 0 5111 5112 sp.CMA_on = sp.c1 + sp.cmu > 0 5113 # print(sp.c1_sep / sp.cc_sep) 5114 5115 if not opts['CMA_on'] and opts['CMA_on'] not in (None, [], (), ''): 5116 sp.CMA_on = False 5117 # sp.c1 = sp.cmu = sp.c1_sep = sp.cmu_sep = 0 5118 mueff_exponent = 0.5 5119 if 1 < 3: 5120 mueff_exponent = opts['CSA_damp_mueff_exponent'] 5121 # TODO: this will disappear, as it is done in class CMAAdaptSigmaCSA 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 # sp.kappa = 1 # 4-D, lam=16, rank1, kappa < 4 does not influence convergence rate 5128 # in larger dim it does, 15-D with defaults, kappa=8 factor 2 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 # return self # the constructor returns itself 5136
5137 - def disp(self):
5138 pprint(self.__dict__)
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, # TODO: add max kappa value as parameter 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 """ # style guides say there should be the above empty line 5383 if 1 < 3: # try: # pass on KeyboardInterrupt 5384 if not objective_function: # cma.fmin(0, 0, 0) 5385 return CMAOptions() # these opts are by definition valid 5386 5387 fmin_options = locals().copy() # archive original options 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) # might modify options 5397 # checked that no options.ftarget = 5398 opts = CMAOptions(options.copy()).complement() 5399 5400 # BIPOP-related variables: 5401 runs_with_small = 0 5402 small_i = [] 5403 large_i = [] 5404 popsize0 = None # to be evaluated after the first iteration 5405 maxiter0 = None # to be evaluated after the first iteration 5406 base_evals = 0 5407 5408 irun = 0 5409 best = BestSolution() 5410 while True: # restart loop 5411 sigma_factor = 1 5412 5413 # Adjust the population according to BIPOP after a restart. 5414 if not bipop: 5415 # BIPOP not in use, simply double the previous population 5416 # on restart. 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 # Initial run is with "normal" population size; it is 5423 # the large population before first doubling, but its 5424 # budget accounting is the same as in case of small 5425 # population. 5426 poptype = 'small' 5427 5428 elif sum(small_i) < sum(large_i): 5429 # An interweaved run with small population size 5430 poptype = 'small' 5431 runs_with_small += 1 # _Before_ it's used in popsize_lastlarge 5432 5433 sigma_factor = 0.01 ** np.random.uniform() # Local search 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 # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) 5438 5439 else: 5440 # A run with large population size; the population 5441 # doubling is implicit with incpopsize. 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 # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) 5448 5449 # recover from a CMA object 5450 if irun == 0 and isinstance(x0, CMAEvolutionStrategy): 5451 es = x0 5452 x0 = es.inputargs['x0'] # for the next restarts 5453 if isscalar(sigma0) and isfinite(sigma0) and sigma0 > 0: 5454 es.sigma = sigma0 5455 # debatable whether this makes sense: 5456 sigma0 = es.inputargs['sigma0'] # for the next restarts 5457 if options is not None: 5458 es.opts.set(options) 5459 # ignore further input args and keep original options 5460 else: # default case 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 # processed options, unambiguous 5477 # a hack: 5478 fmin_opts = CMAOptions(fmin_options.copy(), unchecked=True) 5479 for k in fmin_opts: 5480 # locals() cannot be modified directly, exec won't work 5481 # in 3.x, therefore 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 # es.logger is "the same" logger, because the "identity" 5488 # is only determined by the `filenameprefix` 5489 logger = CMADataLogger(opts['verb_filenameprefix'], 5490 opts['verb_log']) 5491 logger.register(es, append).add() # no fitness values here 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 # the problem: this assumes that good solutions cannot take longer than bad ones: 5505 # with EvalInParallel(objective_function, 2, is_feasible=opts['is_feasible']) as eval_in_parallel: 5506 if 1 < 3: 5507 while not es.stop(): # iteration loop 5508 # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) 5509 X, fit = es.ask_and_eval(objective_function, args, gradf=gradf, 5510 evaluations=noisehandler.evaluations, 5511 aggregation=np.median) # treats NaN with resampling 5512 # TODO: check args and in case use args=(noisehandler.evaluations, ) 5513 5514 es.tell(X, fit) # prepare for next iteration 5515 if noise_handling: # it would be better to also use these f-evaluations in tell 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 # TODO: this is a hack, not important though 5520 # es.more_to_write.append(noisehandler.evaluations_just_done) 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(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], 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 # end while not es.stop 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) # in restarted case 5542 # es.best.update(best) 5543 5544 this_evals = es.countevals - base_evals 5545 base_evals = es.countevals 5546 5547 # BIPOP stats update 5548 5549 if irun == 0: 5550 popsize0 = opts['popsize'] 5551 maxiter0 = opts['maxiter'] 5552 # XXX: This might be a bug? Reproduced from Matlab 5553 # small_i.append(this_evals) 5554 5555 if bipop: 5556 if poptype == 'small': 5557 small_i.append(this_evals) 5558 else: # poptype == 'large' 5559 large_i.append(this_evals) 5560 5561 # final message 5562 if opts['verb_disp']: 5563 es.result_pretty(irun, time.asctime(time.localtime()), 5564 best.f) 5565 5566 irun += 1 5567 # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ 5568 # if irun > restarts or 'ftarget' in es.stop() \ 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 # TODO: use rather options? 5574 opts['seed'] += 1 5575 5576 # while irun 5577 5578 # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell 5579 if 1 < 3: 5580 if irun: 5581 es.best.update(best) 5582 # TODO: there should be a better way to communicate the overall best 5583 return es.result() + (es.stop(), es, logger) 5584 5585 else: # previously: to be removed 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) # last state of options 5592 , ('cma', es) 5593 , ('inputargs', es.inputargs) 5594 )) 5595 ) 5596 # TODO refine output, can #args be flexible? 5597 # is this well usable as it is now? 5598 else: # except KeyboardInterrupt: # Exception, e: 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 # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit
5602
5603 # _____________________________________________________________________ 5604 # _____________________________________________________________________ 5605 # 5606 -class BaseDataLogger(object):
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
5617 - def register(self, optim):
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
5621 - def disp(self):
5622 """display some data trace (not implemented)""" 5623 print('method BaseDataLogger.disp() not implemented, to be done in subclass ' + str(type(self)))
5624 - def plot(self):
5625 """plot data (not implemented)""" 5626 print('method BaseDataLogger.plot() is not implemented, to be done in subclass ' + str(type(self)))
5627 - def data(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
5631 # _____________________________________________________________________ 5632 # _____________________________________________________________________ 5633 # 5634 -class CMADataLogger(BaseDataLogger):
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 # names = ('axlen','fit','stddev','xmean','xrecentbest') 5682 # key_names_with_annotation = ('std', 'xmean', 'xrecent') 5683
5684 - def __init__(self, name_prefix=default_prefix, modulo=1, append=False):
5685 """initialize logging of data from a `CMAEvolutionStrategy` 5686 instance, default ``modulo=1`` means logging with each call 5687 5688 """ 5689 # super(CMAData, self).__init__({'iter':[], 'stds':[], 'D':[], 5690 # 'sig':[], 'fit':[], 'xm':[]}) 5691 # class properties: 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 # reduce costs
5714 - def data(self):
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
5742 - def initialize(self, modulo=None):
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 # must have been registered 5748 except AttributeError: 5749 pass # TODO: revise usage of es... that this can pass 5750 raise _Error('call register() before initialize()') 5751 5752 self.counter = 0 # number of calls of add 5753 self.last_iteration = 0 # some lines are only written if iteration>last_iteration 5754 5755 # write headers for output 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 # strftime("%Y/%m/%d %H:%M:%S", localtime()) + # just asctime() would do 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 # end def __init__ 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 # copy last row to later fill in annotation position for display 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 # must have been registered 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() # write file headers 5891 self.counter = 1 5892 5893 # --- INTERFACE, can be changed if necessary --- 5894 if not isinstance(es, CMAEvolutionStrategy): # not necessary 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 # TODO: should be optionally phenotype? 5903 fmean_noise_free = es.fmean_noise_free 5904 fmean = es.fmean 5905 # TODO: find a different way to communicate current x and f? 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: # first call without f-values is OK 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 # --- end interface --- 5925 5926 try: 5927 # fit 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 # + str(es.sp.popsize) + ' ' 5940 # + str(10**es.noiseS) + ' ' 5941 # + str(es.sp.cmean) + ' ' 5942 + ' '.join(str(i) for i in more_to_write) + ' ' 5943 + ' '.join(str(i) for i in more_data) + ' ' 5944 + '\n') 5945 # axlen 5946 fn = self.name_prefix + 'axlen.dat' 5947 if 1 < 3: 5948 with open(fn, 'a') as f: # does not rely on reference counting 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 # correlation matrix eigenvalues 5957 if 1 < 3: 5958 fn = self.name_prefix + 'axlencorr.dat' 5959 c = es.correlation_matrix() 5960 if c is not None: 5961 # accept at most 50% internal loss 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] # remove diagonal elements 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 # thereby zero "is negative" 5975 c_medplus = 0 # thereby zero "is positive" 5976 else: 5977 c_medminus = c[np.argmin(1/c)] # c is flat 5978 c_medplus = c[np.argmax(1/c)] # c is flat 5979 5980 with open(fn, 'a') as f: 5981 f.write(str(iteration) + ' ' 5982 + str(evals) + ' ' 5983 + str(c_min) + ' ' 5984 + str(c_medminus) + ' ' # the one closest to 0 5985 + str(c_medplus) + ' ' # the one closest to 0 5986 + str(c_max) + ' ' 5987 + ' '.join(map(str, 5988 self.last_correlation_spectrum)) 5989 + '\n') 5990 5991 # stddev 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 # xmean 6001 fn = self.name_prefix + 'xmean.dat' 6002 with open(fn, 'a') as f: 6003 f.write(str(iteration) + ' ' 6004 + str(evals) + ' ' 6005 # + str(sigma) + ' ' 6006 + '0 ' 6007 + str(fmean_noise_free) + ' ' 6008 + str(fmean) + ' ' # TODO: this does not make sense 6009 # TODO should be optional the phenotyp? 6010 + ' '.join(map(str, xmean)) 6011 + '\n') 6012 # xrecent 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
6029 - def closefig(self):
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
6052 - def select_data(self, iteration_indices):
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]) # last entry is artificial 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, # was: plot_mean=True 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 # pyplot: prodedural interface for matplotlib 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() # better load only conditionally? 6124 dat = self 6125 dat.x = dat.xmean # this is the genotyp 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 # index out some data 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 # not in use anymore, see formatter above 6141 # xticklocs = np.arange(5) * np.round(minxend/4., -int(np.log10(minxend/4.))) 6142 6143 # dfit(dfit<1e-98) = NaN; 6144 6145 # TODO: if abscissa==0 plot in chunks, ie loop over subsets where 6146 # dat.f[:,0]==countiter is monotonous 6147 6148 figure(fig) 6149 self._enter_plotting(fontsize) 6150 self.fighandle = gcf() # fighandle.number 6151 6152 subplot(2, 2, 1) 6153 self.plot_divers(iabscissa, foffset) 6154 pyplot.xlabel('') 6155 6156 # Scaling 6157 subplot(2, 2, 3) 6158 self.plot_axes_scaling(iabscissa) 6159 6160 # spectrum of correlation matrix 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 # pyplot.xticks(xticklocs) 6170 6171 # standard deviations 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 # pyplot: prodedural interface for matplotlib 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 # index out some data 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 # not in use anymore, see formatter above 6233 # xticklocs = np.arange(5) * np.round(minxend/4., -int(np.log10(minxend/4.))) 6234 6235 # dfit(dfit<1e-98) = NaN; 6236 6237 # TODO: if abscissa==0 plot in chunks, ie loop over subsets where dat.f[:,0]==countiter is monotonous 6238 6239 figure(fig) 6240 self._enter_plotting(fontsize) 6241 self.fighandle = gcf() # fighandle.number 6242 6243 if 1 < 3: 6244 subplot(2, 3, 1) 6245 self.plot_divers(iabscissa, foffset) 6246 pyplot.xlabel('') 6247 6248 # standard deviations 6249 subplot(2, 3, 4) 6250 self.plot_stds(iabscissa) 6251 6252 # Scaling 6253 subplot(2, 3, 2) 6254 self.plot_axes_scaling(iabscissa) 6255 pyplot.xlabel('') 6256 6257 # spectrum of correlation matrix 6258 subplot(2, 3, 5) 6259 self.plot_correlations(iabscissa) 6260 6261 # x-vectors 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
6271 - def plot_axes_scaling(self, iabscissa=1):
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 # ax[1] = max(minxend, ax[1]) 6281 pyplot.axis(ax) 6282 pyplot.title('Principle Axes Lengths') 6283 # pyplot.xticks(xticklocs) 6284 self._xlabel(iabscissa) 6285 self._finalize_plotting() 6286 return self
6287 - def plot_stds(self, iabscissa=1):
6288 if not hasattr(self, 'std'): 6289 self.load() 6290 dat = self 6291 self._enter_plotting() 6292 # remove sigma from stds (graphs become much better readible) 6293 dat.std[:, 5:] = np.transpose(dat.std[:, 5:].T / dat.std[:, 2].T) 6294 # ax = array(pyplot.axis()) 6295 # ax[1] = max(minxend, ax[1]) 6296 # axis(ax) 6297 if 1 < 2 and dat.std.shape[1] < 100: 6298 # use fake last entry in x and std for line extension-annotation 6299 minxend = int(1.06 * dat.std[-2, iabscissa]) 6300 # minxend = int(1.06 * dat.x[-2, iabscissa]) 6301 dat.std[-1, iabscissa] = minxend # TODO: should be ax[1] 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 # TODO: should be ax[1] 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 # yyl = np.sort(dat.std[-1,5:]) 6314 idx = np.argsort(dat.std[-1, 5:]) 6315 idx2 = np.argsort(idx) 6316 # plot(np.dot(dat.std[-2, iabscissa],[1,1]), array([ax[2]+1e-6, ax[3]-1e-6]), 'k-') # vertical separator 6317 # vertical separator 6318 pyplot.plot(np.dot(dat.std[-2, iabscissa], [1, 1]), 6319 array([ax[2] + 1e-6, ax[3] - 1e-6]), 6320 # array([np.min(dat.std[:, 5:]), np.max(dat.std[:, 5:])]), 6321 'k-') 6322 pyplot.hold(True) 6323 # plot([dat.std[-1, iabscissa], ax[1]], [dat.std[-1,5:], yy[idx2]], 'k-') # line from last data point 6324 for i in rglen((idx)): 6325 # text(ax[1], yy[i], ' '+str(idx[i])) 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 # pyplot.xticks(xticklocs) 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
6350 - def plot_correlations(self, iabscissa=1):
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:] # principle axes 6358 ys = self.corrspec[:, :6] # "special" values 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 # ax[1] = max(minxend, ax[1]) 6378 axis(ax) 6379 title('Spectrum (roots) of correlation matrix') 6380 # pyplot.xticks(xticklocs) 6381 self._xlabel(iabscissa) 6382 self._finalize_plotting() 6383 return self
6384 - def plot_divers(self, iabscissa=1, foffset=1e-19):
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 # why not using idx? 6403 dfit[dfit < 1e-98] = np.NaN 6404 6405 self._enter_plotting() 6406 if dat.f.shape[1] > 7: 6407 # semilogy(dat.f[:, iabscissa], abs(dat.f[:,[6, 7, 10, 12]])+foffset,'-k') 6408 semilogy(dat.f[:, iabscissa], abs(dat.f[:, [6, 7]]) + foffset, '-k') 6409 hold(True) 6410 6411 # (larger indices): additional fitness data, for example constraints values 6412 if dat.f.shape[1] > 8: 6413 # dd = abs(dat.f[:,7:]) + 10*foffset 6414 # dd = np.where(dat.f[:,7:]==0, np.NaN, dd) # cannot be 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] # positive values 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 # negative f-values, dots 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') # , markersize=5 6434 6435 # lines between negative f-values 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') # , markersize=5 6448 # lines between positive and negative f-values 6449 # TODO: the following might plot values very close to zero 6450 if istart > 0: # line to the left of istart 6451 semilogy(dat.f[istart-1:istart+1, iabscissa], 6452 abs(dat.f[istart-1:istart+1, 5]) + 6453 foffset, '--m') 6454 if istop: # line to the left of istop 6455 semilogy(dat.f[istop-1:istop+1, iabscissa], 6456 abs(dat.f[istop-1:istop+1, 5]) + 6457 foffset, '--m') 6458 # mark the respective first positive values 6459 semilogy(dat.f[istop, iabscissa], abs(dat.f[istop, 5]) + 6460 foffset, '.b', markersize=7) 6461 # mark the respective first negative values 6462 semilogy(dat.f[istart, iabscissa], abs(dat.f[istart, 5]) + 6463 foffset, '.r', markersize=7) 6464 6465 # standard deviations std 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 # delta-fitness in cyan 6476 idx = isfinite(dfit) 6477 if 1 < 3: 6478 idx_nan = np.where(idx == False)[0] # gaps 6479 if not len(idx_nan): # should never happen 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 # overall minimum 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 # semilogy(dat.f[-1, iabscissa]*np.ones(2), dat.f[-1,4]*np.ones(2), 'rd') 6501 6502 # AR and sigma 6503 semilogy(dat.f[:, iabscissa], dat.f[:, 3], '-r') # AR 6504 semilogy(dat.f[:, iabscissa], dat.f[:, 2], '-g') # sigma 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 # ax[1] = max(minxend, ax[1]) 6511 axis(ax) 6512 text(ax[0] + 0.01, ax[2], # 10**(log10(ax[2])+0.05*(log10(ax[3])-log10(ax[2]))), 6513 '.min($f$)=' + repr(minfit)) 6514 #'.f_recent=' + repr(dat.f[-1, 5])) 6515 6516 # title('abs(f) (blue), f-min(f) (cyan), Sigma (green), Axis Ratio (red)') 6517 # title(r'blue:$\mathrm{abs}(f)$, cyan:$f - \min(f)$, green:$\sigma$, red:axis ratio', 6518 # fontsize=fontsize - 0.0) 6519 title(r'$|f_{\mathrm{best},\mathrm{med},\mathrm{worst}}|$, $f - \min(f)$, $\sigma$, axis ratio') 6520 6521 # if __name__ != 'cma': # should be handled by the caller 6522 self._xlabel(iabscissa) 6523 self._finalize_plotting() 6524 return self
6525 - def _enter_plotting(self, fontsize=9):
6526 """assumes that a figure is open """ 6527 # interactive_status = matplotlib.is_interactive() 6528 self.original_fontsize = pyplot.rcParams['font.size'] 6529 pyplot.rcParams['font.size'] = fontsize 6530 pyplot.hold(False) # opens a figure window, if non exists 6531 pyplot.ioff()
6532 - def _finalize_plotting(self):
6533 pyplot.ion() 6534 pyplot.draw() # update "screen" 6535 pyplot.show() # show figure 6536 # matplotlib.interactive(interactive_status) 6537 pyplot.rcParams['font.size'] = self.original_fontsize
6538 - def _xlabel(self, iabscissa=1):
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 # for convenience and historical reasons 6554 # modify fake last entry in x for line extension-annotation 6555 if dat.x.shape[1] < 100: 6556 minxend = int(1.06 * dat.x[-2, iabscissa]) 6557 # write y-values for individual annotation into dat.x 6558 dat.x[-1, iabscissa] = minxend # TODO: should be ax[1] 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: # y-axis is in log 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: # TODO: differentate neg and pos? 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 # ax[1] = max(minxend, ax[1]) 6582 axis(ax) 6583 ax[1] -= 1e-6 # to prevent last x-tick annotation, probably superfluous 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 # yyl = np.sort(dat.x[-1,5:]) 6587 if x_opt is not None: 6588 # semilogy([dat.x[-1, iabscissa], ax[1]], [abs(dat.x[-1, 5:]), yy[idx2]], 'k-') # line from last data point 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 # plot([dat.x[-1, iabscissa], ax[1]], [dat.x[-1,5:], yy[idx2]], 'k-') # line from last data point 6593 plot(np.dot(dat.x[-2, iabscissa], [1, 1]), 6594 array([ax[2] + 1e-6, ax[3] - 1e-6]), 'k-') 6595 # plot(array([dat.x[-1, iabscissa], ax[1]]), 6596 # reshape(array([dat.x[-1,5:], yy[idx2]]).flatten(), (2,4)), '-k') 6597 for i in rglen(idx): 6598 # TODOqqq: annotate phenotypic value!? 6599 # text(ax[1], yy[i], 'x(' + str(idx[i]) + ')=' + str(dat.x[-2,5+idx[i]])) 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 # find smallest i where iteration count differs (in case the same row appears twice) 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): # r_[0:5,1e2:1e9:1e2,-10:0]):
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 # map index to iteration number, is difficult if not all iteration numbers exist 6707 # idx = idx[np.where(map(lambda x: x in dat.f[:,0], idx))[0]] # TODO: takes pretty long 6708 # otherwise: 6709 if idx is None: 6710 idx = 100 6711 if isscalar(idx): 6712 # idx = np.arange(0, ndata, idx) 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()
6734 - def disp_header(self):
6735 heading = 'Iterat Nfevals function value axis ratio maxstd minstd' 6736 print(heading)
6737 6738 # end class CMADataLogger 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
6829 # ____________________________________________________________ 6830 -def _fileToMatrix(file_name):
6831 """rudimentary method to read in data from a file""" 6832 # TODO: np.loadtxt() might be an alternative 6833 # try: 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] == []: # remove further leading empty lines 6841 del res[0] 6842 return res 6843 # except: 6844 print('could not read file ' + file_name)
6845
6846 # ____________________________________________________________ 6847 # ____________________________________________________________ 6848 -class NoiseHandler(object):
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 # TODO: for const additive noise a better version might be with alphasigma also used for sigma-increment, 6927 # while all other variance changing sources are removed (because they are intrinsically biased). Then 6928 # using kappa to get convergence (with unit sphere samples): noiseS=0 leads to a certain kappa increasing rate?
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 # 2 + popsize/20, see method indices(), originally 2 + popsize/10 6965 self.epsilon = epsilon 6966 self.parallel = parallel 6967 ## meta_parameters.noise_theta == 0.5 6968 self.theta = 0.5 # 0.5 # originally 0.2 6969 self.cum = 0.3 # originally 1, 0.3 allows one disagreement of current point with resulting noiseS 6970 ## meta_parameters.noise_alphasigma == 2.0 6971 self.alphasigma = 1 + 2.0 / (N + 10) # 2, unit sphere sampling: 1 + 1 / (N + 10) 6972 ## meta_parameters.noise_alphaevals == 2.0 6973 self.alphaevals = 1 + 2.0 / (N + 10) # 2, originally 1.5 6974 ## meta_parameters.noise_alphaevalsdown_exponent == -0.25 6975 self.alphaevalsdown = self.alphaevals** -0.25 # originally 1/1.5 6976 # zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 6977 self.evaluations = 1 # to aggregate for a single f-evaluation 6978 self.minevals = 1 6979 self.maxevals = int(np.max(maxevals)) 6980 if hasattr(maxevals, '__contains__'): # i.e. can deal with ``in`` 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 ## meta_parameters.noise_aggregate == None 6987 self.f_aggregate = aggregate if not None else {1: np.median, 2: np.mean}[ None ] 6988 self.evaluations_just_done = 0 # actually conducted evals, only for documentation 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
7025 - def get_evaluations(self):
7026 """return ``self.evaluations``, the number of evalutions to get a single fitness measurement""" 7027 return self.evaluations
7028
7029 - def treat(self):
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 # / self.alphasigma
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
7069 - def update_measure(self):
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 # compute rank change limits using both ranks[0] and ranks[1] 7083 r = np.arange(1, 2 * lam) # 2 * lam - 2 elements 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 # compute measurement 7090 # max: 1 rankchange in 2*lambda is always fine 7091 s = np.abs(rankDelta[self.idx]) - Mh.amax(limits, 1) # lives roughly in 0..2*lambda 7092 self.noiseS += self.cum * (np.mean(s) - self.noiseS) 7093 return self.noiseS, s
7094
7095 - def indices(self, fit):
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 ## meta_parameters.noise_reeval_multiplier == 1.0 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 ## meta_parameters.noise_choose_reeval == 1 7108 choice = 1 7109 if choice == 1: 7110 # take n_first first and reev - n_first best of the remaining 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 # take lam_reev equally spaced, starting with best 7118 linsp = np.linspace(0, len(fit) - len(fit) / lam_reev, lam_reev) 7119 return idx_sorted[[int(i) for i in linsp]] 7120 # take the ``lam_reeval`` best from the first ``2 * lam_reeval + 2`` values. 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
7127 # ____________________________________________________________ 7128 # ____________________________________________________________ 7129 -class Sections(object):
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 # or semilogy 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 # TODO: res['x'] does not look perfect 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)): # i-th coordinate 7235 if i not in res: 7236 res[i] = {} 7237 # xx = np.array(self.x) 7238 # TODO: store res[i]['dx'] = self.basis[i] here? 7239 for dx in locations: 7240 xx = self.x + dx * self.basis[i] 7241 xkey = dx # xx[i] if (self.basis == np.eye(len(self.basis))).all() else 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()): # we plot not all values here 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 # raw_input('press return') 7279 return self
7280
7281 - def flattened(self):
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 # instance method produces error 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 # disregard the class 7314 return self
7315
7316 #____________________________________________________________ 7317 #____________________________________________________________ 7318 -class _Error(Exception):
7319 """generic exception of cma module""" 7320 pass
7321
7322 # ____________________________________________________________ 7323 # ____________________________________________________________ 7324 # 7325 -class ElapsedTime(object):
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 """
7336 - def __init__(self):
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__
7344 - def __call__(self):
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: # overflow, reset self.tic 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 # reset 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 #
7365 - class MathHelperFunctions(object):
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
7371 - def aclamp(x, upper):
7372 return -Misc.MathHelperFunctions.apos(-x, -upper)
7373 @staticmethod
7374 - def equals_approximately(a, b, eps=1e-12):
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
7379 - def vequals_approximately(a, b, eps=1e-12):
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 # TODO: check that this works reliably for low rank matrices 7390 # first: symmetrize A 7391 D, B = eig(A) 7392 return np.dot(B, (np.exp(D) * B).T)
7393 @staticmethod
7394 - def amax(vec, vec_or_scalar):
7395 return array(Misc.MathHelperFunctions.max(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):
7413 return array(Misc.MathHelperFunctions.min(vec_or_scalar, vec_or_scalar2))
7414 @staticmethod
7415 - def min(a, b):
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 # now only b can be still a scalar 7422 if iss(b): 7423 return [min(x, b) for x in a] 7424 else: # two non-scalars must have the same length 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: # maybe extrapolate? 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
7462 - def sround(nb): # TODO: to be vectorized
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
7473 - def standard_finite_cauchy(size=1):
7474 try: 7475 l = len(size) 7476 except TypeError: 7477 l = 0 7478 7479 if l == 0: 7480 return array([Mh.cauchy_with_variance_one() for _i in xrange(size)]) 7481 elif l == 1: 7482 return array([Mh.cauchy_with_variance_one() for _i in xrange(size[0])]) 7483 elif l == 2: 7484 return array([[Mh.cauchy_with_variance_one() for _i in xrange(size[1])] 7485 for _j in xrange(size[0])]) 7486 else: 7487 raise _Error('len(size) cannot be large than two')
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 # testing: MC integrate must be one: mean(p(x_i)) * volume(where x_i are uniformely sampled) 7494 # for i in xrange(3): print mean([cma.likelihood(20*r-10, dim * [0], None, 3) for r in rand(10000,dim)]) * 20**dim 7495 if m is None: 7496 dx = x 7497 else: 7498 dx = x - m # array(x) - array(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
7508 - def loglikelihood(self, x, previous=False):
7509 """return log-likelihood of `x` regarding the current sample distribution""" 7510 # testing of original fct: MC integrate must be one: mean(p(x_i)) * volume(where x_i are uniformely sampled) 7511 # for i in xrange(3): print mean([cma.likelihood(20*r-10, dim * [0], None, 3) for r in rand(10000,dim)]) * 20**dim 7512 # TODO: test this!! 7513 # c=cma.fmin... 7514 # c[3]['cma'].loglikelihood(...) 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 # array(x) - array(m) 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: # testing 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 # C and B are arrays rather than matrices, because they are 7546 # addressed via B[i][j], matrices can only be addressed via B[i,j] 7547 7548 # tred2(N, B, diagD, offdiag); 7549 # tql2(N, diagD, offdiag, B); 7550 7551 7552 # Symmetric Householder reduction to tridiagonal form, translated from JAMA package. 7553 @staticmethod
7554 - def eig(C):
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 # class eig(object): 7567 # def __call__(self, C): 7568 7569 # Householder transformation of a symmetric matrix V into tridiagonal form. 7570 # -> n : dimension 7571 # -> V : symmetric nxn-matrix 7572 # <- V : orthogonal transformation matrix: 7573 # tridiag matrix == V * V_in * V^t 7574 # <- d : diagonal 7575 # <- e[0..n-1] : off diagonal (elements 1..n-1) 7576 7577 # Symmetric tridiagonal QL algorithm, iterative 7578 # Computes the eigensystem from a tridiagonal matrix in roughtly 3N^3 operations 7579 # -> n : Dimension. 7580 # -> d : Diagonale of tridiagonal matrix. 7581 # -> e[1..n-1] : off-diagonal, output from Householder 7582 # -> V : matrix output von Householder 7583 # <- d : eigenvalues 7584 # <- e : garbage? 7585 # <- V : basis of eigenvectors, according to d 7586 7587 7588 # tred2(N, B, diagD, offdiag); B=C on input 7589 # tql2(N, diagD, offdiag, B); 7590 7591 # private void tred2 (int n, double V[][], double d[], double e[]) { 7592 def tred2 (n, V, d, e): 7593 # This is derived from the Algol procedures tred2 by 7594 # Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 7595 # Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 7596 # Fortran subroutine in EISPACK. 7597 7598 num_opt = False # factor 1.5 in 30-D 7599 7600 for j in xrange(n): 7601 d[j] = V[n - 1][j] # d is output argument 7602 7603 # Householder reduction to tridiagonal form. 7604 7605 for i in xrange(n - 1, 0, -1): 7606 # Scale to avoid under/overflow. 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 # Generate Householder vector. 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 # Apply similarity transformation to remaining columns. 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 # end for i-- 7692 7693 # Accumulate transformations. 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 # Symmetric tridiagonal QL algorithm, taken from JAMA package. 7737 # private void tql2 (int n, double d[], double e[], double V[][]) { 7738 # needs roughly 3N^3 operations 7739 def tql2 (n, d, e, V): 7740 7741 # This is derived from the Algol procedures tql2, by 7742 # Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 7743 # Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 7744 # Fortran subroutine in EISPACK. 7745 7746 num_opt = False # using vectors from numpy makes it faster 7747 7748 if not num_opt: 7749 for i in xrange(1, n): # (int i = 1; i < n; i++): 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): # (int l = 0; l < n; l++) { 7759 7760 # Find small subdiagonal element 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 # If m == l, d[l] is an eigenvalue, 7770 # otherwise, iterate. 7771 7772 if m > l: 7773 iiter = 0 7774 while 1: # do { 7775 iiter += 1 # (Could check iteration count here.) 7776 7777 # Compute implicit shift 7778 7779 g = d[l] 7780 p = (d[l + 1] - g) / (2.0 * e[l]) 7781 r = (p**2 + 1)**0.5 # hypot(p,1.0) 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 # Implicit QL transformation. 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 # hh = V.T[0].copy() # only with num_opt 7808 for i in xrange(m - 1, l - 1, -1): # (int i = m-1; i >= l; i--) { 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 # hypot(p,e[i]) 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 # Accumulate transformation. 7822 7823 if not num_opt: # overall factor 3 in 30-D 7824 for k in xrange(n): # (int k = 0; k < n; k++) { 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: # about 20% faster in 10-D 7829 hh = V.T[i + 1].copy() 7830 # hh[:] = V.T[i+1][:] 7831 V.T[i + 1] = s * V.T[i] + c * hh 7832 V.T[i] = c * V.T[i] - s * hh 7833 # V.T[i] *= c 7834 # V.T[i] -= s * hh 7835 7836 p = -s * s2 * c3 * el1 * e[l] / dl1 7837 e[l] = s * p 7838 d[l] = c * p 7839 7840 # Check for convergence. 7841 if abs(e[l]) <= eps * tst1: 7842 break 7843 # } while (Math.abs(e[l]) > eps*tst1); 7844 7845 d[l] = d[l] + f 7846 e[l] = 0.0 7847 7848 7849 # Sort eigenvalues and corresponding vectors. 7850 # tql2 7851 7852 N = len(C[0]) 7853 if 1 < 3: 7854 V = [[x[i] for i in xrange(N)] for x in C] # copy each "row" 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 # if _experimental: 7864 # from new_stuff import * 7865 7866 -def pprint(to_be_printed):
7867 """nicely formated print""" 7868 try: 7869 import pprint as pp 7870 # generate an instance PrettyPrinter 7871 # pp.PrettyPrinter().pprint(to_be_printed) 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
7887 7888 -class ConstRandnShift(object):
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 """
7899 - def __init__(self, stddev=3, seed=1):
7900 """with ``seed=None`` each instance realizes a different shift""" 7901 self.seed = seed 7902 self.stddev = stddev 7903 self._xopt = {}
7904 - def __call__(self, x):
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
7931 -class Rotation(object):
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 = {} # store matrix if necessary, for each dimension
7952 - def __init__(self, seed=None):
7953 """by default a random but fixed rotation, different for each instance""" 7954 self.seed = seed 7955 self.dicMatrices = {} # otherwise there might be shared bases which is probably not what we want
7956 - def __call__(self, x, inverse=False): # function when calling an object
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] # can be an array or matrix, TODO: accept also a list of arrays? 7962 if str(N) not in self.dicMatrices: # create new N-basis for once and all 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) # compute rotation 7974 else: 7975 return np.dot(self.dicMatrices[str(N)], x) # compute rotation
7976 # Use rotate(x) to rotate x 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 """
7989 - class FitnessTransformation(object):
7990 """This class does nothing but serve as an interface template. 7991 Typical use-case:: 7992 7993 f = FitnessTransformation(f, parameters_if_needed)`` 7994 7995 See: class ``TransformSearchSpace`` 7996 7997 """
7998 - def __init__(self, fitness_function, *args, **kwargs):
7999 """`fitness_function` must be callable (e.g. a function 8000 or a callable class instance)""" 8001 # the original fitness to be called 8002 self.inner_fitness = fitness_function
8003 # self.condition_number = ...
8004 - def __call__(self, x, *args):
8005 """identity as default transformation""" 8006 if hasattr(self, 'x_transformation'): 8007 x = self.x_transformation(x) 8008 f = self.inner_fitness(x, *args) 8009 if hasattr(self, 'f_transformation'): 8010 f = self.f_transformation(f) 8011 return f
8012 - class BookKeeping(FitnessTransformation):
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 """
8022 - def __init__(self, callable=None):
8023 self.count_evaluations = 0 8024 self.inner_fitness = callable
8025 - def __call__(self, *args):
8026 # assert len(args[0]) # x-vector 8027 self.count_evaluations += 1 8028 return self.inner_fitness(*args)
8029 - class TransformSearchSpace(FitnessTransformation):
8030 """:: 8031 8032 f = TransformSearchSpace(f, ConstRandnShift()) 8033 8034 constructs the composed function f <- f o shift. 8035 8036 Details: to some extend this is a nice shortcut for:: 8037 8038 f = lambda x, *args: f_in(ConstRandnShift()(x), *args) 8039 8040 however the `lambda` definition depends on the value of 8041 ``f_in`` even after ``f`` has been assigned. 8042 8043 See: `ShiftedFitness`, `RotatedFitness` 8044 8045 """
8046 - def __init__(self, fitness_function, transformation):
8047 """``TransformSearchSpace(f, s)(x) == f(s(x))`` 8048 8049 >>> import cma 8050 >>> f0 = lambda x: sum(x) 8051 >>> shift_fct = cma.ConstRandnShift() 8052 >>> f = cma.FF2.TransformSearchSpace(f0, shift_fct) 8053 >>> x = [1, 2, 3] 8054 >>> assert f(x) == f0(shift_fct(x)) 8055 8056 """ 8057 self.inner_fitness = fitness_function 8058 # akin to FitnessTransformation.__init__(self, fitness_function) 8059 # akin to super(TransformSearchSpace, self).__init__(fitness_function) 8060 self.x_transformation = transformation
8061 # will be used in base class
8062 - class ScaleCoordinates(TransformSearchSpace):
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 # TransformSearchSpace.__init__(self, fitness_function, 8075 # self.transformation) 8076 self.multiplier = multipliers 8077 if self.multiplier is not None and hasattr(self.multiplier, 'len'): 8078 self.multiplier = array(self.multiplier, copy=True)
8079 - def transformation(x, *args):
8080 if self.multiplier is None: 8081 return array(x, copy=False) 8082 return self.multiplier * array(x, copy=False)
8083
8084 - class ShiftedFitness(TransformSearchSpace):
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 """
8090 - def __init__(self, f, shift=None):
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 # alternatively we could have called super
8102 - class RotatedFitness(TransformSearchSpace):
8103 """``f = cma.RotatedFitness(cma.fcts.elli)`` constructs a 8104 rotated ellipsoid function 8105 8106 """
8107 - def __init__(self, f, rotate=rotate):
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 # self.x_transformation = rotate
8117 - class FixVariables(TransformSearchSpace):
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):
8130 """`f` has """ 8131 super(FFWrapper.FixVariables, self).__init__(f, self.insert_variables) 8132 # same as TransformSearchSpace.__init__(f, self.insert_variables) 8133 self.index_value_pairs = dict(index_value_pairs)
8134 - def insert_variables(self, x):
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
8145 - class SomeNaNFitness(FitnessTransformation):
8146 - def __init__(self, fitness_function, probability_of_nan=0.1):
8147 self.p = probability_of_nan 8148 self.inner_fitness = fitness_function
8149 - def __call__(self, x, *args):
8150 if np.random.rand(1) <= self.p: 8151 return np.NaN 8152 else: 8153 return self.inner_fitness(x, *args)
8154 - class NoisyFitness(FitnessTransformation):
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
8162 - def __call__(self, x, *args):
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
8170 - class GlueArguments(FitnessTransformation):
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
8183 - def __call__(self, x, *args):
8184 return self.inner_fitness(array(x, copy=False), 8185 *(args + self.args), **self.kwargs)
8186 - class UnknownFF(object):
8187 """search in [-10, 10] for the unknown (optimum)"""
8188 - def __init__(self, seed=2):
8189 self.seed = seed 8190 self._x_opt_ = {} 8191 self.rotate = Rotation(seed) 8192 self.count_evaluations = 0
8193 - def _x_opt(self, dim):
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
8200 - def typical_x(self, dim):
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
8208 - def __call__(self, x):
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
8220 8221 -class FitnessFunctions(object):
8222 """ versatile container for test objective functions """ 8223
8224 - def __init__(self):
8225 self.counter = 0 # number of calls or any other practical use
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: # parallelized 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]
8247 - def linear(self, x):
8248 return -x[0]
8249 - def lineard(self, x):
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)
8255 - def sphere(self, x):
8256 """Sphere (squared norm) test objective function""" 8257 # return np.random.rand(1)[0]**0 * sum(x**2) + 1 * np.random.rand(1)[0] 8258 return sum((x + 0)**2)
8259 - def grad_sphere(self, x, *args):
8260 return 2*array(x, copy=False)
8261 - def grad_to_one(self, x, *args):
8262 return array(x, copy=False) - 1
8263 - def sphere_pos(self, x):
8264 """Sphere (squared norm) test objective function""" 8265 # return np.random.rand(1)[0]**0 * sum(x**2) + 1 * np.random.rand(1)[0] 8266 c = 0.0 8267 if x[0] < c: 8268 return np.nan 8269 return -c**2 + sum((x + 0)**2)
8270 - def spherewithoneconstraint(self, x):
8271 return sum((x + 0)**2) if x[0] > 1 else np.nan
8272 - def elliwithoneconstraint(self, x, idx=[-1]):
8273 return self.ellirot(x) if all(array(x)[idx] > 1) else np.nan
8274
8275 - def spherewithnconstraints(self, x):
8276 return sum((x + 0)**2) if all(array(x) > 1) else np.nan
8277 # zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
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()
8281 - def spherew(self, x):
8282 """Sphere (squared norm) with sum x_i = 1 test objective function""" 8283 # return np.random.rand(1)[0]**0 * sum(x**2) + 1 * np.random.rand(1)[0] 8284 # s = sum(abs(x)) 8285 # return sum((x/s+0)**2) - 1/len(x) 8286 # return sum((x/s)**2) - 1/len(x) 8287 return -0.01 * x[0] + abs(x[0])**-2 * sum(x[1:]**2)
8288 - def partsphere(self, x):
8289 """Sphere (squared norm) test objective function""" 8290 self.counter += 1 8291 # return np.random.rand(1)[0]**0 * sum(x**2) + 1 * np.random.rand(1)[0] 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 # f = sum(x[i:i + N]**2) 8297 f = sum(x[np.random.randint(dim, size=N)]**2) 8298 return f
8299 - def sectorsphere(self, x):
8300 """asymmetric Sphere (squared norm) test objective function""" 8301 return sum(x**2) + (1e6 - 1) * sum(x[x < 0]**2)
8302 - def cornersphere(self, x):
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
8308 - def cornerelli(self, x):
8309 """ """ 8310 if any(x < 1): 8311 return np.NaN 8312 return self.elli(x) - self.elli(np.ones(len(x)))
8313 - def cornerellirot(self, x):
8314 """ """ 8315 if any(x < 1): 8316 return np.NaN 8317 return self.ellirot(x)
8318 - def normalSkew(self, f):
8319 N = np.random.randn(1)[0]**2 8320 if N < 1: 8321 N = f * N # diminish blow up lower part 8322 return N
8323 - def noiseC(self, x, func=sphere, fac=10, expon=0.8):
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)
8327 - def noise(self, x, func=sphere, fac=10, expon=1):
8328 f = func(self, x) 8329 # R = np.random.randn(1)[0] 8330 R = np.log10(f) + expon * abs(10 - np.log10(f)) * np.random.rand(1)[0] 8331 # sig = float(fac)/float(len(x)) 8332 # R = log(f) + 0.5*log(f) * random.randn(1)[0] 8333 # return max(1e-19, f + sig * (f**np.log10(f)) * np.exp(R)) 8334 # return max(1e-19, f * np.exp(sig * N / f**expon)) 8335 # return max(1e-19, f * normalSkew(f**expon)**sig) 8336 return f + 10**R # == f + f**(1+0.5*RN)
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 # scalar into list 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] # 1-element-list into scalar
8344 - def grad_cigar(self, x, *args):
8345 grad = 2 * 1e6 * np.array(x) 8346 grad[0] /= 1e6 8347 return grad
8348 - def diagonal_cigar(self, x, cond=1e6):
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
8354 - def tablet(self, x, rot=0):
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 # scalar into list 8359 f = [1e6 * x[0]**2 + sum(x[1:]**2) for x in x] 8360 return f if len(f) > 1 else f[0] # 1-element-list into scalar
8361 - def grad_tablet(self, x, *args):
8362 grad = 2 * np.array(x) 8363 grad[0] *= 1e6 8364 return grad
8365 - def cigtab(self, y):
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]
8370 - def twoaxes(self, y):
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]
8376 - def ellirot(self, x):
8377 return fcts.elli(array(x), 1)
8378 - def hyperelli(self, x):
8379 N = len(x) 8380 return sum((np.arange(1, N + 1) * x)**2)
8381 - def halfelli(self, x):
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]): # parallel evaluation 8388 return [self.elli(xi, rot) for xi in x] # could save 20% overall 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 # felli = ftrue + 1*np.random.randn(1)[0] / (1e-30 + 8402 # np.abs(np.random.randn(1)[0]))**0 8403 if both: 8404 return (felli, ftrue) 8405 else: 8406 # return felli # possibly noisy value 8407 return ftrue # + np.random.randn()
8408 - def grad_elli(self, x, *args):
8409 cond = 1e6 8410 N = len(x) 8411 return 2 * cond**(np.arange(N) / (N - 1.)) * array(x, copy=False)
8412 - def fun_as_arg(self, x, *args):
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)
8425 - def grad_numerical(self, x, func, epsilon=None):
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)) # float is 1.6 times faster than int 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
8435 - def elliconstraint(self, x, cfac=1e8, tough=True, cond=1e6):
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 # scalar into list 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] # 1-element-list into scalar
8452 - def grad_rosen(self, x, *args):
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
8460 - def diffpow(self, x, rot=0):
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
8466 - def rosenelli(self, x):
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 # scalar into list 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] # 1-element-list into scalar
8473 - def ridgecircle(self, x, expo=0.5):
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
8478 - def happycat(self, x, alpha=1. / 8):
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)
8485 - def branin(self, x):
8486 # in [0,15]**2 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
8490 - def goldsteinprice(self, x):
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
8495 - def griewank(self, x):
8496 # was in [-600 600] 8497 x = (600. / 5) * x 8498 return 1 - np.prod(np.cos(x / sqrt(1. + np.arange(len(x))))) + sum(x**2) / 4e3
8499 - def rastrigin(self, x):
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 # return 10*N + sum(x**2 - 10*np.cos(2*np.pi*x), axis=1) 8505 N = len(x) 8506 return 10 * N + sum(x**2 - 10 * np.cos(2 * np.pi * x))
8507 - def schaffer(self, 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
8513 - def schwefelelli(self, x):
8514 s = 0 8515 f = 0 8516 for i in rglen(x): 8517 s += x[i] 8518 f += s**2 8519 return f
8520 - def schwefelmult(self, x, pen_fac=1e4):
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]
8527 - def optprob(self, x):
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]
8536 - def rosen_nesterov(self, x, rho=100):
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
8546 - def powel_singular(self, x):
8547 # ((8 * np.sin(7 * (x[i] - 0.9)**2)**2 ) + (6 * np.sin())) 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
8552 - def styblinski_tang(self, x):
8553 """in [-5, 5] 8554 """ 8555 # x_opt = N * [-2.90353402], seems to have essentially 8556 # (only) 2**N local optima 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
8563 - def bukin(self, x):
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 # for cross compatibility, as if the functions were static members of class Fcts 8576 FF = fcts
8577 8578 -def felli(x):
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): # None is fine when called from inside the module
8586 import doctest 8587 print(doctest.testmod(module)) # this is pretty coool!
8588 -def process_doctest_output(stream=None):
8589 """ """ 8590 import fileinput 8591 s1 = "" 8592 s2 = "" 8593 s3 = "" 8594 state = 0 8595 for line in fileinput.input(stream): # takes argv as file or stdin 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: # found a failed example line 8604 s1 += '\n\n*** Failed Example:' + line 8605 s2 += '\n\n\n' # line 8606 # state = 0 # wait for 'Expected:' line 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('***'): # marks end of failed example 8615 state = 0 8616 elif line.startswith('Failed example:'): 8617 state = -1 8618 elif line.startswith('Exception raised'): 8619 state = -2 8620 8621 # in effect more else: 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 # should have better been sys.argv[1:] 8672 else: 8673 if isinstance(argv, list): 8674 argv = ['python'] + argv # see above 8675 else: 8676 argv = ['python'] + [argv] 8677 8678 # uncomment for unit test 8679 # _test() 8680 # handle input arguments, getopt might be helpful ;-) 8681 if len(argv) >= 1: # function and help 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')): # verbose 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 # if argv[1][2] == 'v': 8693 doctest.testmod(sys.modules[__name__], report=True) # this is quite cool! 8694 else: # was: if len(argv) > 2 and (argv[2].startswith('--qu') or argv[2].startswith('-q')): 8695 print('doctest for cma.py: launching...') # not anymore: (it might be necessary to close the pop up window to finish) 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) # this is quite cool! 8703 t_elapsed = clock() 8704 finally: 8705 sys.stdout = stdout 8706 process_doctest_output(fn) 8707 # clean up 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 # "License :: OSI Approved :: MIT License", 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: # dimension 8774 x0 = np.ones(eval(argv[2])) 8775 if len(argv) > 3: # sigma 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 # run fmin 8783 if fun is not None: 8784 tic = time.time() 8785 fmin(fun, x0, sig0, opts) # ftarget=1e-9, tolfacupx=1e9, verb_log=10) 8786 # plot() 8787 # print ' best function value ', res[2]['es'].best[1] 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 # mainly for testing purpose 8798 # executed when called from an OS shell 8799 if __name__ == "__main__": 8800 # for i in xrange(1000): # how to find the memory leak 8801 # main(["cma.py", "rastrigin", "10", "5", "popsize", "200", "maxfevals", "24999", "verb_log", "0"]) 8802 main() 8803