docstr: Finds counterfactuals by generating cfs through the genetic algorithm
docstr_tok: ["Finds","counterfactuals","by","generating","cfs","through","the","genetic","algorithm"]
code: def find_counterfactuals(
      self,
      query_instance,
      desired_range,
      desired_class,
      features_to_vary,
      maxiterations,
      thresh,
      verbose,
  ):
      """Finds counterfactuals by generating cfs through the genetic algorithm"""
      population = self.cfs.copy()
      iterations = 0
      previous_best_loss = -np.inf
      current_best_loss = np.inf
      stop_cnt = 0
      cfs_preds = [np.inf] * self.total_CFs
      to_pred = None
  
      self.query_instance_normalized = (
          self.data_interface.normalize_data(self.x1)
      )
      self.query_instance_normalized = (
          self.query_instance_normalized.astype("float")
      )
  
      while iterations < maxiterations and self.total_CFs > 0:
          if abs(previous_best_loss - current_best_loss) <= thresh and (
              self.model.model_type == ModelTypes.Classifier
              and all(i == desired_class for i in cfs_preds)
              or (
                  self.model.model_type == ModelTypes.Regressor
                  and all(
                      desired_range[0] <= i <= desired_range[1]
                      for i in cfs_preds
                  )
              )
          ):
              stop_cnt += 1
          else:
              stop_cnt = 0
          if stop_cnt >= 5:
              break
          previous_best_loss = current_best_loss
          population = np.unique(tuple(map(tuple, population)), axis=0)
  
          population_fitness = self.compute_loss(
              population, desired_range, desired_class
          )
          population_fitness = population_fitness[
              population_fitness[:, 1].argsort()
          ]
  
          current_best_loss = population_fitness[0][1]
          to_pred = np.array(
              [
                  population[int(tup[0])]
                  for tup in population_fitness[: self.total_CFs]
              ]
          )
  
          if self.total_CFs > 0:
              if self.model.model_type == ModelTypes.Classifier:
                  cfs_preds = self._predict_fn_custom(
                      to_pred, desired_class
                  )
              else:
                  cfs_preds = self.predict_fn(to_pred)
  
          # self.total_CFS of the next generation obtained from the fittest members of current generation
          top_members = self.total_CFs
          new_generation_1 = np.array(
              [
                  population[int(tup[0])]
                  for tup in population_fitness[:top_members]
              ]
          )
  
          # rest of the next generation obtained from top 50% of fittest members of current generation
          rest_members = self.population_size - top_members
          new_generation_2 = None
          if rest_members > 0:
              new_generation_2 = np.zeros(
                  (rest_members, self.data_interface.number_of_features)
              )
              for new_gen_idx in range(rest_members):
                  parent1 = random.choice(
                      population[: int(len(population) / 2)]
                  )
                  parent2 = random.choice(
                      population[: int(len(population) / 2)]
                  )
                  child = self.mate(
                      parent1, parent2, features_to_vary, query_instance
                  )
                  new_generation_2[new_gen_idx] = child
  
          if new_generation_2 is not None:
              if self.total_CFs > 0:
                  population = np.concatenate(
                      [new_generation_1, new_generation_2]
                  )
              else:
                  population = new_generation_2
          else:
              raise SystemError(
                  "The number of total_Cfs is greater than the population size!"
              )
          iterations += 1
  
      self.cfs_preds = []
      self.final_cfs = []
      i = 0
      while i < self.total_CFs:
          predictions = self.predict_fn_scores(population[i])[0]
          if self.is_cf_valid(predictions):
              self.final_cfs.append(population[i])
              # checking if predictions is a float before taking the length as len() works only for array-like
              # elements. isinstance(predictions, (np.floating, float)) checks if it's any float (numpy or otherwise)
              # We do this as we take the argmax if the prediction is a vector -- like the output of a classifier
              if (
                  not isinstance(predictions, (np.floating, float))
                  and len(predictions) > 1
              ):
                  self.cfs_preds.append(np.argmax(predictions))
              else:
                  self.cfs_preds.append(predictions)
          i += 1
  
      # converting to dataframe
      query_instance_df = self.label_decode(query_instance)
      query_instance_df[
          self.data_interface.outcome_name
      ] = self.get_model_output_from_scores(self.test_pred)
      self.final_cfs_df = self.label_decode_cfs(self.final_cfs)
      self.final_cfs_df_sparse = copy.deepcopy(self.final_cfs_df)
  
      if self.final_cfs_df is not None:
          self.final_cfs_df[
              self.data_interface.outcome_name
          ] = self.cfs_preds
          self.final_cfs_df_sparse[
              self.data_interface.outcome_name
          ] = self.cfs_preds
          self.round_to_precision()
  
      # decoding to original label
      (
          query_instance_df,
          self.final_cfs_df,
          self.final_cfs_df_sparse,
      ) = self.decode_to_original_labels(
          query_instance_df, self.final_cfs_df, self.final_cfs_df_sparse
      )
  
      self.elapsed = timeit.default_timer() - self.start_time
      m, s = divmod(self.elapsed, 60)
  
      if verbose:
          if len(self.final_cfs) == self.total_CFs:
              print(
                  "Diverse Counterfactuals found! total time taken: %02d"
                  % m,
                  "min %02d" % s,
                  "sec",
              )
          else:
              print(
                  "Only %d (required %d) "
                  % (len(self.final_cfs), self.total_CFs),
                  "Diverse Counterfactuals found for the given configuation, perhaps ",
                  "change the query instance or the features to vary..."
                  "; total time taken: %02d" % m,
                  "min %02d" % s,
                  "sec",
              )
  
      return query_instance_df
  
imports: import copy
  import random
  import timeit
  import numpy
  import pandas
  from raiutils.exceptions import UserConfigValidationException
  from dice_ml import diverse_counterfactuals
  from dice_ml.constants import ModelTypes
  from dice_ml.explainer_interfaces.explainer_base import ExplainerBase
label_desc: Use image node_id 17 for calling the DiceGenetic obj's underlying member method code with example usage: obj.find_counterfactuals(query_instance, desired_range, desired_class, features_to_vary, maxiterations, thresh, verbose) and returns: query_instance_df