code: def find_measurable_scans(fgraph, node):
      r"""Find `Scan`\s for which a `logprob` can be computed.
  
      This will convert said `Scan`\s into `MeasurableScan`\s.  It also updates
      random variable and value variable mappings that have been specified for
      parts of a `Scan`\s outputs (e.g. everything except the initial values).
      """
  
      if not hasattr(fgraph, "shape_feature"):
          return None  # pragma: no cover
  
      rv_map_feature: Optional[PreserveRVMappings] = getattr(
          fgraph, "preserve_rv_mappings", None
      )
  
      if rv_map_feature is None:
          return None  # pragma: no cover
  
      if isinstance(node.op, Subtensor):
          node = node.inputs[0].owner
          if not (node and isinstance(node.op, Scan)):
              return None
          if isinstance(node.op, MeasurableScan):
              return None
  
      curr_scanargs = ScanArgs.from_node(node)
  
      # Find the un-output `MeasurableVariable`s created in the inner-graph
      if not any(
          out in rv_map_feature.rv_values for out in node.outputs
      ):
          # TODO: T
          # We need to remap user inputs that have been specified in terms of
          # `Subtensor`s of this `Scan`'s node's outputs.
          #
          # For example, the output that the user got was something like
          # `out[1:]` for `outputs_info = [{"initial": x0, "taps": [-1]}]`, so
          # they likely passed `{out[1:]: x_1T_vv}` to `joint_logprob`.
          # Since `out[1:]` isn't really the output of a `Scan`, but a
          # `Subtensor` of the output `out` of a `Scan`, we need to account for
          # that.
  
          # Get any `Subtensor` outputs that have been applied to outputs of this
          # `Scan` (and get the corresponding indices of the outputs from this
          # `Scan`)
          output_clients: List[Tuple[Variable, int]] = sum(
              [
                  [
                      # This is expected to work for `Subtensor` `Op`s,
                      # because they only ever have one output
                      (cl.default_output(), i)
                      for cl, _ in fgraph.get_clients(out)
                      if isinstance(cl.op, Subtensor)
                  ]
                  for i, out in enumerate(node.outputs)
              ],
              [],
          )
  
          # The second items in these tuples are the value variables mapped to
          # the *user-specified* measurable variables (i.e. the first items) that
          # are `Subtensor`s of the outputs of this `Scan`.  The second items are
          # the index of the corresponding output of this `Scan` node.
          indirect_rv_vars = [
              (out, rv_map_feature.rv_values[out], out_idx)
              for out, out_idx in output_clients
              if out in rv_map_feature.rv_values
          ]
  
          if not indirect_rv_vars:
              return None
  
          # We need this for the `clone` in the loop that follows
          if pytensor.config.compute_test_value != "off":
              compute_test_value(node)
  
          # We're going to replace the user's random variable/value variable mappings
          # with ones that map directly to outputs of this `Scan`.
          for rv_var, val_var, out_idx in indirect_rv_vars:
              # The full/un-`Subtensor`ed `Scan` output that we need to use
              full_out = node.outputs[out_idx]
  
              assert rv_var.owner.inputs[0] == full_out
  
              # A new value variable that spans the full output.
              # We don't want the old graph to appear in the new log-probability
              # graph, so we use the shape feature to (hopefully) get the shape
              # without the entire `Scan` itself.
              full_out_shape = tuple(
                  fgraph.shape_feature.get_shape(full_out, i)
                  for i in range(full_out.ndim)
              )
              new_val_var = pt.empty(
                  full_out_shape, dtype=full_out.dtype
              )
  
              # Set the parts of this new value variable that applied to the
              # user-specified value variable to the user's value variable
              subtensor_indices = indices_from_subtensor(
                  rv_var.owner.inputs[1:], rv_var.owner.op.idx_list
              )
              # E.g. for a single `-1` TAPS, `s_0T[1:] = s_1T` where `s_0T` is
              # `new_val_var` and `s_1T` is the user-specified value variable
              # that only spans times `t=1` to `t=T`.
              new_val_var = pt.set_subtensor(
                  new_val_var[subtensor_indices], val_var
              )
  
              # This is the outer-input that sets `s_0T[i] = taps[i]` where `i`
              # is a TAP index (e.g. a TAP of `-1` maps to index `0` in a vector
              # of the entire series).
              var_info = curr_scanargs.find_among_fields(full_out)
              alt_type = var_info.name[
                  (var_info.name.index("_", 6) + 1) :
              ]
              outer_input_var = getattr(
                  curr_scanargs, f"outer_in_{alt_type}"
              )[var_info.index]
  
              # These outer-inputs are using by `pytensor.scan.utils.expand_empty`, and
              # are expected to consist of only a single `set_subtensor` call.
              # That's why we can simply replace the first argument of the node.
              assert isinstance(
                  outer_input_var.owner.op, inc_subtensor_ops
              )
  
              # We're going to set those values on our `new_val_var` so that it can
              # serve as a complete replacement for the old input `outer_input_var`.
              new_val_var = outer_input_var.owner.clone_with_new_inputs(
                  [new_val_var] + outer_input_var.owner.inputs[1:]
              ).default_output()
  
              # Replace the mapping
              rv_map_feature.update_rv_maps(
                  rv_var, new_val_var, full_out
              )
  
      op = MeasurableScan(
          curr_scanargs.inner_inputs,
          curr_scanargs.inner_outputs,
          curr_scanargs.info,
          mode=node.op.mode,
      )
      new_node = op.make_node(*curr_scanargs.outer_inputs)
  
      return dict(zip(node.outputs, new_node.outputs))
  
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 5 for calling a global function with example usage: find_measurable_scans(fgraph, node) and returns: dict, None, None, None, None, None