code: def safe_new(
      x: Variable,
      tag: str = "",
      dtype: Optional[Union[str, np.dtype]] = None,
  ) -> Variable:
      """Clone variables.
  
      Internal function that constructs a new variable from `x` with the same
      type, but with a different name (old name + tag). This function is used
      by `gradient`, or the R-op to construct new variables for the inputs of
      the inner graph such that there is no interference between the original
      graph and the newly constructed graph.
  
      """
      if hasattr(x, "name") and x.name is not None:
          nw_name = x.name + tag
      else:
          nw_name = None
  
      if isinstance(x, Constant):
          # TODO: Do something better about this
          assert isinstance(x.type, HasDataType)
  
          if dtype and x.type.dtype != dtype:
              casted_x = cast(x, dtype)
              nwx = type(x)(casted_x.type, x.data, x.name)
              nwx.tag = copy.copy(x.tag)
              return nwx
          else:
              return x
      # Note, `as_tensor_variable` will convert the `ScalarType` into a
      # `TensorScalar` that will require a `ScalarFromTensor` `Op`, making the
      # push-out optimization fail
      elif isinstance(x, ps.ScalarVariable):
          if dtype:
              nw_x = ps.get_scalar_type(dtype=dtype)()
          else:
              nw_x = x.type()
          nw_x.name = nw_name
          if config.compute_test_value != "off":
              # Copy test value, cast it if necessary
              try:
                  x_test_value = get_test_value(x)
              except TestValueError:
                  pass
              else:
                  # This clause is executed if no exception was raised
                  nw_x.tag.test_value = nw_x.type.filter(x_test_value)
          return nw_x
      else:
          try:
              x = pt.as_tensor_variable(x)
          except TypeError:
              # This could happen for example for random states
              pass
  
      # Cast `x` if needed. If `x` has a test value, this will also cast it.
      if dtype:
          # TODO: Do something better about this
          assert isinstance(x.type, HasDataType)
  
          if x.type.dtype != dtype:
              x = cast(x, dtype)
  
      nw_x = x.type()
      nw_x.name = nw_name
      # Preserve test values so that the `compute_test_value` option can be used.
      # The test value is deep-copied to ensure there can be no interactions
      # between test values, due to inplace operations for instance. This may
      # not be the most efficient memory-wise, though.
      if config.compute_test_value != "off":
          try:
              nw_x.tag.test_value = copy.deepcopy(get_test_value(x))
          except TestValueError:
              pass
  
      return nw_x
  
imports: import copy
  import dataclasses
  import logging
  from collections import OrderedDict, namedtuple
  from collections.abc import Sequence
  from itertools import chain
  from typing import TYPE_CHECKING, Callable, Optional, Union
  from typing import cast
  import numpy
  from pytensor import scalar
  from pytensor import tensor
  from pytensor.compile.profiling import ProfileStats
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Constant, Variable, equal_computations, graph_inputs
  from pytensor.graph.op import get_test_value
  from pytensor.graph.replace import clone_replace
  from pytensor.graph.type import HasDataType
  from pytensor.graph.utils import TestValueError
  from pytensor.tensor.basic import AllocEmpty, cast
  from pytensor.tensor.subtensor import set_subtensor
  from pytensor.tensor.variable import TensorConstant
label_desc: Use image node_id 1 for calling a global function with example usage: safe_new(x, tag, dtype) and returns: nw_x, nwx, x, nw_x