class_docstr: An attack that maintains a beam of the `beam_width` highest scoring
  AttackedTexts, greedily updating the beam with the highest scoring
  transformations from the current beam.
  
  Args:
      goal_function: A function for determining how well a perturbation is doing at achieving the attack's goal.
      transformation: The type of transformation.
      beam_width (int): the number of candidates to retain at each step
code: def perform_search(self, initial_result):
      beam = [initial_result.attacked_text]
      best_result = initial_result
      while (
          not best_result.goal_status
          == GoalFunctionResultStatus.SUCCEEDED
      ):
          potential_next_beam = []
          for text in beam:
              transformations = self.get_transformations(
                  text, original_text=initial_result.attacked_text
              )
              potential_next_beam += transformations
  
          if len(potential_next_beam) == 0:
              # If we did not find any possible perturbations, give up.
              return best_result
          results, search_over = self.get_goal_results(
              potential_next_beam
          )
          scores = np.array([r.score for r in results])
          best_result = results[scores.argmax()]
          if search_over:
              return best_result
  
          # Refill the beam. This works by sorting the scores
          # in descending order and filling the beam from there.
          best_indices = (-scores).argsort()[: self.beam_width]
          beam = [potential_next_beam[i] for i in best_indices]
  
      return best_result
  
file_path: TextAttack/textattack/search_methods/beam_search.py
imports: import numpy
  from textattack.goal_function_results import GoalFunctionResultStatus
  from textattack.search_methods import SearchMethod
label_desc: Use image node_id 2 for calling the BeamSearch obj's underlying member method code with example usage: obj.perform_search(initial_result) and returns: best_result, best_result, best_result