class_docstr: Implementation of Succesive Halving supporting multi-fidelity, multi-objective, and multi-processing.
  Internally, a tracker keeps track of configurations and their bracket and stage.
  
  The behaviour of this intensifier is as follows:
  
  - First, adds configurations from the runhistory to the tracker. The first stage is always filled-up. For example,
    the user provided 4 configs with the tell-method but the first stage requires 8 configs: 4 new configs are
    sampled and added together with the provided configs as a group to the tracker.
  - While loop:
  
    - If a trial in the tracker has not been yielded yet, yield it.
    - If we are running out of trials, we simply add a new batch of configurations to the first stage.
  
  Note
  ----
  The implementation natively supports brackets from Hyperband. However, in the case of Successive Halving,
  only one bracket is used.
  
  Parameters
  ----------
  eta : int, defaults to 3
      Input that controls the proportion of configurations discarded in each round of Successive Halving.
  n_seeds : int, defaults to 1
      How many seeds to use for each instance.
  instance_seed_order : str, defaults to "shuffle_once"
      How to order the instance-seed pairs. Can be set to:
  
      - `None`: No shuffling at all and use the instance-seed order provided by the user.
      - `shuffle_once`: Shuffle the instance-seed keys once and use the same order across all runs.
      - `shuffle`: Shuffles the instance-seed keys for each bracket individually.
  incumbent_selection : str, defaults to "highest_observed_budget"
      How to select the incumbent when using budgets. Can be set to:
  
      - `any_budget`: Incumbent is the best on any budget i.e., best performance regardless of budget.
      - `highest_observed_budget`: Incumbent is the best in the highest budget run so far.
      - `highest_budget`: Incumbent is selected only based on the highest budget.
  max_incumbents : int, defaults to 10
      How many incumbents to keep track of in the case of multi-objective.
  seed : int, defaults to None
      Internal seed used for random events like shuffle seeds.
code: def __iter__(self) -> Iterator[TrialInfo]:  # noqa: D102
      self.__post_init__()
  
      # Log brackets/stages
      logger.info("Number of configs in stage:")
      for bracket, n in self._n_configs_in_stage.items():
          logger.info(f"--- Bracket {bracket}: {n}")
  
      logger.info("Budgets in stage:")
      for bracket, budgets in self._budgets_in_stage.items():
          logger.info(f"--- Bracket {bracket}: {budgets}")
  
      rh = self.runhistory
  
      # We have to add already existing trials from the runhistory
      # Idea: We simply add existing configs to the tracker (first stage) but assign a random instance shuffle seed.
      # In the best case, trials (added from the users) are included in the seed and it has not re-computed again.
      # Note: If the intensifier was restored, we don't want to go in here
      if len(self._tracker) == 0:
          bracket = 0
          stage = 0
  
          # Print ignored budgets
          ignored_budgets = []
          for k in rh.keys():
              if (
                  k.budget not in self._budgets_in_stage[0]
                  and k.budget not in ignored_budgets
              ):
                  ignored_budgets.append(k.budget)
  
          if len(ignored_budgets) > 0:
              logger.warning(
                  f"Trials with budgets {ignored_budgets} will been ignored. Consider adding trials with budgets "
                  f"{self._budgets_in_stage[0]}."
              )
  
          # We batch the configs because we need n_configs in each iteration
          # If we don't have n_configs, we sample new ones
          # We take the number of configs from the first bracket and the first stage
          n_configs = self._n_configs_in_stage[bracket][stage]
          for configs in batch(rh.get_configs(), n_configs):
              n_rh_configs = len(configs)
  
              if len(configs) < n_configs:
                  try:
                      config = next(self.config_generator)
                      configs.append(config)
                  except StopIteration:
                      # We stop if we don't find any configuration anymore
                      return
  
              seed = self._get_next_order_seed()
              self._tracker[(bracket, stage)].append((seed, configs))
              logger.info(
                  f"Added {n_rh_configs} configs from runhistory and {n_configs - n_rh_configs} new configs to "
                  f"Successive Halving's first bracket and first stage with order seed {seed}."
              )
  
      while True:
          # If we don't yield trials anymore, we have to update
          # Otherwise, we can just keep yielding trials from the tracker
          update = False
  
          # We iterate over the tracker to do two things:
          # 1) Yield trials of configs that are not yet evaluated/running
          # 2) Update tracker and move better configs to the next stage
          # We start in reverse order to complete higher stages first
          logger.debug("Updating tracker:")
  
          # TODO: Process stages ascending or descending?
          for bracket, stage in list(self._tracker.keys()):
              pairs = self._tracker[(bracket, stage)].copy()
              for seed, configs in pairs:
                  isb_keys = (
                      self._get_instance_seed_budget_keys_by_stage(
                          bracket=bracket, stage=stage, seed=seed
                      )
                  )
  
                  # We iterate over the configs and yield trials which are not running/evaluated yet
                  for config in configs:
                      config_hash = get_config_hash(config)
                      trials = self._get_next_trials(
                          config, from_keys=isb_keys
                      )
                      logger.debug(
                          f"--- Yielding {len(trials)}/{len(isb_keys)} for config {config_hash} in "
                          f"stage {stage} with seed {seed}..."
                      )
  
                      for trial in trials:
                          yield trial
                          update = True
  
                  # If all configs were evaluated on ``n_configs_required``, we finally can compare
                  try:
                      successful_configs = self._get_best_configs(
                          configs, bracket, stage, isb_keys
                      )
                  except NotEvaluatedError:
                      # We can't compare anything, so we just continue with the next pairs
                      logger.debug(
                          "--- Could not compare configs because not all trials have been evaluated yet."
                      )
                      continue
  
                  # Update tracker
                  # Remove current shuffle index / config pair
                  self._tracker[(bracket, stage)].remove(
                      (seed, configs)
                  )
  
                  # Add successful to the next stage
                  if stage < self._max_iterations[bracket] - 1:
                      config_ids = [
                          rh.get_config_id(config)
                          for config in successful_configs
                      ]
                      self._tracker[(bracket, stage + 1)].append(
                          (seed, successful_configs)
                      )
  
                      logger.debug(
                          f"--- Promoted {len(config_ids)} configs from stage {stage} to stage {stage + 1} in "
                          f"bracket {bracket}."
                      )
                  else:
                      logger.debug(
                          f"--- Removed {len(successful_configs)} configs to last stage in bracket {bracket}."
                      )
  
                  # Log how many configs are in each stage
                  self.print_tracker()
  
          # Since we yielded something before, we want to go back as long as we do not find any trials anymore
          if update:
              continue
  
          # TODO: Aggressive progressing without knowing how well trials performed
          # Idea: Don't add constantly new batches (see ASHA)
  
          # If we are running out of trials, we want to add configs to the first stage
          # We simply add as many configs to the stage as required (_n_configs_in_stage[0])
          configs = []
          next_bracket = self._get_next_bracket()
          for _ in range(self._n_configs_in_stage[next_bracket][0]):
              try:
                  config = next(self.config_generator)
                  configs.append(config)
              except StopIteration:
                  # We stop if we don't find any configuration anymore
                  return
  
          # We keep track of the seed so we always evaluate on the same instances
          next_seed = self._get_next_order_seed()
          self._tracker[(next_bracket, 0)].append((next_seed, configs))
          logger.debug(
              f"Added {len(configs)} new configs to bracket {next_bracket} stage 0 with shuffle seed {next_seed}."
          )
  
imports: from __future__ import annotations
  from typing import Any, Iterator
  import math
  from collections import defaultdict
  import numpy
  from ConfigSpace import Configuration
  from smac.constants import MAXINT
  from smac.intensifier.abstract_intensifier import AbstractIntensifier
  from smac.runhistory import TrialInfo
  from smac.runhistory.dataclasses import InstanceSeedBudgetKey
  from smac.runhistory.errors import NotEvaluatedError
  from smac.scenario import Scenario
  from smac.utils.configspace import get_config_hash
  from smac.utils.data_structures import batch
  from smac.utils.logging import get_logger
  from smac.utils.pareto_front import calculate_pareto_front, sort_by_crowding_distance
label_desc: Use image node_id 15 for calling the SuccessiveHalving obj's underlying member method code with example usage: obj.__iter__() without return types