class_docstr: The L_0 distance metric is non-differentiable and therefore is ill-suited for standard gradient descent.
  Instead, we use an iterative algorithm that, in each iteration, identifies some features that don’t have much effect
  on the classifier output and then fixes those features, so their value will never be changed.
  The set of fixed features grows in each iteration until we have, by process of elimination, identified a minimal
  (but possibly not minimum) subset of features that can be modified to generate an adversarial example.
  In each iteration, we use our L_2 attack to identify which features are unimportant [Carlini and Wagner, 2016].
  
  | Paper link: https://arxiv.org/abs/1608.04644
docstr: Generate adversarial samples and return them in an array.
  
  :param x: An array with the original inputs to be attacked.
  :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or indices of shape
            (nb_samples,). If `self.targeted` is true, then `y` represents the target labels. If `self.targeted`
            is true, then `y_val` represents the target labels. Otherwise, the targets are the original class
            labels.
  :return: An array holding the adversarial examples.
docstr_tok: ["Generate","adversarial","samples","and","return","them","in","an","array",".",":","param","x",":","An","array","with","the","original","inputs","to","be","attacked",".",":","param","y",":","Target","values","(","class","labels",")","one-hot-encoded","of","shape","(","nb_samples",",","nb_classes",")","or","indices","of","shape","(","nb_samples",",",")",".","If","`","self.targeted","`","is","true",",","then","`","y","`","represents","the","target","labels",".","If","`","self.targeted","`","is","true",",","then","`","y_val","`","represents","the","target","labels",".","Otherwise",",","the","targets","are","the","original","class","labels",".",":","return",":","An","array","holding","the","adversarial","examples","."]
code: def generate(
      self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs
  ) -> np.ndarray:
      """
      Generate adversarial samples and return them in an array.
  
      :param x: An array with the original inputs to be attacked.
      :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or indices of shape
                (nb_samples,). If `self.targeted` is true, then `y` represents the target labels. If `self.targeted`
                is true, then `y_val` represents the target labels. Otherwise, the targets are the original class
                labels.
      :return: An array holding the adversarial examples.
      """
      if y is not None:
          y = check_and_transform_label_format(
              y, nb_classes=self.estimator.nb_classes
          )
      x_adv = x.astype(ART_NUMPY_DTYPE)
  
      if self.estimator.clip_values is not None:
          clip_min, clip_max = self.estimator.clip_values
      else:
          clip_min, clip_max = np.amin(x), np.amax(x)
  
      # Assert that, if attack is targeted, y_val is provided:
      if self.targeted and y is None:
          raise ValueError(
              "Target labels `y` need to be provided for a targeted attack."
          )
  
      # No labels provided, use model prediction as correct class
      if y is None:
          y = get_labels_np_array(
              self.estimator.predict(x, batch_size=self.batch_size)
          )
  
      if self.estimator.nb_classes == 2 and y.shape[1] == 1:
          raise ValueError(
              "This attack has not yet been tested for binary classification with a single output classifier."
          )
  
      if self.mask is None:
          # No initial activation provided, use the full feature set
          activation = np.ones(x.shape)
      else:
          # Check if the initial activation has the same dimension as the input data
          if self.mask.shape != x.shape:  # pragma: no cover
              raise ValueError(
                  "The mask must have the same dimensions as the input data."
              )
          activation = np.array(self.mask).astype(float)
  
      # L_0 attack specific variables
      final_adversarial_example = x.astype(ART_NUMPY_DTYPE)
      old_activation = activation.copy()
      c_final = np.ones(x.shape[0])
      best_l0dist = np.inf * np.ones(x.shape[0])
  
      # Main loop of the L_0 attack.
      # For each iteration :
      #   - Calls the L_2 attack to compute an adversarial example
      #   - Computes the gradients of the objective function evaluated at the adversarial instance
      #   - Fix the attribute with the lowest value (gradient * perturbation)
      # Repeat until the L_2 attack fails to find an adversarial examples.
      for _ in range(x.shape[1] + 1):
          # Compute perturbation with implicit batching
          nb_batches = int(
              np.ceil(x_adv.shape[0] / float(self.batch_size))
          )
          for batch_id in range(nb_batches):
              logger.debug(
                  "Processing batch %i out of %i", batch_id, nb_batches
              )
  
              batch_index_1, batch_index_2 = (
                  batch_id * self.batch_size,
                  (batch_id + 1) * self.batch_size,
              )
              if self.warm_start:
                  # Start the gradient descent from the solution found on the previous iteration
                  x_batch = x_adv[batch_index_1:batch_index_2]
              else:
                  x_batch = x[batch_index_1:batch_index_2]
              y_batch = y[batch_index_1:batch_index_2]
              activation_batch = activation[batch_index_1:batch_index_2]
  
              # The optimization is performed in tanh space to keep the adversarial images bounded in correct range
              x_batch_tanh = original_to_tanh(
                  x_batch, clip_min, clip_max, self._tanh_smoother
              )
  
              # Initialize binary search:
              c_current = self.initial_const * np.ones(x_batch.shape[0])
              c_lower_bound = np.zeros(x_batch.shape[0])
              c_double = np.ones(x_batch.shape[0]) > 0
  
              # Initialize placeholders for best l2 distance and attack found so far
              best_l0dist_batch = np.inf * np.ones(x_batch.shape[0])
              best_x_adv_batch = x_batch.copy()
  
              for bss in range(self.binary_search_steps):
                  logger.debug(
                      "Binary search step %i / %i (c_mean==%f)",
                      bss,
                      self.binary_search_steps,
                      np.mean(c_current),
                  )
                  nb_active = int(
                      np.sum(c_current < self._c_upper_bound)
                  )
                  logger.debug(
                      "Number of samples with c_current < _c_upper_bound: %i out of %i",
                      nb_active,
                      x_batch.shape[0],
                  )
                  if nb_active == 0:
                      break
                  learning_rate = self.learning_rate * np.ones(
                      x_batch.shape[0]
                  )
  
                  # Initialize perturbation in tanh space:
                  x_adv_batch = x_batch.copy()
                  x_adv_batch_tanh = x_batch_tanh.copy()
  
                  z_logits, l2dist, loss = self._loss(x_batch, x_adv_batch, y_batch, c_current)  # type: ignore
                  attack_success = loss - l2dist <= 0
                  overall_attack_success = attack_success
  
                  for i_iter in range(self.max_iter):
                      logger.debug(
                          "Iteration step %i out of %i",
                          i_iter,
                          self.max_iter,
                      )
                      logger.debug("Average Loss: %f", np.mean(loss))
                      logger.debug(
                          "Average L2Dist: %f", np.mean(l2dist)
                      )
                      logger.debug(
                          "Average Margin Loss: %f",
                          np.mean(loss - l2dist),
                      )
                      logger.debug(
                          "Current number of succeeded attacks: %i out of %i",
                          int(np.sum(attack_success)),
                          len(attack_success),
                      )
  
                      l0dist = np.sum(
                          (
                              np.abs(x_batch - x_adv_batch)
                              > self._perturbation_threshold
                          ).astype(int),
                          axis=(1, 2, 3),
                      )
                      improved_adv = attack_success & (
                          l0dist < best_l0dist_batch
                      )
                      logger.debug(
                          "Number of improved L0 distances: %i",
                          int(np.sum(improved_adv)),
                      )
                      if np.sum(improved_adv) > 0:
                          best_l0dist_batch[improved_adv] = l0dist[
                              improved_adv
                          ]
                          best_x_adv_batch[improved_adv] = x_adv_batch[
                              improved_adv
                          ]
  
                      active = (c_current < self._c_upper_bound) & (
                          learning_rate > 0
                      )
                      nb_active = int(np.sum(active))
                      logger.debug(
                          "Number of samples with c_current < _c_upper_bound and learning_rate > 0: %i out of %i",
                          nb_active,
                          x_batch.shape[0],
                      )
                      if nb_active == 0:  # pragma: no cover
                          break
  
                      # compute gradient:
                      logger.debug("Compute loss gradient")
                      perturbation_tanh = -self._loss_gradient(
                          z_logits[active],
                          y_batch[active],
                          x_batch[active],
                          x_adv_batch[active],
                          x_adv_batch_tanh[active],
                          c_current[active],
                          clip_min,
                          clip_max,
                      )
  
                      # perform line search to optimize perturbation
                      # first, halve the learning rate until perturbation actually decreases the loss:
                      prev_loss = loss.copy()
                      best_loss = loss.copy()
                      best_lr = np.zeros(x_batch.shape[0])
                      halving = np.zeros(x_batch.shape[0])
  
                      for i_halve in range(self.max_halving):
                          logger.debug(
                              "Perform halving iteration %i out of %i",
                              i_halve,
                              self.max_halving,
                          )
                          do_halving = loss[active] >= prev_loss[active]
                          logger.debug(
                              "Halving to be performed on %i samples",
                              int(np.sum(do_halving)),
                          )
                          if np.sum(do_halving) == 0:
                              break
                          active_and_do_halving = active.copy()
                          active_and_do_halving[active] = do_halving
  
                          lr_mult = learning_rate[active_and_do_halving]
                          for _ in range(len(x.shape) - 1):
                              lr_mult = lr_mult[:, np.newaxis]
  
                          x_adv1 = x_adv_batch_tanh[
                              active_and_do_halving
                          ]
                          new_x_adv_batch_tanh = (
                              x_adv1
                              + lr_mult
                              * perturbation_tanh[do_halving]
                              * activation_batch[do_halving]
                          )
                          new_x_adv_batch = tanh_to_original(
                              new_x_adv_batch_tanh, clip_min, clip_max
                          )
                          (
                              _,
                              l2dist[active_and_do_halving],
                              loss[active_and_do_halving],
                          ) = self._loss(
                              x_batch[active_and_do_halving],
                              new_x_adv_batch,
                              y_batch[active_and_do_halving],
                              c_current[active_and_do_halving],
                          )
  
                          logger.debug(
                              "New Average Loss: %f", np.mean(loss)
                          )
                          logger.debug(
                              "New Average L2Dist: %f", np.mean(l2dist)
                          )
                          logger.debug(
                              "New Average Margin Loss: %f",
                              np.mean(loss - l2dist),
                          )
  
                          best_lr[loss < best_loss] = learning_rate[
                              loss < best_loss
                          ]
                          best_loss[loss < best_loss] = loss[
                              loss < best_loss
                          ]
                          learning_rate[active_and_do_halving] /= 2
                          halving[active_and_do_halving] += 1
                      learning_rate[active] *= 2
  
                      # if no halving was actually required, double the learning rate as long as this
                      # decreases the loss:
                      for i_double in range(self.max_doubling):
                          logger.debug(
                              "Perform doubling iteration %i out of %i",
                              i_double,
                              self.max_doubling,
                          )
                          do_doubling = (halving[active] == 1) & (
                              loss[active] <= best_loss[active]
                          )
                          logger.debug(
                              "Doubling to be performed on %i samples",
                              int(np.sum(do_doubling)),
                          )
                          if (
                              np.sum(do_doubling) == 0
                          ):  # pragma: no cover
                              break
                          active_and_do_doubling = active.copy()
                          active_and_do_doubling[active] = do_doubling
                          learning_rate[active_and_do_doubling] *= 2
  
                          lr_mult = learning_rate[
                              active_and_do_doubling
                          ]
                          for _ in range(len(x.shape) - 1):
                              lr_mult = lr_mult[:, np.newaxis]
  
                          x_adv2 = x_adv_batch_tanh[
                              active_and_do_doubling
                          ]
                          new_x_adv_batch_tanh = (
                              x_adv2
                              + lr_mult
                              * perturbation_tanh[do_doubling]
                              * activation_batch[do_doubling]
                          )
                          new_x_adv_batch = tanh_to_original(
                              new_x_adv_batch_tanh, clip_min, clip_max
                          )
                          (
                              _,
                              l2dist[active_and_do_doubling],
                              loss[active_and_do_doubling],
                          ) = self._loss(
                              x_batch[active_and_do_doubling],
                              new_x_adv_batch,
                              y_batch[active_and_do_doubling],
                              c_current[active_and_do_doubling],
                          )
                          logger.debug(
                              "New Average Loss: %f", np.mean(loss)
                          )
                          logger.debug(
                              "New Average L2Dist: %f", np.mean(l2dist)
                          )
                          logger.debug(
                              "New Average Margin Loss: %f",
                              np.mean(loss - l2dist),
                          )
                          best_lr[loss < best_loss] = learning_rate[
                              loss < best_loss
                          ]
                          best_loss[loss < best_loss] = loss[
                              loss < best_loss
                          ]
  
                      learning_rate[halving == 1] /= 2
  
                      update_adv = best_lr[active] > 0
                      logger.debug(
                          "Number of adversarial samples to be finally updated: %i",
                          int(np.sum(update_adv)),
                      )
  
                      if np.sum(update_adv) > 0:
                          active_and_update_adv = active.copy()
                          active_and_update_adv[active] = update_adv
                          best_lr_mult = best_lr[active_and_update_adv]
                          for _ in range(len(x.shape) - 1):
                              best_lr_mult = best_lr_mult[:, np.newaxis]
  
                          x_adv4 = x_adv_batch_tanh[
                              active_and_update_adv
                          ]
                          best_lr1 = (
                              best_lr_mult
                              * perturbation_tanh[update_adv]
                          )
                          x_adv_batch_tanh[active_and_update_adv] = (
                              x_adv4
                              + best_lr1
                              * activation_batch[active_and_update_adv]
                          )
  
                          x_adv6 = x_adv_batch_tanh[
                              active_and_update_adv
                          ]
                          x_adv_batch[
                              active_and_update_adv
                          ] = tanh_to_original(
                              x_adv6, clip_min, clip_max
                          )
                          (
                              z_logits[active_and_update_adv],
                              l2dist[active_and_update_adv],
                              loss[active_and_update_adv],
                          ) = self._loss(
                              x_batch[active_and_update_adv],
                              x_adv_batch[active_and_update_adv],
                              y_batch[active_and_update_adv],
                              c_current[active_and_update_adv],
                          )
                          attack_success = loss - l2dist <= 0
                          overall_attack_success = (
                              overall_attack_success | attack_success
                          )
  
                  # Update depending on attack success:
                  l0dist = np.sum(
                      (
                          np.abs(x_batch - x_adv_batch)
                          > self._perturbation_threshold
                      ).astype(int),
                      axis=(1, 2, 3),
                  )
                  improved_adv = attack_success & (
                      l0dist < best_l0dist_batch
                  )
                  logger.debug(
                      "Number of improved L0 distances: %i",
                      int(np.sum(improved_adv)),
                  )
                  if np.sum(improved_adv) > 0:
                      best_l0dist_batch[improved_adv] = l0dist[
                          improved_adv
                      ]
                      best_x_adv_batch[improved_adv] = x_adv_batch[
                          improved_adv
                      ]
  
                  c_double[overall_attack_success] = False
                  c_current[overall_attack_success] = (
                      c_lower_bound + c_current
                  )[overall_attack_success] / 2
  
                  c_old = c_current
                  c_current[~overall_attack_success & c_double] *= 2
  
                  c_current1 = (c_current - c_lower_bound)[
                      ~overall_attack_success & ~c_double
                  ]
                  c_current[~overall_attack_success & ~c_double] += (
                      c_current1 / 2
                  )
                  c_lower_bound[~overall_attack_success] = c_old[
                      ~overall_attack_success
                  ]
  
              c_final[batch_index_1:batch_index_2] = c_current
              x_adv[batch_index_1:batch_index_2] = best_x_adv_batch
  
          logger.info(
              "Success rate of C&W L_2 attack: %.2f%%",
              100
              * compute_success(
                  self.estimator,
                  x,
                  y,
                  x_adv,
                  self.targeted,
                  batch_size=self.batch_size,
              ),
          )
  
          # If the L_2 attack can't find any adversarial examples with the new activation, return the last one
          z_logits, l2dist, loss = self._loss(x, x_adv, y, c_final)  # type: ignore
          attack_success = loss - l2dist <= 0
          l0dist = np.sum(
              (np.abs(x - x_adv) > self._perturbation_threshold).astype(
                  int
              ),
              axis=(1, 2, 3),
          )
          improved_adv = attack_success & (l0dist < best_l0dist)
          if np.sum(improved_adv) > 0:
              final_adversarial_example[improved_adv] = x_adv[
                  improved_adv
              ]
          else:
              return (
                  x * (old_activation == 0).astype(int)
                  + final_adversarial_example * old_activation
              )
  
          # Compute the gradients of the objective function evaluated at the adversarial instance
          x_adv_tanh = original_to_tanh(
              x_adv, clip_min, clip_max, self._tanh_smoother
          )
          objective_loss_gradient = -self._loss_gradient(
              z_logits,
              y,
              x,
              x_adv,
              x_adv_tanh,
              c_final,
              clip_min,
              clip_max,
          )
          perturbation_l1_norm = np.abs(x_adv - x)
  
          # gradient * perturbation tells how much reduction to the objective function we obtain for each attribute
          objective_reduction = (
              np.abs(objective_loss_gradient) * perturbation_l1_norm
          )
  
          # Assign infinity as the objective_reduction value for fixed feature (in order not to select them again)
          objective_reduction += np.array(
              np.where(activation == 0, np.inf, 0)
          )
  
          # Fix the feature with the lowest objective_reduction value (only for the examples that succeeded)
          fix_feature_index = np.argmin(
              objective_reduction.reshape(
                  objective_reduction.shape[0], -1
              ),
              axis=1,
          )
          fix_feature = np.ones(x.shape)
          fix_feature = fix_feature.reshape(fix_feature.shape[0], -1)
          fix_feature[
              np.arange(fix_feature_index.size), fix_feature_index
          ] = 0
          fix_feature = fix_feature.reshape(x.shape)
          old_activation[improved_adv] = activation.copy()[improved_adv]
          activation[improved_adv] *= fix_feature[improved_adv]
          logger.info(
              "L0 norm before fixing :\n%f\nNumber active features :\n%f\nIndex of fixed feature :\n%d",
              np.sum(
                  (
                      perturbation_l1_norm
                      > self._perturbation_threshold
                  ).astype(int),
                  axis=1,
              ),
              np.sum(activation, axis=1),
              fix_feature_index,
          )
      return x_adv
  
imports: from __future__ import absolute_import, division, print_function, unicode_literals
  import logging
  from typing import Optional, Tuple, TYPE_CHECKING
  import numpy
  from tqdm.auto import trange
  from art.config import ART_NUMPY_DTYPE
  from art.optimizers import Adam
  from art.estimators.estimator import BaseEstimator
  from art.estimators.classification.classifier import ClassGradientsMixin
  from art.attacks.attack import EvasionAttack
  from art.utils import compute_success, get_labels_np_array, tanh_to_original, original_to_tanh
  from art.utils import check_and_transform_label_format
label_desc: Use image node_id 2 for calling the CarliniL0Method obj's underlying member method code with example usage: obj.generate(x, y) and returns: x_adv, unknown