class_docstr: Adversarially chosen rotations and translations. [#Engs]
  This implementation is based on the reference implementation by
  Madry et al.: https://github.com/MadryLab/adversarial_spatial
  
  References:
  .. [#Engs] Logan Engstrom*, Brandon Tran*, Dimitris Tsipras*,
         Ludwig Schmidt, Aleksander Mądry: "A Rotation and a
         Translation Suffice: Fooling CNNs with Simple Transformations",
         http://arxiv.org/abs/1712.02779
class_docstr_tok: ["Adversarially","chosen","rotations","and","translations",".","[","#","Engs","]","This","implementation","is","based","on","the","reference","implementation","by","Madry","et","al",".",":","https",":","\/\/github.com\/MadryLab\/adversarial_spatial","References",":","..","[","#","Engs","]","Logan","Engstrom","*",",","Brandon","Tran","*",",","Dimitris","Tsipras","*",",","Ludwig","Schmidt",",","Aleksander","M\u0105dry",":","``","A","Rotation","and","a","Translation","Suffice",":","Fooling","CNNs","with","Simple","Transformations","''",",","http",":","\/\/arxiv.org\/abs\/1712.02779"]
code: def run(
      self,
      model: Model,
      inputs: T,
      criterion: Union[Criterion, T],
      **kwargs: Any,
  ) -> T:
      raise_if_kwargs(kwargs)
  
      x, restore_type = ep.astensor_(inputs)
      del inputs, kwargs
  
      verify_input_bounds(x, model)
  
      criterion = get_criterion(criterion)
      is_adversarial = get_is_adversarial(criterion, model)
  
      found = is_adversarial(x)
      results = x
  
      def grid_search_generator() -> Generator[Any, Any, Any]:
          dphis = np.linspace(
              -self.max_rot, self.max_rot, self.num_rots
          )
          dxs = np.linspace(
              -self.max_trans, self.max_trans, self.num_trans
          )
          dys = np.linspace(
              -self.max_trans, self.max_trans, self.num_trans
          )
          for dphi in dphis:
              for dx in dxs:
                  for dy in dys:
                      yield dphi, dx, dy
  
      def random_search_generator() -> Generator[Any, Any, Any]:
          dphis = np.random.uniform(
              -self.max_rot, self.max_rot, self.random_steps
          )
          dxs = np.random.uniform(
              -self.max_trans, self.max_trans, self.random_steps
          )
          dys = np.random.uniform(
              -self.max_trans, self.max_trans, self.random_steps
          )
          for dphi, dx, dy in zip(dphis, dxs, dys):
              yield dphi, dx, dy
  
      gen = (
          grid_search_generator()
          if self.grid_search
          else random_search_generator()
      )
      for dphi, dx, dy in gen:
          # TODO: reduce the batch size to the ones that haven't been successful
  
          x_p = rotate_and_shift(x, translation=(dx, dy), rotation=dphi)
          is_adv = is_adversarial(x_p)
          new_adv = ep.logical_and(is_adv, found.logical_not())
  
          results = ep.where(
              atleast_kd(new_adv, x_p.ndim), x_p, results
          )
          found = ep.logical_or(new_adv, found)
          if found.all():
              break  # all images in batch misclassified
      return restore_type(results)
  
file_path: foolbox/foolbox/attacks/spatial_attack.py
imports: from typing import Union, Any, Tuple, Generator
  import eagerpy
  import numpy
  from ..devutils import atleast_kd
  from ..criteria import Criterion
  from .base import Model
  from .base import T
  from .base import get_is_adversarial
  from .base import get_criterion
  from .base import Attack
  from .spatial_attack_transformations import rotate_and_shift
  from .base import raise_if_kwargs
  from .base import verify_input_bounds
label_desc: Use image node_id 3 for calling the SpatialAttack obj's underlying member method code with example usage: obj.run(model, inputs, criterion) and returns: restore_type