docstr: function to register finished runs
  
  Every time a run has finished, this function should be called
  to register it with the result logger. If overwritten, make
  sure to call this method from the base class to ensure proper
  logging.
  
  
  Parameters:
  -----------
  job_id: dict
          a dictionary containing all the info about the run
  job_result: dict
          contains all the results of the job, i.e. it's a dict with
          the keys 'loss' and 'info'
code: def new_result(self, job):
      """
      function to register finished runs
  
      Every time a run has finished, this function should be called
      to register it with the result logger. If overwritten, make
      sure to call this method from the base class to ensure proper
      logging.
  
  
      Parameters:
      -----------
      job_id: dict
              a dictionary containing all the info about the run
      job_result: dict
              contains all the results of the job, i.e. it's a dict with
              the keys 'loss' and 'info'
  
      """
  
      super(KernelDensityEstimator, self).new_result(job)
      budget = job.kwargs["budget"]
      if budget not in self.configs.keys():
          self.configs[budget] = []
          self.losses[budget] = []
  
      # We want to get a numerical representation of the configuration in the original space
      conf = ConfigSpace.Configuration(
          self.configspace, job.kwargs["config"]
      )
      self.configs[budget].append(conf.get_array())
      self.losses[budget].append(job.result["result"]["loss"])
  
      # Check if we have enough data points to fit a KDE
      if len(self.configs[budget]) % self.update_after_n_points == 0:
          train_configs, train_losses = [], []
  
          train_configs.extend(self.configs[budget])
          train_losses.extend(self.losses[budget])
  
          n = int(self.top_n_percent * len(train_configs) / 100.0)
  
          remaining_budgets = list(self.configs.keys())
          remaining_budgets.remove(budget)
          remaining_budgets.sort(reverse=True)
  
          for b in remaining_budgets:
              if n >= self.min_points_in_model:
                  break
              train_configs.extend(self.configs[b])
              train_losses.extend(self.losses[b])
              n = int(self.top_n_percent * len(train_configs) / 100.0)
  
          if len(train_losses) < self.min_points_in_model:
              return
  
          n = max(self.min_points_in_model, n)
  
          # Refit KDE for the current budget
          idx = np.argsort(train_losses)
  
          train_data = (np.array(train_configs)[idx])[:n]
          self.kde_models[budget] = sm.nonparametric.KDEMultivariate(
              data=train_data, var_type=self.var_type, bw="cv_ls"
          )
  
file_path: auptimizer/src/aup/Proposer/hpbandster/optimizers/config_generators/kde.py
imports: import ConfigSpace
  import numpy
  import scipy.stats
  import statsmodels.api
  from hpbandster.config_generators.base import base_config_generator
label_desc: Use image node_id 3 for calling the KernelDensityEstimator obj's underlying member method code with example usage: obj.new_result(job) without return types