code: def convert_outer_out_to_in(
      input_scan_args: ScanArgs,
      outer_out_vars: Iterable[TensorVariable],
      new_outer_input_vars: Dict[TensorVariable, TensorVariable],
      inner_out_fn: Callable[
          [Dict[TensorVariable, TensorVariable]],
          Iterable[TensorVariable],
      ],
  ) -> ScanArgs:
      r"""Convert outer-graph outputs into outer-graph inputs.
  
      Parameters
      ----------
      input_scan_args:
          The source `Scan` arguments.
      outer_out_vars:
          The outer-graph output variables that are to be converted into an
          outer-graph input.
      new_outer_input_vars:
          The variables used for the new outer-graph input computed for
          `outer_out_vars`.
      inner_out_fn:
          A function that takes the remapped outer-out variables and produces new
          inner-graph outputs.  This can be used to transform the
          `outer_out_vars`\s' corresponding inner-graph outputs into something
          else entirely, like log-probabilities.
  
      Outputs
      =======
      A `ScanArgs` object for a `Scan` in which `outer_out_vars` has been converted to an
      outer-graph input.
      """
  
      output_scan_args = copy(input_scan_args)
      inner_outs_to_new_inner_ins = {}
  
      # Map inner-outputs to outer-outputs
      old_inner_outs_to_outer_outs = {}
  
      for oo_var in outer_out_vars:
          var_info = output_scan_args.find_among_fields(
              oo_var, field_filter=lambda x: x.startswith("outer_out")
          )
  
          assert var_info is not None
          assert oo_var in new_outer_input_vars
  
          io_var = output_scan_args.get_alt_field(var_info, "inner_out")
          old_inner_outs_to_outer_outs[io_var] = oo_var
  
      # In this loop, we gather information about the new inner-inputs that have
      # been created and what their corresponding inner-outputs were, and we
      # update the outer and inner-inputs to reflect the addition of new
      # inner-inputs.
      for (
          old_inner_out_var,
          oo_var,
      ) in old_inner_outs_to_outer_outs.items():
          # Couldn't one do the same with `var_info`?
          inner_out_info = output_scan_args.find_among_fields(
              old_inner_out_var,
              field_filter=lambda x: x.startswith("inner_out"),
          )
  
          output_scan_args.remove_from_fields(
              old_inner_out_var, rm_dependents=False
          )
  
          # Remove the old outer-output variable.
          # Not sure if this really matters, since we don't use the outer-outputs
          # when building a new `Scan`, but doing it keeps the `ScanArgs` object
          # consistent.
          output_scan_args.remove_from_fields(
              oo_var, rm_dependents=False
          )
  
          # Use the index for the specific inner-graph sub-collection to which this
          # variable belongs (e.g. index `1` among the inner-graph sit-sot terms)
          var_idx = inner_out_info.index
  
          # The old inner-output variable becomes the a new inner-input
          new_inner_in_var = old_inner_out_var.clone()
          if new_inner_in_var.name:
              new_inner_in_var.name = f"{new_inner_in_var.name}_vv"
  
          inner_outs_to_new_inner_ins[
              old_inner_out_var
          ] = new_inner_in_var
  
          # We want to remove elements from both lists and tuples, because the
          # members of `ScanArgs` could switch from being `list`s to `tuple`s
          # soon
          def remove(x, i):
              return x[:i] + x[i + 1 :]
  
          # If we're replacing a [m|s]it-sot, then we need to add a new nit-sot
          add_nit_sot = False
          if inner_out_info.name.endswith("mit_sot"):
              inner_in_mit_sot_var = cast(
                  Tuple[int, ...],
                  tuple(output_scan_args.inner_in_mit_sot[var_idx]),
              )
              new_inner_in_seqs = inner_in_mit_sot_var + (
                  new_inner_in_var,
              )
              new_inner_in_mit_sot = remove(
                  output_scan_args.inner_in_mit_sot, var_idx
              )
              new_outer_in_mit_sot = remove(
                  output_scan_args.outer_in_mit_sot, var_idx
              )
              new_inner_in_sit_sot = tuple(
                  output_scan_args.inner_in_sit_sot
              )
              new_outer_in_sit_sot = tuple(
                  output_scan_args.outer_in_sit_sot
              )
              add_nit_sot = True
          elif inner_out_info.name.endswith("sit_sot"):
              new_inner_in_seqs = (
                  output_scan_args.inner_in_sit_sot[var_idx],
              ) + (new_inner_in_var,)
              new_inner_in_sit_sot = remove(
                  output_scan_args.inner_in_sit_sot, var_idx
              )
              new_outer_in_sit_sot = remove(
                  output_scan_args.outer_in_sit_sot, var_idx
              )
              new_inner_in_mit_sot = tuple(
                  output_scan_args.inner_in_mit_sot
              )
              new_outer_in_mit_sot = tuple(
                  output_scan_args.outer_in_mit_sot
              )
              add_nit_sot = True
          else:
              new_inner_in_seqs = (new_inner_in_var,)
              new_inner_in_mit_sot = tuple(
                  output_scan_args.inner_in_mit_sot
              )
              new_outer_in_mit_sot = tuple(
                  output_scan_args.outer_in_mit_sot
              )
              new_inner_in_sit_sot = tuple(
                  output_scan_args.inner_in_sit_sot
              )
              new_outer_in_sit_sot = tuple(
                  output_scan_args.outer_in_sit_sot
              )
  
          output_scan_args.inner_in_mit_sot = list(new_inner_in_mit_sot)
          output_scan_args.inner_in_sit_sot = list(new_inner_in_sit_sot)
          output_scan_args.outer_in_mit_sot = list(new_outer_in_mit_sot)
          output_scan_args.outer_in_sit_sot = list(new_outer_in_sit_sot)
  
          if inner_out_info.name.endswith("mit_sot"):
              mit_sot_var_taps = cast(
                  Tuple[int, ...],
                  tuple(output_scan_args.mit_sot_in_slices[var_idx]),
              )
              taps = mit_sot_var_taps + (0,)
              new_mit_sot_in_slices = remove(
                  output_scan_args.mit_sot_in_slices, var_idx
              )
          elif inner_out_info.name.endswith("sit_sot"):
              taps = (-1, 0)
              new_mit_sot_in_slices = tuple(
                  output_scan_args.mit_sot_in_slices
              )
          else:
              taps = (0,)
              new_mit_sot_in_slices = tuple(
                  output_scan_args.mit_sot_in_slices
              )
  
          output_scan_args.mit_sot_in_slices = list(
              new_mit_sot_in_slices
          )
  
          taps, new_inner_in_seqs = zip(
              *sorted(zip(taps, new_inner_in_seqs), key=lambda x: x[0])
          )
  
          new_inner_in_seqs = tuple(
              output_scan_args.inner_in_seqs
          ) + tuple(reversed(new_inner_in_seqs))
  
          output_scan_args.inner_in_seqs = list(new_inner_in_seqs)
  
          slice_seqs = zip(
              -np.asarray(taps),
              [n if n < 0 else None for n in reversed(taps)],
          )
  
          # XXX: If the caller passes the variables output by `pytensor.scan`, it's
          # likely that this will fail, because those variables can sometimes be
          # slices of the actual outer-inputs (e.g. `out[1:]` instead of `out`
          # when `taps=[-1]`).
          var_slices = [
              new_outer_input_vars[oo_var][b:e] for b, e in slice_seqs
          ]
          n_steps = pt.min([pt.shape(n)[0] for n in var_slices])
  
          output_scan_args.n_steps = n_steps
  
          new_outer_in_seqs = tuple(
              output_scan_args.outer_in_seqs
          ) + tuple(v[:n_steps] for v in var_slices)
  
          output_scan_args.outer_in_seqs = list(new_outer_in_seqs)
  
          if add_nit_sot:
              new_outer_in_nit_sot = tuple(
                  output_scan_args.outer_in_nit_sot
              ) + (n_steps,)
          else:
              new_outer_in_nit_sot = tuple(
                  output_scan_args.outer_in_nit_sot
              )
  
          output_scan_args.outer_in_nit_sot = list(new_outer_in_nit_sot)
  
      # Now, we can add new inner-outputs for the custom calculations.
      # We don't need to create corresponding outer-outputs, because `Scan` will
      # do that when we call `Scan.make_node`.  All we need is a consistent
      # outer-inputs and inner-graph spec., which we should have in
      # `output_scan_args`.
      remapped_io_to_ii = inner_outs_to_new_inner_ins
      new_inner_out_nit_sot = tuple(
          output_scan_args.inner_out_nit_sot
      ) + tuple(inner_out_fn(remapped_io_to_ii))
      output_scan_args.inner_out_nit_sot = list(new_inner_out_nit_sot)
  
      # Finally, we need to replace any lingering references to the new
      # internal variables that could be in the recurrent states needed
      # to compute the new nit_sots
      traced_outs = (
          output_scan_args.inner_out_mit_sot
          + output_scan_args.inner_out_sit_sot
          + output_scan_args.inner_out_nit_sot
      )
      traced_outs = replace_rvs_by_values(
          traced_outs, rvs_to_values=remapped_io_to_ii
      )
      # Update output mappings
      n_mit_sot = len(output_scan_args.inner_out_mit_sot)
      output_scan_args.inner_out_mit_sot = traced_outs[:n_mit_sot]
      offset = n_mit_sot
      n_sit_sot = len(output_scan_args.inner_out_sit_sot)
      output_scan_args.inner_out_sit_sot = traced_outs[
          offset : offset + n_sit_sot
      ]
      offset += n_sit_sot
      n_nit_sot = len(output_scan_args.inner_out_nit_sot)
      output_scan_args.inner_out_nit_sot = traced_outs[
          offset : offset + n_nit_sot
      ]
  
      return output_scan_args
  
imports: from copy import copy
  from typing import Callable, Dict, Iterable, List, Optional, Tuple, cast
  import numpy
  import pytensor
  import pytensor.tensor
  from pytensor.graph.basic import Variable
  from pytensor.graph.op import compute_test_value
  from pytensor.graph.rewriting.basic import node_rewriter
  from pytensor.graph.rewriting.db import RewriteDatabaseQuery
  from pytensor.scan.op import Scan
  from pytensor.scan.rewriting import scan_eqopt1, scan_eqopt2
  from pytensor.scan.utils import ScanArgs
  from pytensor.tensor.random.type import RandomType
  from pytensor.tensor.subtensor import Subtensor, indices_from_subtensor
  from pytensor.tensor.variable import TensorVariable
  from pytensor.updates import OrderedUpdates
  from pymc.logprob.abstract import MeasurableVariable, _logprob
  from pymc.logprob.basic import conditional_logp
  from pymc.logprob.rewriting import PreserveRVMappings, construct_ir_fgraph, inc_subtensor_ops, logprob_rewrites_db, measurable_ir_rewrites_db
  from pymc.logprob.utils import replace_rvs_by_values
label_desc: Use image node_id 1 for calling a global function with example usage: convert_outer_out_to_in(input_scan_args, outer_out_vars, new_outer_input_vars, inner_out_fn) and returns: output_scan_args, unknown