code: def construct_pfunc_ins_and_outs(
      params,
      outputs=None,
      mode=None,
      updates=None,
      givens=None,
      no_default_updates=False,
      rebuild_strict=True,
      allow_input_downcast=None,
      fgraph: Optional[FunctionGraph] = None,
  ):
      """Construct inputs and outputs for `pfunc`.
  
      This function works by cloning the graph (except for the
      inputs), and then shipping it off to pytensor.compile.function.function
      (There it will be cloned again, unnecessarily, because it doesn't know
      that we already cloned it.)
  
      First, it clones the replacements named in the `givens` argument,
      and points each ``Var1`` to the clone of ``Var2``.  Then it sets the
      inputs in the clone dictionary.  After these steps, we are
      assuming that the clone dictionary contains all the inputs to
      the computation graph.
  
      Then it clones the outputs and the update expressions.  This
      rebuilds a computation graph from the inputs and the `givens`.
  
      When `fgraph` is non-``None``, nothing is cloned and the given `fgraph` is
      simply prepared for direct use.
  
      """
      if updates is None:
          updates = []
  
      if givens is None:
          givens = []
  
      if not isinstance(params, (list, tuple)):
          raise TypeError(
              "The `params` argument must be a list or a tuple"
          )
  
      if not isinstance(no_default_updates, bool) and not isinstance(
          no_default_updates, list
      ):
          raise TypeError(
              "The `no_default_update` argument must be a boolean or list"
          )
  
      if len(updates) > 0 and not all(
          isinstance(pair, (tuple, list))
          and len(pair) == 2
          and isinstance(pair[0], Variable)
          for pair in iter_over_pairs(updates)
      ):
          raise TypeError(
              "The `updates` parameter must be an ordered mapping or a list of pairs"
          )
  
      # Transform params into pytensor.compile.In objects.
      inputs = [
          _pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
          for p in params
      ]
  
      # Check if some variable is present more than once in inputs
      in_variables = [input.variable for input in inputs]
      for i, v in enumerate(in_variables):
          if v in in_variables[(i + 1) :]:
              dup_v_i = in_variables.index(v, (i + 1))
              raise UnusedInputError(
                  f"Variable {v} is used twice in inputs to pytensor.function, "
                  f"at indices {i} and {dup_v_i}.  This would result in values "
                  "provided for it being ignored. Please do not duplicate "
                  "variables in the inputs list."
              )
  
      # Check that we are not using `givens` to replace input variables, because
      # this typically does nothing, contrary to what one may expect.
      in_var_set = set(in_variables)
      try:
          givens_pairs = list(givens.items())
      except AttributeError:
          givens_pairs = givens
      for x, y in givens_pairs:
          if x in in_var_set:
              raise RuntimeError(
                  f"You are trying to replace variable '{x}' through the "
                  "`givens` parameter, but this variable is an input to your "
                  "function. Replacing inputs is currently forbidden because it "
                  "has no effect. One way to modify an input `x` to a function "
                  "evaluating f(x) is to define a new input `y` and use "
                  "`pytensor.function([y], f(x), givens={x: g(y)})`. Another "
                  "solution consists in using `pytensor.clone_replace`, e.g. like this: "
                  "`pytensor.function([x], "
                  "pytensor.clone_replace(f(x), replace={x: g(x)}))`."
              )
  
      if not fgraph:
          # Extend the outputs with the updates on input variables so they are
          # also cloned
          additional_outputs = [i.update for i in inputs if i.update]
          if outputs is None:
              out_list = []
          else:
              if isinstance(outputs, (list, tuple)):
                  out_list = list(outputs)
              else:
                  out_list = [outputs]
          extended_outputs = out_list + additional_outputs
  
          output_vars = rebuild_collect_shared(
              extended_outputs,
              in_variables,
              replace=givens,
              updates=updates,
              rebuild_strict=rebuild_strict,
              copy_inputs_over=True,
              no_default_updates=no_default_updates,
              clone_inner_graphs=True,
          )
          (
              input_variables,
              cloned_extended_outputs,
              other_stuff,
          ) = output_vars
          clone_d, update_d, update_expr, shared_inputs = other_stuff
  
          # Recover only the clones of the original outputs
          if outputs is None:
              new_outputs = []
          else:
              if isinstance(outputs, (list, tuple)):
                  new_outputs = cloned_extended_outputs[: len(outputs)]
              else:
                  new_outputs = cloned_extended_outputs[0]
  
          new_inputs = []
  
          for i, iv in zip(inputs, input_variables):
              new_i = copy(i)
              new_i.variable = iv
  
              # If needed, replace the input's update by its cloned equivalent
              if i.update:
                  new_i.update = clone_d[i.update]
  
              new_inputs.append(new_i)
  
          for sv in shared_inputs:
              if sv in update_d:
                  si = In(
                      variable=sv,
                      value=sv.container,
                      mutable=True,
                      borrow=True,
                      update=update_d[sv],
                      shared=True,
                  )
              else:
                  si = In(
                      variable=sv,
                      value=sv.container,
                      mutable=False,
                      borrow=True,
                      shared=True,
                  )
              new_inputs.append(si)
  
      else:
          assert len(fgraph.inputs) == len(inputs)
          assert len(fgraph.outputs) == len(outputs)
  
          for fg_inp, inp in zip(fgraph.inputs, inputs):
              if fg_inp != getattr(inp, "variable", inp):
                  raise ValueError(
                      f"`fgraph`'s input does not match the provided input: {fg_inp}, {inp}"
                  )
  
          for fg_out, out in zip(fgraph.outputs, outputs):
              if fg_out != getattr(out, "variable", out):
                  raise ValueError(
                      f"`fgraph`'s output does not match the provided output: {fg_out}, {out}"
                  )
  
          new_inputs = inputs
          new_outputs = outputs
  
      return new_inputs, new_outputs
  
file_path: pytensor/pytensor/compile/function/pfunc.py
imports: from collections.abc import Sequence
  from copy import copy
  from typing import Optional, Union, overload
  from pytensor.compile.function.types import Function, UnusedInputError, orig_function
  from pytensor.compile.io import In, Out
  from pytensor.compile.profiling import ProfileStats
  from pytensor.compile.sharedvalue import SharedVariable, shared
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Constant, Variable, clone_node_and_cache
  from pytensor.graph.fg import FunctionGraph
label_desc: Use image node_id 7 for calling a global function with example usage: construct_pfunc_ins_and_outs(params, outputs, mode, updates, givens, no_default_updates, rebuild_strict, allow_input_downcast, fgraph) and returns: new_inputs,  new_outputs