class_docstr: GenerationStrategy describes which model should be used to generate new
  points for which trials, enabling and automating use of different models
  throughout the optimization process. For instance, it allows to use one
  model for the initialization trials, and another one for all subsequent
  trials. In the general case, this allows to automate use of an arbitrary
  number of models to generate an arbitrary numbers of trials
  described in the `trials_per_model` argument.
  
  Args:
      nodes: A list of `GenerationNode`. Each `GenerationNode` in the list
          represents a single node in a `GenerationStrategy` which, when
          composed of `GenerationNodes`, can be conceptualized as a graph instead
          of a linear list. `TransitionCriterion` defined in each `GenerationNode`
          represent the edges in the `GenerationStrategy` graph. `GenerationNodes`
          are more flexible than `GenerationSteps` and new `GenerationStrategies`
          should use nodes. Notably, either, but not both, of `nodes` and `steps`
          must be provided.
      steps: A list of `GenerationStep` describing steps of this strategy.
      name: An optional name for this generaiton strategy. If not specified,
          strategy's name will be names of its nodes' models joined with '+'.
docstr: Initialize and validate the steps provided to this GenerationStrategy.
  
  Some GenerationStrategies are composed of GenerationStep objects, but we also
  need to initialize the correct GenerationNode representation for these steps.
  This function validates:
      1. That only the last step has num_trials=-1, which indicates unlimited
         trial generation is possible.
      2. That each step's num_trials attrivute is either positive or -1
      3. That each step's max_parallelism attribute is either None or positive
  It then sets the corect TransitionCriterion and node_name attributes on the
  underlying GenerationNode objects.
docstr_tok: ["Initialize","and","validate","the","steps","provided","to","this","GenerationStrategy",".","Some","GenerationStrategies","are","composed","of","GenerationStep","objects",",","but","we","also","need","to","initialize","the","correct","GenerationNode","representation","for","these","steps",".","This","function","validates",":","1",".","That","only","the","last","step","has","num_trials=-1",",","which","indicates","unlimited","trial","generation","is","possible",".","2",".","That","each","step","'s","num_trials","attrivute","is","either","positive","or","-1","3",".","That","each","step","'s","max_parallelism","attribute","is","either","None","or","positive","It","then","sets","the","corect","TransitionCriterion","and","node_name","attributes","on","the","underlying","GenerationNode","objects","."]
code: def _validate_and_set_step_sequence(
      self, steps: List[GenerationStep]
  ) -> None:
      """Initialize and validate the steps provided to this GenerationStrategy.
  
      Some GenerationStrategies are composed of GenerationStep objects, but we also
      need to initialize the correct GenerationNode representation for these steps.
      This function validates:
          1. That only the last step has num_trials=-1, which indicates unlimited
             trial generation is possible.
          2. That each step's num_trials attrivute is either positive or -1
          3. That each step's max_parallelism attribute is either None or positive
      It then sets the corect TransitionCriterion and node_name attributes on the
      underlying GenerationNode objects.
      """
      for idx, step in enumerate(steps):
          if (
              step.num_trials == -1
              and len(step.completion_criteria) < 1
          ):
              if idx < len(self._steps) - 1:
                  raise UserInputError(
                      "Only last step in generation strategy can have "
                      "`num_trials` set to -1 to indicate that the model in "
                      "the step shouldbe used to generate new trials "
                      "indefinitely unless completion critera present."
                  )
          elif step.num_trials < 1 and step.num_trials != -1:
              raise UserInputError(
                  "`num_trials` must be positive or -1 (indicating unlimited) "
                  "for all generation steps."
              )
          if (
              step.max_parallelism is not None
              and step.max_parallelism < 1
          ):
              raise UserInputError(
                  "Maximum parallelism should be None (if no limit) or "
                  f"a positive number. Got: {step.max_parallelism} for "
                  f"step {step.model_name}."
              )
  
          step._node_name = f"GenerationStep_{str(idx)}"
          step.index = idx
  
          # Set transition_to field for all but the last step, which remains
          # null.
          if idx != len(self._steps):
              for transition_criteria in step.transition_criteria:
                  if (
                      transition_criteria.criterion_class == "MinTrials"
                      or transition_criteria.criterion_class
                      == "MaxTrials"
                  ):
                      transition_criteria._transition_to = (
                          f"GenerationStep_{str(idx + 1)}"
                      )
          step._generation_strategy = self
      self._curr = steps[0]
  
imports: from __future__ import annotations
  from copy import deepcopy
  from functools import wraps
  from logging import Logger
  from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar
  import pandas
  from ax.core.data import Data
  from ax.core.experiment import Experiment
  from ax.core.generation_strategy_interface import GenerationStrategyInterface
  from ax.core.generator_run import GeneratorRun
  from ax.core.observation import ObservationFeatures
  from ax.core.utils import extend_pending_observations, get_pending_observation_features_based_on_trial_status
  from ax.exceptions.core import DataRequiredError, UnsupportedError, UserInputError
  from ax.exceptions.generation_strategy import GenerationStrategyCompleted, GenerationStrategyMisconfiguredException
  from ax.modelbridge.base import ModelBridge
  from ax.modelbridge.generation_node import GenerationNode, GenerationStep
  from ax.modelbridge.model_spec import FactoryFunctionModelSpec
  from ax.modelbridge.registry import _extract_model_state_after_gen, ModelRegistryBase
  from ax.modelbridge.transition_criterion import TrialBasedCriterion
  from ax.utils.common.logger import _round_floats_for_logging, get_logger
  from ax.utils.common.typeutils import checked_cast, not_none
label_desc: Use image node_id 16 for calling the GenerationStrategy obj's underlying member method code with example usage: obj._validate_and_set_step_sequence(steps) without return types