class_docstr: This class implements the differential evolution solver
  Parameters
  ----------
  func : callable
      The objective function to be minimized.  Must be in the form
      ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array
      and ``args`` is a  tuple of any additional fixed parameters needed to
      completely specify the function.
  bounds : sequence
      Bounds for variables.  ``(min, max)`` pairs for each element in ``x``,
      defining the lower and upper bounds for the optimizing argument of
      `func`. It is required to have ``len(bounds) == len(x)``.
      ``len(bounds)`` is used to determine the number of parameters in ``x``.
  args : tuple, optional
      Any additional fixed parameters needed to
      completely specify the objective function.
  strategy : str, optional
      The differential evolution strategy to use. Should be one of:
          - 'best1bin'
          - 'best1exp'
          - 'rand1exp'
          - 'randtobest1exp'
          - 'currenttobest1exp'
          - 'best2exp'
          - 'rand2exp'
          - 'randtobest1bin'
          - 'currenttobest1bin'
          - 'best2bin'
          - 'rand2bin'
          - 'rand1bin'
      The default is 'best1bin'
  maxiter : int, optional
      The maximum number of generations over which the entire population is
      evolved. The maximum number of function evaluations (with no polishing)
      is: ``(maxiter + 1) * popsize * len(x)``
  popsize : int, optional
      A multiplier for setting the total population size.  The population has
      ``popsize * len(x)`` individuals (unless the initial population is
      supplied via the `init` keyword).
  tol : float, optional
      Relative tolerance for convergence, the solving stops when
      ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``,
      where and `atol` and `tol` are the absolute and relative tolerance
      respectively.
  mutation : float or tuple(float, float), optional
      The mutation constant. In the literature this is also known as
      differential weight, being denoted by F.
      If specified as a float it should be in the range [0, 2].
      If specified as a tuple ``(min, max)`` dithering is employed. Dithering
      randomly changes the mutation constant on a generation by generation
      basis. The mutation constant for that generation is taken from
      U[min, max). Dithering can help speed convergence significantly.
      Increasing the mutation constant increases the search radius, but will
      slow down convergence.
  recombination : float, optional
      The recombination constant, should be in the range [0, 1]. In the
      literature this is also known as the crossover probability, being
      denoted by CR. Increasing this value allows a larger number of mutants
      to progress into the next generation, but at the risk of population
      stability.
  seed : int or `np.random.RandomState`, optional
      If `seed` is not specified the `np.random.RandomState` singleton is
      used.
      If `seed` is an int, a new `np.random.RandomState` instance is used,
      seeded with `seed`.
      If `seed` is already a `np.random.RandomState` instance, then that
      `np.random.RandomState` instance is used.
      Specify `seed` for repeatable minimizations.
  disp : bool, optional
      Display status messages
  callback : callable, `callback(xk, convergence=val)`, optional
      A function to follow the progress of the minimization. ``xk`` is
      the current value of ``x0``. ``val`` represents the fractional
      value of the population convergence.  When ``val`` is greater than one
      the function halts. If callback returns `True`, then the minimization
      is halted (any polishing is still carried out).
  polish : bool, optional
      If True, then `scipy.optimize.minimize` with the `L-BFGS-B` method
      is used to polish the best population member at the end. This requires
      a few more function evaluations.
  maxfun : int, optional
      Set the maximum number of function evaluations. However, it probably
      makes more sense to set `maxiter` instead.
  init : str or array-like, optional
      Specify which type of population initialization is performed. Should be
      one of:
          - 'latinhypercube'
          - 'random'
          - array specifying the initial population. The array should have
            shape ``(M, len(x))``, where len(x) is the number of parameters.
            `init` is clipped to `bounds` before use.
      The default is 'latinhypercube'. Latin Hypercube sampling tries to
      maximize coverage of the available parameter space. 'random'
      initializes the population randomly - this has the drawback that
      clustering can occur, preventing the whole of parameter space being
      covered. Use of an array to specify a population could be used, for
      example, to create a tight bunch of initial guesses in an location
      where the solution is known to exist, thereby reducing time for
      convergence.
  atol : float, optional
      Absolute tolerance for convergence, the solving stops when
      ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``,
      where and `atol` and `tol` are the absolute and relative tolerance
      respectively.
code: def __init__(
      self,
      func,
      bounds,
      args=(),
      strategy="best1bin",
      maxiter=1000,
      popsize=15,
      tol=0.01,
      mutation=(0.5, 1),
      recombination=0.7,
      seed=None,
      maxfun=np.inf,
      callback=None,
      disp=False,
      polish=True,
      init="latinhypercube",
      atol=0,
  ):
      if strategy in self._binomial:
          self.mutation_func = getattr(self, self._binomial[strategy])
      elif strategy in self._exponential:
          self.mutation_func = getattr(
              self, self._exponential[strategy]
          )
      else:
          raise ValueError("Please select a valid mutation strategy")
      self.strategy = strategy
  
      self.callback = callback
      self.polish = polish
  
      # relative and absolute tolerances for convergence
      self.tol, self.atol = tol, atol
  
      # Mutation constant should be in [0, 2). If specified as a sequence
      # then dithering is performed.
      self.scale = mutation
      if (
          not np.all(np.isfinite(mutation))
          or np.any(np.array(mutation) >= 2)
          or np.any(np.array(mutation) < 0)
      ):
          raise ValueError(
              "The mutation constant must be a float in "
              "U[0, 2), or specified as a tuple(min, max)"
              " where min < max and min, max are in U[0, 2)."
          )
  
      self.dither = None
      if hasattr(mutation, "__iter__") and len(mutation) > 1:
          self.dither = [mutation[0], mutation[1]]
          self.dither.sort()
  
      self.cross_over_probability = recombination
  
      self.func = func
      self.args = args
  
      # convert tuple of lower and upper bounds to limits
      # [(low_0, high_0), ..., (low_n, high_n]
      #     -> [[low_0, ..., low_n], [high_0, ..., high_n]]
      self.limits = np.array(bounds, dtype="float").T
      if np.size(self.limits, 0) != 2 or not np.all(
          np.isfinite(self.limits)
      ):
          raise ValueError(
              "bounds should be a sequence containing "
              "real valued (min, max) pairs for each value"
              " in x"
          )
  
      if maxiter is None:  # the default used to be None
          maxiter = 1000
      self.maxiter = maxiter
      if maxfun is None:  # the default used to be None
          maxfun = np.inf
      self.maxfun = maxfun
  
      # population is scaled to between [0, 1].
      # We have to scale between parameter <-> population
      # save these arguments for _scale_parameter and
      # _unscale_parameter. This is an optimization
      self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1])
      self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1])
  
      self.parameter_count = np.size(self.limits, 1)
  
      self.random_number_generator = check_random_state(seed)
  
      # default population initialization is a latin hypercube design, but
      # there are other population initializations possible.
      # the minimum is 5 because 'best2bin' requires a population that's at
      # least 5 long
      self.num_population_members = max(
          5, popsize * self.parameter_count
      )
  
      self.population_shape = (
          self.num_population_members,
          self.parameter_count,
      )
  
      self._nfev = 0
      if isinstance(init, string_types):
          if init == "latinhypercube":
              self.init_population_lhs()
          elif init == "random":
              self.init_population_random()
          else:
              raise ValueError(self.__init_error_msg)
      else:
          self.init_population_array(init)
  
      self.disp = disp
  
imports: from __future__ import absolute_import, division, print_function, unicode_literals
  import logging
  from itertools import product
  from typing import List, Optional, Tuple, TYPE_CHECKING
  import numpy
  from six import string_types
  import scipy
  from scipy._lib._util import check_random_state
  from scipy.optimize import OptimizeResult, minimize
  from tqdm.auto import tqdm
  from art.config import ART_NUMPY_DTYPE
  from art.attacks.attack import EvasionAttack
  from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
  from art.estimators.classification.classifier import ClassifierMixin
  from art.utils import check_and_transform_label_format
label_desc: Use image node_id 1 to create a new DifferentialEvolutionSolver object  with example: obj = DifferentialEvolutionSolver(func, bounds, args, strategy, maxiter, popsize, tol, mutation, recombination, seed, maxfun, callback, disp, polish, init, atol)