code: def make_initial_point_expression(
      *,
      free_rvs: Sequence[TensorVariable],
      rvs_to_transforms: Dict[TensorVariable, Transform],
      initval_strategies: Dict[
          TensorVariable, Optional[Union[np.ndarray, Variable, str]]
      ],
      jitter_rvs: Set[TensorVariable] = None,
      default_strategy: str = "moment",
      return_transformed: bool = False,
  ) -> List[TensorVariable]:
      """Creates the tensor variables that need to be evaluated to obtain an initial point.
  
      Parameters
      ----------
      free_rvs : list
          Tensors of free random variables in the model.
      rvs_to_values : dict
          Mapping of free random variable tensors to value variable tensors.
      initval_strategies : dict
          Mapping of free random variable tensors to initial value strategies.
          For example the `Model.initial_values` dictionary.
      jitter_rvs : set
          The set (or list or tuple) of random variables for which a U(-1, +1) jitter should be
          added to the initial value. Only available for variables that have a transform or real-valued support.
      default_strategy : str
          Which of { "moment", "prior" } to prefer if the initval strategy setting for an RV is None.
      return_transformed : bool
          Switches between returning the tensors for untransformed or transformed initial points.
  
      Returns
      -------
      initial_points : list of TensorVariable
          PyTensor expressions for initial values of the free random variables.
      """
      from pymc.distributions.distribution import moment
  
      if jitter_rvs is None:
          jitter_rvs = set()
  
      initial_values = []
      initial_values_transformed = []
  
      for variable in free_rvs:
          strategy = initval_strategies.get(variable, None)
  
          if strategy is None:
              strategy = default_strategy
  
          if isinstance(strategy, str):
              if strategy == "moment":
                  try:
                      value = moment(variable)
                  except NotImplementedError:
                      warnings.warn(
                          f"Moment not defined for variable {variable} of type "
                          f"{variable.owner.op.__class__.__name__}, defaulting to "
                          f"a draw from the prior. This can lead to difficulties "
                          f"during tuning. You can manually define an initval or "
                          f"implement a moment dispatched function for this "
                          f"distribution.",
                          UserWarning,
                      )
                      value = variable
              elif strategy == "prior":
                  value = variable
              else:
                  raise ValueError(
                      f'Invalid string strategy: {strategy}. It must be one of ["moment", "prior"]'
                  )
          else:
              value = pt.as_tensor(
                  strategy, dtype=variable.dtype
              ).astype(variable.dtype)
  
          transform = rvs_to_transforms.get(variable, None)
  
          if transform is not None:
              value = transform.forward(value, *variable.owner.inputs)
  
          if variable in jitter_rvs:
              jitter = pt.random.uniform(-1, 1, size=value.shape)
              jitter.name = f"{variable.name}_jitter"
              value = value + jitter
  
          value = value.astype(variable.dtype)
          initial_values_transformed.append(value)
  
          if transform is not None:
              value = transform.backward(value, *variable.owner.inputs)
  
          initial_values.append(value)
  
      all_outputs: List[TensorVariable] = []
      all_outputs.extend(free_rvs)
      all_outputs.extend(initial_values)
      all_outputs.extend(initial_values_transformed)
  
      copy_graph = FunctionGraph(outputs=all_outputs, clone=True)
  
      n_variables = len(free_rvs)
      free_rvs_clone = copy_graph.outputs[:n_variables]
      initial_values_clone = copy_graph.outputs[
          n_variables:-n_variables
      ]
      initial_values_transformed_clone = copy_graph.outputs[
          -n_variables:
      ]
  
      # We now replace all rvs by the respective initial_point expressions
      # in the constrained (untransformed) space. We do this in reverse topological
      # order, so that later nodes do not reintroduce expressions with earlier
      # rvs that would need to once again be replaced by their initial_points
      graph = FunctionGraph(outputs=free_rvs_clone, clone=False)
      replacements = reversed(
          list(zip(free_rvs_clone, initial_values_clone))
      )
      graph.replace_all(replacements, import_missing=True)
  
      if not return_transformed:
          return graph.outputs
      # Because the unconstrained (transformed) expressions are a subgraph of the
      # constrained initial point they were also automatically updated inplace
      # when calling graph.replace_all above, so we don't need to do anything else
      return initial_values_transformed_clone
  
file_path: pymc/pymc/initial_point.py
imports: import functools
  import warnings
  from typing import Callable, Dict, List, Optional, Sequence, Set, Union
  import numpy
  import pytensor
  import pytensor.tensor
  from pytensor.graph.basic import Variable
  from pytensor.graph.fg import FunctionGraph
  from pytensor.tensor.variable import TensorVariable
  from pymc.logprob.transforms import Transform
  from pymc.pytensorf import compile_pymc, find_rng_nodes, replace_rng_nodes, reseed_rngs
  from pymc.util import get_transformed_name, get_untransformed_name, is_transformed_name
label_desc: Use image node_id 4 for calling a global function with example usage: make_initial_point_expression() and returns: initial_values_transformed_clone, graph