class_docstr: The Fast Minimum Norm adversarial attack, in Lp norm. [#Pintor21]_
  
  Args:
      steps: Number of iterations.
      max_stepsize: Initial stepsize for the gradient update.
      min_stepsize: Final stepsize for the gradient update. The
          stepsize will be reduced with a cosine annealing policy.
      gamma: Initial stepsize for the epsilon update. It will
          be updated with a cosine annealing reduction up to 0.001.
      init_attack: Optional initial attack. If an initial attack
          is specified (or initial points are provided in the run), the
          attack will first try to search for the boundary between the
          initial point and the points in a class that satisfies the
          adversarial criterion.
      binary_search_steps: Number of steps to use for the search
          from the adversarial points. If no initial attack or adversarial
          starting point is provided, this parameter will be ignored.
  
  References:
      .. [#Pintor21] Maura Pintor, Fabio Roli, Wieland Brendel,
          Battista Biggio, "Fast Minimum-norm Adversarial
          Attacks through Adaptive Norm Constraints."
          arXiv preprint arXiv:2102.12827 (2021).
          https://arxiv.org/abs/2102.12827
code: def run(
      self,
      model: Model,
      inputs: T,
      criterion: Union[Misclassification, TargetedMisclassification, T],
      *,
      starting_points: Optional[ep.Tensor] = None,
      early_stop: Optional[float] = None,
      **kwargs: Any,
  ) -> T:
      raise_if_kwargs(kwargs)
      criterion_ = get_criterion(criterion)
  
      if isinstance(criterion_, Misclassification):
          targeted = False
          classes = criterion_.labels
      elif isinstance(criterion_, TargetedMisclassification):
          targeted = True
          classes = criterion_.target_classes
      else:
          raise ValueError("unsupported criterion")
  
      def loss_fn(
          inputs: ep.Tensor, labels: ep.Tensor
      ) -> Tuple[ep.Tensor, Tuple[ep.Tensor, ep.Tensor]]:
          logits = model(inputs)
  
          if targeted:
              c_minimize = best_other_classes(logits, labels)
              c_maximize = labels  # target_classes
          else:
              c_minimize = labels  # labels
              c_maximize = best_other_classes(logits, labels)
  
          loss = logits[rows, c_minimize] - logits[rows, c_maximize]
  
          return -loss.sum(), (logits, loss)
  
      x, restore_type = ep.astensor_(inputs)
      del inputs, criterion, kwargs
      N = len(x)
      initialized = False
      # start from initialization points/attack
      if starting_points is not None:
          x1 = starting_points
          initialized = True
      else:
          if self.init_attack is not None:
              x1 = self.init_attack.run(model, x, criterion_)
              initialized = True
  
      # if initial points or initialization attacks are provided,
      #   search for the boundary
      if initialized is True:
          is_adv = get_is_adversarial(criterion_, model)
          assert is_adv(x1).all()
          lower_bound = ep.zeros(x, shape=(N,))
          upper_bound = ep.ones(x, shape=(N,))
          for _ in range(self.binary_search_steps):
              epsilons = (lower_bound + upper_bound) / 2
              mid_points = self.mid_points(
                  x, x1, epsilons, model.bounds
              )
              is_advs = is_adv(mid_points)
              lower_bound = ep.where(is_advs, lower_bound, epsilons)
              upper_bound = ep.where(is_advs, epsilons, upper_bound)
          starting_points = self.mid_points(
              x, x1, upper_bound, model.bounds
          )
          delta = starting_points - x
      else:
          # start from x0
          delta = ep.zeros_like(x)
  
      if classes.shape != (N,):
          name = "target_classes" if targeted else "labels"
          raise ValueError(
              f"expected {name} to have shape ({N},), got {classes.shape}"
          )
  
      min_, max_ = model.bounds
      rows = range(N)
      grad_and_logits = ep.value_and_grad_fn(x, loss_fn, has_aux=True)
  
      if self.p != 0:
          epsilon = ep.inf * ep.ones(x, len(x))
      else:
          epsilon = ep.maximum(
              ep.ones(x, len(x)), ep.norms.l0(flatten(delta), axis=-1)
          )
      if self.p != 0:
          worst_norm = ep.norms.lp(
              flatten(ep.maximum(x - min_, max_ - x)), p=self.p, axis=-1
          )
      else:
          worst_norm = (
              flatten(ep.ones_like(x)).bool().sum(axis=1).float32()
          )
  
      best_lp = worst_norm
      best_delta = delta
      adv_found = ep.zeros(x, len(x)).bool()
  
      for i in range(self.steps):
          # perform cosine annealing of learning rates
          stepsize = (
              self.min_stepsize
              + (self.max_stepsize - self.min_stepsize)
              * (1 + math.cos(math.pi * i / self.steps))
              / 2
          )
          gamma = (
              0.001
              + (self.gamma - 0.001)
              * (1 + math.cos(math.pi * (i / self.steps)))
              / 2
          )
  
          x_adv = x + delta
  
          loss, (logits, loss_batch), gradients = grad_and_logits(
              x_adv, classes
          )
          is_adversarial = criterion_(x_adv, logits)
          lp = ep.norms.lp(flatten(delta), p=self.p, axis=-1)
          is_smaller = lp <= best_lp
          is_both = ep.logical_and(is_adversarial, is_smaller)
          adv_found = ep.logical_or(adv_found, is_adversarial)
          best_lp = ep.where(is_both, lp, best_lp)
          best_delta = ep.where(
              atleast_kd(is_both, x.ndim), delta, best_delta
          )
  
          # update epsilon
          if self.p != 0:
              distance_to_boundary = abs(loss_batch) / ep.norms.lp(
                  flatten(gradients), p=self.dual, axis=-1
              )
              epsilon = ep.where(
                  is_adversarial,
                  ep.minimum(
                      epsilon * (1 - gamma),
                      ep.norms.lp(
                          flatten(best_delta), p=self.p, axis=-1
                      ),
                  ),
                  ep.where(
                      adv_found,
                      epsilon * (1 + gamma),
                      ep.norms.lp(flatten(delta), p=self.p, axis=-1)
                      + distance_to_boundary,
                  ),
              )
          else:
              epsilon = ep.where(
                  is_adversarial,
                  ep.minimum(
                      ep.minimum(
                          epsilon - 1,
                          (epsilon * (1 - gamma))
                          .astype(ep.arange(x, 1).dtype)
                          .astype(epsilon.dtype),
                      ),
                      ep.norms.lp(
                          flatten(best_delta), p=self.p, axis=-1
                      ),
                  ),
                  ep.maximum(
                      epsilon + 1,
                      (epsilon * (1 + gamma))
                      .astype(ep.arange(x, 1).dtype)
                      .astype(epsilon.dtype),
                  ),
              )
              epsilon = ep.maximum(1, epsilon).astype(epsilon.dtype)
  
          # clip epsilon
          epsilon = ep.minimum(epsilon, worst_norm)
  
          # computes normalized gradient update
          grad_ = self.normalize(gradients) * stepsize
  
          # do step
          delta = delta + grad_
  
          # project according to the given norm
          delta = self.project(x=x + delta, x0=x, epsilon=epsilon) - x
  
          # clip to valid bounds
          delta = ep.clip(x + delta, *model.bounds) - x
  
      x_adv = x + best_delta
      return restore_type(x_adv)
  
imports: import math
  from abc import abstractmethod, ABC
  from typing import Union, Optional, Any, Tuple
  import eagerpy
  from eagerpy.astensor import T
  from .base import MinimizationAttack, raise_if_kwargs, get_criterion, get_is_adversarial
  from .gradient_descent_base import uniform_l1_n_balls, normalize_lp_norms, uniform_l2_n_balls
  from ..None import Model, Misclassification, TargetedMisclassification
  from ..devutils import atleast_kd, flatten
  from ..distances import l1, linf, l2, l0, LpDistance
label_desc: Use image node_id 2 for calling the FMNAttackLp obj's underlying member method code with example usage: obj.run(model, inputs, criterion) and returns: restore_type, unknown,  logits,  loss