class_docstr: Implements the Sign-OPT attack `SignOPTAttack`. This is a query-efficient
  hard-label adversarial attack.
  
  Paper link: https://arxiv.org/pdf/1909.10773.pdf
docstr: Perform attack
  
  :param x_0: An array with the original inputs to be attacked.
  :param y_0: Target value.
  :param target: Target value. If `self.targeted` is true, it presents the targeted label. Defaults to None.
  :param x_init: The pool of possible targets for finding initial direction. Only for targeted attack.
  :return: the adversarial sample to x_0
docstr_tok: ["Perform","attack",":","param","x_0",":","An","array","with","the","original","inputs","to","be","attacked",".",":","param","y_0",":","Target","value",".",":","param","target",":","Target","value",".","If","`","self.targeted","`","is","true",",","it","presents","the","targeted","label",".","Defaults","to","None",".",":","param","x_init",":","The","pool","of","possible","targets","for","finding","initial","direction",".","Only","for","targeted","attack",".",":","return",":","the","adversarial","sample","to","x_0"]
code: def _attack(
      self,
      x_0: np.ndarray,
      y_0: int,
      target: Optional[int] = None,
      x_init: Optional[np.ndarray] = None,
      distortion: Optional[float] = None,
  ) -> Tuple[np.ndarray, np.ndarray, bool]:
      """
      Perform attack
  
      :param x_0: An array with the original inputs to be attacked.
      :param y_0: Target value.
      :param target: Target value. If `self.targeted` is true, it presents the targeted label. Defaults to None.
      :param x_init: The pool of possible targets for finding initial direction. Only for targeted attack.
      :return: the adversarial sample to x_0
      """
      query_count = 0
      ls_total = 0
  
      # init: Calculate a good starting point (direction)
      num_directions = self.num_trial
      best_theta, g_theta = np.zeros((0, 0)), float("inf")
      if self.verbose:
          print(
              f"Searching for the initial direction on {num_directions} random directions: "
          )
      if self.targeted and x_init is not None:
          if self.verbose:
              print(
                  f"this is targeted attack, org_label={y_0}, target={target}"
              )
          sample_count = 0
          for i, x_i in enumerate(x_init):
              # find a training data which label is target
              yi_pred = self._predict_label(x_i)
              query_count += 1
              if yi_pred != target:
                  continue
  
              theta = x_i - x_0
              initial_lbd = np.linalg.norm(
                  theta
              ).item()  # .item() convert numpy type to python type
              theta /= initial_lbd
              lbd, count = self._fine_grained_binary_search(
                  x_0, y_0, theta, initial_lbd, g_theta, target
              )
              query_count += count
              if lbd < g_theta:
                  best_theta, g_theta = theta, lbd
              sample_count += 1
              if sample_count >= self.num_trial or i > 500:
                  break
      else:
          for i in range(num_directions):
              query_count += 1
              theta = np.random.randn(*x_0.shape).astype(
                  np.float32
              )  # gaussian distortion
              # register adv directions
              if not self._is_label(x_0 + theta, y_0):
                  initial_lbd = np.linalg.norm(
                      theta
                  ).item()  # .item() convert numpy type to python type
                  theta /= (
                      initial_lbd  # l2 normalize: theta is normalized
                  )
                  # getting smaller g_theta
                  lbd, count = self._fine_grained_binary_search(
                      x_0, y_0, theta, initial_lbd, g_theta
                  )
                  query_count += count
                  if lbd < g_theta:
                      best_theta, g_theta = theta, lbd
                      if self.verbose:
                          print(
                              f"Found distortion {g_theta} with iteration/num_directions={i}/{num_directions}"
                          )
  
      # fail if it cannot find adv direction within `num_directions` Gaussian
      if g_theta == float("inf"):
          if self.verbose:
              print("Couldn't find valid initial, failed")
          return (
              x_0,
              np.zeros((0, 0)),
              False,
          )
  
      query_limit = self.query_limit
      alpha = self.alpha
      beta = self.beta
      timestart = time.time()
      # Begin Sign_OPT from here
      # Algorithm 1: Sign-OPT attack
      #     A:Randomly sample u1, . . . , uQ from a Gaussian or Uniform distribution;
      #     B:Compute gˆ ←  ...
      #     C:Update θt+1 ← θt − ηgˆ;
      #     D:Evaluate g(θt) using the same search algorithm in
      #       Cheng et al. (2019) https://openreview.net/pdf?id=rJlk6iRqKX,
      x_g, g_g = best_theta, g_theta
      distortions = [g_g]
      iterations = self.max_iter
      for i in range(iterations):
          sign_gradient, grad_queries = self._sign_grad(
              x_0, y_0, self.epsilon, x_g, g_g, target
          )
  
          # Line search of the step size of gradient descent
          ls_count = 0
          min_theta = x_g  # next theta
          min_g2 = g_g  # current g_theta
          # new_theta = np.zeros((0, 0))
          for _ in range(15):
              # Algorithm 1: Sign-OPT attack
              new_theta = x_g - alpha * sign_gradient
              new_theta /= np.linalg.norm(new_theta)
              # Algorithm 1: Sign-OPT attack
              #     D:Evaluate g(θt) using the same search algorithm in
              #       Cheng et al. (2019) https://openreview.net/pdf?id=rJlk6iRqKX,
              #       **Algorithm 1 Compute g(θ) locally**
              new_g2, count = self._fine_grained_binary_search_local(
                  x_0,
                  y_0,
                  new_theta,
                  target,
                  initial_lbd=min_g2,
                  tol=beta / 500,
              )
              ls_count += count
              alpha = alpha * 2  # gradually increasing step size
              if new_g2 < min_g2:
                  min_theta = new_theta
                  min_g2 = new_g2
              else:
                  break  # meaning alpha is too big, so it needs to be reduced.
  
          if (
              min_g2 >= g_g
          ):  # if the above code failed for the init alpha, we then try to decrease alpha
              for _ in range(15):
                  alpha = alpha * 0.25
                  new_theta = x_g - alpha * sign_gradient
                  new_theta /= np.linalg.norm(new_theta)
                  (
                      new_g2,
                      count,
                  ) = self._fine_grained_binary_search_local(
                      x_0,
                      y_0,
                      new_theta,
                      target,
                      initial_lbd=min_g2,
                      tol=beta / 500,
                  )
                  ls_count += count
                  if new_g2 < g_g:
                      min_theta = new_theta
                      min_g2 = new_g2
                      break
  
          if alpha < 1e-4:  # if the above two blocks of code failed
              alpha = 1.0
              if self.verbose:
                  print("Warning: not moving")
              beta = beta * 0.1
              if beta < 1e-8:
                  break
  
          # if all attempts failed, min_theta, min_g2 will be the current theta (i.e. not moving)
          x_g, g_g = min_theta, min_g2
  
          query_count += grad_queries + ls_count
          ls_total += ls_count
          distortions.append(g_g)
  
          if query_count > query_limit:
              if self.verbose:
                  print(
                      f"query_count={query_count} > query_limit={query_limit}"
                  )
              break
  
          if self.verbose and (i + 1) % 10 == 0:
              print(
                  f"Iteration {i+1} distortion  {g_g} num_queries {query_count}"
              )
      timeend = time.time()
      succeed = False
      if self.targeted is False and (
          distortion is None or g_g < distortion
      ):
          succeed = True
          if self.verbose:
              print(
                  f"Succeed distortion {g_g} org_label {y_0} predict_lable {target} \
                      queries {query_count} Line Search queries {ls_total}"
              )
              target_pred = self._predict_label(x_0 + g_g * x_g)
              if target_pred == y_0:
                  print(
                      f"WARNING: prediction on adv {target_pred} == org label {y_0}"
                  )
          # return self._clip_value(x_0 + g_g * x_g), g_g * x_g, True
      elif self.targeted and self._is_label(x_0 + g_g * x_g, target):
          succeed = True
          if self.verbose:
              print(
                  f"Adversarial Example Found Successfully: distortion {g_g} target, \
                      {target} queries {query_count} Line Search queries {ls_total} Time: {timeend-timestart} seconds"
              )
          # return self._clip_value(x_0 + g_g * x_g), g_g * x_g, True
      else:
          succeed = False
          if self.verbose:
              print(f"Failed: distortion {g_g}")
      return self._clip_value(x_0 + g_g * x_g), g_g * x_g, succeed
  
imports: import logging
  from typing import Optional, TYPE_CHECKING, Tuple
  import time
  import numpy
  from tqdm.auto import tqdm
  from art.attacks.attack import EvasionAttack
  from art.config import ART_NUMPY_DTYPE
  from art.estimators.estimator import BaseEstimator
  from art.estimators.classification.classifier import ClassifierMixin
  from art.utils import compute_success, check_and_transform_label_format, get_labels_np_array
label_desc: Use image node_id 8 for calling the SignOPTAttack obj's underlying member method code with example usage: obj._attack(x_0, y_0, target, x_init, distortion) and returns: self,  unknown,  succeed, x_0,  np,  False