code: def projected_gradient_descent(
      model_fn,
      x,
      eps,
      eps_iter,
      nb_iter,
      norm,
      loss_fn=None,
      clip_min=None,
      clip_max=None,
      y=None,
      targeted=False,
      rand_init=None,
      rand_minmax=None,
      sanity_checks=False,
  ):
      """
      This class implements either the Basic Iterative Method
      (Kurakin et al. 2016) when rand_init is set to 0. or the
      Madry et al. (2017) method when rand_minmax is larger than 0.
      Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf
      Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf
      :param model_fn: a callable that takes an input tensor and returns the model logits.
      :param x: input tensor.
      :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572.
      :param eps_iter: step size for each attack iteration
      :param nb_iter: Number of attack iterations.
      :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2.
      :param loss_fn: (optional) callable. loss function that takes (labels, logits) as arguments and returns loss.
                      default function is 'tf.nn.sparse_softmax_cross_entropy_with_logits'
      :param clip_min: (optional) float. Minimum float value for adversarial example components.
      :param clip_max: (optional) float. Maximum float value for adversarial example components.
      :param y: (optional) Tensor with true labels. If targeted is true, then provide the
                target label. Otherwise, only provide this parameter if you'd like to use true
                labels when crafting adversarial samples. Otherwise, model predictions are used
                as labels to avoid the "label leaking" effect (explained in this paper:
                https://arxiv.org/abs/1611.01236). Default is None.
      :param targeted: (optional) bool. Is the attack targeted or untargeted?
                Untargeted, the default, will try to make the label incorrect.
                Targeted will instead try to move in the direction of being more like y.
      :param rand_init: (optional) float. Start the gradient descent from a point chosen
                          uniformly at random in the norm ball of radius
                          rand_init_eps
      :param rand_minmax: (optional) float. Size of the norm ball from which
                          the initial starting point is chosen. Defaults to eps
      :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime /
                memory or for unit tests that intentionally pass strange input)
      :return: a tensor for the adversarial example
      """
  
      assert eps_iter <= eps, (eps_iter, eps)
      if norm == 1:
          raise NotImplementedError(
              "It's not clear that FGM is a good inner loop"
              " step for PGD when norm=1, because norm=1 FGM "
              " changes only one pixel at a time. We need "
              " to rigorously test a strong norm=1 PGD "
              "before enabling this feature."
          )
      if norm not in [np.inf, 2]:
          raise ValueError("Norm order must be either np.inf or 2.")
  
      if loss_fn is None:
          loss_fn = tf.nn.sparse_softmax_cross_entropy_with_logits
  
      asserts = []
  
      # If a data range was specified, check that the input was in that range
      if clip_min is not None:
          asserts.append(tf.math.greater_equal(x, clip_min))
  
      if clip_max is not None:
          asserts.append(tf.math.less_equal(x, clip_max))
  
      # Initialize loop variables
      if rand_minmax is None:
          rand_minmax = eps
  
      if rand_init:
          eta = random_lp_vector(
              tf.shape(x),
              norm,
              tf.cast(rand_minmax, x.dtype),
              dtype=x.dtype,
          )
      else:
          eta = tf.zeros_like(x)
  
      # Clip eta
      eta = clip_eta(eta, norm, eps)
      adv_x = x + eta
      if clip_min is not None or clip_max is not None:
          adv_x = tf.clip_by_value(adv_x, clip_min, clip_max)
  
      if y is None:
          # Using model predictions as ground truth to avoid label leaking
          y = tf.argmax(model_fn(x), 1)
  
      i = 0
      while i < nb_iter:
          adv_x = fast_gradient_method(
              model_fn,
              adv_x,
              eps_iter,
              norm,
              loss_fn,
              clip_min=clip_min,
              clip_max=clip_max,
              y=y,
              targeted=targeted,
          )
  
          # Clipping perturbation eta to norm norm ball
          eta = adv_x - x
          eta = clip_eta(eta, norm, eps)
          adv_x = x + eta
  
          # Redo the clipping.
          # FGM already did it, but subtracting and re-adding eta can add some
          # small numerical error.
          if clip_min is not None or clip_max is not None:
              adv_x = tf.clip_by_value(adv_x, clip_min, clip_max)
          i += 1
  
      asserts.append(eps_iter <= eps)
      if norm == np.inf and clip_min is not None:
          # TODO necessary to cast to x.dtype?
          asserts.append(eps + clip_min <= clip_max)
  
      if sanity_checks:
          assert np.all(asserts)
      return adv_x
  
imports: import numpy
  import tensorflow
  from cleverhans.tf2.attacks.fast_gradient_method import fast_gradient_method
  from cleverhans.tf2.utils import clip_eta, random_lp_vector
label_desc: Use image node_id 1 for calling a global function with example usage: projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, loss_fn, clip_min, clip_max, y, targeted, rand_init, rand_minmax, sanity_checks) and returns: adv_x