docs: def fmin(objective_function, x0, sigma0,
           options=None,
           args=(),
           gradf=None,
           restarts=0,
           restart_from_best='False',
           incpopsize=2,
           eval_initial_x=False,
           noise_handler=None,
           noise_change_sigma_exponent=1,
           noise_kappa_exponent=0,  # TODO: add max kappa value as parameter
           bipop=False):
      """functional interface to the stochastic optimizer CMA-ES
      for non-convex function minimization.
  
      Calling Sequences
      =================
          ``fmin(objective_function, x0, sigma0)``
              minimizes `objective_function` starting at `x0` and with standard deviation
              `sigma0` (step-size)
          ``fmin(objective_function, x0, sigma0, options={'ftarget': 1e-5})``
              minimizes `objective_function` up to target function value 1e-5, which
              is typically useful for benchmarking.
          ``fmin(objective_function, x0, sigma0, args=('f',))``
              minimizes `objective_function` called with an additional argument ``'f'``.
          ``fmin(objective_function, x0, sigma0, options={'ftarget':1e-5, 'popsize':40})``
              uses additional options ``ftarget`` and ``popsize``
          ``fmin(objective_function, esobj, None, options={'maxfevals': 1e5})``
              uses the `CMAEvolutionStrategy` object instance `esobj` to optimize
              `objective_function`, similar to `esobj.optimize()`.
  
      Arguments
      =========
          `objective_function`
              function to be minimized. Called as ``objective_function(x,
              *args)``. `x` is a one-dimensional `numpy.ndarray`.
              `objective_function` can return `numpy.NaN`,
              which is interpreted as outright rejection of solution `x`
              and invokes an immediate resampling and (re-)evaluation
              of a new solution not counting as function evaluation.
          `x0`
              list or `numpy.ndarray`, initial guess of minimum solution
              before the application of the geno-phenotype transformation
              according to the ``transformation`` option.  It can also be
              a string holding a Python expression that is evaluated
              to yield the initial guess - this is important in case
              restarts are performed so that they start from different
              places.  Otherwise `x0` can also be a `cma.CMAEvolutionStrategy`
              object instance, in that case `sigma0` can be ``None``.
          `sigma0`
              scalar, initial standard deviation in each coordinate.
              `sigma0` should be about 1/4th of the search domain width
              (where the optimum is to be expected). The variables in
              `objective_function` should be scaled such that they
              presumably have similar sensitivity.
              See also option `scaling_of_variables`.
          `options`
              a dictionary with additional options passed to the constructor
              of class ``CMAEvolutionStrategy``, see ``cma.CMAOptions()``
              for a list of available options.
          ``args=()``
              arguments to be used to call the `objective_function`
          ``gradf``
              gradient of f, where ``len(gradf(x, *args)) == len(x)``.
              `gradf` is called once in each iteration if
              ``gradf is not None``.
          ``restarts=0``
              number of restarts with increasing population size, see also
              parameter `incpopsize`, implementing the IPOP-CMA-ES restart
              strategy, see also parameter `bipop`; to restart from
              different points (recommended), pass `x0` as a string.
          ``restart_from_best=False``
              which point to restart from
          ``incpopsize=2``
              multiplier for increasing the population size `popsize` before
              each restart
          ``eval_initial_x=None``
              evaluate initial solution, for `None` only with elitist option
          ``noise_handler=None``
              a ``NoiseHandler`` instance or ``None``, a simple usecase is
              ``cma.fmin(f, 6 * [1], 1, noise_handler=cma.NoiseHandler(6))``
              see ``help(cma.NoiseHandler)``.
          ``noise_change_sigma_exponent=1``
              exponent for sigma increment for additional noise treatment
          ``noise_evaluations_as_kappa``
              instead of applying reevaluations, the "number of evaluations"
              is (ab)used as scaling factor kappa (experimental).
          ``bipop``
              if True, run as BIPOP-CMA-ES; BIPOP is a special restart
              strategy switching between two population sizings - small
              (like the default CMA, but with more focused search) and
              large (progressively increased as in IPOP). This makes the
              algorithm perform well both on functions with many regularly
              or irregularly arranged local optima (the latter by frequently
              restarting with small populations).  For the `bipop` parameter
              to actually take effect, also select non-zero number of
              (IPOP) restarts; the recommended setting is ``restarts<=9``
              and `x0` passed as a string.  Note that small-population
              restarts do not count into the total restart count.
  
      Optional Arguments
      ==================
      All values in the `options` dictionary are evaluated if they are of
      type `str`, besides `verb_filenameprefix`, see class `CMAOptions` for
      details. The full list is available via ``cma.CMAOptions()``.
  
      >>> import cma
      >>> cma.CMAOptions()
  
      Subsets of options can be displayed, for example like
      ``cma.CMAOptions('tol')``, or ``cma.CMAOptions('bound')``,
      see also class `CMAOptions`.
  
      Return
      ======
      Return the list provided by `CMAEvolutionStrategy.result()` appended
      with termination conditions, an `OOOptimizer` and a `BaseDataLogger`::
  
          res = es.result() + (es.stop(), es, logger)
  
      where
          - ``res[0]`` (``xopt``) -- best evaluated solution
          - ``res[1]`` (``fopt``) -- respective function value
          - ``res[2]`` (``evalsopt``) -- respective number of function evaluations
          - ``res[3]`` (``evals``) -- number of overall conducted objective function evaluations
          - ``res[4]`` (``iterations``) -- number of overall conducted iterations
          - ``res[5]`` (``xmean``) -- mean of the final sample distribution
          - ``res[6]`` (``stds``) -- effective stds of the final sample distribution
          - ``res[-3]`` (``stop``) -- termination condition(s) in a dictionary
          - ``res[-2]`` (``cmaes``) -- class `CMAEvolutionStrategy` instance
          - ``res[-1]`` (``logger``) -- class `CMADataLogger` instance
  
      Details
      =======
      This function is an interface to the class `CMAEvolutionStrategy`. The
      latter class should be used when full control over the iteration loop
      of the optimizer is desired.
  
      Examples
      ========
      The following example calls `fmin` optimizing the Rosenbrock function
      in 10-D with initial solution 0.1 and initial step-size 0.5. The
      options are specified for the usage with the `doctest` module.
  
      >>> import cma
      >>> # cma.CMAOptions()  # returns all possible options
      >>> options = {'CMA_diagonal':100, 'seed':1234, 'verb_time':0}
      >>>
      >>> res = cma.fmin(cma.fcts.rosen, [0.1] * 10, 0.5, options)
      (5_w,10)-CMA-ES (mu_w=3.2,w_1=45%) in dimension 10 (seed=1234)
         Covariance matrix is diagonal for 10 iterations (1/ccov=29.0)
      Iterat #Fevals   function value     axis ratio  sigma   minstd maxstd min:sec
          1      10 1.264232686260072e+02 1.1e+00 4.40e-01  4e-01  4e-01
          2      20 1.023929748193649e+02 1.1e+00 4.00e-01  4e-01  4e-01
          3      30 1.214724267489674e+02 1.2e+00 3.70e-01  3e-01  4e-01
        100    1000 6.366683525319511e+00 6.2e+00 2.49e-02  9e-03  3e-02
        200    2000 3.347312410388666e+00 1.2e+01 4.52e-02  8e-03  4e-02
        300    3000 1.027509686232270e+00 1.3e+01 2.85e-02  5e-03  2e-02
        400    4000 1.279649321170636e-01 2.3e+01 3.53e-02  3e-03  3e-02
        500    5000 4.302636076186532e-04 4.6e+01 4.78e-03  3e-04  5e-03
        600    6000 6.943669235595049e-11 5.1e+01 5.41e-06  1e-07  4e-06
        650    6500 5.557961334063003e-14 5.4e+01 1.88e-07  4e-09  1e-07
      termination on tolfun : 1e-11
      final/bestever f-value = 5.55796133406e-14 2.62435631419e-14
      mean solution:  [ 1.          1.00000001  1.          1.
          1.          1.00000001  1.00000002  1.00000003 ...]
      std deviation: [ 3.9193387e-09  3.7792732e-09  4.0062285e-09  4.6605925e-09
          5.4966188e-09   7.4377745e-09   1.3797207e-08   2.6020765e-08 ...]
      >>>
      >>> print('best solutions fitness = %f' % (res[1]))
      best solutions fitness = 2.62435631419e-14
      >>> assert res[1] < 1e-12
  
      The above call is pretty much equivalent with the slightly more
      verbose call::
  
          es = cma.CMAEvolutionStrategy([0.1] * 10, 0.5,
                      options=options).optimize(cma.fcts.rosen)
  
      The following example calls `fmin` optimizing the Rastrigin function
      in 3-D with random initial solution in [-2,2], initial step-size 0.5
      and the BIPOP restart strategy (that progressively increases population).
      The options are specified for the usage with the `doctest` module.
  
      >>> import cma
      >>> # cma.CMAOptions()  # returns all possible options
      >>> options = {'seed':12345, 'verb_time':0, 'ftarget': 1e-8}
      >>>
      >>> res = cma.fmin(cma.fcts.rastrigin, '2. * np.random.rand(3) - 1', 0.5,
      ...                options, restarts=9, bipop=True)
      (3_w,7)-aCMA-ES (mu_w=2.3,w_1=58%) in dimension 3 (seed=12345)
      Iterat #Fevals   function value    axis ratio  sigma  minstd maxstd min:sec
          1       7 1.633489455763566e+01 1.0e+00 4.35e-01  4e-01  4e-01
          2      14 9.762462950258016e+00 1.2e+00 4.12e-01  4e-01  4e-01
          3      21 2.461107851413725e+01 1.4e+00 3.78e-01  3e-01  4e-01
        100     700 9.949590571272680e-01 1.7e+00 5.07e-05  3e-07  5e-07
        123     861 9.949590570932969e-01 1.3e+00 3.93e-06  9e-09  1e-08
      termination on tolfun=1e-11
      final/bestever f-value = 9.949591e-01 9.949591e-01
      mean solution: [  9.94958638e-01  -7.19265205e-10   2.09294450e-10]
      std deviation: [  8.71497860e-09   8.58994807e-09   9.85585654e-09]
      [...]
      (4_w,9)-aCMA-ES (mu_w=2.8,w_1=49%) in dimension 3 (seed=12349)
      Iterat #Fevals   function value    axis ratio  sigma  minstd maxstd min:sec
          1  5342.0 2.114883315350800e+01 1.0e+00 3.42e-02  3e-02  4e-02
          2  5351.0 1.810102940125502e+01 1.4e+00 3.79e-02  3e-02  4e-02
          3  5360.0 1.340222457448063e+01 1.4e+00 4.58e-02  4e-02  6e-02
         50  5783.0 8.631491965616078e-09 1.6e+00 2.01e-04  8e-06  1e-05
      termination on ftarget=1e-08 after 4 restarts
      final/bestever f-value = 8.316963e-09 8.316963e-09
      mean solution: [ -3.10652459e-06   2.77935436e-06  -4.95444519e-06]
      std deviation: [  1.02825265e-05   8.08348144e-06   8.47256408e-06]
  
      In either case, the method::
  
          cma.plot();
  
      (based on `matplotlib.pyplot`) produces a plot of the run and, if
      necessary::
  
          cma.show()
  
      shows the plot in a window. Finally::
  
          cma.savefig('myfirstrun')  # savefig from matplotlib.pyplot
  
      will save the figure in a png.
  
      We can use the gradient like
  
      >>> import cma
      >>> res = cma.fmin(cma.fcts.rosen, np.zeros(10), 0.1,
      ...             options = {'ftarget':1e-8,},
      ...             gradf=cma.fcts.grad_rosen,
      ...         )
      >>> assert cma.fcts.rosen(res[0]) < 1e-8
      >>> assert res[2] < 3600  # 1% are > 3300
      >>> assert res[3] < 3600  # 1% are > 3300
  
      :See: `CMAEvolutionStrategy`, `OOOptimizer.optimize(), `plot()`,
          `CMAOptions`, `scipy.optimize.fmin()`
  
      """  # style guides say there should be the above empty line
      if 1 < 3:  # try: # pass on KeyboardInterrupt
          if not objective_function:  # cma.fmin(0, 0, 0)
              return CMAOptions()  # these opts are by definition valid
  
          fmin_options = locals().copy()  # archive original options
          del fmin_options['objective_function']
          del fmin_options['x0']
          del fmin_options['sigma0']
          del fmin_options['options']
          del fmin_options['args']
  
          if options is None:
              options = cma_default_options
          CMAOptions().check_attributes(options)  # might modify options
          # checked that no options.ftarget =
          opts = CMAOptions(options.copy()).complement()
  
          # BIPOP-related variables:
          runs_with_small = 0
          small_i = []
          large_i = []
          popsize0 = None  # to be evaluated after the first iteration
          maxiter0 = None  # to be evaluated after the first iteration
          base_evals = 0
  
          irun = 0
          best = BestSolution()
          while True:  # restart loop
              sigma_factor = 1
  
              # Adjust the population according to BIPOP after a restart.
              if not bipop:
                  # BIPOP not in use, simply double the previous population
                  # on restart.
                  if irun > 0:
                      popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
                      opts['popsize'] = popsize0 * popsize_multiplier
  
              elif irun == 0:
                  # Initial run is with "normal" population size; it is
                  # the large population before first doubling, but its
                  # budget accounting is the same as in case of small
                  # population.
                  poptype = 'small'
  
              elif sum(small_i) < sum(large_i):
                  # An interweaved run with small population size
                  poptype = 'small'
                  runs_with_small += 1  # _Before_ it's used in popsize_lastlarge
  
                  sigma_factor = 0.01 ** np.random.uniform()  # Local search
                  popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
                  opts['popsize'] = np.floor(popsize0 * popsize_multiplier ** (np.random.uniform() ** 2))
                  opts['maxiter'] = min(maxiter0, 0.5 * sum(large_i) / opts['popsize'])
                  # print('small basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter']))
  
              else:
                  # A run with large population size; the population
                  # doubling is implicit with incpopsize.
                  poptype = 'large'
  
                  popsize_multiplier = fmin_options['incpopsize'] ** (irun - runs_with_small)
                  opts['popsize'] = popsize0 * popsize_multiplier
                  opts['maxiter'] = maxiter0
                  # print('large basemul %s --> %s; maxiter %s' % (popsize_multiplier, opts['popsize'], opts['maxiter']))
  
              # recover from a CMA object
              if irun == 0 and isinstance(x0, CMAEvolutionStrategy):
                  es = x0
                  x0 = es.inputargs['x0']  # for the next restarts
                  if isscalar(sigma0) and isfinite(sigma0) and sigma0 > 0:
                      es.sigma = sigma0
                  # debatable whether this makes sense:
                  sigma0 = es.inputargs['sigma0']  # for the next restarts
                  if options is not None:
                      es.opts.set(options)
                  # ignore further input args and keep original options
              else:  # default case
                  if irun and eval(str(fmin_options['restart_from_best'])):
                      print_warning('CAVE: restart_from_best is often not useful',
                                    verbose=opts['verbose'])
                      es = CMAEvolutionStrategy(best.x, sigma_factor * sigma0, opts)
                  else:
                      es = CMAEvolutionStrategy(x0, sigma_factor * sigma0, opts)
                  if eval_initial_x or es.opts['CMA_elitist'] == 'initial' \
                     or (es.opts['CMA_elitist'] and eval_initial_x is None):
                      x = es.gp.pheno(es.mean,
                                      into_bounds=es.boundary_handler.repair,
                                      archive=es.sent_solutions)
                      es.best.update([x], es.sent_solutions,
                                     [objective_function(x, *args)], 1)
                      es.countevals += 1
  
              opts = es.opts  # processed options, unambiguous
              # a hack:
              fmin_opts = CMAOptions(fmin_options.copy(), unchecked=True)
              for k in fmin_opts:
                  # locals() cannot be modified directly, exec won't work
                  # in 3.x, therefore
                  fmin_opts.eval(k, loc={'N': es.N,
                                         'popsize': opts['popsize']},
                                 correct_key=False)
  
              append = opts['verb_append'] or es.countiter > 0 or irun > 0
              # es.logger is "the same" logger, because the "identity"
              # is only determined by the `filenameprefix`
              logger = CMADataLogger(opts['verb_filenameprefix'],
                                     opts['verb_log'])
              logger.register(es, append).add()  # no fitness values here
              es.logger = logger
  
              if noise_handler:
                  noisehandler = noise_handler
                  noise_handling = True
                  if fmin_opts['noise_change_sigma_exponent'] > 0:
                      es.opts['tolfacupx'] = inf
              else:
                  noisehandler = NoiseHandler(es.N, 0)
                  noise_handling = False
              es.noise_handler = noisehandler
  
              # the problem: this assumes that good solutions cannot take longer than bad ones:
              # with EvalInParallel(objective_function, 2, is_feasible=opts['is_feasible']) as eval_in_parallel:
              if 1 < 3:
                  while not es.stop():  # iteration loop
                      # X, fit = eval_in_parallel(lambda: es.ask(1)[0], es.popsize, args, repetitions=noisehandler.evaluations-1)
                      X, fit = es.ask_and_eval(objective_function, args, gradf=gradf,
                                               evaluations=noisehandler.evaluations,
                                               aggregation=np.median)  # treats NaN with resampling
                      # TODO: check args and in case use args=(noisehandler.evaluations, )
  
                      es.tell(X, fit)  # prepare for next iteration
                      if noise_handling:  # it would be better to also use these f-evaluations in tell
                          es.sigma *= noisehandler(X, fit, objective_function, es.ask,
                                                   args=args)**fmin_opts['noise_change_sigma_exponent']
  
                          es.countevals += noisehandler.evaluations_just_done  # TODO: this is a hack, not important though
                          # es.more_to_write.append(noisehandler.evaluations_just_done)
                          if noisehandler.maxevals > noisehandler.minevals:
                              es.more_to_write.append(noisehandler.get_evaluations())
                          if 1 < 3:
                              es.sp.cmean *= exp(-noise_kappa_exponent * np.tanh(noisehandler.noiseS))
                              if es.sp.cmean > 1:
                                  es.sp.cmean = 1
  
                      es.disp()
                      logger.add(# more_data=[noisehandler.evaluations, 10**noisehandler.noiseS] if noise_handling else [],
                                 modulo=1 if es.stop() and logger.modulo else None)
                      if (opts['verb_log'] and opts['verb_plot'] and
                            (es.countiter % max(opts['verb_plot'], opts['verb_log']) == 0 or es.stop())):
                          logger.plot(324)
  
              # end while not es.stop
              mean_pheno = es.gp.pheno(es.mean, into_bounds=es.boundary_handler.repair, archive=es.sent_solutions)
              fmean = objective_function(mean_pheno, *args)
              es.countevals += 1
  
              es.best.update([mean_pheno], es.sent_solutions, [fmean], es.countevals)
              best.update(es.best, es.sent_solutions)  # in restarted case
              # es.best.update(best)
  
              this_evals = es.countevals - base_evals
              base_evals = es.countevals
  
              # BIPOP stats update
  
              if irun == 0:
                  popsize0 = opts['popsize']
                  maxiter0 = opts['maxiter']
                  # XXX: This might be a bug? Reproduced from Matlab
                  # small_i.append(this_evals)
  
              if bipop:
                  if poptype == 'small':
                      small_i.append(this_evals)
                  else:  # poptype == 'large'
                      large_i.append(this_evals)
  
              # final message
              if opts['verb_disp']:
                  es.result_pretty(irun, time.asctime(time.localtime()),
                                   best.f)
  
              irun += 1
              # if irun > fmin_opts['restarts'] or 'ftarget' in es.stop() \
              # if irun > restarts or 'ftarget' in es.stop() \
              if irun - runs_with_small > fmin_opts['restarts'] or 'ftarget' in es.stop() \
                      or 'maxfevals' in es.stop(check=False):
                  break
              opts['verb_append'] = es.countevals
              opts['popsize'] = fmin_opts['incpopsize'] * es.sp.popsize  # TODO: use rather options?
              opts['seed'] += 1
  
          # while irun
  
          # es.out['best'] = best  # TODO: this is a rather suboptimal type for inspection in the shell
          if 1 < 3:
              if irun:
                  es.best.update(best)
                  # TODO: there should be a better way to communicate the overall best
              return es.result() + (es.stop(), es, logger)
  
          else:  # previously: to be removed
              return (best.x.copy(), best.f, es.countevals,
                      dict((('stopdict', _CMAStopDict(es._stopdict))
                            , ('mean', es.gp.pheno(es.mean))
                            , ('std', es.sigma * es.sigma_vec * sqrt(es.dC) * es.gp.scales)
                            , ('out', es.out)
                            , ('opts', es.opts)  # last state of options
                            , ('cma', es)
                            , ('inputargs', es.inputargs)
                            ))
                     )
          # TODO refine output, can #args be flexible?
          # is this well usable as it is now?
      else:  # except KeyboardInterrupt:  # Exception, e:
          if eval(str(options['verb_disp'])) > 0:
              print(' in/outcomment ``raise`` in last line of cma.fmin to prevent/restore KeyboardInterrupt exception')
          raise