class_docstr: Base class for GenerationNode, capable of fitting one or more model specs under
  the hood and generating candidates from them.
  
  Args:
      model_specs: A list of ModelSpecs to be selected from for generation in this
          GenerationNode
      should_deduplicate: Whether to deduplicate the parameters of proposed arms
          against those of previous arms via rejection sampling. If this is True,
          the GenerationStrategy will discard generator runs produced from the
          GenerationNode that has `should_deduplicate=True` if they contain arms
          already present on the experiment and replace them with new generator runs.
          If no generator run with entirely unique arms could be produced in 5
          attempts, a `GenerationStrategyRepeatedPoints` error will be raised, as we
          assume that the optimization converged when the model can no longer suggest
          unique arms.
      node_name: A unique name for the GenerationNode. Used for storage purposes.
      transition_criteria: List of TransitionCriterion, each of which describes a
          condition that must be met before completing a GenerationNode. All `is_met`
          must evaluateTrue for the GenerationStrategy to move on to the next
          GenerationNode.
      gen_unlimited_trials: If True the number of trials that can be generated from
          this GenerationNode is unlimited.
  
  Note for developers: by "model" here we really mean an Ax ModelBridge object, which
  contains an Ax Model under the hood. We call it "model" here to simplify and focus
  on explaining the logic of GenerationStep and GenerationStrategy.
class_docstr_tok: ["Base","class","for","GenerationNode",",","capable","of","fitting","one","or","more","model","specs","under","the","hood","and","generating","candidates","from","them",".","Args",":","model_specs",":","A","list","of","ModelSpecs","to","be","selected","from","for","generation","in","this","GenerationNode","should_deduplicate",":","Whether","to","deduplicate","the","parameters","of","proposed","arms","against","those","of","previous","arms","via","rejection","sampling",".","If","this","is","True",",","the","GenerationStrategy","will","discard","generator","runs","produced","from","the","GenerationNode","that","has","`","should_deduplicate=True","`","if","they","contain","arms","already","present","on","the","experiment","and","replace","them","with","new","generator","runs",".","If","no","generator","run","with","entirely","unique","arms","could","be","produced","in","5","attempts",",","a","`","GenerationStrategyRepeatedPoints","`","error","will","be","raised",",","as","we","assume","that","the","optimization","converged","when","the","model","can","no","longer","suggest","unique","arms",".","node_name",":","A","unique","name","for","the","GenerationNode",".","Used","for","storage","purposes",".","transition_criteria",":","List","of","TransitionCriterion",",","each","of","which","describes","a","condition","that","must","be","met","before","completing","a","GenerationNode",".","All","`","is_met","`","must","evaluateTrue","for","the","GenerationStrategy","to","move","on","to","the","next","GenerationNode",".","gen_unlimited_trials",":","If","True","the","number","of","trials","that","can","be","generated","from","this","GenerationNode","is","unlimited",".","Note","for","developers",":","by","``","model","''","here","we","really","mean","an","Ax","ModelBridge","object",",","which","contains","an","Ax","Model","under","the","hood",".","We","call","it","``","model","''","here","to","simplify","and","focus","on","explaining","the","logic","of","GenerationStep","and","GenerationStrategy","."]
docstr: Checks whether we should transition to the next node based on this node's
  TransitionCriterion
  
  Args:
      raise_data_required_error: Whether to raise ``DataRequiredError`` in the
          case detailed above. Not raising the error is useful if just looking to
          check how many generator runs (to be made into trials) can be produced,
          but not actually producing them yet.
  Returns:
      bool: Whether we should transition to the next node.
code: def should_transition_to_next_node(
      self, raise_data_required_error: bool = True
  ) -> Tuple[bool, Optional[str]]:
      """Checks whether we should transition to the next node based on this node's
      TransitionCriterion
  
      Args:
          raise_data_required_error: Whether to raise ``DataRequiredError`` in the
              case detailed above. Not raising the error is useful if just looking to
              check how many generator runs (to be made into trials) can be produced,
              but not actually producing them yet.
      Returns:
          bool: Whether we should transition to the next node.
      """
      # TODO: @mgarrard remove check when legacy usecase is updated
      criterion_names = [
          str(criterion) for criterion in self.transition_criteria
      ]
      if "AEPsych" in str(self) or any(
          "MinimumPreferenceOccurances" in name
          for name in criterion_names
      ):
          if all(
              criterion.is_met(experiment=self.experiment)
              for criterion in self.transition_criteria
          ):
              # legacy usecase criterion don't define `transition_to` but the
              # assumption of this naming structure for steps hold for all legacy
              # generation strategies.
              next_node_name = (
                  self.node_name.split("_")[0]
                  + "_"
                  + str(int(self.node_name.split("_")[-1]) + 1)
              )
              return True, next_node_name
          else:
              return False, None
  
      if (
          self.gen_unlimited_trials
          and len(self.transition_criteria) == 0
      ):
          return False, None
  
      transition_blocking_criterion = [
          criterion
          for criterion in self.transition_criteria
          if criterion.block_transition_if_unmet
      ]
      all_transition_blocking_criteria_are_met = all(
          transition_criterion.is_met(
              self.experiment,
              trials_from_node=self.trials_from_node,
          )
          for transition_criterion in transition_blocking_criterion
      )
      # Raise any necessary generation errors: for any met criterion,
      # call its `block_continued_generation_error` method if not all
      # transition-blocking criteria are met. The method might not raise an
      # error, depending on its implementation on given criterion, so the error
      # from the first met one that does block continued generation, will be raised.
      if not all_transition_blocking_criteria_are_met:
          for criterion in self.transition_criteria:
              if (
                  criterion.is_met(
                      self.experiment,
                      trials_from_node=self.trials_from_node,
                  )
                  and raise_data_required_error
              ):
                  criterion.block_continued_generation_error(
                      node_name=self.node_name,
                      model_name=self.model_to_gen_from_name,
                      experiment=self.experiment,
                      trials_from_node=self.trials_from_node,
                  )
  
      # Determine transition state
      if (
          len(transition_blocking_criterion) > 0
          and all_transition_blocking_criteria_are_met
      ):
          transition_nodes = [
              criterion.transition_to
              for criterion in transition_blocking_criterion
              if criterion._transition_to is not None
          ]
          if len(set(transition_nodes)) > 1:
              # TODO: support intelligent selection between multiple transition nodes
              raise NotImplementedError(
                  "Cannot currently select between multiple transition nodes."
              )
          return True, transition_nodes[0]
      return False, None
  
imports: from __future__ import annotations
  from dataclasses import dataclass, field
  from logging import Logger
  from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union
  from ax import modelbridge
  from ax.core.arm import Arm
  from ax.core.base_trial import TrialStatus
  from ax.core.data import Data
  from ax.core.experiment import Experiment
  from ax.core.generator_run import GeneratorRun
  from ax.core.observation import ObservationFeatures
  from ax.core.optimization_config import OptimizationConfig
  from ax.core.search_space import SearchSpace
  from ax.exceptions.core import UserInputError
  from ax.exceptions.generation_strategy import GenerationStrategyRepeatedPoints
  from ax.modelbridge.base import ModelBridge
  from ax.modelbridge.cross_validation import BestModelSelector, CVDiagnostics, CVResult
  from ax.modelbridge.model_spec import FactoryFunctionModelSpec, ModelSpec
  from ax.modelbridge.registry import ModelRegistryBase
  from ax.modelbridge.transition_criterion import MaxGenerationParallelism, MaxTrials, MinTrials, TransitionCriterion, TrialBasedCriterion
  from ax.utils.common.base import Base, SortableBase
  from ax.utils.common.logger import get_logger
  from ax.utils.common.serialization import SerializationMixin
  from ax.utils.common.typeutils import not_none
label_desc: Use image node_id 24 for calling the GenerationNode obj's underlying member method code with example usage: obj.should_transition_to_next_node(raise_data_required_error) and returns: False,  None, False,  None, True,  transition_nodes, True,  next_node_name, False,  None