1# -*- coding: utf-8 -*- 2"""CMA-ES (evolution strategy), the main sub-module of `cma` providing 3in particular `CMAOptions`, `CMAEvolutionStrategy`, and `fmin` 4""" 5 6# TODO (mainly done): remove separable CMA within the code (keep as sampler only) 7# TODO (low): implement a (deep enough) copy-constructor for class 8# CMAEvolutionStrategy to repeat the same step in different 9# configurations for online-adaptation of meta parameters 10# TODO (complex): reconsider geno-pheno transformation. Can it be a 11# separate module that operates inbetween optimizer and objective? 12# Can we still propagate a repair of solutions to the optimizer? 13# A repair-only internal geno-pheno transformation is less 14# problematic, given the repair is idempotent. In any case, consider 15# passing a repair function in the interface instead. 16# How about gradients (should be fine)? 17# Must be *thoroughly* explored before to switch, in particular the 18# order of application of repair and other transformations, as the 19# internal repair can only operate on the internal representation. 20# TODO: split tell into a variable transformation part and the "pure" 21# functionality 22# usecase: es.tell_geno(X, [func(es.pheno(x)) for x in X]) 23# genotypic repair is not part of tell_geno 24# TODO: self.opts['mindx'] is checked without sigma_vec, which is a little 25# inconcise. Cheap solution: project sigma_vec on smallest eigenvector? 26# TODO: class _CMAStopDict implementation looks way too complicated, 27# design generically from scratch? 28# TODO: separate display and logging options, those CMAEvolutionStrategy 29# instances don't use themselves (probably all?) 30# TODO: check scitools.easyviz and how big the adaptation would be 31# TODO: separate initialize==reset_state from __init__ 32# TODO: keep best ten solutions 33# TODO: implement constraints handling 34# TODO: eigh(): thorough testing would not hurt 35# TODO: (partly done) apply style guide 36# WON'T FIX ANYTIME SOON (done within fmin): implement bipop in a separate 37# algorithm as meta portfolio algorithm of IPOP and a local restart 38# option to be implemented 39# in fmin (e.g. option restart_mode in [IPOP, local]) 40# DONE: extend function unitdoctest, or use unittest? 41# DONE: copy_always optional parameter does not make much sense, 42# as one can always copy the input argument first. Similar, 43# copy_if_changed should be keep_arg_unchanged or just copy 44# DONE: expand/generalize to dynamically read "signals" from a file 45# see import ConfigParser, DictFromTagsInString, 46# function read_properties, or myproperties.py (to be called after 47# tell()), signals_filename, if given, is parsed in stop() 48# DONE: switch to np.loadtxt 49# 50# typical parameters in scipy.optimize: disp, xtol, ftol, maxiter, maxfun, 51# callback=None 52# maxfev, diag (A sequency of N positive entries that serve as 53# scale factors for the variables.) 54# full_output -- non-zero to return all optional outputs. 55# If xtol < 0.0, xtol is set to sqrt(machine_precision) 56# 'infot -- a dictionary of optional outputs with the keys: 57# 'nfev': the number of function calls... 58# 59# see eg fmin_powell 60# typical returns 61# x, f, dictionary d 62# (xopt, {fopt, gopt, Hopt, func_calls, grad_calls, warnflag}, 63# <allvecs>) 64# 65 66# changes: 67# 20/04/xx: no negative weights for injected solutions 68# 16/10/xx: versatile options are read from signals_filename 69# RecombinationWeights refined and work without numpy 70# new options: recombination_weights, timeout, 71# integer_variable with basic integer handling 72# step size parameters removed from CMAEvolutionStrategy class 73# ComposedFunction class implements function composition 74# 16/10/02: copy_always parameter is gone everywhere, use 75# np.array(., copy=True) 76# 16/xx/xx: revised doctests with doctest: +ELLIPSIS option, test call(s) 77# moved all test related to test.py, is quite clean now 78# "python -m cma.test" is how it works now 79# 16/xx/xx: cleaning up, all kind of larger changes. 80# 16/xx/xx: single file cma.py broken into pieces such that cma has now 81# become a package. 82# 15/02/xx: (v1.2) sampling from the distribution sampling refactorized 83# in class Sampler which also does the (natural gradient) 84# update. New AdaptiveDecoding class for sigma_vec. 85# 15/01/26: bug fix in multiplyC with sep/diagonal option 86# 15/01/20: larger condition numbers for C realized by using tf_pheno 87# of GenoPheno attribute gp. 88# 15/01/19: injection method, first implementation, short injections 89# and long injections with good fitness need to be addressed yet 90# 15/01/xx: _prepare_injection_directions to simplify/centralize injected 91# solutions from mirroring and TPA 92# 14/12/26: bug fix in correlation_matrix computation if np.diag is a view 93# 14/12/06: meta_parameters now only as annotations in ## comments 94# 14/12/03: unified use of base class constructor call, now always 95# super(ThisClass, self).__init__(args_for_base_class_constructor) 96# 14/11/29: termination via "stop now" in file cmaes_signals.par 97# 14/11/28: bug fix initialization of C took place before setting the 98# seed. Now in some dimensions (e.g. 10) results are (still) not 99# determistic due to np.linalg.eigh, in some dimensions (<9, 12) 100# they seem to be deterministic. 101# 14/11/23: bipop option integration, contributed by Petr Baudis 102# 14/09/30: initial_elitism option added to fmin 103# 14/08/1x: developing fitness wrappers in FFWrappers class 104# 14/08/xx: return value of OOOptimizer.optimize changed to self. 105# CMAOptions now need to uniquely match an *initial substring* 106# only (via method corrected_key). 107# Bug fix in CMAEvolutionStrategy.stop: termination conditions 108# are now recomputed iff check and self.countiter > 0. 109# Doc corrected that self.gp.geno _is_ applied to x0 110# Vaste reorganization/modularization/improvements of plotting 111# 14/08/01: bug fix to guaranty pos. def. in active CMA 112# 14/06/04: gradient of f can now be used with fmin and/or ask 113# 14/05/11: global rcParams['font.size'] not permanently changed anymore, 114# a little nicer annotations for the plots 115# 14/05/07: added method result_pretty to pretty print optimization result 116# 14/05/06: associated show() everywhere with ion() which should solve the 117# blocked terminal problem 118# 14/05/05: all instances of "unicode" removed (was incompatible to 3.x) 119# 14/05/05: replaced type(x) == y with isinstance(x, y), reorganized the 120# comments before the code starts 121# 14/05/xx: change the order of kwargs of OOOptimizer.optimize, 122# remove prepare method in AdaptSigma classes, various changes/cleaning 123# 14/03/01: bug fix BoundaryHandlerBase.has_bounds didn't check lower bounds correctly 124# bug fix in BoundPenalty.repair len(bounds[0]) was used instead of len(bounds[1]) 125# bug fix in GenoPheno.pheno, where x was not copied when only boundary-repair was applied 126# 14/02/27: bug fixed when BoundPenalty was combined with fixed variables. 127# 13/xx/xx: step-size adaptation becomes a class derived from CMAAdaptSigmaBase, 128# to make testing different adaptation rules (much) easier 129# 12/12/14: separated CMAOptions and arguments to fmin 130# 12/10/25: removed useless check_points from fmin interface 131# 12/10/17: bug fix printing number of infeasible samples, moved not-in-use methods 132# timesCroot and divCroot to the right class 133# 12/10/16 (0.92.00): various changes commit: bug bound[0] -> bounds[0], more_to_write fixed, 134# sigma_vec introduced, restart from elitist, trace normalization, max(mu,popsize/2) 135# is used for weight calculation. 136# 12/07/23: (bug:) BoundPenalty.update respects now genotype-phenotype transformation 137# 12/07/21: convert value True for noisehandling into 1 making the output compatible 138# 12/01/30: class Solution and more old stuff removed r3101 139# 12/01/29: class Solution is depreciated, GenoPheno and SolutionDict do the job (v0.91.00, r3100) 140# 12/01/06: CMA_eigenmethod option now takes a function (integer still works) 141# 11/09/30: flat fitness termination checks also history length 142# 11/09/30: elitist option (using method clip_or_fit_solutions) 143# 11/09/xx: method clip_or_fit_solutions for check_points option for all sorts of 144# injected or modified solutions and even reliable adaptive encoding 145# 11/08/19: fixed: scaling and typical_x type clashes 1 vs array(1) vs ones(dim) vs dim * [1] 146# 11/07/25: fixed: fmin wrote first and last line even with verb_log==0 147# fixed: method settableOptionsList, also renamed to versatileOptions 148# default seed depends on time now 149# 11/07/xx (0.9.92): added: active CMA, selective mirrored sampling, noise/uncertainty handling 150# fixed: output argument ordering in fmin, print now only used as function 151# removed: parallel option in fmin 152# 11/07/01: another try to get rid of the memory leak by replacing self.unrepaired = self[:] 153# 11/07/01: major clean-up and reworking of abstract base classes and of the documentation, 154# also the return value of fmin changed and attribute stop is now a method. 155# 11/04/22: bug-fix: option fixed_variables in combination with scaling 156# 11/04/21: stopdict is not a copy anymore 157# 11/04/15: option fixed_variables implemented 158# 11/03/23: bug-fix boundary update was computed even without boundaries 159# 11/03/12: bug-fix of variable annotation in plots 160# 11/02/05: work around a memory leak in numpy 161# 11/02/05: plotting routines improved 162# 10/10/17: cleaning up, now version 0.9.30 163# 10/10/17: bug-fix: return values of fmin now use phenotyp (relevant 164# if input scaling_of_variables is given) 165# 08/10/01: option evalparallel introduced, 166# bug-fix for scaling being a vector 167# 08/09/26: option CMAseparable becomes CMA_diagonal 168# 08/10/18: some names change, test functions go into a class 169# 08/10/24: more refactorizing 170# 10/03/09: upper bound np.exp(min(1,...)) for step-size control 171 172from __future__ import (absolute_import, division, print_function, 173 ) # unicode_literals, with_statement) 174# from builtins import ... 175from .utilities.python3for2 import range # redefine range in Python 2 176 177import sys 178import os 179import time # not really essential 180import warnings # catch numpy warnings 181import ast # for literal_eval 182try: 183 import collections # not available in Python 2.5 184except ImportError: 185 pass 186import math 187import numpy as np 188# arange, cos, size, eye, inf, dot, floor, outer, zeros, linalg.eigh, 189# sort, argsort, random, ones,... 190from numpy import inf, array 191# to access the built-in sum fct: ``__builtins__.sum`` or ``del sum`` 192# removes the imported sum and recovers the shadowed build-in 193 194# import logging 195# logging.basicConfig(level=logging.INFO) # only works before logging is used 196# logging.info('message') # prints INFO:root:message on red background 197# logger = logging.getLogger(__name__) # should not be done during import 198# logger.info('message') # prints INFO:cma...:message on red background 199# see https://fangpenlin.com/posts/2012/08/26/good-logging-practice-in-python/ 200 201from . import interfaces 202from . import transformations 203from . import optimization_tools as ot 204from . import sampler 205from .utilities import utils as _utils 206from .constraints_handler import BoundNone, BoundPenalty, BoundTransform, AugmentedLagrangian 207from .recombination_weights import RecombinationWeights 208from .logger import CMADataLogger # , disp, plot 209from .utilities.utils import BlancClass as _BlancClass 210from .utilities.utils import rglen #, global_verbosity 211from .utilities.utils import pprint 212from .utilities.utils import seval as eval 213from .utilities.utils import SolutionDict as _SolutionDict 214from .utilities.math import Mh 215from .sigma_adaptation import * 216from . import restricted_gaussian_sampler as _rgs 217 218_where = np.nonzero # to make pypy work, this is how where is used here anyway 219del division, print_function, absolute_import #, unicode_literals, with_statement 220 221class InjectionWarning(UserWarning): 222 """Injected solutions are not passed to tell as expected""" 223 224# use_archives uses collections 225use_archives = sys.version_info[0] >= 3 or sys.version_info[1] >= 6 226# use_archives = False # on False some unit tests fail 227"""speed up for very large population size. `use_archives` prevents the 228need for an inverse gp-transformation, relies on collections module, 229not sure what happens if set to ``False``. """ 230 231class MetaParameters(object): 232 """collection of many meta parameters. 233 234 Meta parameters are either annotated constants or refer to 235 options from `CMAOptions` or are arguments to `fmin` or to the 236 `NoiseHandler` class constructor. 237 238 `MetaParameters` take only effect if the source code is modified by 239 a meta parameter weaver module searching for ## meta_parameters.... 240 and modifying the next line. 241 242 Details 243 ------- 244 This code contains a single class instance `meta_parameters` 245 246 Some interfaces rely on parameters being either `int` or 247 `float` only. More sophisticated choices are implemented via 248 ``choice_value = {1: 'this', 2: 'or that'}[int_param_value]`` here. 249 250 CAVEAT 251 ------ 252 `meta_parameters` should not be used to determine default 253 arguments, because these are assigned only once and for all during 254 module import. 255 256 """ 257 def __init__(self): 258 """assign settings to be used""" 259 self.sigma0 = None ## [~0.01, ~10] # no default available 260 261 # learning rates and back-ward time horizons 262 self.CMA_cmean = 1.0 ## [~0.1, ~10] # 263 self.c1_multiplier = 1.0 ## [~1e-4, ~20] l 264 self.cmu_multiplier = 2.0 ## [~1e-4, ~30] l # zero means off 265 self.CMA_active = 1.0 ## [~1e-4, ~10] l # 0 means off, was CMA_activefac 266 self.cc_multiplier = 1.0 ## [~0.01, ~20] l 267 self.cs_multiplier = 1.0 ## [~0.01, ~10] l # learning rate for cs 268 self.CSA_dampfac = 1.0 ## [~0.01, ~10] 269 self.CMA_dampsvec_fac = None ## [~0.01, ~100] # def=np.Inf or 0.5, not clear whether this is a log parameter 270 self.CMA_dampsvec_fade = 0.1 ## [0, ~2] 271 272 # exponents for learning rates 273 self.c1_exponent = 2.0 ## [~1.25, 2] 274 self.cmu_exponent = 2.0 ## [~1.25, 2] 275 self.cact_exponent = 1.5 ## [~1.25, 2] 276 self.cc_exponent = 1.0 ## [~0.25, ~1.25] 277 self.cs_exponent = 1.0 ## [~0.25, ~1.75] # upper bound depends on CSA_clip_length_value 278 279 # selection related parameters 280 self.lambda_exponent = 0.0 ## [0, ~2.5] # usually <= 2, used by adding N**lambda_exponent to popsize-1 281 self.CMA_elitist = 0 ## [0, 2] i # a choice variable 282 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', 283 284 # sampling strategies 285 # self.CMA_sample_on_sphere_surface = 0 ## [0, 1] i # boolean 286 self.mean_shift_line_samples = 0 ## [0, 1] i # boolean 287 self.pc_line_samples = 0 ## [0, 1] i # boolean 288 289 # step-size adapation related parameters 290 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', 291 self.CSA_disregard_length = 0 ## [0, 1] i 292 self.CSA_squared = 0 ## [0, 1] i 293 self.CSA_clip_length_value = None ## [0, ~20] # None reflects inf 294 295 # noise handling 296 self.noise_reeval_multiplier = 1.0 ## [0.2, 4] # usually 2 offspring are reevaluated 297 self.noise_choose_reeval = 1 ## [1, 3] i # which ones to reevaluate 298 self.noise_theta = 0.5 ## [~0.05, ~0.9] 299 self.noise_alphasigma = 2.0 ## [0, 10] 300 self.noise_alphaevals = 2.0 ## [0, 10] 301 self.noise_alphaevalsdown_exponent = -0.25 ## [-1.5, 0] 302 self.noise_aggregate = None ## [1, 2] i # None and 0 == default or user option choice, 1 == median, 2 == mean 303 # TODO: more noise handling options (maxreevals...) 304 305 # restarts 306 self.restarts = 0 ## [0, ~30] # but depends on popsize inc 307 self.restart_from_best = 0 ## [0, 1] i # bool 308 self.incpopsize = 2.0 ## [~1, ~5] 309 310 # termination conditions (for restarts) 311 self.maxiter_multiplier = 1.0 ## [~0.01, ~100] l 312 self.mindx = 0.0 ## [1e-17, ~1e-3] l #v minimal std in any direction, cave interference with tol*', 313 self.minstd = 0.0 ## [1e-17, ~1e-3] l #v minimal std in any coordinate direction, cave interference with tol*', 314 self.maxstd = None ## [~1, ~1e9] l #v maximal std in any coordinate direction, default is inf', 315 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', 316 self.tolupsigma = 1e20 ## [~100, ~1e99] l #v sigma/sigma0 > tolupsigma * max(sqrt(eivenvals(C))) indicates "creeping behavior" with usually minor improvements', 317 self.tolx = 1e-11 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in x-changes', 318 self.tolfun = 1e-11 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in function value, quite useful', 319 self.tolfunrel = 0 ## [1e-17, ~1e-2] l #v termination criterion: relative tolerance in function value', 320 self.tolfunhist = 1e-12 ## [1e-17, ~1e-3] l #v termination criterion: tolerance in function value history', 321 self.tolstagnation_multiplier = 1.0 ## [0.01, ~100] # ': 'int(100 + 100 * N**1.5 / popsize) #v termination if no improvement over tolstagnation iterations', 322 323 # abandoned: 324 # self.noise_change_sigma_exponent = 1.0 ## [0, 2] 325 # self.noise_epsilon = 1e-7 ## [0, ~1e-2] l # 326 # self.maxfevals = None ## [1, ~1e11] l # is not a performance parameter 327 # self.lambda_log_multiplier = 3 ## [0, ~10] 328 # self.lambda_multiplier = 0 ## (0, ~10] 329 330meta_parameters = MetaParameters() 331 332def is_feasible(x, f): 333 """default to check feasibility of f-values. 334 335 Used for rejection sampling in method `ask_and_eval`. 336 337 :See also: CMAOptions, ``CMAOptions('feas')``. 338 """ 339 return f is not None and not np.isnan(f) 340 341 342if use_archives: 343 344 class _CMASolutionDict(_SolutionDict): 345 def __init__(self, *args, **kwargs): 346 # _SolutionDict.__init__(self, *args, **kwargs) 347 super(_CMASolutionDict, self).__init__(*args, **kwargs) 348 self.last_solution_index = 0 349 350 # TODO: insert takes 30% of the overall CPU time, mostly in def key() 351 # with about 15% of the overall CPU time 352 def insert(self, key, geno=None, iteration=None, fitness=None, 353 value=None, cma_norm=None): 354 """insert an entry with key ``key`` and value 355 ``value if value is not None else {'geno':key}`` and 356 ``self[key]['kwarg'] = kwarg if kwarg is not None`` for the further kwargs. 357 358 """ 359 # archive returned solutions, first clean up archive 360 if iteration is not None and iteration > self.last_iteration and (iteration % 10) < 1: 361 self.truncate(300, iteration - 3) 362 elif value is not None and value.get('iteration'): 363 iteration = value['iteration'] 364 if (iteration % 10) < 1: 365 self.truncate(300, iteration - 3) 366 367 self.last_solution_index += 1 368 if value is not None: 369 try: 370 iteration = value['iteration'] 371 except: 372 pass 373 if iteration is not None: 374 if iteration > self.last_iteration: 375 self.last_solution_index = 0 376 self.last_iteration = iteration 377 else: 378 iteration = self.last_iteration + 0.5 # a hack to get a somewhat reasonable value 379 if value is not None: 380 self[key] = value 381 else: 382 self[key] = {'pheno': key} 383 if geno is not None: 384 self[key]['geno'] = geno 385 if iteration is not None: 386 self[key]['iteration'] = iteration 387 if fitness is not None: 388 self[key]['fitness'] = fitness 389 if cma_norm is not None: 390 self[key]['cma_norm'] = cma_norm 391 return self[key] 392 393else: # if not use_archives: 394 class _CMASolutionDict(dict): 395 """a hack to get most code examples running""" 396 def insert(self, *args, **kwargs): 397 pass 398 def get(self, key): 399 return None 400 def __getitem__(self, key): 401 return None 402 def __setitem__(self, key, value): 403 pass 404 405# ____________________________________________________________ 406# ____________________________________________________________ 407# check out built-in package abc: class ABCMeta, abstractmethod, abstractproperty... 408# see http://docs.python.org/whatsnew/2.6.html PEP 3119 abstract base classes 409# 410 411_debugging = False # not in use 412_new_injections = True 413_assertions_quadratic = True # issue warnings 414_assertions_cubic = True 415_depreciated = True 416 417def cma_default_options_( # to get keyword completion back 418 # the follow string arguments are evaluated if they do not contain "filename" 419 AdaptSigma='True # or False or any CMAAdaptSigmaBase class e.g. CMAAdaptSigmaTPA, CMAAdaptSigmaCSA', 420 CMA_active='True # negative update, conducted after the original update', 421# CMA_activefac='1 # learning rate multiplier for active update', 422 CMA_active_injected='0 #v weight multiplier for negative weights of injected solutions', 423 CMA_cmean='1 # learning rate for the mean value', 424 CMA_const_trace='False # normalize trace, 1, True, "arithm", "geom", "aeig", "geig" are valid', 425 CMA_diagonal='0*100*N/popsize**0.5 # nb of iterations with diagonal covariance matrix,'\ 426 ' True for always', # TODO 4/ccov_separable? 427 CMA_eigenmethod='np.linalg.eigh # or cma.utilities.math.eig or pygsl.eigen.eigenvectors', 428 CMA_elitist='False #v or "initial" or True, elitism likely impairs global search performance', 429 CMA_injections_threshold_keep_len='1 #v keep length if Mahalanobis length is below the given relative threshold', 430 CMA_mirrors='popsize < 6 # values <0.5 are interpreted as fraction, values >1 as numbers (rounded),'\ 431 ' for `True` about 0.16 is used', 432 CMA_mirrormethod='2 # 0=unconditional, 1=selective, 2=selective with delay', 433 CMA_mu='None # parents selection parameter, default is popsize // 2', 434 CMA_on='1 # multiplier for all covariance matrix updates', 435 # CMA_sample_on_sphere_surface='False #v replaced with option randn=cma.utilities.math.randhss, all mutation vectors have the same length, currently (with new_sampling) not in effect', 436 CMA_sampler='None # a class or instance that implements the interface of'\ 437 ' `cma.interfaces.StatisticalModelSamplerWithZeroMeanBaseClass`', 438 CMA_sampler_options='{} # options passed to `CMA_sampler` class init as keyword arguments', 439 CMA_rankmu='1.0 # multiplier for rank-mu update learning rate of covariance matrix', 440 CMA_rankone='1.0 # multiplier for rank-one update learning rate of covariance matrix', 441 CMA_recombination_weights='None # a list, see class RecombinationWeights, overwrites CMA_mu and popsize options', 442 CMA_dampsvec_fac='np.Inf # tentative and subject to changes, 0.5 would be a "default" damping for sigma vector update', 443 CMA_dampsvec_fade='0.1 # tentative fading out parameter for sigma vector update', 444 CMA_teststds='None # factors for non-isotropic initial distr. of C, mainly for test purpose, see CMA_stds for production', 445 CMA_stds='None # multipliers for sigma0 in each coordinate, not represented in C, makes scaling_of_variables obsolete', 446 # CMA_AII='False # not yet tested', 447 CSA_dampfac='1 #v positive multiplier for step-size damping, 0.3 is close to optimal on the sphere', 448 CSA_damp_mueff_exponent='0.5 # zero would mean no dependency of damping on mueff, useful with CSA_disregard_length option', 449 CSA_disregard_length='False #v True is untested, also changes respective parameters', 450 CSA_clip_length_value='None #v poorly tested, [0, 0] means const length N**0.5, [-1, 1] allows a variation of +- N/(N+2), etc.', 451 CSA_squared='False #v use squared length for sigma-adaptation ', 452 BoundaryHandler='BoundTransform # or BoundPenalty, unused when ``bounds in (None, [None, None])``', 453 bounds='[None, None] # lower (=bounds[0]) and upper domain boundaries, each a scalar or a list/vector', 454 # , eval_parallel2='not in use {"processes": None, "timeout": 12, "is_feasible": lambda x: True} # distributes function calls to processes processes' 455 # 'callback='None # function or list of functions called as callback(self) at the end of the iteration (end of tell)', # only necessary in fmin and optimize 456 conditioncov_alleviate='[1e8, 1e12] # when to alleviate the condition in the coordinates and in main axes', 457 eval_final_mean='True # evaluate the final mean, which is a favorite return candidate', 458 fixed_variables='None # dictionary with index-value pairs like {0:1.1, 2:0.1} that are not optimized', 459 ftarget='-inf #v target function value, minimization', 460 integer_variables='[] # index list, invokes basic integer handling: prevent std dev to become too small in the given variables', 461 is_feasible='is_feasible #v a function that computes feasibility, by default lambda x, f: f not in (None, np.NaN)', 462 maxfevals='inf #v maximum number of function evaluations', 463 maxiter='100 + 150 * (N+3)**2 // popsize**0.5 #v maximum number of iterations', 464 mean_shift_line_samples='False #v sample two new solutions colinear to previous mean shift', 465 mindx='0 #v minimal std in any arbitrary direction, cave interference with tol*', 466 minstd='0 #v minimal std (scalar or vector) in any coordinate direction, cave interference with tol*', 467 maxstd='inf #v maximal std in any coordinate direction', 468 pc_line_samples='False #v one line sample along the evolution path pc', 469 popsize='4 + 3 * np.log(N) # population size, AKA lambda, int(popsize) is the number of new solution per iteration', 470 popsize_factor='1 # multiplier for popsize, convenience option to increase default popsize', 471 randn='np.random.randn #v randn(lam, N) must return an np.array of shape (lam, N), see also cma.utilities.math.randhss', 472 scaling_of_variables='None # deprecated, rather use fitness_transformations.ScaleCoordinates instead (or possibly CMA_stds). Scale for each variable in that effective_sigma0 = sigma0*scaling. Internally the variables are divided by scaling_of_variables and sigma is unchanged, default is `np.ones(N)`', 473 seed='time # random number seed for `numpy.random`; `None` and `0` equate to `time`,'\ 474 ' `np.nan` means "do nothing", see also option "randn"', 475 signals_filename='cma_signals.in # read versatile options from this file (use `None` or `""` for no file)'\ 476 ' which contains a single options dict, e.g. ``{"timeout": 0}`` to stop,'\ 477 ' string-values are evaluated, e.g. "np.inf" is valid', 478 termination_callback='[] #v a function or list of functions returning True for termination, called in'\ 479 ' `stop` with `self` as argument, could be abused for side effects', 480 timeout='inf #v stop if timeout seconds are exceeded, the string "2.5 * 60**2" evaluates to 2 hours and 30 minutes', 481 tolconditioncov='1e14 #v stop if the condition of the covariance matrix is above `tolconditioncov`', 482 tolfacupx='1e3 #v termination when step-size increases by tolfacupx (diverges). That is, the initial'\ 483 ' step-size was chosen far too small and better solutions were found far away from the initial solution x0', 484 tolupsigma='1e20 #v sigma/sigma0 > tolupsigma * max(eivenvals(C)**0.5) indicates "creeping behavior" with usually'\ 485 ' minor improvements', 486 tolflatfitness='1 #v iterations tolerated with flat fitness before termination', 487 tolfun='1e-11 #v termination criterion: tolerance in function value, quite useful', 488 tolfunhist='1e-12 #v termination criterion: tolerance in function value history', 489 tolfunrel='0 #v termination criterion: relative tolerance in function value:'\ 490 ' Delta f current < tolfunrel * (median0 - median_min)', 491 tolstagnation='int(100 + 100 * N**1.5 / popsize) #v termination if no improvement over tolstagnation iterations', 492 tolx='1e-11 #v termination criterion: tolerance in x-changes', 493 transformation='''None # depreciated, use cma.fitness_transformations.FitnessTransformation instead. 494 [t0, t1] are two mappings, t0 transforms solutions from CMA-representation to f-representation (tf_pheno), 495 t1 is the (optional) back transformation, see class GenoPheno''', 496 typical_x='None # used with scaling_of_variables', 497 updatecovwait='None #v number of iterations without distribution update, name is subject to future changes', # TODO: rename: iterwaitupdatedistribution? 498 verbose='3 #v verbosity e.g. of initial/final message, -1 is very quiet, -9 maximally quiet, may not be fully implemented', 499 verb_append='0 # initial evaluation counter, if append, do not overwrite output files', 500 verb_disp='100 #v verbosity: display console output every verb_disp iteration', 501 verb_filenameprefix=CMADataLogger.default_prefix + ' # output path and filenames prefix', 502 verb_log='1 #v verbosity: write data to files every verb_log iteration, writing can be'\ 503 ' time critical on fast to evaluate functions', 504 verb_log_expensive='N * (N <= 50) # allow to execute eigendecomposition for logging every verb_log_expensive iteration,'\ 505 ' 0 or False for never', 506 verb_plot='0 #v in fmin(): plot() is called every verb_plot iteration', 507 verb_time='True #v output timings on console', 508 vv='{} #? versatile set or dictionary for hacking purposes, value found in self.opts["vv"]' 509 ): 510 """use this function to get keyword completion for `CMAOptions`. 511 512 ``cma.CMAOptions('substr')`` provides even substring search. 513 514 returns default options as a `dict` (not a `cma.CMAOptions` `dict`). 515 """ 516 return dict(locals()) # is defined before and used by CMAOptions, so it can't return CMAOptions 517 518cma_default_options = cma_default_options_() # will later be reassigned as CMAOptions(dict) 519cma_versatile_options = tuple(sorted(k for (k, v) in cma_default_options.items() 520 if v.find(' #v ') > 0)) 521cma_allowed_options_keys = dict([s.lower(), s] for s in cma_default_options) 522 523class CMAOptions(dict): 524 """a dictionary with the available options and their default values 525 for class `CMAEvolutionStrategy`. 526 527 ``CMAOptions()`` returns a `dict` with all available options and their 528 default values with a comment string. 529 530 ``CMAOptions('verb')`` returns a subset of recognized options that 531 contain 'verb' in there keyword name or (default) value or 532 description. 533 534 ``CMAOptions(opts)`` returns the subset of recognized options in 535 ``dict(opts)``. 536 537 Option values can be "written" in a string and, when passed to `fmin` 538 or `CMAEvolutionStrategy`, are evaluated using "N" and "popsize" as 539 known values for dimension and population size (sample size, number 540 of new solutions per iteration). All default option values are given 541 as such a string. 542 543 Details 544 ------- 545 `CMAOptions` entries starting with ``tol`` are termination 546 "tolerances". 547 548 For `tolstagnation`, the median over the first and the second half 549 of at least `tolstagnation` iterations are compared for both, the 550 per-iteration best and per-iteration median function value. 551 552 Example 553 ------- 554 :: 555 556 import cma 557 cma.CMAOptions('tol') 558 559 is a shortcut for ``cma.CMAOptions().match('tol')`` that returns all 560 options that contain 'tol' in their name or description. 561 562 To set an option:: 563 564 import cma 565 opts = cma.CMAOptions() 566 opts.set('tolfun', 1e-12) 567 opts['tolx'] = 1e-11 568 569 todo: this class is overly complex and should be re-written, possibly 570 with reduced functionality. 571 572 :See also: `fmin` (), `CMAEvolutionStrategy`, `_CMAParameters` 573 574 """ 575 576 # @classmethod # self is the class, not the instance 577 # @property 578 # def default(self): 579 # """returns all options with defaults""" 580 # return fmin([],[]) 581 582 @staticmethod 583 def defaults(): 584 """return a dictionary with default option values and description""" 585 return cma_default_options 586 # return dict((str(k), str(v)) for k, v in cma_default_options_().items()) 587 # getting rid of the u of u"name" by str(u"name") 588 # return dict(cma_default_options) 589 590 @staticmethod 591 def versatile_options(): 592 """return list of options that can be changed at any time (not 593 only be initialized). 594 595 Consider that this list might not be entirely up 596 to date. 597 598 The string ' #v ' in the default value indicates a versatile 599 option that can be changed any time, however a string will not 600 necessarily be evaluated again. 601 602 """ 603 return cma_versatile_options 604 # return tuple(sorted(i[0] for i in list(CMAOptions.defaults().items()) if i[1].find(' #v ') > 0)) 605 def check(self, options=None): 606 """check for ambiguous keys and move attributes into dict""" 607 self.check_values(options) 608 self.check_attributes(options) 609 self.check_values(options) 610 return self 611 def check_values(self, options=None): 612 corrected_key = CMAOptions().corrected_key # caveat: infinite recursion 613 validated_keys = [] 614 original_keys = [] 615 if options is None: 616 options = self 617 for key in options: 618 correct_key = corrected_key(key) 619 if correct_key is None: 620 raise ValueError('%s is not a valid option.\n' 621 'Similar valid options are %s\n' 622 'Valid options are %s' % 623 (key, str(list(cma_default_options(key))), 624 str(list(cma_default_options)))) 625 if correct_key in validated_keys: 626 if key == correct_key: 627 key = original_keys[validated_keys.index(key)] 628 raise ValueError("%s was not a unique key for %s option" 629 % (key, correct_key)) 630 validated_keys.append(correct_key) 631 original_keys.append(key) 632 return options 633 def check_attributes(self, opts=None): 634 """check for attributes and moves them into the dictionary""" 635 if opts is None: 636 opts = self 637 if 11 < 3: 638 if hasattr(opts, '__dict__'): 639 for key in opts.__dict__: 640 if key not in self._attributes: 641 raise ValueError(""" 642 Assign options with ``opts['%s']`` 643 instead of ``opts.%s`` 644 """ % (opts.__dict__.keys()[0], 645 opts.__dict__.keys()[0])) 646 return self 647 else: 648 # the problem with merge is that ``opts['ftarget'] = new_value`` 649 # would be overwritten by the old ``opts.ftarget``. 650 # The solution here is to empty opts.__dict__ after the merge 651 if hasattr(opts, '__dict__'): 652 for key in list(opts.__dict__): 653 if key in self._attributes: 654 continue 655 utils.print_warning( 656 """ 657 An option attribute has been merged into the dictionary, 658 thereby possibly overwriting the dictionary value, and the 659 attribute has been removed. Assign options with 660 661 ``opts['%s'] = value`` # dictionary assignment 662 663 or use 664 665 ``opts.set('%s', value) # here isinstance(opts, CMAOptions) 666 667 instead of 668 669 ``opts.%s = value`` # attribute assignment 670 """ % (key, key, key), 'check', 'CMAOptions') 671 672 opts[key] = opts.__dict__[key] # getattr(opts, key) 673 delattr(opts, key) # is that cosher? 674 # delattr is necessary to prevent that the attribute 675 # overwrites the dict entry later again 676 return opts 677 678 def __init__(self, s=None, **kwargs): 679 """return an `CMAOptions` instance. 680 681 Return default options if ``s is None and not kwargs``, 682 or all options whose name or description contains `s`, if 683 `s` is a (search) string (case is disregarded in the match), 684 or with entries from dictionary `s` as options, 685 or with kwargs as options if ``s is None``, 686 in any of the latter cases not complemented with default options 687 or settings. 688 689 Returns: see above. 690 691 Details: as several options start with ``'s'``, ``s=value`` is 692 not valid as an option setting. 693 694 """ 695 # if not CMAOptions.defaults: # this is different from self.defaults!!! 696 # CMAOptions.defaults = fmin([],[]) 697 if s is None and not kwargs: 698 super(CMAOptions, self).__init__(CMAOptions.defaults()) # dict.__init__(self, CMAOptions.defaults()) should be the same 699 # self = CMAOptions.defaults() 700 s = 'nocheck' 701 elif utils.is_str(s) and not s.startswith('unchecked'): 702 super(CMAOptions, self).__init__(CMAOptions().match(s)) 703 # we could return here 704 s = 'nocheck' 705 elif isinstance(s, dict): 706 if kwargs: 707 raise ValueError('Dictionary argument must be the only argument') 708 super(CMAOptions, self).__init__(s) 709 elif kwargs and (s is None or s.startswith('unchecked')): 710 super(CMAOptions, self).__init__(kwargs) 711 else: 712 raise ValueError('The first argument must be a string or a dict or a keyword argument or `None`') 713 if not utils.is_str(s) or not s.startswith(('unchecked', 'nocheck')): 714 # was main offender 715 self.check() # caveat: infinite recursion 716 for key in list(self.keys()): 717 correct_key = self.corrected_key(key) 718 if correct_key not in CMAOptions.defaults(): 719 utils.print_warning('invalid key ``' + str(key) + 720 '`` removed', '__init__', 'CMAOptions') 721 self.pop(key) 722 elif key != correct_key: 723 self[correct_key] = self.pop(key) 724 # self.evaluated = False # would become an option entry 725 self._lock_setting = False 726 self._attributes = self.__dict__.copy() # are not valid keys 727 self._attributes['_attributes'] = len(self._attributes) 728 729 def init(self, dict_or_str, val=None, warn=True): 730 """initialize one or several options. 731 732 Arguments 733 --------- 734 `dict_or_str` 735 a dictionary if ``val is None``, otherwise a key. 736 If `val` is provided `dict_or_str` must be a valid key. 737 `val` 738 value for key 739 740 Details 741 ------- 742 Only known keys are accepted. Known keys are in `CMAOptions.defaults()` 743 744 """ 745 # dic = dict_or_key if val is None else {dict_or_key:val} 746 self.check(dict_or_str) 747 dic = dict_or_str 748 if val is not None: 749 dic = {dict_or_str:val} 750 751 for key, val in dic.items(): 752 key = self.corrected_key(key) 753 if key not in CMAOptions.defaults(): 754 # TODO: find a better solution? 755 if warn: 756 print('Warning in cma.CMAOptions.init(): key ' + 757 str(key) + ' ignored') 758 else: 759 self[key] = val 760 761 return self 762 763 def set(self, dic, val=None, force=False): 764 """assign versatile options. 765 766 Method `CMAOptions.versatile_options` () gives the versatile 767 options, use `init()` to set the others. 768 769 Arguments 770 --------- 771 `dic` 772 either a dictionary or a key. In the latter 773 case, `val` must be provided 774 `val` 775 value for `key`, approximate match is sufficient 776 `force` 777 force setting of non-versatile options, use with caution 778 779 This method will be most probably used with the ``opts`` attribute of 780 a `CMAEvolutionStrategy` instance. 781 782 """ 783 if val is not None: # dic is a key in this case 784 dic = {dic:val} # compose a dictionary 785 for key_original, val in list(dict(dic).items()): 786 key = self.corrected_key(key_original) 787 if (not self._lock_setting or 788 key in CMAOptions.versatile_options() or 789 force): 790 self[key] = val 791 else: 792 utils.print_warning('key ' + str(key_original) + 793 ' ignored (not recognized as versatile)', 794 'set', 'CMAOptions') 795 return self # to allow o = CMAOptions(o).set(new) 796 797 def complement(self): 798 """add all missing options with their default values""" 799 800 # add meta-parameters, given options have priority 801 self.check() 802 for key in CMAOptions.defaults(): 803 if key not in self: 804 self[key] = CMAOptions.defaults()[key] 805 return self 806 807 @property 808 def settable(self): 809 """return the subset of those options that are settable at any 810 time. 811 812 Settable options are in `versatile_options` (), but the 813 list might be incomplete. 814 815 """ 816 return CMAOptions(dict(i for i in list(self.items()) 817 if i[0] in CMAOptions.versatile_options())) 818 819 def __call__(self, key, default=None, loc=None): 820 """evaluate and return the value of option `key` on the fly, or 821 return those options whose name or description contains `key`, 822 case disregarded. 823 824 Details 825 ------- 826 Keys that contain `filename` are not evaluated. 827 For ``loc==None``, `self` is used as environment 828 but this does not define ``N``. 829 830 :See: `eval()`, `evalall()` 831 832 """ 833 try: 834 val = self[key] 835 except: 836 return self.match(key) 837 838 if loc is None: 839 loc = self # TODO: this hack is not so useful: popsize could be there, but N is missing 840 try: 841 if utils.is_str(val): 842 val = val.split('#')[0].strip() # remove comments 843 if key.find('filename') < 0: 844 # and key.find('mindx') < 0: 845 val = eval(val, globals(), loc) 846 # invoke default 847 # TODO: val in ... fails with array type, because it is applied element wise! 848 # elif val in (None,(),[],{}) and default is not None: 849 elif val is None and default is not None: 850 val = eval(str(default), globals(), loc) 851 except: 852 pass # slighly optimistic: the previous is bug-free 853 return val 854 855 def corrected_key(self, key): 856 """return the matching valid key, if ``key.lower()`` is a unique 857 starting sequence to identify the valid key, ``else None`` 858 859 """ 860 matching_keys = [] 861 key = key.lower() # this was somewhat slow, so it is speed optimized now 862 if key in cma_allowed_options_keys: 863 return cma_allowed_options_keys[key] 864 for allowed_key in cma_allowed_options_keys: 865 if allowed_key.startswith(key): 866 if len(matching_keys) > 0: 867 return None 868 matching_keys.append(allowed_key) 869 return matching_keys[0] if len(matching_keys) == 1 else None 870 871 def eval(self, key, default=None, loc=None, correct_key=True): 872 """Evaluates and sets the specified option value in 873 environment `loc`. Many options need ``N`` to be defined in 874 `loc`, some need `popsize`. 875 876 Details 877 ------- 878 Keys that contain 'filename' are not evaluated. 879 For `loc` is None, the self-dict is used as environment 880 881 :See: `evalall()`, `__call__` 882 883 """ 884 # TODO: try: loc['dim'] = loc['N'] etc 885 if correct_key: 886 # in_key = key # for debugging only 887 key = self.corrected_key(key) 888 self[key] = self(key, default, loc) 889 return self[key] 890 891 def evalall(self, loc=None, defaults=None): 892 """Evaluates all option values in environment `loc`. 893 894 :See: `eval()` 895 896 """ 897 self.check() 898 if defaults is None: 899 defaults = cma_default_options_() 900 # TODO: this needs rather the parameter N instead of loc 901 if 'N' in loc: # TODO: __init__ of CMA can be simplified 902 popsize = self('popsize', defaults['popsize'], loc) 903 for k in list(self.keys()): 904 k = self.corrected_key(k) 905 self.eval(k, defaults[k], 906 {'N':loc['N'], 'popsize':popsize}) 907 self._lock_setting = True 908 return self 909 910 def match(self, s=''): 911 """return all options that match, in the name or the description, 912 with string `s`, case is disregarded. 913 914 Example: ``cma.CMAOptions().match('verb')`` returns the verbosity 915 options. 916 917 """ 918 match = s.lower() 919 res = {} 920 for k in sorted(self): 921 s = str(k) + '=\'' + str(self[k]) + '\'' 922 if match in s.lower(): 923 res[k] = self[k] 924 return CMAOptions(res) 925 926 @property 927 def to_namedtuple(self): 928 """return options as const attributes of the returned object, 929 only useful for inspection. """ 930 raise NotImplementedError 931 # return collections.namedtuple('CMAOptionsNamedTuple', 932 # self.keys())(**self) 933 934 def from_namedtuple(self, t): 935 """update options from a `collections.namedtuple`. 936 :See also: `to_namedtuple` 937 """ 938 return self.update(t._asdict()) 939 940 def pprint(self, linebreak=80): 941 for i in sorted(self.items()): 942 s = str(i[0]) + "='" + str(i[1]) + "'" 943 a = s.split(' ') 944 945 # print s in chunks 946 l = '' # start entire to the left 947 while a: 948 while a and len(l) + len(a[0]) < linebreak: 949 l += ' ' + a.pop(0) 950 print(l) 951 l = ' ' # tab for subsequent lines 952 953class CMAEvolutionStrategyResult(collections.namedtuple( 954 'CMAEvolutionStrategyResult', [ 955 'xbest', 956 'fbest', 957 'evals_best', 958 'evaluations', 959 'iterations', 960 'xfavorite', 961 'stds', 962 'stop', 963 ])): 964 """A results tuple from `CMAEvolutionStrategy` property ``result``. 965 966 This tuple contains in the given position and as attribute 967 968 - 0 ``xbest`` best solution evaluated 969 - 1 ``fbest`` objective function value of best solution 970 - 2 ``evals_best`` evaluation count when ``xbest`` was evaluated 971 - 3 ``evaluations`` evaluations overall done 972 - 4 ``iterations`` 973 - 5 ``xfavorite`` distribution mean in "phenotype" space, to be 974 considered as current best estimate of the optimum 975 - 6 ``stds`` effective standard deviations, can be used to 976 compute a lower bound on the expected coordinate-wise distance 977 to the true optimum, which is (very) approximately stds[i] * 978 dimension**0.5 / min(mueff, dimension) / 1.5 / 5 ~ std_i * 979 dimension**0.5 / min(popsize / 2, dimension) / 5, where 980 dimension = CMAEvolutionStrategy.N and mueff = 981 CMAEvolutionStrategy.sp.weights.mueff ~ 0.3 * popsize. 982 - 7 ``stop`` termination conditions in a dictionary 983 984 The penalized best solution of the last completed iteration can be 985 accessed via attribute ``pop_sorted[0]`` of `CMAEvolutionStrategy` 986 and the respective objective function value via ``fit.fit[0]``. 987 988 Details: 989 990 - This class is of purely declarative nature and for providing 991 this docstring. It does not provide any further functionality. 992 - ``list(fit.fit).find(0)`` is the index of the first sampled 993 solution of the last completed iteration in ``pop_sorted``. 994 995 """ 996 997cma_default_options = CMAOptions(cma_default_options_()) 998 999class _CMAEvolutionStrategyResult(tuple): 1000 """A results tuple from `CMAEvolutionStrategy` property ``result``. 1001 1002 This tuple contains in the given position 1003 1004 - 0 best solution evaluated, ``xbest`` 1005 - 1 objective function value of best solution, ``f(xbest)`` 1006 - 2 evaluation count when ``xbest`` was evaluated 1007 - 3 evaluations overall done 1008 - 4 iterations 1009 - 5 distribution mean in "phenotype" space, to be considered as 1010 current best estimate of the optimum 1011 - 6 effective standard deviations, give a lower bound on the expected 1012 coordinate-wise distance to the true optimum of (very) approximately 1013 std_i * dimension**0.5 / min(mueff, dimension) / 1.2 / 5 1014 ~ std_i * dimension**0.5 / min(popsize / 0.4, dimension) / 5, where 1015 mueff = CMAEvolutionStrategy.sp.weights.mueff ~ 0.3 * popsize. 1016 1017 The penalized best solution of the last completed iteration can be 1018 accessed via attribute ``pop_sorted[0]`` of `CMAEvolutionStrategy` 1019 and the respective objective function value via ``fit.fit[0]``. 1020 1021 Details: 1022 1023 - This class is of purely declarative nature and for providing this 1024 docstring. It does not provide any further functionality. 1025 - ``list(fit.fit).find(0)`` is the index of the first sampled solution 1026 of the last completed iteration in ``pop_sorted``. 1027 1028""" # here starts the code: (beating the code folding glitch) 1029 # remark: a tuple is immutable, hence we cannot change it anymore 1030 # in __init__. This would work if we inherited from a `list`. 1031 @staticmethod 1032 def _generate(self): 1033 """return a results tuple of type `CMAEvolutionStrategyResult`. 1034 1035 `_generate` is a surrogate for the ``__init__`` method, which 1036 cannot be used to initialize the immutable `tuple` super class. 1037 """ 1038 return _CMAEvolutionStrategyResult( 1039 self.best.get() + ( # (x, f, evals) triple 1040 self.countevals, 1041 self.countiter, 1042 self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair), 1043 self.gp.scales * self.sigma * self.sigma_vec.scaling * 1044 self.dC**0.5)) 1045 1046class CMAEvolutionStrategy(interfaces.OOOptimizer): 1047 """CMA-ES stochastic optimizer class with ask-and-tell interface. 1048 1049 Calling Sequences 1050 ================= 1051 1052 - ``es = CMAEvolutionStrategy(x0, sigma0)`` 1053 1054 - ``es = CMAEvolutionStrategy(x0, sigma0, opts)`` 1055 1056 - ``es = CMAEvolutionStrategy(x0, sigma0).optimize(objective_fct)`` 1057 1058 - :: 1059 1060 res = CMAEvolutionStrategy(x0, sigma0, 1061 opts).optimize(objective_fct).result 1062 1063 Arguments 1064 ========= 1065 `x0` 1066 initial solution, starting point. `x0` is given as "phenotype" 1067 which means, if:: 1068 1069 opts = {'transformation': [transform, inverse]} 1070 1071 is given and ``inverse is None``, the initial mean is not 1072 consistent with `x0` in that ``transform(mean)`` does not 1073 equal to `x0` unless ``transform(mean)`` equals ``mean``. 1074 `sigma0` 1075 initial standard deviation. The problem variables should 1076 have been scaled, such that a single standard deviation 1077 on all variables is useful and the optimum is expected to 1078 lie within about `x0` +- ``3*sigma0``. Often one wants to 1079 check for solutions close to the initial point. This allows, 1080 for example, for an easier check of consistency of the 1081 objective function and its interfacing with the optimizer. 1082 In this case, a much smaller `sigma0` is advisable. 1083 `opts` 1084 options, a dictionary with optional settings, 1085 see class `CMAOptions`. 1086 1087 Main interface / usage 1088 ====================== 1089 The interface is inherited from the generic `OOOptimizer` 1090 class (see also there). An object instance is generated from:: 1091 1092 es = cma.CMAEvolutionStrategy(8 * [0.5], 0.2) 1093 1094 The least verbose interface is via the optimize method:: 1095 1096 es.optimize(objective_func) 1097 res = es.result 1098 1099 More verbosely, the optimization is done using the 1100 methods `stop`, `ask`, and `tell`:: 1101 1102 while not es.stop(): 1103 solutions = es.ask() 1104 es.tell(solutions, [cma.ff.rosen(s) for s in solutions]) 1105 es.disp() 1106 es.result_pretty() 1107 1108 1109 where `ask` delivers new candidate solutions and `tell` updates 1110 the `optim` instance by passing the respective function values 1111 (the objective function `cma.ff.rosen` can be replaced by any 1112 properly defined objective function, see `cma.ff` for more 1113 examples). 1114 1115 To change an option, for example a termination condition to 1116 continue the optimization, call:: 1117 1118 es.opts.set({'tolfacupx': 1e4}) 1119 1120 The class `CMAEvolutionStrategy` also provides:: 1121 1122 (solutions, func_values) = es.ask_and_eval(objective_func) 1123 1124 and an entire optimization can also be written like:: 1125 1126 while not es.stop(): 1127 es.tell(*es.ask_and_eval(objective_func)) 1128 1129 Besides for termination criteria, in CMA-ES only the ranks of the 1130 `func_values` are relevant. 1131 1132 Attributes and Properties 1133 ========================= 1134 - `inputargs`: passed input arguments 1135 - `inopts`: passed options 1136 - `opts`: actually used options, some of them can be changed any 1137 time via ``opts.set``, see class `CMAOptions` 1138 - `popsize`: population size lambda, number of candidate 1139 solutions returned by `ask` () 1140 - `logger`: a `CMADataLogger` instance utilized by `optimize` 1141 1142 Examples 1143 ======== 1144 Super-short example, with output shown: 1145 1146 >>> import cma 1147 >>> # construct an object instance in 4-D, sigma0=1: 1148 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1, {'seed':234}) 1149 ... # doctest: +ELLIPSIS 1150 (4_w,8)-aCMA-ES (mu_w=2.6,w_1=52%) in dimension 4 (seed=234...) 1151 1152 and optimize the ellipsoid function 1153 1154 >>> es.optimize(cma.ff.elli, verb_disp=1) # doctest: +ELLIPSIS 1155 Iterat #Fevals function value axis ratio sigma min&max std t[m:s] 1156 1 8 2.09... 1157 >>> assert len(es.result) == 8 1158 >>> assert es.result[1] < 1e-9 1159 1160 The optimization loop can also be written explicitly: 1161 1162 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1) # doctest: +ELLIPSIS 1163 (4_w,8)-aCMA-ES (mu_w=2.6,w_1=52%) in dimension 4 (seed=... 1164 >>> while not es.stop(): 1165 ... X = es.ask() 1166 ... es.tell(X, [cma.ff.elli(x) for x in X]) 1167 ... es.disp() # doctest: +ELLIPSIS 1168 Iterat #Fevals function value axis ratio sigma min&max std t[m:s] 1169 1 8 ... 1170 1171 achieving the same result as above. 1172 1173 An example with lower bounds (at zero) and handling infeasible 1174 solutions: 1175 1176 >>> import numpy as np 1177 >>> es = cma.CMAEvolutionStrategy(10 * [0.2], 0.5, 1178 ... {'bounds': [0, np.inf]}) #doctest: +ELLIPSIS 1179 (5_w,... 1180 >>> while not es.stop(): 1181 ... fit, X = [], [] 1182 ... while len(X) < es.popsize: 1183 ... curr_fit = None 1184 ... while curr_fit in (None, np.NaN): 1185 ... x = es.ask(1)[0] 1186 ... curr_fit = cma.ff.somenan(x, cma.ff.elli) # might return np.NaN 1187 ... X.append(x) 1188 ... fit.append(curr_fit) 1189 ... es.tell(X, fit) 1190 ... es.logger.add() 1191 ... es.disp() #doctest: +ELLIPSIS 1192 Itera... 1193 >>> 1194 >>> assert es.result[1] < 1e-9 1195 >>> assert es.result[2] < 9000 # by internal termination 1196 >>> # es.logger.plot() # will plot data 1197 >>> # cma.s.figshow() # display plot window 1198 1199 An example with user-defined transformation, in this case to realize 1200 a lower bound of 2. 1201 1202 >>> import warnings 1203 >>> with warnings.catch_warnings(record=True) as warns: 1204 ... es = cma.CMAEvolutionStrategy(5 * [3], 0.1, 1205 ... {"transformation": [lambda x: x**2+1.2, None], 1206 ... "verbose": -2,}) 1207 >>> warns[0].message # doctest:+ELLIPSIS 1208 UserWarning('in class GenoPheno: user defined transformations have not been tested thoroughly ()'... 1209 >>> warns[1].message # doctest:+ELLIPSIS 1210 UserWarning('computed initial point... 1211 >>> es.optimize(cma.ff.rosen, verb_disp=0) #doctest: +ELLIPSIS 1212 <cma... 1213 >>> assert cma.ff.rosen(es.result[0]) < 1e-7 + 5.54781521192 1214 >>> assert es.result[2] < 3300 1215 1216 The inverse transformation is (only) necessary if the `BoundPenalty` 1217 boundary handler is used at the same time. 1218 1219 The `CMAEvolutionStrategy` class also provides a default logger 1220 (cave: files are overwritten when the logger is used with the same 1221 filename prefix): 1222 1223 >>> es = cma.CMAEvolutionStrategy(4 * [0.2], 0.5, {'verb_disp': 0}) 1224 >>> es.logger.disp_header() # annotate the print of disp 1225 Iterat Nfevals function value axis ratio maxstd minstd 1226 >>> while not es.stop(): 1227 ... X = es.ask() 1228 ... es.tell(X, [cma.ff.sphere(x) for x in X]) 1229 ... es.logger.add() # log current iteration 1230 ... es.logger.disp([-1]) # display info for last iteration #doctest: +ELLIPSIS 1231 1 ... 1232 >>> es.logger.disp_header() 1233 Iterat Nfevals function value axis ratio maxstd minstd 1234 >>> # es.logger.plot() # will make a plot 1235 1236 Example implementing restarts with increasing popsize (IPOP): 1237 1238 >>> bestever = cma.optimization_tools.BestSolution() 1239 >>> for lam in 10 * 2**np.arange(8): # 10, 20, 40, 80, ..., 10 * 2**7 1240 ... es = cma.CMAEvolutionStrategy(6 - 8 * np.random.rand(4), # 4-D 1241 ... 5, # initial std sigma0 1242 ... {'popsize': lam, # options 1243 ... 'verb_append': bestever.evalsall}) 1244 ... # logger = cma.CMADataLogger().register(es, append=bestever.evalsall) 1245 ... while not es.stop(): 1246 ... X = es.ask() # get list of new solutions 1247 ... fit = [cma.ff.rastrigin(x) for x in X] # evaluate each solution 1248 ... es.tell(X, fit) # besides for termination only the ranking in fit is used 1249 ... 1250 ... # display some output 1251 ... # logger.add() # add a "data point" to the log, writing in files 1252 ... es.disp() # uses option verb_disp with default 100 1253 ... 1254 ... print('termination:', es.stop()) 1255 ... cma.s.pprint(es.best.__dict__) 1256 ... 1257 ... bestever.update(es.best) 1258 ... 1259 ... # show a plot 1260 ... # logger.plot(); 1261 ... if bestever.f < 1e-8: # global optimum was hit 1262 ... break #doctest: +ELLIPSIS 1263 (5_w,... 1264 >>> assert es.result[1] < 1e-8 1265 1266 On the Rastrigin function, usually after five restarts the global 1267 optimum is located. 1268 1269 Using the `multiprocessing` module, we can evaluate the function in 1270 parallel with a simple modification of the example (however 1271 multiprocessing seems not always reliable): 1272 1273 >>> from cma.fitness_functions import elli # cannot be an instance method 1274 >>> from cma.optimization_tools import EvalParallel2 1275 >>> es = cma.CMAEvolutionStrategy(22 * [0.0], 1.0, {'maxiter':10}) # doctest:+ELLIPSIS 1276 (6_w,13)-aCMA-ES (mu_w=... 1277 >>> with EvalParallel2(elli, es.popsize + 1) as eval_all: 1278 ... while not es.stop(): 1279 ... X = es.ask() 1280 ... es.tell(X, eval_all(X)) 1281 ... es.disp() 1282 ... # es.logger.add() # doctest:+ELLIPSIS 1283 Iterat... 1284 1285 The final example shows how to resume: 1286 1287 >>> import pickle 1288 >>> 1289 >>> es0 = cma.CMAEvolutionStrategy(12 * [0.1], # a new instance, 12-D 1290 ... 0.12) # initial std sigma0 1291 ... #doctest: +ELLIPSIS 1292 (5_w,... 1293 >>> es0.optimize(cma.ff.rosen, iterations=100) #doctest: +ELLIPSIS 1294 I... 1295 >>> s = es0.pickle_dumps() # return pickle.dumps(es) with safeguards 1296 >>> # save string s to file like open(filename, 'wb').write(s) 1297 >>> del es0 # let's start fresh 1298 >>> # s = open(filename, 'rb').read() # load string s from file 1299 >>> es = pickle.loads(s) # read back es instance from string 1300 >>> # resuming 1301 >>> es.optimize(cma.ff.rosen, verb_disp=200) #doctest: +ELLIPSIS 1302 200 ... 1303 >>> assert es.result[2] < 15000 1304 >>> assert cma.s.Mh.vequals_approximately(es.result[0], 12 * [1], 1e-5) 1305 >>> assert len(es.result) == 8 1306 1307 Details 1308 ======= 1309 The following two enhancements are implemented, the latter is only 1310 turned on by default for very small population sizes. 1311 1312 *Active CMA* is implemented with option ``CMA_active`` and 1313 conducts an update of the covariance matrix with negative weights. 1314 The negative update is implemented, such that positive definiteness 1315 is guarantied. A typical speed up factor (number of f-evaluations) 1316 is between 1.1 and two. 1317 1318 References: Jastrebski and Arnold, Improving evolution strategies 1319 through active covariance matrix adaptation, CEC 2006. 1320 Hansen, The CMA evolution strategy: a tutorial, arXiv 2016. 1321 1322 *Selective mirroring* is implemented with option ``CMA_mirrors`` 1323 in the method `get_mirror` and `get_selective_mirrors`. 1324 The method `ask_and_eval` (used by `fmin`) will then sample 1325 selectively mirrored vectors within the iteration 1326 (``CMA_mirrormethod==1``). Otherwise, or if ``CMA_mirromethod==2``, 1327 selective mirrors are injected for the next iteration. 1328 In selective mirroring, only the worst solutions are mirrored. With 1329 the default small number of mirrors, *pairwise selection* (where at 1330 most one of the two mirrors contribute to the update of the 1331 distribution mean) is implicitly guarantied under selective 1332 mirroring and therefore not explicitly implemented. 1333 1334 Update: pairwise selection for injected mirrors is also applied in the 1335 covariance matrix update: for all injected solutions, as for those from 1336 TPA, this is now implemented in that the recombination weights are 1337 constrained to be nonnegative for injected solutions in the covariance 1338 matrix (otherwise recombination weights are anyway nonnegative). This 1339 is a precaution to prevent failure when injected solutions are 1340 systematically bad (see e.g. https://github.com/CMA-ES/pycma/issues/124), 1341 but may not be "optimal" for mirrors. 1342 1343 References: Brockhoff et al, PPSN 2010, Auger et al, GECCO 2011. 1344 1345 :See also: `fmin` (), `OOOptimizer`, `CMAOptions`, `plot` (), `ask` (), 1346 `tell` (), `ask_and_eval` () 1347 1348""" # here starts the code: (beating the code folding glitch) 1349 @property # read only attribute decorator for a method 1350 def popsize(self): 1351 """number of samples by default returned by `ask` () 1352 """ 1353 return self.sp.popsize 1354 1355 # this is not compatible with python2.5: 1356 # @popsize.setter 1357 # def popsize(self, p): 1358 # """popsize cannot be set (this might change in future) 1359 # """ 1360 # raise RuntimeError("popsize cannot be changed") 1361 1362 def stop(self, check=True, ignore_list=(), check_in_same_iteration=False, 1363 get_value=None): 1364 """return the termination status as dictionary. 1365 1366 With ``check == False``, the termination conditions are not checked 1367 and the status might not reflect the current situation. 1368 ``check_on_same_iteration == False`` (new) does not re-check during 1369 the same iteration. When termination options are manually changed, 1370 it must be set to `True` to advance afterwards. 1371 ``stop().clear()`` removes the currently active termination 1372 conditions. 1373 1374 As a convenience feature, keywords in `ignore_list` are removed from 1375 the conditions. 1376 1377 If `get_value` is set to a condition name (not the empty string), 1378 `stop` does not update the termination dictionary but returns the 1379 measured value that would be compared to the threshold. This only 1380 works for some conditions, like 'tolx'. If the condition name is 1381 not known or cannot be computed, `None` is returned and no warning 1382 is issued. 1383 1384 Testing `get_value` functionality: 1385 1386 >>> import cma 1387 >>> es = cma.CMAEvolutionStrategy(2 * [1], 1e4, {'verbose': -9}) 1388 >>> with warnings.catch_warnings(record=True) as w: 1389 ... es.stop(get_value='tolx') # triggers zero iteration warning 1390 ... assert len(w) == 1, [str(wi) for wi in w] 1391 >>> es = es.optimize(cma.ff.sphere, iterations=4) 1392 >>> assert 1e3 < es.stop(get_value='tolx') < 1e4, es.stop(get_value='tolx') 1393 >>> assert es.stop() == {} 1394 >>> assert es.stop(get_value='catch 22') is None 1395 1396 """ 1397 if (check and self.countiter > 0 and self.opts['termination_callback'] and 1398 self.opts['termination_callback'] != str(self.opts['termination_callback'])): 1399 self.callbackstop = utils.ListOfCallables(self.opts['termination_callback'])(self) 1400 1401 self._stopdict._get_value = get_value # a hack to avoid passing arguments down to _add_stop and back 1402 # check_on_same_iteration == False makes como code much faster 1403 res = self._stopdict(self, check_in_same_iteration or get_value or ( # update the stopdict and return a Dict (self) 1404 check and self.countiter != self._stopdict.lastiter)) 1405 if ignore_list: 1406 for key in ignore_list: 1407 res.pop(key, None) 1408 if get_value: # deliver _value and reset 1409 res, self._stopdict._value = self._stopdict._value, None 1410 return res 1411 1412 def __init__(self, x0, sigma0, inopts=None): 1413 """see class `CMAEvolutionStrategy` 1414 1415 """ 1416 self.inputargs = dict(locals()) # for the record 1417 del self.inputargs['self'] # otherwise the instance self has a cyclic reference 1418 if inopts is None: 1419 inopts = {} 1420 self.inopts = inopts 1421 opts = CMAOptions(inopts).complement() # CMAOptions() == fmin([],[]) == defaultOptions() 1422 if opts.eval('verbose') is None: 1423 opts['verbose'] = CMAOptions()['verbose'] 1424 utils.global_verbosity = global_verbosity = opts.eval('verbose') 1425 if global_verbosity < -8: 1426 opts['verb_disp'] = 0 1427 opts['verb_log'] = 0 1428 opts['verb_plot'] = 0 1429 1430 if 'noise_handling' in opts and opts.eval('noise_handling'): 1431 raise ValueError('noise_handling not available with class CMAEvolutionStrategy, use function fmin') 1432 if 'restarts' in opts and opts.eval('restarts'): 1433 raise ValueError('restarts not available with class CMAEvolutionStrategy, use function fmin') 1434 1435 self._set_x0(x0) # manage weird shapes, set self.x0 1436 self.N_pheno = len(self.x0) 1437 1438 self.sigma0 = sigma0 1439 if utils.is_str(sigma0): 1440 raise ValueError("sigma0 must be a scalar, a string is no longer permitted") 1441 # self.sigma0 = eval(sigma0) # like '1./N' or 'np.random.rand(1)[0]+1e-2' 1442 if np.size(self.sigma0) != 1 or np.shape(self.sigma0): 1443 raise ValueError('input argument sigma0 must be (or evaluate to) a scalar') 1444 self.sigma = self.sigma0 # goes to inialize 1445 1446 # extract/expand options 1447 N = self.N_pheno 1448 if utils.is_str(opts['fixed_variables']): 1449 opts['fixed_variables'] = ast.literal_eval( 1450 opts['fixed_variables']) 1451 assert (isinstance(opts['fixed_variables'], dict) 1452 or opts['fixed_variables'] is None) 1453 if isinstance(opts['fixed_variables'], dict): 1454 N = self.N_pheno - len(opts['fixed_variables']) 1455 opts.evalall(locals()) # using only N 1456 if np.isinf(opts['CMA_diagonal']): 1457 opts['CMA_diagonal'] = True 1458 self.opts = opts 1459 self.randn = opts['randn'] 1460 if not utils.is_nan(opts['seed']): 1461 if self.randn is np.random.randn: 1462 if not opts['seed'] or opts['seed'] is time: 1463 np.random.seed() 1464 six_decimals = (time.time() - 1e6 * (time.time() // 1e6)) 1465 opts['seed'] = int(1e5 * np.random.rand() + six_decimals 1466 + 1e5 * (time.time() % 1)) 1467 np.random.seed(opts['seed']) # a printable seed 1468 elif opts['seed'] not in (None, time): 1469 utils.print_warning("seed=%s will never be used (seed is only used if option 'randn' is np.random.randn)" 1470 % str(opts['seed'])) 1471 self.gp = transformations.GenoPheno(self.N_pheno, 1472 opts['scaling_of_variables'], 1473 opts['typical_x'], 1474 opts['fixed_variables'], 1475 opts['transformation']) 1476 1477 self.boundary_handler = opts['BoundaryHandler'] 1478 if isinstance(self.boundary_handler, type): 1479 self.boundary_handler = self.boundary_handler(opts['bounds']) 1480 elif opts['bounds'] not in (None, False, [], [None, None]): 1481 utils.print_warning(""" 1482 Option 'bounds' ignored because a BoundaryHandler *instance* was found. 1483 Consider to pass only the desired BoundaryHandler class. """, 1484 CMAEvolutionStrategy.__init__) 1485 if not self.boundary_handler.has_bounds(): 1486 self.boundary_handler = BoundNone() # just a little faster and well defined 1487 elif not self.boundary_handler.is_in_bounds(self.x0): 1488 if opts['verbose'] >= 0: 1489 utils.print_warning(""" 1490 Initial solution is out of the domain boundaries: 1491 x0 = %s 1492 ldom = %s 1493 udom = %s 1494 THIS MIGHT LEAD TO AN EXCEPTION RAISED LATER ON. 1495 """ % (str(self.gp.pheno(self.x0)), 1496 str(self.boundary_handler.bounds[0]), 1497 str(self.boundary_handler.bounds[1])), 1498 '__init__', 'CMAEvolutionStrategy') 1499 1500 # set self.mean to geno(x0) 1501 tf_geno_backup = self.gp.tf_geno 1502 if self.gp.tf_pheno and self.gp.tf_geno is None: 1503 self.gp.tf_geno = lambda x: x # a hack to avoid an exception 1504 utils.print_warning( 1505 "computed initial point may well be wrong, because no\n" 1506 "inverse for the user provided phenotype transformation " 1507 "was given") 1508 self.mean = self.gp.geno(np.array(self.x0, copy=True), 1509 from_bounds=self.boundary_handler.inverse, 1510 copy=False) 1511 self.mean0 = array(self.mean, copy=True) # relevant for initial injection 1512 self.gp.tf_geno = tf_geno_backup 1513 # without copy_always interface: 1514 # self.mean = self.gp.geno(array(self.x0, copy=True), copy_if_changed=False) 1515 self.N = len(self.mean) 1516 assert N == self.N 1517 # self.fmean = np.NaN # TODO name should change? prints nan in output files (OK with matlab&octave) 1518 # self.fmean_noise_free = 0. # for output only 1519 1520 self.sp = _CMAParameters(N, opts, verbose=opts['verbose'] > 0) 1521 self.sp0 = self.sp # looks useless, as it is not a copy 1522 1523 def instantiate_adapt_sigma(adapt_sigma, self): 1524 """return instantiated sigma adaptation object""" 1525 if adapt_sigma is None: 1526 utils.print_warning( 1527 "Value `None` for option 'AdaptSigma' is ambiguous and\n" 1528 "hence deprecated. AdaptSigma can be set to `True` or\n" 1529 "`False` or a class or class instance which inherited from\n" 1530 "`cma.sigma_adaptation.CMAAdaptSigmaBase`") 1531 adapt_sigma = CMAAdaptSigmaCSA 1532 elif adapt_sigma is True: 1533 if self.opts['CMA_diagonal'] is True and self.N > 299: 1534 adapt_sigma = CMAAdaptSigmaTPA 1535 else: 1536 adapt_sigma = CMAAdaptSigmaCSA 1537 elif adapt_sigma is False: 1538 adapt_sigma = CMAAdaptSigmaNone() 1539 if isinstance(adapt_sigma, type): # is a class? 1540 # then we want the instance 1541 adapt_sigma = adapt_sigma(dimension=self.N, popsize=self.sp.popsize) 1542 return adapt_sigma 1543 self.adapt_sigma = instantiate_adapt_sigma(opts['AdaptSigma'], self) 1544 1545 self.mean_shift_samples = True if (isinstance(self.adapt_sigma, CMAAdaptSigmaTPA) or 1546 opts['mean_shift_line_samples']) else False 1547 1548 def eval_vector(in_, opts, N, default_value=1.0): 1549 """return `default_value` as scalar or `in_` after removing 1550 fixed variables if ``len(in_) == N`` 1551 """ 1552 res = default_value 1553 if in_ is not None: 1554 if np.size(in_) == 1: # return scalar value 1555 try: 1556 res = float(in_[0]) 1557 except TypeError: 1558 res = float(in_) 1559 elif opts['fixed_variables'] and np.size(in_) > N: 1560 res = array([in_[i] for i in range(len(in_)) 1561 if i not in opts['fixed_variables']], 1562 dtype=float) 1563 if len(res) != N: 1564 utils.print_warning( 1565 "resulting len %d != N = %d" % (len(res), N), 1566 'eval_vector', iteration=self.countiter) 1567 else: 1568 res = array(in_, dtype=float) 1569 if np.size(res) not in (1, N): 1570 raise ValueError( 1571 "CMA_stds option must have dimension %d " 1572 "instead of %d" % (N, np.size(res))) 1573 return res 1574 1575 opts['minstd'] = eval_vector(opts['minstd'], opts, N, 0) 1576 opts['maxstd'] = eval_vector(opts['maxstd'], opts, N, np.inf) 1577 1578 # iiinteger handling, currently very basic: 1579 # CAVEAT: integer indices may give unexpected results if fixed_variables is used 1580 if len(opts['integer_variables']) and opts['fixed_variables']: 1581 utils.print_warning( 1582 "CAVEAT: fixed_variables change the meaning of " 1583 "integer_variables indices") 1584 # 1) prepare minstd to be a vector 1585 if (len(opts['integer_variables']) and 1586 np.isscalar(opts['minstd'])): 1587 opts['minstd'] = N * [opts['minstd']] 1588 # 2) set minstd to 1 / (2 Nint + 1), 1589 # the setting 2 / (2 Nint + 1) already prevents convergence 1590 for i in opts['integer_variables']: 1591 if -N <= i < N: 1592 opts['minstd'][i] = max((opts['minstd'][i], 1593 1 / (2 * len(opts['integer_variables']) + 1))) 1594 else: 1595 utils.print_warning( 1596 """integer index %d not in range of dimension %d""" % 1597 (i, N)) 1598 1599 # initialization of state variables 1600 self.countiter = 0 1601 self.countevals = max((0, opts['verb_append'])) \ 1602 if not isinstance(opts['verb_append'], bool) else 0 1603 self.pc = np.zeros(N) 1604 self.pc_neg = np.zeros(N) 1605 if 1 < 3: # new version with class 1606 self.sigma_vec0 = eval_vector(self.opts['CMA_stds'], opts, N) 1607 self.sigma_vec = transformations.DiagonalDecoding(self.sigma_vec0) 1608 if np.isfinite(self.opts['CMA_dampsvec_fac']): 1609 self.sigma_vec *= np.ones(N) # make sure to get a vector 1610 else: 1611 self.sigma_vec = eval_vector(self.opts['CMA_stds'], opts, N) 1612 if np.isfinite(self.opts['CMA_dampsvec_fac']): 1613 self.sigma_vec *= np.ones(N) # make sure to get a vector 1614 self.sigma_vec0 = self.sigma_vec if np.isscalar(self.sigma_vec) \ 1615 else self.sigma_vec.copy() 1616 stds = eval_vector(self.opts['CMA_teststds'], opts, N) 1617 if self.opts['CMA_diagonal']: # is True or > 0 1618 # linear time and space complexity 1619 self.sigma_vec = transformations.DiagonalDecoding(stds * np.ones(N)) 1620 self.sm = sampler.GaussStandardConstant(N, randn=self.opts['randn']) 1621 self._updateBDfromSM(self.sm) 1622 if self.opts['CMA_diagonal'] is True: 1623 self.sp.weights.finalize_negative_weights(N, 1624 self.sp.c1_sep, 1625 self.sp.cmu_sep, 1626 pos_def=False) 1627 elif self.opts['CMA_diagonal'] == 1: 1628 raise ValueError("""Option 'CMA_diagonal' == 1 is disallowed. 1629 Use either `True` or an iteration number > 1 up to which C should be diagonal. 1630 Only `True` has linear memory demand.""") 1631 else: # would ideally be done when switching 1632 self.sp.weights.finalize_negative_weights(N, 1633 self.sp.c1, 1634 self.sp.cmu) 1635 else: 1636 if 11 < 3: 1637 if hasattr(self.opts['vv'], '__getitem__') and \ 1638 'sweep_ccov' in self.opts['vv']: 1639 self.opts['CMA_const_trace'] = True 1640 if self.opts['CMA_sampler'] is None: 1641 self.sm = sampler.GaussFullSampler(stds * np.ones(N), 1642 lazy_update_gap=( 1643 1. / (self.sp.c1 + self.sp.cmu + 1e-23) / self.N / 10 1644 if self.opts['updatecovwait'] is None 1645 else self.opts['updatecovwait']), 1646 constant_trace=self.opts['CMA_const_trace'], 1647 randn=self.opts['randn'], 1648 eigenmethod=self.opts['CMA_eigenmethod'], 1649 ) 1650 p = self.sm.parameters(mueff=self.sp.weights.mueff, 1651 lam=self.sp.weights.lambda_) 1652 self.sp.weights.finalize_negative_weights(N, p['c1'], p['cmu']) 1653 elif isinstance(self.opts['CMA_sampler'], type): 1654 try: 1655 self.sm = self.opts['CMA_sampler']( 1656 stds * np.ones(N), 1657 **self.opts['CMA_sampler_options']) 1658 except: 1659 if max(stds) > min(stds): 1660 utils.print_warning("different initial standard" 1661 " deviations are not supported by the current" 1662 " sampler and hence ignored") 1663 elif stds[0] != 1: 1664 utils.print_warning("""ignoring scaling factor %f 1665 for sample distribution""" % stds[0]) 1666 self.sm = self.opts['CMA_sampler'](N, 1667 **self.opts['CMA_sampler_options']) 1668 else: # CMA_sampler is already initialized as class instance 1669 self.sm = self.opts['CMA_sampler'] 1670 if not isinstance(self.sm, interfaces.StatisticalModelSamplerWithZeroMeanBaseClass): 1671 utils.print_warning("""statistical model sampler did 1672 not evaluate to the expected type `%s` but to type `%s`. This is 1673 likely to lead to an exception later on. """ % ( 1674 str(type(interfaces.StatisticalModelSamplerWithZeroMeanBaseClass)), 1675 str(type(self.sm)))) 1676 self._updateBDfromSM(self.sm) 1677 self.dC = self.sm.variances 1678 self.D = self.dC**0.5 # we assume that the initial C is diagonal 1679 self.pop_injection_solutions = [] 1680 self.pop_injection_directions = [] 1681 self.number_of_solutions_asked = 0 1682 self.number_of_injections_delivered = 0 # used/delivered in asked 1683 1684 # self.gp.pheno adds fixed variables 1685 relative_stds = ((self.gp.pheno(self.mean + self.sigma * self.sigma_vec * self.D) 1686 - self.gp.pheno(self.mean - self.sigma * self.sigma_vec * self.D)) / 2.0 1687 / (self.boundary_handler.get_bounds('upper', self.N_pheno) 1688 - self.boundary_handler.get_bounds('lower', self.N_pheno))) 1689 if np.any(relative_stds > 1): 1690 idx = np.nonzero(relative_stds > 1)[0] 1691 s = ("Initial standard deviation " 1692 "%s larger than the bounded domain size in variable %s.\n" 1693 "Consider using option 'CMA_stds', if the bounded " 1694 "domain sizes differ significantly. " 1695 % (("s (sigma0*stds) are", str(idx)) 1696 if len(idx) > 1 else (" (sigma0*stds) is", 1697 str(idx[0])))) 1698 raise ValueError(s) 1699 self._flgtelldone = True 1700 self.itereigenupdated = self.countiter 1701 self.count_eigen = 0 1702 self.noiseS = 0 # noise "signal" 1703 self.hsiglist = [] 1704 1705 self.sent_solutions = _CMASolutionDict() 1706 self.archive = _CMASolutionDict() 1707 self._injected_solutions_archive = _SolutionDict() 1708 self.best = ot.BestSolution() 1709 1710 self.const = _BlancClass() 1711 self.const.chiN = N**0.5 * (1 - 1. / (4.*N) + 1. / (21.*N**2)) # expectation of norm(randn(N,1)) 1712 1713 self.logger = CMADataLogger(opts['verb_filenameprefix'], 1714 modulo=opts['verb_log'], 1715 expensive_modulo=opts['verb_log_expensive']).register(self) 1716 1717 self._stopdict = _CMAStopDict() 1718 " attribute for stopping criteria in function stop" 1719 self.callbackstop = () 1720 " return values of callbacks, used like ``if any(callbackstop)``" 1721 self.fit = _BlancClass() 1722 self.fit.fit = [] # not really necessary 1723 self.fit.hist = [] # short history of best 1724 self.fit.histbest = [] # long history of best 1725 self.fit.histmedian = [] # long history of median 1726 self.fit.median = None 1727 self.fit.median0 = None 1728 self.fit.median_min = np.inf 1729 self.fit.flatfit_iterations = 0 1730 1731 self.more_to_write = utils.MoreToWrite() # [1, 1, 1, 1] # N*[1] # needed when writing takes place before setting 1732 1733 # say hello 1734 if opts['verb_disp'] > 0 and opts['verbose'] >= 0: 1735 sweighted = '_w' if self.sp.weights.mu > 1 else '' 1736 smirr = 'mirr%d' % (self.sp.lam_mirr) if self.sp.lam_mirr else '' 1737 print('(%d' % (self.sp.weights.mu) + sweighted + ',%d' % (self.sp.popsize) + smirr + 1738 ')-' + ('a' if opts['CMA_active'] else '') + 'CMA-ES' + 1739 ' (mu_w=%2.1f,w_1=%d%%)' % (self.sp.weights.mueff, int(100 * self.sp.weights[0])) + 1740 ' in dimension %d (seed=%s, %s)' % (N, str(opts['seed']), time.asctime())) # + func.__name__ 1741 if opts['CMA_diagonal'] and self.sp.CMA_on: 1742 s = '' 1743 if opts['CMA_diagonal'] is not True: 1744 s = ' for ' 1745 if opts['CMA_diagonal'] < np.inf: 1746 s += str(int(opts['CMA_diagonal'])) 1747 else: 1748 s += str(np.floor(opts['CMA_diagonal'])) 1749 s += ' iterations' 1750 s += ' (1/ccov=' + str(round(1. / (self.sp.c1 + self.sp.cmu))) + ')' 1751 print(' Covariance matrix is diagonal' + s) 1752 1753 def _set_x0(self, x0): 1754 """Assign `self.x0` from argument `x0`. 1755 1756 Input `x0` may be a `callable` or a `list` or `numpy.ndarray` of 1757 the desired length. 1758 1759 Below an artificial example is given, where calling `x0` 1760 delivers in the first two calls ``dimension * [5]`` and in 1761 succeeding calls``dimension * [0.01]``. Only the initial value of 1762 0.01 solves the Rastrigin function: 1763 1764 >>> import cma 1765 >>> class X0: 1766 ... def __init__(self, dimension): 1767 ... self.irun = 0 1768 ... self.dimension = dimension 1769 ... def __call__(self): 1770 ... """""" 1771 ... self.irun += 1 1772 ... return (self.dimension * [5] if self.irun < 3 1773 ... else self.dimension * [0.01]) 1774 >>> xopt, es = cma.fmin2(cma.ff.rastrigin, X0(3), 0.01, 1775 ... {'verbose':-9}, restarts=1) 1776 >>> assert es.result.fbest > 1e-5 1777 >>> xopt, es = cma.fmin2(cma.ff.rastrigin, X0(3), 0.01, 1778 ... {'verbose':-9}, restarts=2) 1779 >>> assert es.result.fbest < 1e-5 # third run succeeds due to x0 1780 1781 """ 1782 try: 1783 x0 = x0() 1784 except TypeError: 1785 if utils.is_str(x0): 1786 raise ValueError("x0 may be a callable, but a string is no longer permitted") 1787 # x0 = eval(x0) 1788 self.x0 = array(x0, dtype=float, copy=True) # should not have column or row, is just 1-D 1789 if self.x0.ndim == 2 and 1 in self.x0.shape: 1790 utils.print_warning('input x0 should be a list or 1-D array, trying to flatten ' + 1791 str(self.x0.shape) + '-array') 1792 if self.x0.shape[0] == 1: 1793 self.x0 = self.x0[0] 1794 elif self.x0.shape[1] == 1: 1795 self.x0 = array([x[0] for x in self.x0]) 1796 if self.x0.ndim != 1: 1797 raise ValueError('x0 must be 1-D array') 1798 if len(self.x0) <= 1: 1799 raise ValueError('Could not digest initial solution argument x0=%s.\n' 1800 'Optimization in 1-D is not supported (code was never tested)' 1801 % str(self.x0)) 1802 try: 1803 self.x0.resize(self.x0.shape[0]) # 1-D array, not really necessary?! 1804 except NotImplementedError: 1805 pass 1806 1807 def _copy_light(self, sigma=None, inopts=None): 1808 """tentative copy of self, versatile (interface and functionalities may change). 1809 1810 `sigma` overwrites the original initial `sigma`. 1811 `inopts` allows to overwrite any of the original options. 1812 1813 This copy may not work as expected depending on the used sampler. 1814 1815 Copy mean and sample distribution parameters and input options. Do 1816 not copy evolution paths, termination status or other state variables. 1817 1818 >>> import cma 1819 >>> es = cma.CMAEvolutionStrategy(3 * [1], 0.1, 1820 ... {'verbose':-9}).optimize(cma.ff.elli, iterations=10) 1821 >>> es2 = es._copy_light() 1822 >>> assert es2.sigma == es.sigma 1823 >>> assert sum((es.sm.C - es2.sm.C).flat < 1e-12) 1824 >>> es3 = es._copy_light(sigma=10) 1825 >>> assert es3.sigma == es3.sigma0 == 10 1826 >>> es4 = es._copy_light(inopts={'CMA_on': False}) 1827 >>> assert es4.sp.c1 == es4.sp.cmu == 0 1828 1829 """ 1830 if sigma is None: 1831 sigma = self.sigma 1832 opts = dict(self.inopts) 1833 if inopts is not None: 1834 opts.update(inopts) 1835 es = type(self)(self.mean[:], sigma, opts) 1836 es.sigma_vec = transformations.DiagonalDecoding(self.sigma_vec.scaling) 1837 try: es.sm.C = self.sm.C.copy() 1838 except: warnings.warn("self.sm.C.copy failed") 1839 es.sm.update_now(-1) # make B and D consistent with C 1840 es._updateBDfromSM() 1841 return es 1842 1843 # ____________________________________________________________ 1844 # ____________________________________________________________ 1845 def ask(self, number=None, xmean=None, sigma_fac=1, 1846 gradf=None, args=(), **kwargs): 1847 """get/sample new candidate solutions. 1848 1849 Solutions are sampled from a multi-variate 1850 normal distribution and transformed to f-representation 1851 (phenotype) to be evaluated. 1852 1853 Arguments 1854 --------- 1855 `number` 1856 number of returned solutions, by default the 1857 population size ``popsize`` (AKA ``lambda``). 1858 `xmean` 1859 distribution mean, phenotyp? 1860 `sigma_fac` 1861 multiplier for internal sample width (standard 1862 deviation) 1863 `gradf` 1864 gradient, ``len(gradf(x)) == len(x)``, if 1865 ``gradf is not None`` the third solution in the 1866 returned list is "sampled" in supposedly Newton 1867 direction ``np.dot(C, gradf(xmean, *args))``. 1868 `args` 1869 additional arguments passed to gradf 1870 1871 Return 1872 ------ 1873 A list of N-dimensional candidate solutions to be evaluated 1874 1875 Example 1876 ------- 1877 >>> import cma 1878 >>> es = cma.CMAEvolutionStrategy([0,0,0,0], 0.3) #doctest: +ELLIPSIS 1879 (4_w,... 1880 >>> while not es.stop() and es.best.f > 1e-6: 1881 ... X = es.ask() # get list of new solutions 1882 ... fit = [cma.ff.rosen(x) for x in X] # call fct with each solution 1883 ... es.tell(X, fit) # feed values 1884 1885 :See: `ask_and_eval`, `ask_geno`, `tell` 1886 """ 1887 assert self.countiter >= 0 1888 if kwargs: 1889 utils.print_warning("""Optional argument%s \n\n %s\n\nignored""" % ( 1890 '(s)' if len(kwargs) > 1 else '', str(kwargs)), 1891 "ask", "CMAEvolutionStrategy", 1892 self.countiter, maxwarns=1) 1893 if self.countiter == 0: 1894 self.timer = utils.ElapsedWCTime() 1895 else: 1896 self.timer.tic 1897 pop_geno = self.ask_geno(number, xmean, sigma_fac) 1898 1899 # N,lambda=20,200: overall CPU 7s vs 5s == 40% overhead, even without bounds! 1900 # new data: 11.5s vs 9.5s == 20% 1901 # TODO: check here, whether this is necessary? 1902 # return [self.gp.pheno(x, copy=False, into_bounds=self.boundary_handler.repair) for x in pop] # probably fine 1903 # return [Solution(self.gp.pheno(x, copy=False), copy=False) for x in pop] # here comes the memory leak, now solved 1904 pop_pheno = [self.gp.pheno(x, copy=True, 1905 into_bounds=self.boundary_handler.repair) 1906 for x in pop_geno] 1907 1908 if gradf is not None: 1909 if not isinstance(self.sm, sampler.GaussFullSampler): 1910 utils.print_warning("Gradient injection may fail, because\n" 1911 "sampler attributes `B` and `D` are not present", 1912 "ask", "CMAEvolutionStrategy", 1913 self.countiter, maxwarns=1) 1914 try: 1915 # see Hansen (2011), Injecting external solutions into CMA-ES 1916 if not self.gp.islinear: 1917 utils.print_warning(""" 1918 using the gradient (option ``gradf``) with a non-linear 1919 coordinate-wise transformation (option ``transformation``) 1920 has never been tested.""") 1921 # TODO: check this out 1922 def grad_numerical_of_coordinate_map(x, map, epsilon=None): 1923 """map is a coordinate-wise independent map, return 1924 the estimated diagonal of the Jacobian. 1925 """ 1926 eps = 1e-8 * (1 + abs(x)) if epsilon is None else epsilon 1927 return (map(x + eps) - map(x - eps)) / (2 * eps) 1928 def grad_numerical_sym(x, func, epsilon=None): 1929 """return symmetric numerical gradient of func : R^n -> R. 1930 """ 1931 eps = 1e-8 * (1 + abs(x)) if epsilon is None else epsilon 1932 grad = np.zeros(len(x)) 1933 ei = np.zeros(len(x)) # float is 1.6 times faster than int 1934 for i in rglen(x): 1935 ei[i] = eps[i] 1936 grad[i] = (func(x + ei) - func(x - ei)) / (2*eps[i]) 1937 ei[i] = 0 1938 return grad 1939 try: 1940 if self.last_iteration_with_gradient == self.countiter: 1941 utils.print_warning('gradient is used several times in ' + 1942 'this iteration', iteration=self.countiter, 1943 verbose=self.opts['verbose']) 1944 self.last_iteration_with_gradient = self.countiter 1945 except AttributeError: 1946 pass 1947 index_for_gradient = min((2, len(pop_pheno)-1)) 1948 if xmean is None: 1949 xmean = self.mean 1950 xpheno = self.gp.pheno(xmean, copy=True, 1951 into_bounds=self.boundary_handler.repair) 1952 grad_at_mean = gradf(xpheno, *args) 1953 # lift gradient into geno-space 1954 if not self.gp.isidentity or (self.boundary_handler is not None 1955 and self.boundary_handler.has_bounds()): 1956 boundary_repair = None 1957 gradpen = 0 1958 if isinstance(self.boundary_handler, BoundTransform): 1959 boundary_repair = self.boundary_handler.repair 1960 elif isinstance(self.boundary_handler, 1961 BoundPenalty): 1962 fpenalty = lambda x: self.boundary_handler.__call__( 1963 x, _SolutionDict({tuple(x): {'geno': x}}), self.gp) 1964 gradpen = grad_numerical_sym( 1965 xmean, fpenalty) 1966 elif self.boundary_handler is None or \ 1967 isinstance(self.boundary_handler, 1968 BoundNone): 1969 pass 1970 else: 1971 raise NotImplementedError( 1972 "unknown boundary handling method" + 1973 str(self.boundary_handler) + 1974 " when using gradf") 1975 gradgp = grad_numerical_of_coordinate_map( 1976 xmean, 1977 lambda x: self.gp.pheno(x, copy=True, 1978 into_bounds=boundary_repair)) 1979 grad_at_mean = grad_at_mean * gradgp + gradpen 1980 1981 # TODO: frozen variables brake the code (e.g. at grad of map) 1982 if len(grad_at_mean) != self.N or self.opts['fixed_variables']: 1983 NotImplementedError(""" 1984 gradient with fixed variables is not (yet) implemented, 1985 implement a simple transformation of the objective instead""") 1986 v = self.sm.D * np.dot(self.sm.B.T, self.sigma_vec * grad_at_mean) 1987 # newton_direction = sv * B * D * D * B^T * sv * gradient = sv * B * D * v 1988 # v = D^-1 * B^T * sv^-1 * newton_direction = D * B^T * sv * gradient 1989 q = sum(v**2) 1990 if q: 1991 # Newton direction 1992 pop_geno[index_for_gradient] = xmean - self.sigma \ 1993 * (self.N / q)**0.5 \ 1994 * (self.sigma_vec * np.dot(self.sm.B, self.sm.D * v)) 1995 if 11 < 3 and self.opts['vv']: 1996 # gradient direction 1997 q = sum((np.dot(self.sm.B.T, self.sigma_vec**-1 * grad_at_mean) / self.sm.D)**2) 1998 pop_geno[index_for_gradient] = xmean - self.sigma \ 1999 * (self.N / q)**0.5 * grad_at_mean \ 2000 if q else xmean 2001 else: 2002 pop_geno[index_for_gradient] = xmean 2003 utils.print_warning('gradient zero observed', 2004 iteration=self.countiter) 2005 # test "pure" gradient: 2006 # pop_geno[index_for_gradient] = -0.52 * grad_at_mean 2007 pop_pheno[index_for_gradient] = self.gp.pheno( 2008 pop_geno[index_for_gradient], copy=True, 2009 into_bounds=self.boundary_handler.repair) 2010 if 11 < 3: 2011 print("x/m", pop_pheno[index_for_gradient] / self.mean) 2012 print(" x-m=", 2013 pop_pheno[index_for_gradient] - self.mean) 2014 print(" g=", grad_at_mean) 2015 print(" (x-m-g)/||g||=", (pop_pheno[index_for_gradient] - self.mean - grad_at_mean) / sum(grad_at_mean**2)**0.5 2016 ) 2017 except AttributeError: 2018 warnings.warn("Gradient injection failed presumably due\n" 2019 "to missing attribute ``self.sm.B or self.sm.D``") 2020 2021 # insert solutions, this could also (better?) be done in self.gp.pheno 2022 for i in rglen((pop_geno)): 2023 self.sent_solutions.insert(pop_pheno[i], geno=pop_geno[i], 2024 iteration=self.countiter) 2025 ### iiinteger handling could come here 2026 return pop_pheno 2027 2028 # ____________________________________________________________ 2029 # ____________________________________________________________ 2030 def ask_geno(self, number=None, xmean=None, sigma_fac=1): 2031 """get new candidate solutions in genotyp. 2032 2033 Solutions are sampled from a multi-variate normal distribution. 2034 2035 Arguments are 2036 `number` 2037 number of returned solutions, by default the 2038 population size `popsize` (AKA lambda). 2039 `xmean` 2040 distribution mean 2041 `sigma_fac` 2042 multiplier for internal sample width (standard 2043 deviation) 2044 2045 `ask_geno` returns a list of N-dimensional candidate solutions 2046 in genotyp representation and is called by `ask`. 2047 2048 Details: updates the sample distribution if needed and might 2049 change the geno-pheno transformation during this update. 2050 2051 :See: `ask`, `ask_and_eval` 2052 """ 2053 # TODO: return one line samples depending on a switch 2054 # loosely akin to the mean_shift_samples part of 2055 # _prepare_injection_directions 2056 if number is None or number < 1: 2057 number = self.sp.popsize 2058 if self.number_of_solutions_asked == 0: 2059 self.number_of_injections = ( 2060 len(self.pop_injection_directions) + 2061 len(self.pop_injection_solutions)) 2062 2063 # update distribution, might change self.mean 2064 2065 # if not self.opts['tolconditioncov'] or not np.isfinite(self.opts['tolconditioncov']): 2066 if self.opts['conditioncov_alleviate']: 2067 self.alleviate_conditioning_in_coordinates(self.opts['conditioncov_alleviate'][0]) 2068 self.alleviate_conditioning(self.opts['conditioncov_alleviate'][-1]) 2069 2070 xmean_arg = xmean 2071 if xmean is None: 2072 xmean = self.mean 2073 else: 2074 try: 2075 xmean = self.archive[xmean]['geno'] 2076 # noise handling after call of tell 2077 except KeyError: 2078 try: 2079 xmean = self.sent_solutions[xmean]['geno'] 2080 # noise handling before calling tell 2081 except KeyError: 2082 pass 2083 2084 if 11 < 3: 2085 if self.opts['CMA_AII']: 2086 if self.countiter == 0: 2087 # self.aii = AII(self.x0, self.sigma0) 2088 pass 2089 self._flgtelldone = False 2090 pop = self.aii.ask(number) 2091 return pop 2092 2093 sigma = sigma_fac * self.sigma 2094 2095 # update parameters for sampling the distribution 2096 # fac 0 1 10 2097 # 150-D cigar: 2098 # 50749 50464 50787 2099 # 200-D elli: == 6.9 2100 # 99900 101160 2101 # 100995 103275 == 2% loss 2102 # 100-D elli: == 6.9 2103 # 363052 369325 < 2% loss 2104 # 365075 365755 2105 2106 # sample distribution 2107 if self._flgtelldone: # could be done in tell()!? 2108 self._flgtelldone = False 2109 self.ary = [] 2110 2111 # check injections from pop_injection_directions 2112 arinj = [] 2113 # a hack: do not use injection when only a single solution is asked for or a solution with a specific mean 2114 if number > 1 and (xmean_arg is None or Mh.vequals_approximately(xmean_arg, self.mean)): 2115 if self.countiter < 4 and \ 2116 len(self.pop_injection_directions) > self.popsize - 2: 2117 utils.print_warning(' %d special injected samples with popsize %d, ' 2118 % (len(self.pop_injection_directions), self.popsize) 2119 + "popsize %d will be used" % (len(self.pop_injection_directions) + 2) 2120 + (" and the warning is suppressed in the following" if self.countiter == 3 else "")) 2121 # directions must come first because of mean_shift_samples/TPA 2122 while self.pop_injection_directions: 2123 if len(arinj) >= number: 2124 break 2125 # TODO: if len(arinj) > number, ask doesn't fulfill the contract 2126 y = self.pop_injection_directions.pop(0) 2127 # sigma_vec _is_ taken into account here 2128 # this may be done again in tell 2129 if self.mahalanobis_norm(y) > self.N**0.5 * self.opts['CMA_injections_threshold_keep_len']: 2130 nominator = self._random_rescaling_factor_to_mahalanobis_size(y) 2131 else: 2132 nominator = 1 2133 y *= nominator / self.sigma 2134 arinj.append(y) 2135 while self.pop_injection_solutions: 2136 arinj.append((self.pop_injection_solutions.pop(0) - self.mean) / self.sigma) 2137 if self.mean_shift_samples and self.countiter > 1: 2138 # TPA is implemented by injection of the Delta mean 2139 if len(arinj) < 2: 2140 raise RuntimeError( 2141 "Mean shift samples are expected but missing.\n" 2142 "This happens if, for example, `ask` is called" 2143 " more than once, without calling `tell`\n" 2144 "(because the first call removes the samples from" 2145 " the injection list).\n" 2146 "`cma.sigma_adaptation.CMAAdaptSigmaTPA`" 2147 " step-size adaptation generates mean shift\n" 2148 "samples and relies on them. \n" 2149 "Using ``ask(1)`` for any subsequent calls of" 2150 " `ask` works OK and TPA works if the\n" 2151 "first two samples from the" 2152 " first call are retained as first samples when" 2153 " calling `tell`. \n" 2154 "EXAMPLE: \n" 2155 " X = es.ask()\n" 2156 " X.append(es.ask(1)[0])\n" 2157 " ...\n" 2158 " es.tell(X, ...)" 2159 ) 2160 # for TPA, set both vectors to the same length and don't 2161 # ever keep the original length 2162 arinj[0] *= self._random_rescaling_factor_to_mahalanobis_size(arinj[0]) / self.sigma 2163 arinj[1] *= (np.sum(arinj[0]**2) / np.sum(arinj[1]**2))**0.5 2164 if not Mh.vequals_approximately(arinj[0], -arinj[1]): 2165 utils.print_warning( 2166 "mean_shift_samples, but the first two solutions" 2167 " are not mirrors.", 2168 "ask_geno", "CMAEvolutionStrategy", 2169 self.countiter) 2170 # arinj[1] /= sum(arinj[0]**2)**0.5 / s1 # revert change 2171 self.number_of_injections_delivered += len(arinj) 2172 assert (self.countiter < 2 or not self.mean_shift_samples 2173 or self.number_of_injections_delivered >= 2) 2174 2175 Niid = number - len(arinj) # each row is a solution 2176 # compute ary 2177 if Niid >= 0: # should better be true 2178 ary = self.sigma_vec * np.asarray(self.sm.sample(Niid)) 2179 self._updateBDfromSM(self.sm) # sm.sample invoked lazy update 2180 # unconditional mirroring 2181 if self.sp.lam_mirr and self.opts['CMA_mirrormethod'] == 0: 2182 for i in range(Mh.sround(self.sp.lam_mirr * number / self.popsize)): 2183 if 2 * (i + 1) > len(ary): 2184 utils.print_warning("fewer mirrors generated than given in parameter setting (%d<%d)" 2185 % (i, self.sp.lam_mirr), 2186 "ask_geno", "CMAEvolutionStrategy", 2187 iteration=self.countiter, 2188 maxwarns=4) 2189 break 2190 ary[-1 - 2 * i] = -ary[-2 - 2 * i] 2191 if len(arinj): 2192 ary = np.vstack((arinj, ary)) 2193 else: 2194 ary = array(arinj) 2195 assert number == len(arinj) 2196 2197 if (self.opts['verbose'] > 4 and self.countiter < 3 and len(arinj) and 2198 self.adapt_sigma is not CMAAdaptSigmaTPA): 2199 utils.print_message(' %d pre-injected solutions will be used (popsize=%d)' % 2200 (len(arinj), len(ary))) 2201 2202 pop = xmean + sigma * ary 2203 for i, x in enumerate(pop[:len(arinj)]): 2204 self._injected_solutions_archive[x] = { 2205 'iteration': self.countiter, # values are currently never used 2206 'index': i, 2207 'counter': len(self._injected_solutions_archive) 2208 } 2209 # pprint(dict(self._injected_solutions_archive)) 2210 self.evaluations_per_f_value = 1 2211 self.ary = ary 2212 self.number_of_solutions_asked += len(pop) 2213 return pop 2214 2215 def random_rescale_to_mahalanobis(self, x): 2216 """change `x` like for injection, all on genotypic level""" 2217 x = x - self.mean # -= fails if dtypes don't agree 2218 if any(x): # let's not divide by zero 2219 x *= sum(self.randn(1, len(x))[0]**2)**0.5 / self.mahalanobis_norm(x) 2220 x += self.mean 2221 return x 2222 def _random_rescaling_factor_to_mahalanobis_size(self, y): 2223 """``self.mean + self._random_rescaling_factor_to_mahalanobis_size(y) * y`` 2224 is guarantied to appear like from the sample distribution. 2225 """ 2226 if len(y) != self.N: 2227 raise ValueError('len(y)=%d != %d=dimension' % (len(y), self.N)) 2228 if not any(y): 2229 utils.print_warning("input was all-zeros, which is probably a bug", 2230 "_random_rescaling_factor_to_mahalanobis_size", 2231 iteration=self.countiter) 2232 return 1.0 2233 return np.sum(self.randn(1, len(y))[0]**2)**0.5 / self.mahalanobis_norm(y) 2234 2235 2236 def get_mirror(self, x, preserve_length=False): 2237 """return ``pheno(self.mean - (geno(x) - self.mean))``. 2238 2239 >>> import numpy as np, cma 2240 >>> es = cma.CMAEvolutionStrategy(np.random.randn(3), 1) #doctest: +ELLIPSIS 2241 (3_w,... 2242 >>> x = np.random.randn(3) 2243 >>> assert cma.utilities.math.Mh.vequals_approximately(es.mean - (x - es.mean), es.get_mirror(x, preserve_length=True)) 2244 >>> x = es.ask(1)[0] 2245 >>> vals = (es.get_mirror(x) - es.mean) / (x - es.mean) 2246 >>> assert cma.utilities.math.Mh.equals_approximately(sum(vals), len(vals) * vals[0]) 2247 2248 TODO: this implementation is yet experimental. 2249 2250 TODO: this implementation includes geno-pheno transformation, 2251 however in general GP-transformation should be separated from 2252 specific code. 2253 2254 Selectively mirrored sampling improves to a moderate extend but 2255 overadditively with active CMA for quite understandable reasons. 2256 2257 Optimal number of mirrors are suprisingly small: 1,2,3 for 2258 maxlam=7,13,20 where 3,6,10 are the respective maximal possible 2259 mirrors that must be clearly suboptimal. 2260 2261 """ 2262 try: 2263 dx = self.sent_solutions[x]['geno'] - self.mean 2264 except: # can only happen with injected solutions?! 2265 dx = self.gp.geno(x, from_bounds=self.boundary_handler.inverse, 2266 copy=True) - self.mean 2267 2268 if not preserve_length: 2269 # dx *= sum(self.randn(1, self.N)[0]**2)**0.5 / self.mahalanobis_norm(dx) 2270 dx *= self._random_rescaling_factor_to_mahalanobis_size(dx) 2271 x = self.mean - dx 2272 y = self.gp.pheno(x, into_bounds=self.boundary_handler.repair) 2273 # old measure: costs 25% in CPU performance with N,lambda=20,200 2274 self.sent_solutions.insert(y, geno=x, iteration=self.countiter) 2275 return y 2276 2277 # ____________________________________________________________ 2278 # ____________________________________________________________ 2279 # 2280 def ask_and_eval(self, func, args=(), gradf=None, number=None, xmean=None, sigma_fac=1, 2281 evaluations=1, aggregation=np.median, kappa=1, parallel_mode=False): 2282 """sample `number` solutions and evaluate them on `func`. 2283 2284 Each solution ``s`` is resampled until 2285 ``self.is_feasible(s, func(s)) is True``. 2286 2287 Arguments 2288 --------- 2289 `func`: 2290 objective function, ``func(x)`` accepts a `numpy.ndarray` 2291 and returns a scalar ``if not parallel_mode``. Else returns a 2292 `list` of scalars from a `list` of `numpy.ndarray`. 2293 `args`: 2294 additional parameters for `func` 2295 `gradf`: 2296 gradient of objective function, ``g = gradf(x, *args)`` 2297 must satisfy ``len(g) == len(x)`` 2298 `number`: 2299 number of solutions to be sampled, by default 2300 population size ``popsize`` (AKA lambda) 2301 `xmean`: 2302 mean for sampling the solutions, by default ``self.mean``. 2303 `sigma_fac`: 2304 multiplier for sampling width, standard deviation, for example 2305 to get a small perturbation of solution `xmean` 2306 `evaluations`: 2307 number of evaluations for each sampled solution 2308 `aggregation`: 2309 function that aggregates `evaluations` values to 2310 as single value. 2311 `kappa`: 2312 multiplier used for the evaluation of the solutions, in 2313 that ``func(m + kappa*(x - m))`` is the f-value for ``x``. 2314 2315 Return 2316 ------ 2317 ``(X, fit)``, where 2318 2319 - `X`: list of solutions 2320 - `fit`: list of respective function values 2321 2322 Details 2323 ------- 2324 While ``not self.is_feasible(x, func(x))`` new solutions are 2325 sampled. By default 2326 ``self.is_feasible == cma.feasible == lambda x, f: f not in (None, np.NaN)``. 2327 The argument to `func` can be freely modified within `func`. 2328 2329 Depending on the ``CMA_mirrors`` option, some solutions are not 2330 sampled independently but as mirrors of other bad solutions. This 2331 is a simple derandomization that can save 10-30% of the 2332 evaluations in particular with small populations, for example on 2333 the cigar function. 2334 2335 Example 2336 ------- 2337 >>> import cma 2338 >>> x0, sigma0 = 8 * [10], 1 # 8-D 2339 >>> es = cma.CMAEvolutionStrategy(x0, sigma0) #doctest: +ELLIPSIS 2340 (5_w,... 2341 >>> while not es.stop(): 2342 ... X, fit = es.ask_and_eval(cma.ff.elli) # handles NaN with resampling 2343 ... es.tell(X, fit) # pass on fitness values 2344 ... es.disp(20) # print every 20-th iteration #doctest: +ELLIPSIS 2345 Iterat #Fevals... 2346 >>> print('terminated on ' + str(es.stop())) #doctest: +ELLIPSIS 2347 terminated on ... 2348 2349 A single iteration step can be expressed in one line, such that 2350 an entire optimization after initialization becomes:: 2351 2352 while not es.stop(): 2353 es.tell(*es.ask_and_eval(cma.ff.elli)) 2354 2355 """ 2356 # initialize 2357 popsize = self.sp.popsize 2358 if number is not None: 2359 popsize = int(number) 2360 2361 if self.opts['CMA_mirrormethod'] == 1: # direct selective mirrors 2362 nmirrors = Mh.sround(self.sp.lam_mirr * popsize / self.sp.popsize) 2363 self._mirrormethod1_done = self.countiter 2364 else: 2365 # method==0 unconditional mirrors are done in ask_geno 2366 # method==2 delayed selective mirrors are done via injection 2367 nmirrors = 0 2368 assert nmirrors <= popsize // 2 2369 self.mirrors_idx = np.arange(nmirrors) # might never be used 2370 is_feasible = self.opts['is_feasible'] 2371 2372 # do the work 2373 fit = [] # or np.NaN * np.empty(number) 2374 X_first = self.ask(popsize, xmean=xmean, gradf=gradf, args=args) 2375 if xmean is None: 2376 xmean = self.mean # might have changed in self.ask 2377 X = [] 2378 if parallel_mode: 2379 if hasattr(func, 'evaluations'): 2380 evals0 = func.evaluations 2381 fit_first = func(X_first, *args) 2382 # the rest is only book keeping and warnings spitting 2383 if hasattr(func, 'evaluations'): 2384 self.countevals += func.evaluations - evals0 - self.popsize # why not .sp.popsize ? 2385 if nmirrors and self.opts['CMA_mirrormethod'] > 0 and self.countiter < 2: 2386 utils.print_warning( 2387 "selective mirrors will not work in parallel mode", 2388 "ask_and_eval", "CMAEvolutionStrategy") 2389 if evaluations > 1 and self.countiter < 2: 2390 utils.print_warning( 2391 "aggregating evaluations will not work in parallel mode", 2392 "ask_and_eval", "CMAEvolutionStrategy") 2393 else: 2394 fit_first = len(X_first) * [None] 2395 for k in range(popsize): 2396 x, f = X_first.pop(0), fit_first.pop(0) 2397 rejected = -1 2398 while f is None or not is_feasible(x, f): # rejection sampling 2399 if parallel_mode: 2400 utils.print_warning( 2401 "rejection sampling will not work in parallel mode" 2402 " unless the parallel_objective makes a distinction\n" 2403 "between called with a numpy array vs a list (of" 2404 " numpy arrays) as first argument.", 2405 "ask_and_eval", "CMAEvolutionStrategy") 2406 rejected += 1 2407 if rejected: # resample 2408 x = self.ask(1, xmean, sigma_fac)[0] 2409 elif k >= popsize - nmirrors: # selective mirrors 2410 if k == popsize - nmirrors: 2411 self.mirrors_idx = np.argsort(fit)[-1:-1 - nmirrors:-1] 2412 x = self.get_mirror(X[self.mirrors_idx[popsize - 1 - k]]) 2413 2414 # constraints handling test hardwired ccccccccccc 2415 2416 length_normalizer = 1 2417 # zzzzzzzzzzzzzzzzzzzzzzzzz 2418 if 11 < 3: 2419 # for some unclear reason, this normalization does not work as expected: the step-size 2420 # becomes sometimes too large and overall the mean might diverge. Is the reason that 2421 # we observe random fluctuations, because the length is not selection relevant? 2422 # However sigma-adaptation should mainly work on the correlation, not the length? 2423 # Or is the reason the deviation of the direction introduced by using the original 2424 # length, which also can effect the measured correlation? 2425 # Update: if the length of z in CSA is clipped at chiN+1, it works, but only sometimes? 2426 length_normalizer = self.N**0.5 / self.mahalanobis_norm(x - xmean) # self.const.chiN < N**0.5, the constant here is irrelevant (absorbed by kappa) 2427 # print(self.N**0.5 / self.mahalanobis_norm(x - xmean)) 2428 # self.more_to_write += [length_normalizer * 1e-3, length_normalizer * self.mahalanobis_norm(x - xmean) * 1e2] 2429 2430 f = func(x, *args) if kappa == 1 else \ 2431 func(xmean + kappa * length_normalizer * (x - xmean), 2432 *args) 2433 if is_feasible(x, f) and evaluations > 1: 2434 f = aggregation([f] + [(func(x, *args) if kappa == 1 else 2435 func(xmean + kappa * length_normalizer * (x - xmean), *args)) 2436 for _i in range(int(evaluations - 1))]) 2437 if (rejected + 1) % 1000 == 0: 2438 utils.print_warning(' %d solutions rejected (f-value NaN or None) at iteration %d' % 2439 (rejected, self.countiter)) 2440 fit.append(f) 2441 X.append(x) 2442 self.evaluations_per_f_value = int(evaluations) 2443 if any(f is None or np.isnan(f) for f in fit): 2444 idxs = [i for i in range(len(fit)) 2445 if fit[i] is None or np.isnan(fit[i])] 2446 utils.print_warning("f-values %s contain None or NaN at indices %s" 2447 % (str(fit[:30]) + ('...' if len(fit) > 30 else ''), 2448 str(idxs)), 2449 'ask_and_tell', 2450 'CMAEvolutionStrategy', 2451 self.countiter) 2452 return X, fit 2453 2454 def _prepare_injection_directions(self): 2455 """provide genotypic directions for TPA and selective mirroring, 2456 with no specific length normalization, to be used in the 2457 coming iteration. 2458 2459 Details: 2460 This method is called in the end of `tell`. The result is 2461 assigned to ``self.pop_injection_directions`` and used in 2462 `ask_geno`. 2463 2464 """ 2465 # self.pop_injection_directions is supposed to be empty here 2466 if self.pop_injection_directions or self.pop_injection_solutions: 2467 raise ValueError("""Found unused injected direction/solutions. 2468 This could be a bug in the calling order/logics or due to 2469 a too small popsize used in `ask()` or when only using 2470 `ask(1)` repeatedly. """) 2471 ary = [] 2472 if self.mean_shift_samples: 2473 ary = [self.mean - self.mean_old] 2474 ary.append(self.mean_old - self.mean) # another copy! 2475 if np.alltrue(ary[-1] == 0.0): 2476 utils.print_warning('zero mean shift encountered', 2477 '_prepare_injection_directions', 2478 'CMAEvolutionStrategy', self.countiter) 2479 if self.opts['pc_line_samples']: # caveat: before, two samples were used 2480 ary.append(self.pc.copy()) 2481 if self.sp.lam_mirr and ( 2482 self.opts['CMA_mirrormethod'] == 2 or ( 2483 self.opts['CMA_mirrormethod'] == 1 and ( # replacement for direct selective mirrors 2484 not hasattr(self, '_mirrormethod1_done') or 2485 self._mirrormethod1_done < self.countiter - 1))): 2486 i0 = len(ary) 2487 ary += self.get_selective_mirrors() 2488 self._indices_of_selective_mirrors = range(i0, len(ary)) 2489 self.pop_injection_directions = ary 2490 return ary 2491 2492 def get_selective_mirrors(self, number=None): 2493 """get mirror genotypic directions from worst solutions. 2494 2495 Details: 2496 2497 To be called after the mean has been updated. 2498 2499 Takes the last ``number=sp.lam_mirr`` entries in the 2500 ``self.pop[self.fit.idx]`` as solutions to be mirrored. 2501 2502 Do not take a mirror if it is suspected to stem from a 2503 previous mirror in order to not go endlessly back and forth. 2504 """ 2505 if number is None: 2506 number = self.sp.lam_mirr 2507 if not hasattr(self, '_indices_of_selective_mirrors'): 2508 self._indices_of_selective_mirrors = [] 2509 res = [] 2510 for i in range(1, number + 1): 2511 if 'all-selective-mirrors' in self.opts['vv'] or self.fit.idx[-i] not in self._indices_of_selective_mirrors: 2512 res.append(self.mean_old - self.pop[self.fit.idx[-i]]) 2513 assert len(res) >= number - len(self._indices_of_selective_mirrors) 2514 return res 2515 2516 # ____________________________________________________________ 2517 def tell(self, solutions, function_values, check_points=None, 2518 copy=False): 2519 """pass objective function values to prepare for next 2520 iteration. This core procedure of the CMA-ES algorithm updates 2521 all state variables, in particular the two evolution paths, the 2522 distribution mean, the covariance matrix and a step-size. 2523 2524 Arguments 2525 --------- 2526 `solutions` 2527 list or array of candidate solution points (of 2528 type `numpy.ndarray`), most presumably before 2529 delivered by method `ask()` or `ask_and_eval()`. 2530 `function_values` 2531 list or array of objective function values 2532 corresponding to the respective points. Beside for termination 2533 decisions, only the ranking of values in `function_values` 2534 is used. 2535 `check_points` 2536 If ``check_points is None``, only solutions that are not generated 2537 by `ask()` are possibly clipped (recommended). ``False`` does not clip 2538 any solution (not recommended). 2539 If ``True``, clips solutions that realize long steps (i.e. also 2540 those that are unlikely to be generated with `ask()`). `check_points` 2541 can be a list of indices to be checked in solutions. 2542 `copy` 2543 ``solutions`` can be modified in this routine, if ``copy is False`` 2544 2545 Details 2546 ------- 2547 `tell()` updates the parameters of the multivariate 2548 normal search distribution, namely covariance matrix and 2549 step-size and updates also the attributes ``countiter`` and 2550 ``countevals``. To check the points for consistency is quadratic 2551 in the dimension (like sampling points). 2552 2553 Bugs 2554 ---- 2555 The effect of changing the solutions delivered by `ask()` 2556 depends on whether boundary handling is applied. With boundary 2557 handling, modifications are disregarded. This is necessary to 2558 apply the default boundary handling that uses unrepaired 2559 solutions but might change in future. 2560 2561 Example 2562 ------- 2563 2564 >>> import cma 2565 >>> func = cma.ff.sphere # choose objective function 2566 >>> es = cma.CMAEvolutionStrategy(np.random.rand(2) / 3, 1.5) 2567 ... # doctest:+ELLIPSIS 2568 (3_... 2569 >>> while not es.stop(): 2570 ... X = es.ask() 2571 ... es.tell(X, [func(x) for x in X]) 2572 >>> es.result # result is a `namedtuple` # doctest:+ELLIPSIS 2573 CMAEvolutionStrategyResult(xbest=array([... 2574 2575 :See: class `CMAEvolutionStrategy`, `ask`, `ask_and_eval`, `fmin` 2576 """ 2577 if self._flgtelldone: 2578 raise RuntimeError('tell should only be called once per iteration') 2579 2580 lam = len(solutions) 2581 if lam != len(function_values): 2582 raise ValueError('#f-values = %d must equal #solutions = %d' 2583 % (len(function_values), lam)) 2584 if lam + self.sp.lam_mirr < 3: 2585 raise ValueError('population size ' + str(lam) + 2586 ' is too small with option ' + 2587 'CMA_mirrors * popsize < 0.5') 2588 if not np.isscalar(function_values[0]): 2589 try: 2590 if np.isscalar(function_values[0][0]): 2591 if self.countiter <= 1: 2592 utils.print_warning('''function_values is not a list of scalars, 2593 the first element equals %s with non-scalar type %s. 2594 Using now ``[v[0] for v in function_values]`` instead (further warnings are suppressed)''' 2595 % (str(function_values[0]), str(type(function_values[0])))) 2596 function_values = [val[0] for val in function_values] 2597 else: 2598 raise ValueError('objective function values must be a list of scalars') 2599 except: 2600 utils.print_message("function values=%s" % function_values, 2601 method_name='tell', class_name='CMAEvolutionStrategy', 2602 verbose=9, iteration=self.countiter) 2603 raise 2604 if any(f is None or np.isnan(f) for f in function_values): 2605 idx_none = [i for i, f in enumerate(function_values) if f is None] 2606 idx_nan = [i for i, f in enumerate(function_values) if f is not None and np.isnan(f)] 2607 m = np.median([f for f in function_values 2608 if f is not None and not np.isnan(f)]) 2609 utils.print_warning("function values with index %s/%s are nan/None and will be set to the median value %s" 2610 % (str(idx_nan), str(idx_none), str(m)), 'ask', 2611 'CMAEvolutionStrategy', self.countiter) 2612 for i in idx_nan + idx_none: 2613 function_values[i] = m 2614 if not np.isfinite(function_values).all(): 2615 idx = [i for i, f in enumerate(function_values) 2616 if not np.isfinite(f)] 2617 utils.print_warning("function values with index %s are not finite but %s." 2618 % (str(idx), str([function_values[i] for i in idx])), 'ask', 2619 'CMAEvolutionStrategy', self.countiter) 2620 if self.number_of_solutions_asked <= self.number_of_injections: 2621 utils.print_warning("""no independent samples generated because the 2622 number of injected solutions, %d, equals the number of 2623 solutions asked, %d, where %d solutions remain to be injected 2624 """ % (self.number_of_injections, 2625 self.number_of_solutions_asked, 2626 len(self.pop_injection_directions) + len(self.pop_injection_solutions)), 2627 "ask_geno", "CMAEvolutionStrategy", self.countiter) 2628 self.number_of_solutions_asked = 0 2629 2630 # ## prepare 2631 N = self.N 2632 sp = self.sp 2633 if 11 < 3 and lam != sp.popsize: # turned off, because mu should stay constant, still not desastrous 2634 utils.print_warning('population size has changed, recomputing parameters') 2635 self.sp.set(self.opts, lam) # not really tested 2636 if lam < sp.weights.mu: # rather decrease cmean instead of having mu > lambda//2 2637 raise ValueError('not enough solutions passed to function tell (mu>lambda)') 2638 2639 self.countiter += 1 # >= 1 now 2640 self.countevals += lam * self.evaluations_per_f_value 2641 self.best.update(solutions, # caveat: these solutions may be out-of-bounds 2642 self.sent_solutions, function_values, self.countevals) 2643 flg_diagonal = self.opts['CMA_diagonal'] is True \ 2644 or self.countiter <= self.opts['CMA_diagonal'] 2645 if not flg_diagonal and isinstance(self.sm, sampler.GaussStandardConstant): 2646 # switching from diagonal to full covariance learning 2647 self.sm = sampler.GaussFullSampler(N) 2648 self._updateBDfromSM(self.sm) 2649 2650 self._record_rankings(function_values[:2], function_values[2:]) # for analysis 2651 2652 # ## manage fitness 2653 fit = self.fit # make short cut 2654 2655 # CPU for N,lam=20,200: this takes 10s vs 7s 2656 fit.bndpen = self.boundary_handler.update(function_values, self)(solutions, self.sent_solutions, self.gp) 2657 # for testing: 2658 # fit.bndpen = self.boundary_handler.update(function_values, self)([s.unrepaired for s in solutions]) 2659 fit.idx = np.argsort(array(fit.bndpen) + array(function_values)) 2660 fit.fit = array(function_values, copy=False)[fit.idx] 2661 2662 # update output data TODO: this is obsolete!? However: need communicate current best x-value? 2663 # old: out['recent_x'] = self.gp.pheno(pop[0]) 2664 # self.out['recent_x'] = array(solutions[fit.idx[0]]) # TODO: change in a data structure(?) and use current as identify 2665 # self.out['recent_f'] = fit.fit[0] 2666 2667 # fitness histories 2668 fit.hist.insert(0, fit.fit[0]) # caveat: this may neither be the best nor the best in-bound fitness, TODO 2669 fit.median = (fit.fit[self.popsize // 2] if self.popsize % 2 2670 else np.mean(fit.fit[self.popsize // 2 - 1: self.popsize // 2 + 1])) 2671 # if len(self.fit.histbest) < 120+30*N/sp.popsize or # does not help, as tablet in the beginning is the critical counter-case 2672 if ((self.countiter % 5) == 0): # 20 percent of 1e5 gen. 2673 fit.histbest.insert(0, fit.fit[0]) 2674 fit.histmedian.insert(0, fit.median) 2675 if len(fit.histbest) > 2e4: # 10 + 30*N/sp.popsize: 2676 fit.histbest.pop() 2677 fit.histmedian.pop() 2678 if len(fit.hist) > 10 + 30 * N / sp.popsize: 2679 fit.hist.pop() 2680 if fit.median0 is None: 2681 fit.median0 = fit.median 2682 if fit.median_min > fit.median: 2683 fit.median_min = fit.median 2684 2685 ### line 2665 2686 2687 # TODO: clean up inconsistency when an unrepaired solution is available and used 2688 # now get the genotypes 2689 self.pop_sorted = None 2690 pop = [] # create pop from input argument solutions 2691 for k, s in enumerate(solutions): # use phenotype before Solution.repair() 2692 if 1 < 3: 2693 pop += [self.gp.geno(s, 2694 from_bounds=self.boundary_handler.inverse, 2695 repair=(self.repair_genotype if check_points not in (False, 0, [], ()) else None), 2696 archive=self.sent_solutions)] # takes genotype from sent_solutions, if available 2697 try: 2698 self.archive.insert(s, value=self.sent_solutions.pop(s), fitness=function_values[k]) 2699 # self.sent_solutions.pop(s) 2700 except KeyError: 2701 pass 2702 # check that TPA mirrors are available 2703 self.pop = pop # used in check_consistency of CMAAdaptSigmaTPA 2704 self.adapt_sigma.check_consistency(self) 2705 2706 if self.countiter > 1: 2707 self.mean_old_old = self.mean_old 2708 self.mean_old = self.mean 2709 mold = self.mean_old # just an alias 2710 2711 # check and normalize each x - m 2712 # check_points is a flag (None is default: check non-known solutions) or an index list 2713 # should also a number possible (first check_points points)? 2714 if check_points not in (None, False, 0, [], ()): 2715 # useful in case of injected solutions and/or adaptive encoding, however is automatic with use_sent_solutions 2716 # by default this is not executed 2717 try: 2718 if len(check_points): 2719 idx = check_points 2720 except: 2721 idx = range(sp.popsize) 2722 2723 for k in idx: 2724 self.repair_genotype(pop[k]) 2725 2726 # sort pop for practicability, now pop != self.pop, which is unsorted 2727 pop = np.asarray(pop)[fit.idx] 2728 2729 # prepend best-ever solution to population, in case 2730 # note that pop and fit.fit do not agree anymore in this case 2731 if self.opts['CMA_elitist'] == 'initial': 2732 if not hasattr(self, 'f0'): 2733 utils.print_warning( 2734 'Set attribute `es.f0` to make initial elitism\n' + 2735 'available or use cma.fmin.', 2736 'tell', 'CMAEvolutionStrategy', self.countiter) 2737 elif fit.fit[0] > self.f0: 2738 x_elit = self.mean0.copy() 2739 # self.clip_or_fit_solutions([x_elit], [0]) # just calls repair_genotype 2740 self.random_rescale_to_mahalanobis(x_elit) 2741 pop = array([x_elit] + list(pop), copy=False) 2742 utils.print_message('initial solution injected %f<%f' % 2743 (self.f0, fit.fit[0]), 2744 'tell', 'CMAEvolutionStrategy', 2745 self.countiter, verbose=self.opts['verbose']) 2746 elif self.opts['CMA_elitist'] and self.best.f < fit.fit[0]: 2747 if self.best.x_geno is not None: 2748 xp = [self.best.x_geno] 2749 # xp = [self.best.xdict['geno']] 2750 # xp = [self.gp.geno(self.best.x[:])] # TODO: remove 2751 # print self.mahalanobis_norm(xp[0]-self.mean) 2752 else: 2753 xp = [self.gp.geno(array(self.best.x, copy=True), 2754 self.boundary_handler.inverse, 2755 copy=False)] 2756 utils.print_warning('genotype for elitist not found', 'tell') 2757 # self.clip_or_fit_solutions(xp, [0]) 2758 self.random_rescale_to_mahalanobis(xp[0]) 2759 pop = np.asarray([xp[0]] + list(pop)) 2760 2761 self.pop_sorted = pop 2762 # compute new mean 2763 self.mean = mold + self.sp.cmean * \ 2764 (np.sum(np.asarray(sp.weights.positive_weights) * pop[0:sp.weights.mu].T, 1) - mold) 2765 2766 # check Delta m (this is not default, but could become at some point) 2767 # CAVE: upper_length=sqrt(2)+2 is too restrictive, test upper_length = sqrt(2*N) thoroughly. 2768 # replaced by repair_geno? 2769 # simple test case injecting self.mean: 2770 # self.mean = 1e-4 * self.sigma * np.random.randn(N) 2771 if 11 < 3 and self.opts['vv'] and check_points: # CAVEAT: check_points might be an index-list 2772 cmean = self.sp.cmean / min(1, ((self.opts['vv'] * N)**0.5 + 2) / (# abuse of cmean 2773 (self.sp.weights.mueff**0.5 / self.sp.cmean) * 2774 self.mahalanobis_norm(self.mean - mold))) 2775 else: 2776 cmean = self.sp.cmean 2777 2778 # zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 2779 if 11 < 3: 2780 self.more_to_write += [sum(self.mean**2)] 2781 if 11 < 3: # plot length of mean - mold 2782 self.more_to_write += [self.sp.weights.mueff**0.5 * 2783 sum(((1. / self.D) * np.dot(self.B.T, self.mean - mold))**2)**0.5 / 2784 self.sigma / N**0.5 / cmean] 2785 ### line 2799 2786 2787 # get learning rate constants 2788 cc = sp.cc 2789 c1 = self.opts['CMA_on'] * self.opts['CMA_rankone'] * self.sm.parameters( 2790 mueff=sp.weights.mueff, lam=sp.weights.lambda_).get('c1', sp.c1) # mueff and lambda_ should not be necessary here 2791 cmu = self.opts['CMA_on'] * self.opts['CMA_rankmu'] * self.sm.parameters().get('cmu', sp.cmu) 2792 if flg_diagonal: 2793 cc, c1, cmu = sp.cc_sep, sp.c1_sep, sp.cmu_sep 2794 2795 # now the real work can start 2796 2797 # _update_ps must be called before the distribution is changed, 2798 # hsig() calls _update_ps 2799 hsig = self.adapt_sigma.hsig(self) 2800 2801 if 11 < 3: 2802 # hsig = 1 2803 # sp.cc = 4 / (N + 4) 2804 # sp.cs = 4 / (N + 4) 2805 # sp.cc = 1 2806 # sp.damps = 2 # 2807 # sp.CMA_on = False 2808 # c1 = 0 # 2 / ((N + 1.3)**2 + 0 * sp.weights.mu) # 1 / N**2 2809 # cmu = min([1 - c1, cmu]) 2810 if self.countiter == 1: 2811 print('parameters modified') 2812 # hsig = sum(self.ps**2) / self.N < 2 + 4./(N+1) 2813 # adjust missing variance due to hsig, in 4-D with damps=1e99 and sig0 small 2814 # hsig leads to premature convergence of C otherwise 2815 # hsiga = (1-hsig**2) * c1 * cc * (2-cc) # to be removed in future 2816 c1a = c1 * (1 - (1 - hsig**2) * cc * (2 - cc)) # adjust for variance loss 2817 2818 if 11 < 3: # diagnostic data 2819 # self.out['hsigcount'] += 1 - hsig 2820 if not hsig: 2821 self.hsiglist.append(self.countiter) 2822 if 11 < 3: # diagnostic message 2823 if not hsig: 2824 print(str(self.countiter) + ': hsig-stall') 2825 if 11 < 3: # for testing purpose 2826 hsig = 1 # TODO: 2827 # put correction term, but how? 2828 if self.countiter == 1: 2829 print('hsig=1') 2830 2831 self.pc = (1 - cc) * self.pc + hsig * ( 2832 (cc * (2 - cc) * self.sp.weights.mueff)**0.5 / self.sigma 2833 / cmean) * (self.mean - mold) / self.sigma_vec.scaling 2834 2835 # covariance matrix adaptation/udpate 2836 pop_zero = pop - mold 2837 if c1a + cmu > 0: 2838 # TODO: make sure cc is 1 / N**0.5 rather than 1 / N 2839 # TODO: simplify code: split the c1 and cmu update and call self.sm.update twice 2840 # caveat: for this the decay factor ``c1_times_delta_hsigma - sum(weights)`` should be zero in the second update 2841 sampler_weights = [c1a] + [cmu * w for w in sp.weights] 2842 if len(pop_zero) > len(sp.weights): 2843 sampler_weights = ( 2844 sampler_weights[:1+sp.weights.mu] + 2845 (len(pop_zero) - len(sp.weights)) * [0] + 2846 sampler_weights[1+sp.weights.mu:]) 2847 if 'inc_cmu_pos' in self.opts['vv']: 2848 sampler_weights = np.asarray(sampler_weights) 2849 sampler_weights[sampler_weights > 0] *= 1 + self.opts['vv']['inc_cmu_pos'] 2850 # logger = logging.getLogger(__name__) # "global" level needs to be DEBUG 2851 # logger.debug("w[0,1]=%f,%f", sampler_weights[0], 2852 # sampler_weights[1]) if self.countiter < 2 else None 2853 # print(' injected solutions', tuple(self._injected_solutions_archive.values())) 2854 for i, x in enumerate(pop): 2855 try: 2856 self._injected_solutions_archive.pop(x) 2857 # self.gp.repaired_solutions.pop(x) 2858 except KeyError: 2859 pass # print(i) 2860 else: 2861 # print(i + 1, '-th weight set to zero') 2862 # sampler_weights[i + 1] = 0 # weight index 0 is for pc 2863 sampler_weights[i + 1] *= self.opts['CMA_active_injected'] # weight index 0 is for pc 2864 for s in list(self._injected_solutions_archive): 2865 if self._injected_solutions_archive[s]['iteration'] < self.countiter - 2: 2866 warnings.warn("""orphanated injected solution %s 2867 This could be a bug in the calling order/logics or due to 2868 a too small popsize used in `ask()` or when only using 2869 `ask(1)` repeatedly. Please check carefully. 2870 In case this is desired, the warning can be surpressed with 2871 ``warnings.simplefilter("ignore", cma.evolution_strategy.InjectionWarning)`` 2872 """ % str(self._injected_solutions_archive.pop(s)), 2873 InjectionWarning) 2874 assert len(sampler_weights) == len(pop_zero) + 1 2875 if flg_diagonal: 2876 self.sigma_vec.update( 2877 [self.sm.transform_inverse(self.pc)] + 2878 list(self.sm.transform_inverse(pop_zero / 2879 (self.sigma * self.sigma_vec.scaling))), 2880 array(sampler_weights) / 2) # TODO: put the 1/2 into update function!? 2881 else: 2882 self.sm.update([(c1 / (c1a + 1e-23))**0.5 * self.pc] + # c1a * pc**2 gets c1 * pc**2 2883 list(pop_zero / (self.sigma * self.sigma_vec.scaling)), 2884 sampler_weights) 2885 if any(np.asarray(self.sm.variances) < 0): 2886 raise RuntimeError("A sampler variance has become negative " 2887 "after the update, this must be considered as a bug.\n" 2888 "Variances `self.sm.variances`=%s" % str(self.sm.variances)) 2889 self._updateBDfromSM(self.sm) 2890 2891 # step-size adaptation, adapt sigma 2892 # in case of TPA, function_values[0] and [1] must reflect samples colinear to xmean - xmean_old 2893 try: 2894 self.sigma *= self.adapt_sigma.update2(self, 2895 function_values=function_values) 2896 except (NotImplementedError, AttributeError): 2897 self.adapt_sigma.update(self, function_values=function_values) 2898 2899 if 11 < 3 and self.opts['vv']: 2900 if self.countiter < 2: 2901 print('constant sigma applied') 2902 print(self.opts['vv']) # N=10,lam=10: 0.8 is optimal 2903 self.sigma = self.opts['vv'] * self.sp.weights.mueff * sum(self.mean**2)**0.5 / N 2904 2905 if any(self.sigma * self.sigma_vec.scaling * self.dC**0.5 < 2906 np.asarray(self.opts['minstd'])): 2907 self.sigma = max(np.asarray(self.opts['minstd']) / 2908 (self.sigma_vec * self.dC**0.5)) 2909 assert all(self.sigma * self.sigma_vec * self.dC**0.5 >= 2910 (1-1e-9) * np.asarray(self.opts['minstd'])) 2911 elif any(self.sigma * self.sigma_vec.scaling * self.dC**0.5 > 2912 np.asarray(self.opts['maxstd'])): 2913 self.sigma = min(np.asarray(self.opts['maxstd']) / 2914 self.sigma_vec * self.dC**0.5) 2915 # g = self.countiter 2916 # N = self.N 2917 # mindx = eval(self.opts['mindx']) 2918 # if utils.is_str(self.opts['mindx']) else self.opts['mindx'] 2919 if self.sigma * min(self.D) < self.opts['mindx']: # TODO: sigma_vec is missing here 2920 self.sigma = self.opts['mindx'] / min(self.D) 2921 2922 if self.sigma > 1e9 * self.sigma0: 2923 alpha = self.sigma / max(self.sm.variances)**0.5 2924 if alpha > 1: 2925 self.sigma /= alpha**0.5 # adjust only half 2926 self.opts['tolupsigma'] /= alpha**0.5 # to be compared with sigma 2927 self.sm *= alpha 2928 self._updateBDfromSM() 2929 2930 # TODO increase sigma in case of a plateau? 2931 2932 # Uncertainty noise measurement is done on an upper level 2933 2934 # move mean into "feasible preimage", leads to weird behavior on 2935 # 40-D tablet with bound 0.1, not quite explained (constant 2936 # dragging is problematic, but why doesn't it settle), still a bug? 2937 if 11 < 3 and isinstance(self.boundary_handler, BoundTransform) \ 2938 and not self.boundary_handler.is_in_bounds(self.mean): 2939 self.mean = array(self.boundary_handler.inverse( 2940 self.boundary_handler.repair(self.mean, copy_if_changed=False), 2941 copy_if_changed=False), copy=False) 2942 if _new_injections: 2943 self.pop_injection_directions = self._prepare_injection_directions() 2944 if self.opts['verbose'] > 4 and self.countiter < 3 and type(self.adapt_sigma) is not CMAAdaptSigmaTPA and len(self.pop_injection_directions): 2945 utils.print_message(' %d directions prepared for injection %s' % 2946 (len(self.pop_injection_directions), 2947 "(no more messages will be shown)" if 2948 self.countiter == 2 else "")) 2949 self.number_of_injections_delivered = 0 2950 self.pop = [] # remove this in case pop is still needed 2951 # self.pop_sorted = [] 2952 self._flgtelldone = True 2953 try: # shouldn't fail, but let's be nice to code abuse 2954 self.timer.pause() 2955 except AttributeError: 2956 warnings.warn('CMAEvolutionStrategy.tell(countiter=%d): "timer" attribute ' 2957 'not found, probably because `ask` was never called. \n' 2958 'Timing is likely to work only until `tell` is called (again), ' 2959 'because `tic` will never be called again afterwards.' 2960 % self.countiter) 2961 self.timer = utils.ElapsedWCTime() 2962 2963 self.more_to_write.check() 2964 # end tell() 2965 2966 def _record_rankings(self, vals, function_values): 2967 "do nothing by default, otherwise assign to `_record_rankings_` after instantiation" 2968 def _record_rankings_(self, vals, function_values): 2969 """compute ranks of `vals` in `function_values` and 2970 2971 in `self.fit.fit` and store the results in `_recorded_rankings`. 2972 The ranking differences between two solutions appear to be similar 2973 in the current and last iteration. 2974 """ 2975 vals = list(vals) 2976 r0 = _utils.ranks(vals + list(self.fit.fit)) 2977 r1 = _utils.ranks(vals + list(function_values)) 2978 self._recorded_rankings = [r0[:2], r1[:2]] 2979 return self._recorded_rankings 2980 2981 def inject(self, solutions, force=None): 2982 """inject list of one or several genotypic solution(s). 2983 2984 This is the preferable way to pass outside proposal solutions 2985 into `CMAEvolutionStrategy`. Passing (bad) solutions directly 2986 via `tell` is likely to fail when ``CMA_active is True`` as by 2987 default. 2988 2989 Unless ``force is True``, the `solutions` are used as direction 2990 relative to the distribution mean to compute a new candidate 2991 solution returned in method `ask_geno` which in turn is used in 2992 method `ask`. Even when ``force is True``, the update in `tell` 2993 takes later care of possibly trimming the update vector. 2994 2995 `inject` is to be called before `ask` or after `tell` and can be 2996 called repeatedly. 2997 2998 >>> import cma 2999 >>> es = cma.CMAEvolutionStrategy(4 * [1], 2) #doctest: +ELLIPSIS 3000 (4_w,... 3001 >>> while not es.stop(): 3002 ... es.inject([4 * [0.0]]) 3003 ... X = es.ask() 3004 ... if es.countiter == 0: 3005 ... assert X[0][0] == X[0][1] # injected sol. is on the diagonal 3006 ... es.tell(X, [cma.ff.sphere(x) for x in X]) 3007 3008 Details: injected solutions are not used in the "active" update which 3009 would decrease variance in the covariance matrix in this direction. 3010 """ 3011 for solution in solutions: 3012 if solution is None: 3013 continue 3014 if len(solution) != self.N: 3015 raise ValueError('method `inject` needs a list or array' 3016 + (' each el with dimension (`len`) %d' % self.N)) 3017 solution = array(solution, copy=False, dtype=float) 3018 if force: 3019 self.pop_injection_solutions.append(solution) 3020 else: 3021 self.pop_injection_directions.append(solution - self.mean) 3022 3023 @property 3024 def result(self): 3025 """return a `CMAEvolutionStrategyResult` `namedtuple`. 3026 3027 :See: `cma.evolution_strategy.CMAEvolutionStrategyResult` 3028 or try ``help(...result)`` on the ``result`` property 3029 of an `CMAEvolutionStrategy` instance or on the 3030 `CMAEvolutionStrategyResult` instance itself. 3031 3032 """ 3033 # TODO: how about xcurrent? 3034 # return CMAEvolutionStrategyResult._generate(self) 3035 x, f, evals = self.best.get() 3036 return CMAEvolutionStrategyResult( 3037 x, 3038 f, 3039 evals, 3040 self.countevals, 3041 self.countiter, 3042 self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair), 3043 self.gp.scales * self.sigma * self.sigma_vec.scaling * 3044 self.dC**0.5, 3045 self.stop() 3046 ) 3047 3048 def result_pretty(self, number_of_runs=0, time_str=None, 3049 fbestever=None): 3050 """pretty print result. 3051 3052 Returns `result` of ``self``. 3053 3054 """ 3055 if fbestever is None: 3056 fbestever = self.best.f 3057 s = (' after %i restart' + ('s' if number_of_runs > 1 else '')) \ 3058 % number_of_runs if number_of_runs else '' 3059 for k, v in self.stop().items(): 3060 print('termination on %s=%s%s' % (k, str(v), s + 3061 (' (%s)' % time_str if time_str else ''))) 3062 3063 print('final/bestever f-value = %e %e' % (self.best.last.f, 3064 fbestever)) 3065 if self.N < 9: 3066 print('incumbent solution: ' + str(list(self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair)))) 3067 print('std deviation: ' + str(list(self.sigma * self.sigma_vec.scaling * np.sqrt(self.dC) * self.gp.scales))) 3068 else: 3069 print('incumbent solution: %s ...]' % (str(self.gp.pheno(self.mean, into_bounds=self.boundary_handler.repair)[:8])[:-1])) 3070 print('std deviations: %s ...]' % (str((self.sigma * self.sigma_vec.scaling * np.sqrt(self.dC) * self.gp.scales)[:8])[:-1])) 3071 return self.result 3072 3073 def pickle_dumps(self): 3074 """return ``pickle.dumps(self)``, 3075 3076 if necessary remove unpickleable (and also unnecessary) local 3077 function reference beforehand. 3078 3079 The resulting `bytes` string-object can be saved to a file like:: 3080 3081 import cma 3082 es = cma.CMAEvolutionStrategy(3 * [1], 1) 3083 es.optimize(cma.ff.elli, iterations=22) 3084 filename = 'es-pickle-test' 3085 open(filename, 'wb').write(es.pickle_dumps()) 3086 3087 and recovered like:: 3088 3089 import pickle 3090 es = pickle.load(open(filename, 'rb')) 3091 3092 or:: 3093 3094 es = pickle.loads(open(filename, 'rb').read()) 3095 es.optimize(cma.ff.elli, iterations=22) # continue optimizing 3096 3097 """ 3098 import pickle 3099 try: # fine if local function self.objective_function was not assigned 3100 s = pickle.dumps(self) 3101 except: 3102 self.objective_function, fun = None, self.objective_function 3103 try: 3104 s = pickle.dumps(self) 3105 except: raise # didn't work out 3106 finally: # reset changed attribute either way 3107 self.objective_function = fun 3108 return s 3109 3110 def repair_genotype(self, x, copy_if_changed=False): 3111 """make sure that solutions fit to the sample distribution. 3112 3113 This interface is versatile and likely to change. 3114 3115 The Mahalanobis distance ``x - self.mean`` is clipping at 3116 ``N**0.5 + 2 * N / (N + 2)``, but the specific repair 3117 mechanism may change in future. 3118 """ 3119 x = array(x, copy=False) 3120 mold = array(self.mean, copy=False) 3121 if 1 < 3: # hard clip at upper_length 3122 upper_length = self.N**0.5 + 2 * self.N / (self.N + 2) 3123 # should become an Option, but how? e.g. [0, 2, 2] 3124 fac = self.mahalanobis_norm(x - mold) / upper_length 3125 3126 if fac > 1: 3127 if copy_if_changed: 3128 x = (x - mold) / fac + mold 3129 else: # should be 25% faster: 3130 x -= mold 3131 x /= fac 3132 x += mold 3133 # print self.countiter, k, fac, self.mahalanobis_norm(pop[k] - mold) 3134 # adapt also sigma: which are the trust-worthy/injected solutions? 3135 elif 11 < 3: 3136 return np.exp(np.tanh(((upper_length * fac)**2 / self.N - 1) / 2) / 2) 3137 3138 return x 3139 3140 def manage_plateaus(self, sigma_fac=1.5, sample_fraction=0.5): 3141 """increase `sigma` by `sigma_fac` in case of a plateau. 3142 3143 A plateau is assumed to be present if the best sample and 3144 ``popsize * sample_fraction``-th best sample have the same 3145 fitness. 3146 3147 Example: 3148 3149 >>> import cma 3150 >>> def f(X): 3151 ... return (len(X) - 1) * [1] + [2] 3152 >>> es = cma.CMAEvolutionStrategy(4 * [0], 5, {'verbose':-9, 'tolflatfitness':1e4}) 3153 >>> while not es.stop(): 3154 ... X = es.ask() 3155 ... es.tell(X, f(X)) 3156 ... es.manage_plateaus() 3157 >>> if es.sigma < 1.5**es.countiter: print((es.sigma, 1.5**es.countiter, es.stop())) 3158 3159 """ 3160 if not self._flgtelldone: 3161 utils.print_warning("Inbetween `ask` and `tell` plateaus cannot" + 3162 " be managed, because `sigma` should not change.", 3163 "manage_plateaus", "CMAEvolutionStrategy", 3164 self.countiter) 3165 return 3166 f0, fm = Mh.prctile(self.fit.fit, [0, sample_fraction * 100]) 3167 if f0 == fm: 3168 self.sigma *= sigma_fac 3169 3170 @property 3171 def condition_number(self): 3172 """condition number of the statistical-model sampler. 3173 3174 Details: neither encoding/decoding from `sigma_vec`-scaling nor 3175 `gp`-transformation are taken into account for this computation. 3176 """ 3177 try: 3178 return self.sm.condition_number 3179 except (AttributeError): 3180 return (max(self.D) / min(self.D))**2 3181 except (NotImplementedError): 3182 return (max(self.D) / min(self.D))**2 3183 3184 def alleviate_conditioning_in_coordinates(self, condition=1e8): 3185 """pass scaling from `C` to `sigma_vec`. 3186 3187 As a result, `C` is a correlation matrix, i.e., all diagonal 3188 entries of `C` are `1`. 3189 """ 3190 if condition and np.isfinite(condition) and np.max(self.dC) / np.min(self.dC) > condition: 3191 # allows for much larger condition numbers, if axis-parallel 3192 if hasattr(self, 'sm') and isinstance(self.sm, sampler.GaussFullSampler): 3193 old_coordinate_condition = np.max(self.dC) / np.min(self.dC) 3194 old_condition = self.sm.condition_number 3195 factors = self.sm.to_correlation_matrix() 3196 self.sigma_vec *= factors 3197 self.pc /= factors 3198 self._updateBDfromSM(self.sm) 3199 utils.print_message('\ncondition in coordinate system exceeded' 3200 ' %.1e, rescaled to %.1e, ' 3201 '\ncondition changed from %.1e to %.1e' 3202 % (old_coordinate_condition, np.max(self.dC) / np.min(self.dC), 3203 old_condition, self.sm.condition_number), 3204 iteration=self.countiter) 3205 3206 def _tfp(self, x): 3207 return np.dot(self.gp._tf_matrix, x) 3208 def _tfg(self, x): 3209 return np.dot(self.gp._tf_matrix_inv, x) 3210 3211 def alleviate_conditioning(self, condition=1e12): 3212 """pass conditioning of `C` to linear transformation in `self.gp`. 3213 3214 Argument `condition` defines the limit condition number above 3215 which the action is taken. 3216 3217 Details: the action applies only if `self.gp.isidentity`. Then, 3218 the covariance matrix `C` is set (back) to identity and a 3219 respective linear transformation is "added" to `self.gp`. 3220 """ 3221 # new interface: if sm.condition_number > condition ... 3222 if not self.gp.isidentity or not condition or self.condition_number < condition: 3223 return 3224 try: 3225 old_condition_number = self.condition_number 3226 tf_inv = self.sm.to_linear_transformation_inverse() 3227 tf = self.sm.to_linear_transformation(reset=True) 3228 self.pc = np.dot(tf_inv, self.pc) 3229 old_C_scales = self.dC**0.5 3230 self._updateBDfromSM(self.sm) 3231 except NotImplementedError: 3232 utils.print_warning("Not Implemented", 3233 method_name="alleviate_conditioning") 3234 return 3235 self._updateBDfromSM(self.sm) 3236 # dmean_prev = np.dot(self.B, (1. / self.D) * np.dot(self.B.T, (self.mean - 0*self.mean_old) / self.sigma_vec)) 3237 3238 # we may like to traverse tf through sigma_vec such that 3239 # gp.tf * sigma_vec == sigma_vec * tf 3240 # but including sigma_vec shouldn't pose a problem even for 3241 # a large scaling which essentially just changes the exponents 3242 # uniformly in the rows of tf 3243 self.gp._tf_matrix = (self.sigma_vec * tf.T).T # sig*tf.T .*-multiplies each column of tf with sig 3244 self.gp._tf_matrix_inv = tf_inv / self.sigma_vec # here was the bug 3245 self.sigma_vec = transformations.DiagonalDecoding(1) 3246 3247 # TODO: refactor old_scales * old_sigma_vec into sigma_vec0 to prevent tolfacupx stopping 3248 3249 self.gp.tf_pheno = self._tfp # lambda x: np.dot(self.gp._tf_matrix, x) 3250 self.gp.tf_geno = self._tfg # lambda x: np.dot(self.gp._tf_matrix_inv, x) # not really necessary 3251 self.gp.isidentity = False 3252 assert self.mean is not self.mean_old 3253 3254 # transform current mean and injected solutions accordingly 3255 self.mean = self.gp.tf_geno(self.mean) # same as gp.geno() 3256 for i, x in enumerate(self.pop_injection_solutions): 3257 self.pop_injection_solutions[i] = self.gp.tf_geno(x) 3258 for i, x in enumerate(self.pop_injection_directions): 3259 self.pop_injection_directions[i] = self.gp.tf_geno(x) 3260 3261 self.mean_old = self.gp.tf_geno(self.mean_old) # not needed!? 3262 3263 # self.pc = self.gp.geno(self.pc) # now done above, which is a better place 3264 3265 # dmean_now = np.dot(self.B, (1. / self.D) * np.dot(self.B.T, (self.mean - 0*self.mean_old) / self.sigma_vec)) 3266 # assert Mh.vequals_approximately(dmean_now, dmean_prev) 3267 utils.print_warning(''' 3268 geno-pheno transformation introduced based on the 3269 current covariance matrix with condition %.1e -> %.1e, 3270 injected solutions become "invalid" in this iteration''' 3271 % (old_condition_number, self.condition_number), 3272 'alleviate_conditioning', 'CMAEvolutionStrategy', 3273 self.countiter) 3274 3275 def _updateBDfromSM(self, sm_=None): 3276 """helper function for a smooth transition to sampling classes. 3277 3278 By now all tests run through without this method in effect. 3279 Gradient injection and noeffectaxis however rely on the 3280 non-documented attributes B and D in the sampler. """ 3281 # return # should be outcommented soon, but self.D is still in use (for some tests)! 3282 if sm_ is None: 3283 sm_ = self.sm 3284 if isinstance(sm_, sampler.GaussStandardConstant): 3285 self.B = array(1) 3286 self.D = sm_.variances**0.5 3287 self.C = array(1) 3288 self.dC = self.D 3289 elif isinstance(sm_, (_rgs.GaussVDSampler, _rgs.GaussVkDSampler)): 3290 self.dC = sm_.variances 3291 else: 3292 self.C = self.sm.covariance_matrix # TODO: this should go away 3293 try: 3294 self.B = self.sm.B 3295 self.D = self.sm.D 3296 except AttributeError: 3297 if self.C is not None: 3298 self.D, self.B = self.opts['CMA_eigenmethod'](self.C) 3299 self.D **= 0.5 3300 elif 11 < 3: # would retain consistency but fails 3301 self.B = None 3302 self.D = None 3303 if self.C is not None: 3304 self.dC = np.diag(self.C) 3305 3306 # ____________________________________________________________ 3307 # ____________________________________________________________ 3308 def feed_for_resume(self, X, function_values): 3309 """Resume a run using the solution history. 3310 3311 CAVEAT: this hasn't been thoroughly tested or in intensive use. 3312 3313 Given all "previous" candidate solutions and their respective 3314 function values, the state of a `CMAEvolutionStrategy` object 3315 can be reconstructed from this history. This is the purpose of 3316 function `feed_for_resume`. 3317 3318 Arguments 3319 --------- 3320 `X`: 3321 (all) solution points in chronological order, phenotypic 3322 representation. The number of points must be a multiple 3323 of popsize. 3324 `function_values`: 3325 respective objective function values 3326 3327 Details 3328 ------- 3329 `feed_for_resume` can be called repeatedly with only parts of 3330 the history. The part must have the length of a multiple 3331 of the population size. 3332 `feed_for_resume` feeds the history in popsize-chunks into `tell`. 3333 The state of the random number generator might not be 3334 reconstructed, but this would be only relevant for the future. 3335 3336 Example 3337 ------- 3338 :: 3339 3340 import cma 3341 3342 # prepare 3343 (x0, sigma0) = ... # initial values from previous trial 3344 X = ... # list of generated solutions from a previous trial 3345 f = ... # respective list of f-values 3346 3347 # resume 3348 es = cma.CMAEvolutionStrategy(x0, sigma0) 3349 es.feed_for_resume(X, f) 3350 3351 # continue with func as objective function 3352 while not es.stop(): 3353 X = es.ask() 3354 es.tell(X, [func(x) for x in X]) 3355 3356 3357 Credits to Dirk Bueche and Fabrice Marchal for the feeding idea. 3358 3359 :See also: class `CMAEvolutionStrategy` for a simple dump/load 3360 to resume. 3361 3362 """ 3363 if self.countiter > 0: 3364 utils.print_warning('feed should generally be used with a new object instance') 3365 if len(X) != len(function_values): 3366 raise ValueError('number of solutions ' + str(len(X)) + 3367 ' and number function values ' + 3368 str(len(function_values)) + ' must not differ') 3369 popsize = self.sp.popsize 3370 if (len(X) % popsize) != 0: 3371 raise ValueError('number of solutions ' + str(len(X)) + 3372 ' must be a multiple of popsize (lambda) ' + 3373 str(popsize)) 3374 for i in rglen((X) / popsize): 3375 # feed in chunks of size popsize 3376 self.ask() # a fake ask, mainly for a conditioned calling of 3377 # updateBD and secondary to get possibly the same 3378 # random state 3379 self.tell(X[i * popsize:(i + 1) * popsize], 3380 function_values[i * popsize:(i + 1) * popsize]) 3381 3382 # ____________________________________________________________ 3383 # ____________________________________________________________ 3384 # ____________________________________________________________ 3385 # ____________________________________________________________ 3386 def mahalanobis_norm(self, dx): 3387 """return Mahalanobis norm based on the current sample 3388 distribution. 3389 3390 The norm is based on Covariance matrix ``C`` times ``sigma**2``, 3391 and includes ``sigma_vec``. The expected Mahalanobis distance to 3392 the sample mean is about ``sqrt(dimension)``. 3393 3394 Argument 3395 -------- 3396 A *genotype* difference `dx`. 3397 3398 Example 3399 ------- 3400 >>> import cma, numpy 3401 >>> es = cma.CMAEvolutionStrategy(numpy.ones(10), 1) #doctest: +ELLIPSIS 3402 (5_w,... 3403 >>> xx = numpy.random.randn(2, 10) 3404 >>> d = es.mahalanobis_norm(es.gp.geno(xx[0]-xx[1])) 3405 3406 `d` is the distance "in" the true sample distribution, 3407 sampled points have a typical distance of ``sqrt(2*es.N)``, 3408 where ``es.N`` is the dimension, and an expected distance of 3409 close to ``sqrt(N)`` to the sample mean. In the example, 3410 `d` is the Euclidean distance, because C = I and sigma = 1. 3411 3412 """ 3413 return self.sm.norm(np.asarray(dx) / self.sigma_vec.scaling) / self.sigma 3414 3415 @property 3416 def isotropic_mean_shift(self): 3417 """normalized last mean shift, under random selection N(0,I) 3418 3419 distributed. 3420 3421 Caveat: while it is finite and close to sqrt(n) under random 3422 selection, the length of the normalized mean shift under 3423 *systematic* selection (e.g. on a linear function) tends to 3424 infinity for mueff -> infty. Hence it must be used with great 3425 care for large mueff. 3426 """ 3427 z = self.sm.transform_inverse((self.mean - self.mean_old) / 3428 self.sigma_vec.scaling) 3429 # TODO: 3430 # works unless a re-parametrisation has been done, and otherwise? 3431 # assert Mh.vequals_approximately(z, np.dot(es.B, (1. / es.D) * 3432 # np.dot(es.B.T, (es.mean - es.mean_old) / es.sigma_vec))) 3433 z /= self.sigma * self.sp.cmean 3434 z *= self.sp.weights.mueff**0.5 3435 return z 3436 3437 def disp_annotation(self): 3438 """print annotation line for `disp` ()""" 3439 print('Iterat #Fevals function value axis ratio sigma min&max std t[m:s]') 3440 sys.stdout.flush() 3441 3442 def disp(self, modulo=None): 3443 """print current state variables in a single-line. 3444 3445 Prints only if ``iteration_counter % modulo == 0``. 3446 3447 :See also: `disp_annotation`. 3448 """ 3449 if modulo is None: 3450 modulo = self.opts['verb_disp'] 3451 3452 # console display 3453 3454 if modulo: 3455 if (self.countiter - 1) % (10 * modulo) < 1: 3456 self.disp_annotation() 3457 if not hasattr(self, 'times_displayed'): 3458 self.time_last_displayed = 0 3459 self.times_displayed = 0 3460 3461 if self.countiter > 0 and (self.stop() or self.countiter < 4 3462 or self.countiter % modulo < 1 3463 or self.timer.elapsed - self.time_last_displayed > self.times_displayed): 3464 self.time_last_displayed = self.timer.elapsed 3465 self.times_displayed += 1 3466 if self.opts['verb_time']: 3467 toc = self.timer.elapsed 3468 stime = str(int(toc // 60)) + ':' + ("%2.1f" % (toc % 60)).rjust(4, '0') 3469 else: 3470 stime = '' 3471 print(' '.join((repr(self.countiter).rjust(5), 3472 repr(self.countevals).rjust(6), 3473 '%.15e' % (min(self.fit.fit)), 3474 '%4.1e' % (self.D.max() / self.D.min() 3475 if not self.opts['CMA_diagonal'] or self.countiter > self.opts['CMA_diagonal'] 3476 else max(self.sigma_vec*1) / min(self.sigma_vec*1)), 3477 '%6.2e' % self.sigma, 3478 '%6.0e' % (self.sigma * min(self.sigma_vec * self.dC**0.5)), 3479 '%6.0e' % (self.sigma * max(self.sigma_vec * self.dC**0.5)), 3480 stime))) 3481 # if self.countiter < 4: 3482 sys.stdout.flush() 3483 return self 3484 def plot(self, *args, **kwargs): 3485 """plot current state variables using `matplotlib`. 3486 3487 Details: calls `self.logger.plot`. 3488 """ 3489 try: 3490 self.logger.plot(*args, **kwargs) 3491 except AttributeError: 3492 utils.print_warning('plotting failed, no logger attribute found') 3493 except: 3494 utils.print_warning(('plotting failed with:', sys.exc_info()[0]), 3495 'plot', 'CMAEvolutionStrategy') 3496 return self 3497 3498class _CMAStopDict(dict): 3499 """keep and update a termination condition dictionary. 3500 3501 The dictionary is "usually" empty and returned by 3502 `CMAEvolutionStrategy.stop()`. The class methods entirely depend on 3503 `CMAEvolutionStrategy` class attributes. 3504 3505 Details 3506 ------- 3507 This class is not relevant for the end-user and could be a nested 3508 class, but nested classes cannot be serialized. 3509 3510 Example 3511 ------- 3512 >>> import cma 3513 >>> es = cma.CMAEvolutionStrategy(4 * [1], 1, {'verbose':-9}) #doctest: +ELLIPSIS 3514 >>> print(es.stop()) 3515 {} 3516 >>> es.optimize(cma.ff.sphere, verb_disp=0) #doctest: +ELLIPSIS 3517 <... 3518 >>> es.stop()['tolfun'] == 1e-11 3519 True 3520 3521 :See: `OOOptimizer.stop()`, `CMAEvolutionStrategy.stop()` 3522 3523 """ 3524 def __init__(self, d={}): 3525 update = isinstance(d, CMAEvolutionStrategy) 3526 super(_CMAStopDict, self).__init__({} if update else d) 3527 self.stoplist = [] # to keep multiple entries 3528 self.lastiter = 0 # probably not necessary 3529 self._get_value = None # a hack to pass some value 3530 self._value = None # in iteration zero value is always None 3531 try: 3532 self.stoplist = d.stoplist # multiple entries 3533 except: 3534 pass 3535 try: 3536 self.lastiter = d.lastiter # probably not necessary 3537 except: 3538 pass 3539 if update: 3540 self._update(d) 3541 3542 def __call__(self, es=None, check=True): 3543 """update and return the termination conditions dictionary 3544 3545 """ 3546 if not check: 3547 return self 3548 if es is None and self.es is None: 3549 raise ValueError('termination conditions need an optimizer to act upon') 3550 self._update(es) 3551 return self 3552 3553 def _update(self, es): 3554 """Test termination criteria and update dictionary 3555 3556 """ 3557 if es is None: 3558 es = self.es 3559 assert es is not None 3560 3561 if es.countiter == 0: # in this case termination tests fail 3562 if self._get_value: 3563 warnings.warn("Cannot get stop value before the first iteration") 3564 self.__init__() 3565 return self 3566 3567 if 11 < 3: # options might have changed, so countiter == 3568 if es.countiter == self.lastiter: # lastiter doesn't help 3569 try: 3570 if es == self.es: 3571 return self 3572 except: # self.es not yet assigned 3573 pass 3574 3575 self.lastiter = es.countiter 3576 self.es = es 3577 3578 self._get_value or self.clear() # compute conditions from scratch 3579 3580 N = es.N 3581 opts = es.opts 3582 self.opts = opts # a hack to get _addstop going 3583 3584 # check user versatile options from signals file 3585 3586 # in 5-D: adds 0% if file does not exist and 25% = 0.2ms per iteration if it exists and verbose=-9 3587 # old measure: adds about 40% time in 5-D, 15% if file is not present 3588 # to avoid any file checking set signals_filename to None or '' 3589 if opts['verbose'] >= -9 and opts['signals_filename'] and os.path.isfile(self.opts['signals_filename']): 3590 with open(opts['signals_filename'], 'r') as f: 3591 s = f.read() 3592 try: 3593 d = dict(ast.literal_eval(s.strip())) 3594 except SyntaxError: 3595 warnings.warn("SyntaxError when parsing the following expression with `ast.literal_eval`:" 3596 "\n\n%s\n(contents of file %s)" % (s, str(self.opts['signals_filename']))) 3597 else: 3598 for key in list(d): 3599 if key not in opts.versatile_options(): 3600 utils.print_warning(" unkown or non-versatile option '%s' found in file %s.\n" 3601 " Check out the #v annotation in ``cma.CMAOptions()``." 3602 % (key, self.opts['signals_filename'])) 3603 d.pop(key) 3604 opts.update(d) 3605 for key in d: 3606 opts.eval(key, {'N': N, 'dim': N}) 3607 3608 # fitness: generic criterion, user defined w/o default 3609 self._addstop('ftarget', 3610 es.best.f < opts['ftarget']) 3611 # maxiter, maxfevals: generic criteria 3612 self._addstop('maxfevals', 3613 es.countevals - 1 >= opts['maxfevals']) 3614 self._addstop('maxiter', 3615 ## meta_parameters.maxiter_multiplier == 1.0 3616 es.countiter >= 1.0 * opts['maxiter']) 3617 # tolx, tolfacupx: generic criteria 3618 # tolfun, tolfunhist (CEC:tolfun includes hist) 3619 3620 sigma_x_sigma_vec_x_sqrtdC = es.sigma * (es.sigma_vec.scaling * np.sqrt(es.dC)) 3621 self._addstop('tolfacupx', 3622 np.any(sigma_x_sigma_vec_x_sqrtdC > 3623 es.sigma0 * es.sigma_vec0 * opts['tolfacupx'])) 3624 self._addstop('tolx', 3625 all(sigma_x_sigma_vec_x_sqrtdC < opts['tolx']) and 3626 all(es.sigma * (es.sigma_vec.scaling * es.pc) < opts['tolx']), 3627 max(sigma_x_sigma_vec_x_sqrtdC) if self._get_value else None) 3628 # None only to be backwards compatible for the time being 3629 3630 current_fitness_range = max(es.fit.fit) - min(es.fit.fit) 3631 historic_fitness_range = max(es.fit.hist) - min(es.fit.hist) 3632 self._addstop('tolfun', 3633 current_fitness_range < opts['tolfun'] and # fit.fit is sorted including bound penalties 3634 historic_fitness_range < opts['tolfun']) 3635 self._addstop('tolfunrel', 3636 current_fitness_range < opts['tolfunrel'] * (es.fit.median0 - es.fit.median_min), 3637 current_fitness_range if self._get_value else None) 3638 self._addstop('tolfunhist', 3639 len(es.fit.hist) > 9 and 3640 historic_fitness_range < opts['tolfunhist']) 3641 3642 # worst seen false positive: table N=80,lam=80, getting worse for fevals=35e3 \approx 50 * N**1.5 3643 # but the median is not so much getting worse 3644 # / 5 reflects the sparsity of histbest/median 3645 # / 2 reflects the left and right part to be compared 3646 ## meta_parameters.tolstagnation_multiplier == 1.0 3647 l = max(( 1.0 * opts['tolstagnation'] / 5. / 2, len(es.fit.histbest) / 10)) 3648 # TODO: why max(..., len(histbest)/10) ??? 3649 # TODO: the problem in the beginning is only with best ==> ??? 3650 if 11 < 3: # print for debugging 3651 print(es.countiter, (opts['tolstagnation'], es.countiter > N * (5 + 100 / es.popsize), 3652 len(es.fit.histbest) > 100, 3653 np.median(es.fit.histmedian[:l]) >= np.median(es.fit.histmedian[l:2 * l]), 3654 np.median(es.fit.histbest[:l]) >= np.median(es.fit.histbest[l:2 * l]))) 3655 # equality should handle flat fitness 3656 if l <= es.countiter: 3657 l = int(l) # doesn't work for infinite l 3658 self._addstop('tolstagnation', # leads sometimes early stop on ftablet, fcigtab, N>=50? 3659 1 < 3 and opts['tolstagnation'] and es.countiter > N * (5 + 100 / es.popsize) and 3660 len(es.fit.histbest) > 100 and 2 * l < len(es.fit.histbest) and 3661 np.median(es.fit.histmedian[:l]) >= np.median(es.fit.histmedian[l:2 * l]) and 3662 np.median(es.fit.histbest[:l]) >= np.median(es.fit.histbest[l:2 * l])) 3663 # iiinteger: stagnation termination can prevent to find the optimum 3664 3665 self._addstop('tolupsigma', opts['tolupsigma'] and 3666 es.sigma / np.max(es.D) > es.sigma0 * opts['tolupsigma'], 3667 es.sigma / np.max(es.D) if self._get_value else None) 3668 try: 3669 self._addstop('timeout', 3670 es.timer.elapsed > opts['timeout'], 3671 es.timer.elapsed if self._get_value else None) 3672 except AttributeError: 3673 if es.countiter <= 0: 3674 pass 3675 # else: raise 3676 3677 if 11 < 3 and 2 * l < len(es.fit.histbest): # TODO: this might go wrong, because the nb of written columns changes 3678 tmp = np.array((-np.median(es.fit.histmedian[:l]) + np.median(es.fit.histmedian[l:2 * l]), 3679 - np.median(es.fit.histbest[:l]) + np.median(es.fit.histbest[l:2 * l]))) 3680 es.more_to_write += [(10**t if t < 0 else t + 1) for t in tmp] # the latter to get monotonicy 3681 3682 if 1 < 3: 3683 # non-user defined, method specific 3684 # noeffectaxis (CEC: 0.1sigma), noeffectcoord (CEC:0.2sigma), conditioncov 3685 idx = np.nonzero(es.mean == es.mean + 0.2 * sigma_x_sigma_vec_x_sqrtdC)[0] 3686 self._addstop('noeffectcoord', any(idx), list(idx)) 3687# any([es.mean[i] == es.mean[i] + 0.2 * es.sigma * 3688# (es.sigma_vec if np.isscalar(es.sigma_vec) else es.sigma_vec[i]) * 3689# sqrt(es.dC[i]) 3690# for i in range(N)]) 3691# ) 3692 if (opts['CMA_diagonal'] is not True and es.countiter > opts['CMA_diagonal'] and 3693 (es.countiter % 1) == 0): # save another factor of two? 3694 i = es.countiter % N 3695 try: 3696 self._addstop('noeffectaxis', 3697 np.sum(es.mean == es.mean + 0.1 * es.sigma * 3698 es.sm.D[i] * es.sigma_vec.scaling * 3699 (es.sm.B[:, i] if len(es.sm.B.shape) > 1 else es.sm.B[0])) == N) 3700 except AttributeError: 3701 pass 3702 self._addstop('tolconditioncov', 3703 opts['tolconditioncov'] and 3704 es.D[-1] > opts['tolconditioncov']**0.5 * es.D[0], opts['tolconditioncov']) 3705 3706 self._addstop('callback', any(es.callbackstop), es.callbackstop) # termination_callback 3707 3708 if 1 < 3 or len(self): # only if another termination criterion is satisfied 3709 if 1 < 3: 3710 if es.fit.fit[0] < es.fit.fit[int(0.75 * es.popsize)]: 3711 es.fit.flatfit_iterations = 0 3712 else: 3713 # print(es.fit.fit) 3714 es.fit.flatfit_iterations += 1 3715 if (es.fit.flatfit_iterations > opts['tolflatfitness'] # or 3716 # mainly for historical reasons: 3717 # max(es.fit.hist[:1 + int(opts['tolflatfitness'])]) == min(es.fit.hist[:1 + int(opts['tolflatfitness'])]) 3718 ): 3719 self._addstop('tolflatfitness') 3720 if 11 < 3 and max(es.fit.fit) == min(es.fit.fit) == es.best.last.f: # keep warning for historical reasons for the time being 3721 utils.print_warning( 3722 "flat fitness (f=%f, sigma=%.2e). " 3723 "For small sigma, this could indicate numerical convergence. \n" 3724 "Otherwise, please (re)consider how to compute the fitness more elaborately." % 3725 (es.fit.fit[0], es.sigma), iteration=es.countiter) 3726 if 11 < 3: # add stop condition, in case, replaced by above, subject to removal 3727 self._addstop('flat fitness', # message via stopdict 3728 len(es.fit.hist) > 9 and 3729 max(es.fit.hist) == min(es.fit.hist) and 3730 max(es.fit.fit) == min(es.fit.fit), 3731 "please (re)consider how to compute the fitness more elaborately if sigma=%.2e is large" % es.sigma) 3732 if 11 < 3 and opts['vv'] == 321: 3733 self._addstop('||xmean||^2<ftarget', sum(es.mean**2) <= opts['ftarget']) 3734 3735 return self 3736 3737 def _addstop(self, key, cond=True, val=None): 3738 if key == self._get_value: 3739 self._value = val 3740 self._get_value = None 3741 elif cond: 3742 self.stoplist.append(key) # can have the same key twice 3743 self[key] = val if val is not None \ 3744 else self.opts.get(key, None) 3745 3746 def clear(self): 3747 """empty the stopdict""" 3748 for k in list(self): 3749 self.pop(k) 3750 self.stoplist = [] 3751 3752class _CMAParameters(object): 3753 """strategy parameters like population size and learning rates. 3754 3755 Note: 3756 contrary to `CMAOptions`, `_CMAParameters` is not (yet) part of the 3757 "user-interface" and subject to future changes (it might become 3758 a `collections.namedtuple`) 3759 3760 Example 3761 ------- 3762 >>> import cma 3763 >>> es = cma.CMAEvolutionStrategy(20 * [0.1], 1) #doctest: +ELLIPSIS 3764 (6_w,12)-aCMA-ES (mu_w=3.7,w_1=40%) in dimension 20 (seed=...) 3765 >>> 3766 >>> type(es.sp) # sp contains the strategy parameters 3767 <class 'cma.evolution_strategy._CMAParameters'> 3768 >>> es.sp.disp() #doctest: +ELLIPSIS 3769 {'CMA_on': True, 3770 'N': 20, 3771 'c1': 0.00437235..., 3772 'c1_sep': 0.0343279..., 3773 'cc': 0.171767..., 3774 'cc_sep': 0.252594..., 3775 'cmean': array(1..., 3776 'cmu': 0.00921656..., 3777 'cmu_sep': 0.0565385..., 3778 'lam_mirr': 0, 3779 'mu': 6, 3780 'popsize': 12, 3781 'weights': [0.4024029428..., 3782 0.2533890840..., 3783 0.1662215645..., 3784 0.1043752252..., 3785 0.05640347757..., 3786 0.01720770576..., 3787 -0.05018713636..., 3788 -0.1406167894..., 3789 -0.2203813963..., 3790 -0.2917332686..., 3791 -0.3562788884..., 3792 -0.4152044225...]} 3793 >>> 3794 3795 :See: `CMAOptions`, `CMAEvolutionStrategy` 3796 3797 """ 3798 def __init__(self, N, opts, ccovfac=1, verbose=True): 3799 """Compute strategy parameters, mainly depending on 3800 dimension and population size, by calling `set` 3801 3802 """ 3803 self.N = N 3804 if ccovfac == 1: 3805 ccovfac = opts['CMA_on'] # that's a hack 3806 self.popsize = None # type: int - declaring the attribute, not necessary though 3807 self.set(opts, ccovfac=ccovfac, verbose=verbose) 3808 3809 def set(self, opts, popsize=None, ccovfac=1, verbose=True): 3810 """Compute strategy parameters as a function 3811 of dimension and population size """ 3812 3813 limit_fac_cc = 4.0 # in future: 10**(1 - N**-0.33)? 3814 3815 def conedf(df, mu, N): 3816 """used for computing separable learning rate""" 3817 return 1. / (df + 2. * np.sqrt(df) + float(mu) / N) 3818 3819 def cmudf(df, mu, alphamu): 3820 """used for computing separable learning rate""" 3821 return (alphamu + mu + 1. / mu - 2) / (df + 4 * np.sqrt(df) + mu / 2.) 3822 3823 sp = self # mainly for historical reasons 3824 N = sp.N 3825 if popsize: 3826 opts.evalall({'N':N, 'popsize':popsize}) 3827 else: 3828 popsize = opts.evalall({'N':N})['popsize'] # the default popsize is computed in CMAOptions() 3829 popsize *= opts['popsize_factor'] 3830 ## meta_parameters.lambda_exponent == 0.0 3831 popsize = int(popsize + N** 0.0 - 1) 3832 3833 # set weights 3834 if utils.is_(opts['CMA_recombination_weights']): 3835 sp.weights = RecombinationWeights(opts['CMA_recombination_weights']) 3836 popsize = len(sp.weights) 3837 elif opts['CMA_mu']: 3838 sp.weights = RecombinationWeights(2 * opts['CMA_mu']) 3839 while len(sp.weights) < popsize: 3840 sp.weights.insert(sp.weights.mu, 0.0) # doesn't change mu or mueff 3841 else: # default 3842 sp.weights = RecombinationWeights(popsize) 3843 # weights.finalize_negative_weights will be called below 3844 sp.popsize = popsize 3845 sp.mu = sp.weights.mu # not used anymore but for the record 3846 3847 if opts['CMA_mirrors'] < 0.5: 3848 sp.lam_mirr = int(0.5 + opts['CMA_mirrors'] * popsize) 3849 elif opts['CMA_mirrors'] > 1: 3850 sp.lam_mirr = int(0.5 + opts['CMA_mirrors']) 3851 else: 3852 sp.lam_mirr = int(0.5 + 0.16 * min((popsize, 2 * N + 2)) + 0.29) # 0.158650... * popsize is optimal 3853 # lam = arange(2,22) 3854 # mirr = 0.16 + 0.29/lam 3855 # print(lam); print([int(0.5 + l) for l in mirr*lam]) 3856 # [ 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21] 3857 # [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4] 3858 # in principle we have mu_opt = popsize/2 + lam_mirr/2, 3859 # which means in particular weights should only be negative for q > 0.5+mirr_frac/2 3860 if sp.popsize // 2 > sp.popsize - 2 * sp.lam_mirr + 1: 3861 utils.print_warning("pairwise selection is not implemented, therefore " + 3862 " mu = %d > %d = %d - 2*%d + 1 = popsize - 2*mirr + 1 can produce a bias" % ( 3863 sp.popsize // 2, sp.popsize - 2 * sp.lam_mirr + 1, sp.popsize, sp.lam_mirr)) 3864 if sp.lam_mirr > sp.popsize // 2: 3865 raise ValueError("fraction of mirrors in the population as read from option CMA_mirrors cannot be larger 0.5, " + 3866 "theoretically optimal is 0.159") 3867 3868 mueff = sp.weights.mueff 3869 3870 # line 3415 3871 ## meta_parameters.cc_exponent == 1.0 3872 b = 1.0 3873 ## meta_parameters.cc_multiplier == 1.0 3874 sp.cc = 1.0 * (limit_fac_cc + mueff / N)**b / \ 3875 (N**b + (limit_fac_cc + 2 * mueff / N)**b) 3876 sp.cc_sep = (1 + 1 / N + mueff / N) / \ 3877 (N**0.5 + 1 / N + 2 * mueff / N) 3878 if hasattr(opts['vv'], '__getitem__'): 3879 if 'sweep_ccov1' in opts['vv']: 3880 sp.cc = 1.0 * (4 + mueff / N)**0.5 / ((N + 4)**0.5 + 3881 (2 * mueff / N)**0.5) 3882 if 'sweep_cc' in opts['vv']: 3883 sp.cc = opts['vv']['sweep_cc'] 3884 sp.cc_sep = sp.cc 3885 print('cc is %f' % sp.cc) 3886 3887 ## meta_parameters.c1_multiplier == 1.0 3888 sp.c1 = (1.0 * opts['CMA_rankone'] * ccovfac * min(1, sp.popsize / 6) * 3889 ## meta_parameters.c1_exponent == 2.0 3890 2 / ((N + 1.3)** 2.0 + mueff)) 3891 # 2 / ((N + 1.3)** 1.5 + mueff)) # TODO 3892 # 2 / ((N + 1.3)** 1.75 + mueff)) # TODO 3893 # 1/0 3894 sp.c1_sep = opts['CMA_rankone'] * ccovfac * conedf(N, mueff, N) 3895 if 11 < 3: 3896 sp.c1 = 0. 3897 print('c1 is zero') 3898 if utils.is_(opts['CMA_rankmu']): # also empty 3899 ## meta_parameters.cmu_multiplier == 2.0 3900 alphacov = 2.0 3901 ## meta_parameters.rankmu_offset == 0.25 3902 rankmu_offset = 0.25 3903 # the influence of rankmu_offset in [0, 1] on performance is 3904 # barely visible 3905 if hasattr(opts['vv'], '__getitem__') and 'sweep_rankmu_offset' in opts['vv']: 3906 rankmu_offset = opts['vv']['sweep_rankmu_offset'] 3907 print("rankmu_offset = %.2f" % rankmu_offset) 3908 mu = mueff 3909 sp.cmu = min(1 - sp.c1, 3910 opts['CMA_rankmu'] * ccovfac * alphacov * 3911 # simpler nominator would be: (mu - 0.75) 3912 (rankmu_offset + mu + 1 / mu - 2) / 3913 ## meta_parameters.cmu_exponent == 2.0 3914 ((N + 2)** 2.0 + alphacov * mu / 2)) 3915 # ((N + 2)** 1.5 + alphacov * mu / 2)) # TODO 3916 # ((N + 2)** 1.75 + alphacov * mu / 2)) # TODO 3917 # cmu -> 1 for mu -> N**2 * (2 / alphacov) 3918 if hasattr(opts['vv'], '__getitem__') and 'sweep_ccov' in opts['vv']: 3919 sp.cmu = opts['vv']['sweep_ccov'] 3920 sp.cmu_sep = min(1 - sp.c1_sep, ccovfac * cmudf(N, mueff, rankmu_offset)) 3921 else: 3922 sp.cmu = sp.cmu_sep = 0 3923 if hasattr(opts['vv'], '__getitem__') and 'sweep_ccov1' in opts['vv']: 3924 sp.c1 = opts['vv']['sweep_ccov1'] 3925 3926 if any(w < 0 for w in sp.weights): 3927 if opts['CMA_active'] and opts['CMA_on'] and opts['CMA_rankmu']: 3928 sp.weights.finalize_negative_weights(N, sp.c1, sp.cmu) 3929 # this is re-done using self.sm.parameters()['c1']... 3930 else: 3931 sp.weights.zero_negative_weights() 3932 3933 # line 3834 3934 sp.CMA_on = sp.c1 + sp.cmu > 0 3935 # print(sp.c1_sep / sp.cc_sep) 3936 3937 if not opts['CMA_on'] and opts['CMA_on'] not in (None, [], (), ''): 3938 sp.CMA_on = False 3939 # sp.c1 = sp.cmu = sp.c1_sep = sp.cmu_sep = 0 3940 # line 3480 3941 if 11 < 3: 3942 # this is worse than damps = 1 + sp.cs for the (1,10000)-ES on 40D parabolic ridge 3943 sp.damps = 0.3 + 2 * max([mueff / sp.popsize, ((mueff - 1) / (N + 1))**0.5 - 1]) + sp.cs 3944 if 11 < 3: 3945 # this does not work for lambda = 4*N^2 on the parabolic ridge 3946 sp.damps = opts['CSA_dampfac'] * (2 - 0 * sp.lam_mirr / sp.popsize) * mueff / sp.popsize + 0.3 + sp.cs 3947 # nicer future setting 3948 print('damps =', sp.damps) 3949 if 11 < 3: 3950 sp.damps = 10 * sp.damps # 1e99 # (1 + 2*max(0,sqrt((mueff-1)/(N+1))-1)) + sp.cs; 3951 # sp.damps = 20 # 1. + 20 * sp.cs**-1 # 1e99 # (1 + 2*max(0,sqrt((mueff-1)/(N+1))-1)) + sp.cs; 3952 print('damps is %f' % (sp.damps)) 3953 3954 sp.cmean = np.asarray(opts['CMA_cmean'], dtype=float) 3955 # sp.kappa = 1 # 4-D, lam=16, rank1, kappa < 4 does not influence convergence rate 3956 # in larger dim it does, 15-D with defaults, kappa=8 factor 2 3957 if 11 < 3 and np.any(sp.cmean != 1): 3958 print(' cmean = ' + str(sp.cmean)) 3959 3960 if verbose: 3961 if not sp.CMA_on: 3962 print('covariance matrix adaptation turned off') 3963 if opts['CMA_mu'] != None: 3964 print('mu = %d' % (sp.weights.mu)) 3965 3966 # return self # the constructor returns itself 3967 3968 def disp(self): 3969 pprint(self.__dict__) 3970 3971 3972def fmin2(*args, **kwargs): 3973 """wrapper around `cma.fmin` returning the tuple ``(xbest, es)``, 3974 3975 and with the same in input arguments as `fmin`. Hence a typical 3976 calling pattern may be:: 3977 3978 x, es = cma.fmin2(...) # recommended pattern 3979 es = cma.fmin2(...)[1] # `es` contains all available information 3980 x = cma.fmin2(...)[0] # keep only the best evaluated solution 3981 3982 `fmin2` is an alias for:: 3983 3984 res = fmin(...) 3985 return res[0], res[-2] 3986 3987 `fmin` from `fmin2` is:: 3988 3989 es = fmin2(...)[1] # fmin2(...)[0] is es.result[0] 3990 return es.result + (es.stop(), es, es.logger) 3991 3992 The best found solution is equally available under:: 3993 3994 fmin(...)[0] 3995 fmin2(...)[0] 3996 fmin2(...)[1].result[0] 3997 fmin2(...)[1].result.xbest 3998 fmin2(...)[1].best.x 3999 4000 The incumbent, current estimate for the optimum is available under:: 4001 4002 fmin(...)[5] 4003 fmin2(...)[1].result[5] 4004 fmin2(...)[1].result.xfavorite 4005 4006 """ 4007 res = fmin(*args, **kwargs) 4008 return res[0], res[-2] 4009 4010 4011all_stoppings = [] # accessable via cma.evolution_strategy.all_stoppings, bound to change 4012def fmin(objective_function, x0, sigma0, 4013 options=None, 4014 args=(), 4015 gradf=None, 4016 restarts=0, 4017 restart_from_best='False', 4018 incpopsize=2, 4019 eval_initial_x=False, 4020 parallel_objective=None, 4021 noise_handler=None, 4022 noise_change_sigma_exponent=1, 4023 noise_kappa_exponent=0, # TODO: add max kappa value as parameter 4024 bipop=False, 4025 callback=None): 4026 """functional interface to the stochastic optimizer CMA-ES 4027 for non-convex function minimization. 4028 4029 Calling Sequences 4030 ================= 4031 ``fmin(objective_function, x0, sigma0)`` 4032 minimizes ``objective_function`` starting at ``x0`` and with 4033 standard deviation ``sigma0`` (step-size) 4034 ``fmin(objective_function, x0, sigma0, options={'ftarget': 1e-5})`` 4035 minimizes ``objective_function`` up to target function value 1e-5, 4036 which is typically useful for benchmarking. 4037 ``fmin(objective_function, x0, sigma0, args=('f',))`` 4038 minimizes ``objective_function`` called with an additional 4039 argument ``'f'``. 4040 ``fmin(objective_function, x0, sigma0, options={'ftarget':1e-5, 'popsize':40})`` 4041 uses additional options ``ftarget`` and ``popsize`` 4042 ``fmin(objective_function, esobj, None, options={'maxfevals': 1e5})`` 4043 uses the `CMAEvolutionStrategy` object instance ``esobj`` to 4044 optimize ``objective_function``, similar to ``esobj.optimize()``. 4045 4046 Arguments 4047 ========= 4048 ``objective_function`` 4049 called as ``objective_function(x, *args)`` to be minimized. 4050 ``x`` is a one-dimensional `numpy.ndarray`. See also the 4051 `parallel_objective` argument. 4052 ``objective_function`` can return `numpy.NaN`, which is 4053 interpreted as outright rejection of solution ``x`` and invokes 4054 an immediate resampling and (re-)evaluation of a new solution 4055 not counting as function evaluation. The attribute 4056 ``variable_annotations`` is passed into the 4057 ``CMADataLogger.persistent_communication_dict``. 4058 ``x0`` 4059 list or `numpy.ndarray`, initial guess of minimum solution 4060 before the application of the geno-phenotype transformation 4061 according to the ``transformation`` option. It can also be a 4062 callable that is called (without input argument) before each 4063 restart to yield the initial guess such that each restart may start 4064 from a different place. Otherwise, ``x0`` can also be a 4065 `cma.CMAEvolutionStrategy` object instance, in that case ``sigma0`` 4066 can be ``None``. 4067 ``sigma0`` 4068 scalar, initial standard deviation in each coordinate. 4069 ``sigma0`` should be about 1/4th of the search domain width 4070 (where the optimum is to be expected). The variables in 4071 ``objective_function`` should be scaled such that they 4072 presumably have similar sensitivity. 4073 See also `ScaleCoordinates`. 4074 ``options`` 4075 a dictionary with additional options passed to the constructor 4076 of class ``CMAEvolutionStrategy``, see ``cma.CMAOptions`` () 4077 for a list of available options. 4078 ``args=()`` 4079 arguments to be used to call the ``objective_function`` 4080 ``gradf=None`` 4081 gradient of f, where ``len(gradf(x, *args)) == len(x)``. 4082 ``gradf`` is called once in each iteration if 4083 ``gradf is not None``. 4084 ``restarts=0`` 4085 number of restarts with increasing population size, see also 4086 parameter ``incpopsize``, implementing the IPOP-CMA-ES restart 4087 strategy, see also parameter ``bipop``; to restart from 4088 different points (recommended), pass ``x0`` as a string. 4089 ``restart_from_best=False`` 4090 which point to restart from 4091 ``incpopsize=2`` 4092 multiplier for increasing the population size ``popsize`` before 4093 each restart 4094 ``parallel_objective`` 4095 an objective function that accepts a list of `numpy.ndarray` as 4096 input and returns a `list`, which is mostly used instead of 4097 `objective_function`, but for the initial (also initial 4098 elitist) and the final evaluations unless 4099 ``not callable(objective_function)``. If ``parallel_objective`` 4100 is given, the ``objective_function`` (first argument) may be 4101 ``None``. 4102 ``eval_initial_x=None`` 4103 evaluate initial solution, for ``None`` only with elitist option 4104 ``noise_handler=None`` 4105 a ``NoiseHandler`` class or instance or ``None``. Example: 4106 ``cma.fmin(f, 6 * [1], 1, noise_handler=cma.NoiseHandler(6))`` 4107 see ``help(cma.NoiseHandler)``. 4108 ``noise_change_sigma_exponent=1`` 4109 exponent for the sigma increment provided by the noise handler for 4110 additional noise treatment. 0 means no sigma change. 4111 ``noise_evaluations_as_kappa=0`` 4112 instead of applying reevaluations, the "number of evaluations" 4113 is (ab)used as scaling factor kappa (experimental). 4114 ``bipop=False`` 4115 if `True`, run as BIPOP-CMA-ES; BIPOP is a special restart 4116 strategy switching between two population sizings - small 4117 (like the default CMA, but with more focused search) and 4118 large (progressively increased as in IPOP). This makes the 4119 algorithm perform well both on functions with many regularly 4120 or irregularly arranged local optima (the latter by frequently 4121 restarting with small populations). For the `bipop` parameter 4122 to actually take effect, also select non-zero number of 4123 (IPOP) restarts; the recommended setting is ``restarts<=9`` 4124 and `x0` passed as a string using `numpy.rand` to generate 4125 initial solutions. Note that small-population restarts 4126 do not count into the total restart count. 4127 ``callback=None`` 4128 `callable` or list of callables called at the end of each 4129 iteration with the current `CMAEvolutionStrategy` instance 4130 as argument. 4131 4132 Optional Arguments 4133 ================== 4134 All values in the `options` dictionary are evaluated if they are of 4135 type `str`, besides `verb_filenameprefix`, see class `CMAOptions` for 4136 details. The full list is available by calling ``cma.CMAOptions()``. 4137 4138 >>> import cma 4139 >>> cma.CMAOptions() #doctest: +ELLIPSIS 4140 {... 4141 4142 Subsets of options can be displayed, for example like 4143 ``cma.CMAOptions('tol')``, or ``cma.CMAOptions('bound')``, 4144 see also class `CMAOptions`. 4145 4146 Return 4147 ====== 4148 Return the list provided in `CMAEvolutionStrategy.result` appended 4149 with termination conditions, an `OOOptimizer` and a `BaseDataLogger`:: 4150 4151 res = es.result + (es.stop(), es, logger) 4152 4153 where 4154 - ``res[0]`` (``xopt``) -- best evaluated solution 4155 - ``res[1]`` (``fopt``) -- respective function value 4156 - ``res[2]`` (``evalsopt``) -- respective number of function evaluations 4157 - ``res[3]`` (``evals``) -- number of overall conducted objective function evaluations 4158 - ``res[4]`` (``iterations``) -- number of overall conducted iterations 4159 - ``res[5]`` (``xmean``) -- mean of the final sample distribution 4160 - ``res[6]`` (``stds``) -- effective stds of the final sample distribution 4161 - ``res[-3]`` (``stop``) -- termination condition(s) in a dictionary 4162 - ``res[-2]`` (``cmaes``) -- class `CMAEvolutionStrategy` instance 4163 - ``res[-1]`` (``logger``) -- class `CMADataLogger` instance 4164 4165 Details 4166 ======= 4167 This function is an interface to the class `CMAEvolutionStrategy`. The 4168 latter class should be used when full control over the iteration loop 4169 of the optimizer is desired. 4170 4171 Examples 4172 ======== 4173 The following example calls `fmin` optimizing the Rosenbrock function 4174 in 10-D with initial solution 0.1 and initial step-size 0.5. The 4175 options are specified for the usage with the `doctest` module. 4176 4177 >>> import cma 4178 >>> # cma.CMAOptions() # returns all possible options 4179 >>> options = {'CMA_diagonal':100, 'seed':1234, 'verb_time':0} 4180 >>> 4181 >>> res = cma.fmin(cma.ff.rosen, [0.1] * 10, 0.3, options) #doctest: +ELLIPSIS 4182 (5_w,10)-aCMA-ES (mu_w=3.2,w_1=45%) in dimension 10 (seed=1234...) 4183 Covariance matrix is diagonal for 100 iterations (1/ccov=26... 4184 Iterat #Fevals function value axis ratio sigma ... 4185 1 10 ... 4186 termination on tolfun=1e-11 ... 4187 final/bestever f-value = ... 4188 >>> assert res[1] < 1e-12 # f-value of best found solution 4189 >>> assert res[2] < 8000 # evaluations 4190 4191 The above call is pretty much equivalent with the slightly more 4192 verbose call:: 4193 4194 res = cma.CMAEvolutionStrategy([0.1] * 10, 0.3, 4195 options=options).optimize(cma.ff.rosen).result 4196 4197 where `optimize` returns a `CMAEvolutionStrategy` instance. The 4198 following example calls `fmin` optimizing the Rastrigin function 4199 in 3-D with random initial solution in [-2,2], initial step-size 0.5 4200 and the BIPOP restart strategy (that progressively increases population). 4201 The options are specified for the usage with the `doctest` module. 4202 4203 >>> import cma 4204 >>> # cma.CMAOptions() # returns all possible options 4205 >>> options = {'seed':12345, 'verb_time':0, 'ftarget': 1e-8} 4206 >>> 4207 >>> res = cma.fmin(cma.ff.rastrigin, lambda : 2. * np.random.rand(3) - 1, 0.5, 4208 ... options, restarts=9, bipop=True) #doctest: +ELLIPSIS 4209 (3_w,7)-aCMA-ES (mu_w=2.3,w_1=58%) in dimension 3 (seed=12345... 4210 4211 In either case, the method:: 4212 4213 cma.plot(); 4214 4215 (based on `matplotlib.pyplot`) produces a plot of the run and, if 4216 necessary:: 4217 4218 cma.s.figshow() 4219 4220 shows the plot in a window. Finally:: 4221 4222 cma.s.figsave('myfirstrun') # figsave from matplotlib.pyplot 4223 4224 will save the figure in a png. 4225 4226 We can use the gradient like 4227 4228 >>> import cma 4229 >>> res = cma.fmin(cma.ff.rosen, np.zeros(10), 0.1, 4230 ... options = {'ftarget':1e-8,}, 4231 ... gradf=cma.ff.grad_rosen, 4232 ... ) #doctest: +ELLIPSIS 4233 (5_w,... 4234 >>> assert cma.ff.rosen(res[0]) < 1e-8 4235 >>> assert res[2] < 3600 # 1% are > 3300 4236 >>> assert res[3] < 3600 # 1% are > 3300 4237 4238 If solution can only be comparatively ranked, either use 4239 `CMAEvolutionStrategy` directly or the objective accepts a list 4240 of solutions as input: 4241 4242 >>> def parallel_sphere(X): return [cma.ff.sphere(x) for x in X] 4243 >>> x, es = cma.fmin2(None, 3 * [0], 0.1, {'verbose': -9}, 4244 ... parallel_objective=parallel_sphere) 4245 >>> assert es.result[1] < 1e-9 4246 4247 :See also: `CMAEvolutionStrategy`, `OOOptimizer.optimize`, `plot`, 4248 `CMAOptions`, `scipy.optimize.fmin` 4249 4250 """ # style guides say there should be the above empty line 4251 if 1 < 3: # try: # pass on KeyboardInterrupt 4252 if not objective_function and not parallel_objective: # cma.fmin(0, 0, 0) 4253 return CMAOptions() # these opts are by definition valid 4254 4255 fmin_options = locals().copy() # archive original options 4256 del fmin_options['objective_function'] 4257 del fmin_options['x0'] 4258 del fmin_options['sigma0'] 4259 del fmin_options['options'] 4260 del fmin_options['args'] 4261 4262 if options is None: 4263 options = cma_default_options 4264 CMAOptions().check_attributes(options) # might modify options 4265 # checked that no options.ftarget = 4266 opts = CMAOptions(options.copy()).complement() 4267 4268 if callback is None: 4269 callback = [] 4270 elif callable(callback): 4271 callback = [callback] 4272 4273 # BIPOP-related variables: 4274 runs_with_small = 0 4275 small_i = [] 4276 large_i = [] 4277 popsize0 = None # to be evaluated after the first iteration 4278 maxiter0 = None # to be evaluated after the first iteration 4279 base_evals = 0 4280 4281 irun = 0 4282 best = ot.BestSolution() 4283 all_stoppings = [] 4284 while True: # restart loop 4285 sigma_factor = 1 4286 4287 # Adjust the population according to BIPOP after a restart. 4288 if not bipop: 4289 # BIPOP not in use, simply double the previous population 4290 # on restart. 4291 if irun > 0: 4292 popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) 4293 opts['popsize'] = popsize0 * popsize_multiplier 4294 4295 elif irun == 0: 4296 # Initial run is with "normal" population size; it is 4297 # the large population before first doubling, but its 4298 # budget accounting is the same as in case of small 4299 # population. 4300 poptype = 'small' 4301 4302 elif sum(small_i) < sum(large_i): 4303 # An interweaved run with small population size 4304 poptype = 'small' 4305 if 11 < 3: # not needed when compared to irun - runs_with_small 4306 restarts += 1 # A small restart doesn't count in the total 4307 runs_with_small += 1 # _Before_ it's used in popsize_lastlarge 4308 4309 sigma_factor = 0.01**np.random.uniform() # Local search 4310 popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) 4311 opts['popsize'] = np.floor(popsize0 * popsize_multiplier**(np.random.uniform()**2)) 4312 opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize']) 4313 # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) 4314 4315 else: 4316 # A run with large population size; the population 4317 # doubling is implicit with incpopsize. 4318 poptype = 'large' 4319 4320 popsize_multiplier = fmin_options['incpopsize']**(irun - runs_with_small) 4321 opts['popsize'] = popsize0 * popsize_multiplier 4322 opts['maxiter'] = maxiter0 4323 # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter'])) 4324 4325 if not callable(objective_function) and callable(parallel_objective): 4326 objective_function = lambda x, *args: parallel_objective([x], *args)[0] 4327 objective_function.__doc__ = ('created from `parallel_objective`, ' 4328 'assign to `None` to pickle') 4329 4330 # recover from a CMA object 4331 if irun == 0 and isinstance(x0, CMAEvolutionStrategy): 4332 es = x0 4333 x0 = es.inputargs['x0'] # for the next restarts 4334 if np.isscalar(sigma0) and np.isfinite(sigma0) and sigma0 > 0: 4335 es.sigma = sigma0 4336 # debatable whether this makes sense: 4337 sigma0 = es.inputargs['sigma0'] # for the next restarts 4338 if options is not None: 4339 es.opts.set(options) 4340 # ignore further input args and keep original options 4341 else: # default case 4342 if irun and eval(str(fmin_options['restart_from_best'])): 4343 utils.print_warning('CAVE: restart_from_best is often not useful', 4344 verbose=opts['verbose']) 4345 es = CMAEvolutionStrategy(best.x, sigma_factor * sigma0, opts) 4346 else: 4347 es = CMAEvolutionStrategy(x0, sigma_factor * sigma0, opts) 4348 # return opts, es 4349 if callable(objective_function) and ( 4350 eval_initial_x 4351 or es.opts['CMA_elitist'] == 'initial' 4352 or (es.opts['CMA_elitist'] and 4353 eval_initial_x is None)): 4354 x = es.gp.pheno(es.mean, 4355 into_bounds=es.boundary_handler.repair, 4356 archive=es.sent_solutions) 4357 es.f0 = objective_function(x, *args) 4358 es.best.update([x], es.sent_solutions, 4359 [es.f0], 1) 4360 es.countevals += 1 4361 es.objective_function = objective_function # only for the record 4362 4363 opts = es.opts # processed options, unambiguous 4364 # a hack: 4365 fmin_opts = CMAOptions("unchecked", **fmin_options.copy()) 4366 for k in fmin_opts: 4367 # locals() cannot be modified directly, exec won't work 4368 # in 3.x, therefore 4369 fmin_opts.eval(k, loc={'N': es.N, 4370 'popsize': opts['popsize']}, 4371 correct_key=False) 4372 4373 es.logger.append = opts['verb_append'] or es.countiter > 0 or irun > 0 4374 # es.logger is "the same" logger, because the "identity" 4375 # is only determined by the `verb_filenameprefix` option 4376 logger = es.logger # shortcut 4377 try: 4378 logger.persistent_communication_dict.update( 4379 {'variable_annotations': 4380 objective_function.variable_annotations}) 4381 except AttributeError: 4382 pass 4383 4384 if 11 < 3: 4385 if es.countiter == 0 and es.opts['verb_log'] > 0 and \ 4386 not es.opts['verb_append']: 4387 logger = CMADataLogger(es.opts['verb_filenameprefix'] 4388 ).register(es) 4389 logger.add() 4390 es.writeOutput() # initial values for sigma etc 4391 4392 if noise_handler: 4393 if isinstance(noise_handler, type): 4394 noisehandler = noise_handler(es.N) 4395 else: 4396 noisehandler = noise_handler 4397 noise_handling = True 4398 if fmin_opts['noise_change_sigma_exponent'] > 0: 4399 es.opts['tolfacupx'] = inf 4400 else: 4401 noisehandler = ot.NoiseHandler(es.N, 0) # switched off 4402 noise_handling = False 4403 es.noise_handler = noisehandler 4404 4405 # the problem: this assumes that good solutions cannot take longer than bad ones: 4406 # with EvalInParallel(objective_function, 2, is_feasible=opts['is_feasible']) as eval_in_parallel: 4407 if 1 < 3: 4408 while not es.stop(): # iteration loop 4409 # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1) 4410 X, fit = es.ask_and_eval(parallel_objective or objective_function, 4411 args, gradf=gradf, 4412 evaluations=noisehandler.evaluations, 4413 aggregation=np.median, 4414 parallel_mode=parallel_objective) # treats NaN with resampling if not parallel_mode 4415 # TODO: check args and in case use args=(noisehandler.evaluations, ) 4416 4417 if 11 < 3 and opts['vv']: # inject a solution 4418 # use option check_point = [0] 4419 if 0 * np.random.randn() >= 0: 4420 X[0] = 0 + opts['vv'] * es.sigma**0 * np.random.randn(es.N) 4421 fit[0] = objective_function(X[0], *args) 4422 # print fit[0] 4423 if es.opts['verbose'] > 4: # may be undesirable with dynamic fitness (e.g. Augmented Lagrangian) 4424 if es.countiter < 2 or min(fit) <= es.best.last.f: 4425 degrading_iterations_count = 0 # comes first to avoid code check complaint 4426 else: # min(fit) > es.best.last.f: 4427 degrading_iterations_count += 1 4428 if degrading_iterations_count > 4: 4429 utils.print_message('%d f-degrading iterations (set verbose<=4 to suppress)' 4430 % degrading_iterations_count, 4431 iteration=es.countiter) 4432 es.tell(X, fit) # prepare for next iteration 4433 if noise_handling: # it would be better to also use these f-evaluations in tell 4434 es.sigma *= noisehandler(X, fit, objective_function, es.ask, 4435 args=args)**fmin_opts['noise_change_sigma_exponent'] 4436 4437 es.countevals += noisehandler.evaluations_just_done # TODO: this is a hack, not important though 4438 # es.more_to_write.append(noisehandler.evaluations_just_done) 4439 if noisehandler.maxevals > noisehandler.minevals: 4440 es.more_to_write.append(noisehandler.evaluations) 4441 if 1 < 3: 4442 # If sigma was above multiplied by the same 4443 # factor cmean is divided by here, this is 4444 # like only multiplying kappa instead of 4445 # changing cmean and sigma. 4446 es.sp.cmean *= np.exp(-noise_kappa_exponent * np.tanh(noisehandler.noiseS)) 4447 es.sp.cmean[es.sp.cmean > 1] = 1.0 # also works with "scalar arrays" like np.array(1.2) 4448 for f in callback: 4449 f is None or f(es) 4450 es.disp() 4451 logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [], 4452 modulo=1 if es.stop() and logger.modulo else None) 4453 if (opts['verb_log'] and opts['verb_plot'] and 4454 (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())): 4455 logger.plot(324) 4456 4457 # end while not es.stop 4458 if opts['eval_final_mean'] and callable(objective_function): 4459 mean_pheno = es.gp.pheno(es.mean, 4460 into_bounds=es.boundary_handler.repair, 4461 archive=es.sent_solutions) 4462 fmean = objective_function(mean_pheno, *args) 4463 es.countevals += 1 4464 es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals) 4465 4466 best.update(es.best, es.sent_solutions) # in restarted case 4467 # es.best.update(best) 4468 4469 this_evals = es.countevals - base_evals 4470 base_evals = es.countevals 4471 4472 # BIPOP stats update 4473 4474 if irun == 0: 4475 popsize0 = opts['popsize'] 4476 maxiter0 = opts['maxiter'] 4477 # XXX: This might be a bug? Reproduced from Matlab 4478 # small_i.append(this_evals) 4479 4480 if bipop: 4481 if poptype == 'small': 4482 small_i.append(this_evals) 4483 else: # poptype == 'large' 4484 large_i.append(this_evals) 4485 4486 # final message 4487 if opts['verb_disp']: 4488 es.result_pretty(irun, time.asctime(time.localtime()), 4489 best.f) 4490 4491 irun += 1 4492 # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \ 4493 # if irun > restarts or 'ftarget' in es.stop() \ 4494 all_stoppings.append(dict(es.stop(check=False))) # keeping the order 4495 if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \ 4496 or 'maxfevals' in es.stop(check=False) or 'callback' in es.stop(check=False): 4497 break 4498 opts['verb_append'] = es.countevals 4499 opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize # TODO: use rather options? 4500 try: 4501 opts['seed'] += 1 4502 except TypeError: 4503 pass 4504 4505 # while irun 4506 4507 # es.out['best'] = best # TODO: this is a rather suboptimal type for inspection in the shell 4508 if irun: 4509 es.best.update(best) 4510 # TODO: there should be a better way to communicate the overall best 4511 return es.result + (es.stop(), es, logger) 4512 ### 4560 4513 # TODO refine output, can #args be flexible? 4514 # is this well usable as it is now? 4515 else: # except KeyboardInterrupt: # Exception as e: 4516 if eval(str(options['verb_disp'])) > 0: 4517 print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception') 4518 raise KeyboardInterrupt # cave: swallowing this exception can silently mess up experiments, if ctrl-C is hit 4519 4520def no_constraints(x): 4521 return [] 4522 4523def _al_set_logging(al, kwargs): 4524 def extract_logging_value(kwargs): 4525 if 'logging' in kwargs: 4526 return kwargs['logging'] 4527 if 'verbose' in kwargs and kwargs['verbose'] <= -3: 4528 return False 4529 if 'options' in kwargs: 4530 ko = kwargs['options'] 4531 if 'verb_log' in ko: 4532 return ko['verb_log'] 4533 if 'verbose' in ko and ko['verbose'] <= -3: 4534 return False 4535 logging = extract_logging_value(kwargs) 4536 if logging is not None: 4537 al.logging = logging 4538 4539def fmin_con(objective_function, x0, sigma0, 4540 g=no_constraints, h=no_constraints, **kwargs): 4541 """optimize f with constraints g (inequalities) and h (equalities). 4542 4543 Construct an Augmented Lagrangian instance ``f_aug_lag`` of the type 4544 `cma.constraints_handler.AugmentedLagrangian` from `objective_function` 4545 and `g` and `h`. 4546 4547 Equality constraints should preferably be passed as two inequality 4548 constraints like ``[h - eps, -h - eps]``, with eps >= 0. When eps > 0, 4549 also feasible solution tracking can succeed. 4550 4551 Return a `tuple` ``es.results.xfavorite:numpy.array, es:CMAEvolutionStrategy``, 4552 where ``es == cma.fmin2(f_aug_lag, x0, sigma0, **kwargs)[1]``. 4553 4554 Depending on ``kwargs['logging']`` and on the verbosity settings in 4555 ``kwargs['options']``, the `AugmentedLagrangian` writes (hidden) 4556 logging files. 4557 4558 The second return value:`CMAEvolutionStrategy` has an (additional) 4559 attribute ``best_feasible`` which contains the information about the 4560 best feasible solution in the ``best_feasible.info`` dictionary, given 4561 any feasible solution was found. This only works with inequality 4562 constraints (equality constraints are wrongly interpreted as inequality 4563 constraints). 4564 4565 See `cma.fmin` for further parameters ``**kwargs``. 4566 4567 >>> import cma 4568 >>> x, es = cma.evolution_strategy.fmin_con( 4569 ... cma.ff.sphere, 3 * [0], 1, g=lambda x: [1 - x[0]**2, -(1 - x[0]**2) - 1e-6], 4570 ... options={'termination_callback': lambda es: -1e-5 < es.mean[0]**2 - 1 < 1e-5, 4571 ... 'verbose':-9}) 4572 >>> assert 'callback' in es.stop() 4573 >>> assert es.result.evaluations < 1500 # 10%-ish above 1000, 1%-ish above 1300 4574 >>> assert (sum(es.mean**2) - 1)**2 < 1e-9 4575 4576 >>> x, es = cma.evolution_strategy.fmin_con( 4577 ... cma.ff.sphere, 2 * [0], 1, g=lambda x: [1 - x[0]**2], 4578 ... options={'termination_callback': lambda es: -1e-5 < es.mean[0]**2 - 1 < 1e-5, 4579 ... 'seed':1, 'verbose':-9}) 4580 >>> es.best_feasible.f < 1 + 1e-5 4581 True 4582 >>> ".info attribute dictionary keys: {}".format(sorted(es.best_feasible.info)) 4583 ".info attribute dictionary keys: ['f', 'g', 'g_al', 'x']" 4584 4585 Details: this is a versatile function subject to changes. It is possible to access 4586 the `AugmentedLagrangian` instance like 4587 4588 >>> al = es.augmented_lagrangian 4589 >>> isinstance(al, cma.constraints_handler.AugmentedLagrangian) 4590 True 4591 >>> # al.logger.plot() # plots the evolution of AL coefficients 4592 4593 """ 4594 # TODO: need to rethink equality/inequality interface? 4595 4596 if 'parallel_objective' in kwargs: 4597 raise ValueError("`parallel_objective` parameter is not supported by cma.fmin_con") 4598 # prepare callback list 4599 if callable(kwargs.setdefault('callback', [])): 4600 kwargs['callback'] = [kwargs['callback']] 4601 4602 global _al # for debugging, may be removed at some point 4603 F = [] 4604 G = [] 4605 _al = AugmentedLagrangian(len(x0)) 4606 _al_set_logging(_al, kwargs) 4607 4608 # _al.chi_domega = 1.1 4609 # _al.dgamma = 1.5 4610 4611 best_feasible_solution = ot.BestSolution2() 4612 4613 def f(x): 4614 F.append(objective_function(x)) 4615 return F[-1] 4616 def constraints(x): 4617 gvals, hvals = g(x), h(x) 4618 # set m and equality attributes of al 4619 if _al.lam is None: # TODO: better abide by an "official" interface? 4620 _al.set_m(len(gvals) + len(hvals)) 4621 _al._equality = np.asarray(len(gvals) * [False] + len(hvals) * [True], 4622 dtype='bool') 4623 G.append(list(gvals) + list(hvals)) 4624 return G[-1] 4625 def auglag(x): 4626 fval, gvals = f(x), constraints(x) 4627 alvals = _al(gvals) 4628 if all([gi <= 0 for gi in gvals]): 4629 best_feasible_solution.update(fval, 4630 info={'x':x, 'f': fval, 'g':gvals, 'g_al':alvals}) 4631 return fval + sum(alvals) 4632 def set_coefficients(es): 4633 _al.set_coefficients(F, G) 4634 F[:], G[:] = [], [] 4635 def update(es): 4636 x = es.ask(1, sigma_fac=0)[0] 4637 _al.update(f(x), constraints(x)) 4638 4639 kwargs['callback'].extend([set_coefficients, update]) 4640 # The smallest observed f-values may be below the limit value f(x^*_feas) 4641 # because f-values depend on the adaptive multipliers. Hence we overwrite 4642 # the default tolstagnation value: 4643 kwargs.setdefault('options', {}).setdefault('tolstagnation', 0) 4644 _, es = fmin2(auglag, x0, sigma0, **kwargs) 4645 es.objective_function_complements = [_al] 4646 es.augmented_lagrangian = _al 4647 es.best_feasible = best_feasible_solution 4648 return es.result.xfavorite, es 4649