class_docstr: Implementation of the Geometric Decision-based Attack (GeoDA), a black-box attack requiring class predictions.
  Based on reference implementation: https://github.com/thisisalirah/GeoDA
  
  | Paper link: https://arxiv.org/abs/2003.06468
class_docstr_tok: ["Implementation","of","the","Geometric","Decision-based","Attack","(","GeoDA",")",",","a","black-box","attack","requiring","class","predictions",".","Based","on","reference","implementation",":","https",":","\/\/github.com\/thisisalirah\/GeoDA","|","Paper","link",":","https",":","\/\/arxiv.org\/abs\/2003.06468"]
docstr: Calculate gradient towards decision boundary.
docstr_tok: ["Calculate","gradient","towards","decision","boundary","."]
code: def _black_grad_batch(
      self,
      x_boundary: np.ndarray,
      q_max: int,
      batch_size: int,
      original_label: np.ndarray,
  ) -> Tuple[np.ndarray, int]:
      """
      Calculate gradient towards decision boundary.
      """
      self.nb_calls += q_max
      grad_tmp: List[
          np.ndarray
      ] = []  # estimated gradients in each estimate_batch
      z_list: List[int] = []  # sign of grad_tmp
      outs = []
      num_batches = math.ceil(q_max / batch_size)
      last_batch = q_max - (num_batches - 1) * batch_size
      all_noises = []
  
      for j in range(num_batches):
          if j == num_batches - 1:
              current_batch = self._sub_noise(
                  last_batch, self.sub_basis
              )
              noisy_boundary = [
                  x_boundary[0, :, :, :]
              ] * last_batch + self.sigma * current_batch
          else:
              current_batch = self._sub_noise(
                  batch_size, self.sub_basis
              )
              noisy_boundary = [
                  x_boundary[0, :, :, :]
              ] * batch_size + self.sigma * current_batch
  
          all_noises.append(current_batch)
          predict_labels = np.argmax(
              self.estimator.predict(noisy_boundary), axis=1
          ).astype(int)
          outs.append(predict_labels)
  
      all_noise = np.concatenate(all_noises, axis=0)
      outs = np.concatenate(outs, axis=0)
  
      for i, predict_label in enumerate(outs):
          if predict_label == np.argmax(original_label, axis=1)[0]:
              z_list.append(1)
              grad_tmp.append(all_noise[i])
          else:
              z_list.append(-1)
              grad_tmp.append(-all_noise[i])
  
      grad: np.ndarray = -(1 / q_max) * sum(grad_tmp)  # type: ignore
      grad_f = grad[None, :, :, :]
  
      return grad_f, sum(z_list)
  
imports: import os
  import math
  import logging
  from typing import List, Optional, Tuple, Union, TYPE_CHECKING
  import numpy
  from tqdm.auto import trange
  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 check_and_transform_label_format, get_labels_np_array
label_desc: Use image node_id 8 for calling the GeoDA obj's underlying member method code with example usage: obj._black_grad_batch(x_boundary, q_max, batch_size, original_label) and returns: grad_f,  sum