code: def simulate(
      data_path: str,
      evaluations_path: str,
      output_path: str,
      max_ensemble_size: int,
      default_samples: int,
      hyperensemble_samples: int,
      random_samples: int,
      sample_datasets: bool,
      seed: int,
  ):
      """
      Simulates the performance of various ensembles.
  
      The ensembles are built from configurations (i.e. model types and
      hyperparameters) for which offline evaluations are available.
      """
      assert any(
          [
              default_samples != 0,
              hyperensemble_samples != 0,
              random_samples != 0,
          ]
      ), "No samples are specified."
  
      random.seed(seed)
  
      # Load the experiments
      print("Loading experiments...")
      tracker = ModelTracker.from_directory(
          Path(evaluations_path), data_path=Path(data_path)
      )
  
      # Sample configurations
      print("Sampling configurations...")
      unique_configurations = tracker.unique_model_configs()
      default_configurations = [
          cast(ModelConfig, c)
          for c in unique_configurations
          if not isinstance(c, TrainConfig) or c == c.__class__()
      ]
      choices: List[Tuple[ModelConfig]] = []
  
      # If desired, we combine all base configurations into ensembles of sizes between 2 and the
      # provided maximum. Then, we potentially sample from this collection. For 13 default
      # configurations and a maximum ensemble size of 10, this results in 8,086 ensembles.
      if default_samples != 0:
          available_ensembles = [
              combination
              for i in range(2, max_ensemble_size + 1)
              for combination in combinations(default_configurations, i)
          ]
          if default_samples == -1:
              choices.extend(available_ensembles)
          else:
              choices.extend(
                  random.sample(available_ensembles, default_samples)
              )
  
      if hyperensemble_samples != 0:
          available_ensembles = []
          for config in default_configurations:
              all_configs = [
                  c
                  for c in unique_configurations
                  if isinstance(c, config.__class__)
                  and (
                      not isinstance(c, TrainConfig)
                      or c.training_fraction == 1
                  )
              ]
              if len(all_configs) == 1:
                  continue
              hyper_ensembles = [
                  combination
                  for i in range(2, max_ensemble_size + 1)
                  for combination in combinations(all_configs, i)
              ]
              available_ensembles.extend(hyper_ensembles)
  
          if hyperensemble_samples == -1:
              choices.extend(available_ensembles)
          else:
              choices.extend(
                  random.sample(
                      available_ensembles, hyperensemble_samples
                  )
              )
  
      # Then, we add some randomly sampled ensembles of model configurations
      for _ in range(random_samples):
          ensemble_size = random.randrange(2, max_ensemble_size + 1)
          configs = random.sample(unique_configurations, ensemble_size)
          choices.append(tuple(configs))
  
      # Then, we either evaluate each chosen configuration on all datasets or on a randomly sampled
      # one.
      datasets = list(
          {c.dataset for c in tracker.get_evaluations().configurations}
      )
      if sample_datasets:
          evaluations = [
              (model_config, random.choice(datasets))
              for model_config in choices
          ]
      else:
          evaluations = list(product(choices, datasets))
  
      # Eventually, we can evaluate the ensembles that we have sampled
      print("Evaluating ensembles...")
      evaluator = EnsembleAnalyzer(tracker)
      results = run_parallel(
          partial(_evaluate_ensemble, evaluator=evaluator),
          evaluations,
          num_fitting_processes(
              cpus_per_process=1, memory_per_process=8
          ),
      )
  
      # Afterwards, we can store all configurations along with their results. For now, we are just
      # storing them as pickled objects.
      with Path(output_path).open("wb+") as f:
          pickle.dump(
              [
                  {
                      "configurations": list(evaluation[0]),
                      "dataset": evaluation[1],
                      "performance": result,
                  }
                  for evaluation, result in zip(evaluations, results)
              ],
              f,
          )
  
file_path: gluonts/src/gluonts/nursery/tsbench/src/cli/ensembles/simulate.py
imports: import pickle
  import random
  from functools import partial
  from itertools import combinations, product
  from pathlib import Path
  from typing import cast, List, Tuple
  import click
  from tsbench.analysis import EnsembleAnalyzer
  from tsbench.analysis.utils import num_fitting_processes, run_parallel
  from tsbench.config import DatasetConfig, ModelConfig, TrainConfig
  from tsbench.constants import DEFAULT_EVALUATIONS_PATH
  from tsbench.evaluations.metrics import Performance
  from tsbench.evaluations.tracking import ModelTracker
  from ._main import ensembles
label_desc: Use image node_id 1 for calling a global function with example usage: simulate(data_path, evaluations_path, output_path, max_ensemble_size, default_samples, hyperensemble_samples, random_samples, sample_datasets, seed) without return types