class_docstr: A `FunctionGraph` represents a subgraph bound by a set of input variables and
  a set of output variables, ie a subgraph that specifies an PyTensor function.
  The inputs list should contain all the inputs on which the outputs depend.
  `Variable`\s of type `Constant` are not counted as inputs.
  
  The `FunctionGraph` supports the replace operation which allows to replace
  a variable in the subgraph by another, e.g. replace ``(x + x).out`` by
  ``(2 * x).out``. This is the basis for optimization in PyTensor.
  
  This class is also responsible for verifying that a graph is valid
  (ie, all the dtypes and broadcast patterns are compatible with the
  way the `Variable`\s are used) and for tracking the `Variable`\s with
  a :attr:`FunctionGraph.clients` ``dict`` that specifies which `Apply` nodes
  use the `Variable`.  The :attr:`FunctionGraph.clients` field, combined with
  the :attr:`Variable.owner` and each :attr:`Apply.inputs`, allows the graph
  to be traversed in both directions.
  
  It can also be extended with new features using
  :meth:`FunctionGraph.attach_feature`.  See `Feature` for event types and
  documentation.  Extra features allow the `FunctionGraph` to verify new
  properties of a graph as it is optimized.
  
  The constructor creates a `FunctionGraph` which operates on the subgraph
  bound by the inputs and outputs sets.
  
  This class keeps lists for the inputs and outputs and modifies them
  in-place.
docstr: Change ``node.inputs[i]`` to `new_var`.
  
  ``new_var.type.is_super(old_var.type)`` must be ``True``, where
  ``old_var`` is the current value of ``node.inputs[i]`` which we want to
  replace.
  
  For each feature that has an `on_change_input` method, this method calls:
  ``feature.on_change_input(function_graph, node, i, old_var, new_var, reason)``
  
  Parameters
  ----------
  node
      The node for which an input is to be changed.  If the value is
      the string ``"output"`` then the ``self.outputs`` will be used
      instead of ``node.inputs``.
  i
      The index in `node.inputs` that we want to change.
  new_var
      The new variable to take the place of ``node.inputs[i]``.
  import_missing
      Add missing inputs instead of raising an exception.
  check
      When ``True``, perform a type check between the variable being
      replaced and its replacement.  This is primarily used by the
      `History` `Feature`, which needs to revert types that have been
      narrowed and would otherwise fail this check.
docstr_tok: ["Change","``","node.inputs","[","i","]","``","to","`","new_var","`",".","``","new_var.type.is_super","(","old_var.type",")","``","must","be","``","True","``",",","where","``","old_var","``","is","the","current","value","of","``","node.inputs","[","i","]","``","which","we","want","to","replace",".","For","each","feature","that","has","an","`","on_change_input","`","method",",","this","method","calls",":","``","feature.on_change_input","(","function_graph",",","node",",","i",",","old_var",",","new_var",",","reason",")","``","Parameters","--","--","--","--","--","node","The","node","for","which","an","input","is","to","be","changed",".","If","the","value","is","the","string","``","``","output","''","``","then","the","``","self.outputs","``","will","be","used","instead","of","``","node.inputs","``",".","i","The","index","in","`","node.inputs","`","that","we","want","to","change",".","new_var","The","new","variable","to","take","the","place","of","``","node.inputs","[","i","]","``",".","import_missing","Add","missing","inputs","instead","of","raising","an","exception",".","check","When","``","True","``",",","perform","a","type","check","between","the","variable","being","replaced","and","its","replacement",".","This","is","primarily","used","by","the","`","History","`","`","Feature","`",",","which","needs","to","revert","types","that","have","been","narrowed","and","would","otherwise","fail","this","check","."]
code: def change_node_input(
      self,
      node: ApplyOrOutput,
      i: int,
      new_var: Variable,
      reason: Optional[str] = None,
      import_missing: bool = False,
      check: bool = True,
  ) -> None:
      """Change ``node.inputs[i]`` to `new_var`.
  
      ``new_var.type.is_super(old_var.type)`` must be ``True``, where
      ``old_var`` is the current value of ``node.inputs[i]`` which we want to
      replace.
  
      For each feature that has an `on_change_input` method, this method calls:
      ``feature.on_change_input(function_graph, node, i, old_var, new_var, reason)``
  
      Parameters
      ----------
      node
          The node for which an input is to be changed.  If the value is
          the string ``"output"`` then the ``self.outputs`` will be used
          instead of ``node.inputs``.
      i
          The index in `node.inputs` that we want to change.
      new_var
          The new variable to take the place of ``node.inputs[i]``.
      import_missing
          Add missing inputs instead of raising an exception.
      check
          When ``True``, perform a type check between the variable being
          replaced and its replacement.  This is primarily used by the
          `History` `Feature`, which needs to revert types that have been
          narrowed and would otherwise fail this check.
      """
      # TODO: ERROR HANDLING FOR LISTENERS (should it complete the change or revert it?)
      if node == "output":
          r = self.outputs[i]
          if check and not r.type.is_super(new_var.type):
              raise TypeError(
                  f"The type of the replacement ({new_var.type}) must be "
                  f"compatible with the type of the original Variable ({r.type})."
              )
          self.outputs[i] = new_var
      else:
          assert isinstance(node, Apply)
          r = node.inputs[i]
          if check and not r.type.is_super(new_var.type):
              raise TypeError(
                  f"The type of the replacement ({new_var.type}) must be "
                  f"compatible with the type of the original Variable ({r.type})."
              )
          node.inputs[i] = new_var
  
      if r is new_var:
          return
  
      self.import_var(
          new_var, reason=reason, import_missing=import_missing
      )
      self.add_client(new_var, (node, i))
      self.remove_client(r, (node, i), reason=reason)
      # Precondition: the substitution is semantically valid However it may
      # introduce cycles to the graph, in which case the transaction will be
      # reverted later.
      self.execute_callbacks(
          "on_change_input", node, i, r, new_var, reason=reason
      )
  
imports: import time
  from collections import OrderedDict
  from collections.abc import Iterable, Sequence
  from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast
  import pytensor
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Apply, AtomicVariable, Variable, applys_between, clone_get_equiv, graph_inputs, io_toposort, vars_between
  from pytensor.graph.basic import as_string
  from pytensor.graph.features import AlreadyThere, Feature, ReplaceValidate
  from pytensor.graph.utils import MetaObject, MissingInputError, TestValueError
  from pytensor.misc.ordered_set import OrderedSet
label_desc: Use image node_id 10 for calling the FunctionGraph obj's underlying member method code with example usage: obj.change_node_input(node, i, new_var, reason, import_missing, check) without return types