code: def push_out_dot1_scan(fgraph, node):
      r"""
      This is another optimization that attempts to detect certain patterns of
      computation in a `Scan` `Op`'s inner function and move this computation to the
      outer graph.
      """
      if not isinstance(node.op, Scan):
          return False
  
      # Replace pattern of the form
      # x[t] = x[t-1] + dot(seq[t], value)
      # with Sequence.reshape((-1, seq.shape[2])) \dot Value
      # When seq[t] is a vector/matrix  and `value` is a matrix
      # Note that this works when only you need X[-1] in the end
      # and assumes dimshuffle are applied to vectors before calling dot
      op = node.op
      sitsot_ins = op.inner_sitsot(op.inner_inputs)
      sitsot_outs = op.inner_sitsot_outs(op.inner_outputs)
      outer_sitsot = op.outer_sitsot_outs(node.outputs)
      seqs = op.inner_seqs(op.inner_inputs)
      for inp, out, outer_out in zip(
          sitsot_ins, sitsot_outs, outer_sitsot
      ):
          if (
              out.owner
              and isinstance(out.owner.op, Elemwise)
              and isinstance(out.owner.op.scalar_op, ps.Add)
              and inp in out.owner.inputs
              and len(fgraph.clients[outer_out]) == 1
              and not isinstance(fgraph.clients[outer_out][0][0], str)
              and isinstance(
                  fgraph.clients[outer_out][0][0].op, Subtensor
              )
              and fgraph.clients[outer_out][0][0].op.idx_list == (-1,)
          ):
              x = out.owner.inputs[0]
              if x == inp:
                  x = out.owner.inputs[1]
              # We need to check if x is the result of an outer product
              if (
                  x.owner
                  and isinstance(x.owner.op, Dot)
                  and x.owner.inputs[0].ndim == 2
                  and x.owner.inputs[1].ndim == 2
              ):
                  # We need to check if any of the inputs are a sequence
                  inp1 = x.owner.inputs[0]
                  inp2 = x.owner.inputs[1]
  
                  if inp1 in seqs or inp2 in seqs:
                      new_scan_out = inp1
  
                      if inp1 in seqs:
                          new_scan_out = inp2
                      idx = sitsot_outs.index(out)
                      # We've found our pattern and need to construct a new
                      # scan node to replace this one. For this we need to
                      # replace the sit_sot output with a nit_sot output
  
                      # First let us split all arguments according to their
                      # corresponding categories
  
                      inner_seqs = op.inner_seqs(op.inner_inputs)
                      outer_seqs = op.outer_seqs(node.inputs)
                      inner_mitmot = op.inner_mitmot(op.inner_inputs)
                      outer_mitmot = op.outer_mitmot(node.inputs)
                      inner_mitmot_outs = op.inner_mitmot_outs(
                          op.inner_outputs
                      )
                      inner_mitsot = op.inner_mitsot(op.inner_inputs)
                      outer_mitsot = op.outer_mitsot(node.inputs)
                      inner_mitsot_outs = op.inner_mitsot_outs(
                          op.inner_outputs
                      )
                      inner_sitsot = op.inner_sitsot(op.inner_inputs)
                      outer_sitsot = op.outer_sitsot(node.inputs)
                      inner_sitsot_outs = op.inner_sitsot_outs(
                          op.inner_outputs
                      )
                      outer_nitsot = op.outer_nitsot(node.inputs)
                      inner_nitsot_outs = op.inner_nitsot_outs(
                          op.inner_outputs
                      )
                      inner_shared = op.inner_shared(op.inner_inputs)
                      outer_shared = op.outer_shared(node.inputs)
                      inner_shared_outs = op.inner_shared_outs(
                          op.inner_outputs
                      )
                      inner_non_seqs = op.inner_non_seqs(
                          op.inner_inputs
                      )
                      outer_non_seqs = op.outer_non_seqs(node.inputs)
  
                      new_info = dataclasses.replace(
                          op.info,
                          sit_sot_in_slices=op.info.sit_sot_in_slices[
                              :idx
                          ]
                          + op.info.sit_sot_in_slices[idx + 1 :],
                          n_nit_sot=op.info.n_nit_sot + 1,
                      )
                      inner_sitsot = (
                          inner_sitsot[:idx] + inner_sitsot[idx + 1 :]
                      )
                      outer_sitsot = (
                          outer_sitsot[:idx] + outer_sitsot[idx + 1 :]
                      )
                      inner_sitsot_outs = (
                          inner_sitsot_outs[:idx]
                          + inner_sitsot_outs[idx + 1 :]
                      )
                      # add n_steps as the length
                      inner_nitsot_outs.append(new_scan_out)
  
                      _new_inner_inps = (
                          inner_seqs
                          + inner_mitmot
                          + inner_mitsot
                          + inner_sitsot
                          + inner_shared
                          + inner_non_seqs
                      )
                      _new_inner_outs = (
                          inner_mitmot_outs
                          + inner_mitsot_outs
                          + inner_sitsot_outs
                          + inner_nitsot_outs
                          + inner_shared_outs
                      )
                      (
                          new_inner_inps,
                          new_inner_outs,
                      ) = reconstruct_graph(
                          _new_inner_inps, _new_inner_outs
                      )
                      new_op = Scan(
                          new_inner_inps,
                          new_inner_outs,
                          new_info,
                          mode=op.mode,
                          profile=op.profile,
                          truncate_gradient=op.truncate_gradient,
                          # TODO: This seems questionable
                          name=op.name,
                          allow_gc=op.allow_gc,
                      )
                      _scan_inputs = (
                          [node.inputs[0]]
                          + outer_seqs
                          + outer_mitmot
                          + outer_mitsot
                          + outer_sitsot
                          + outer_shared
                          + outer_nitsot
                          + [node.inputs[0]]
                          + outer_non_seqs
                      )
  
                      new_outs = new_op(*_scan_inputs)
                      if not isinstance(new_outs, (list, tuple)):
                          new_outs = [new_outs]
  
                      # We need now to pair correctly the new outputs
                      # with the old ones
  
                      outer_nitsot_outs = new_op.outer_nitsot_outs(
                          new_outs
                      )
  
                      _val = outer_nitsot_outs[-1]
                      outer_nitsot_outs = outer_nitsot_outs[:-1]
                      if inp1 in seqs:
                          _out_seq = op.outer_seqs(node.inputs)[
                              seqs.index(inp1)
                          ]
                          # We need to clip the seq to the number of steps
                          _out_seq = _out_seq[: node.inputs[0]]
                          sh0 = _out_seq.shape[0]
                          sh1 = _out_seq.shape[1]
                          sh2 = _out_seq.shape[2]
                          out_seq = _out_seq.dimshuffle(1, 0, 2)
                          out_seq = out_seq.reshape((sh1, sh0 * sh2))
                          sh0 = _val.shape[0]
                          sh1 = _val.shape[1]
                          sh2 = _val.shape[2]
  
                          val = _val.reshape((sh0 * sh1, sh2))
                          new_out = dot(out_seq, val)
                      else:
                          _out_seq = op.outer_seqs(node.inputs)[
                              seqs.index(inp2)
                          ]
                          out_seq = _out_seq.reshape(
                              (
                                  _out_seq.shape[0] * _out_seq.shape[1],
                                  _out_seq.shape[2],
                              )
                          )
  
                          val = _val.dimshuffle(1, 0, 2).reshape(
                              (
                                  _val.shape[1],
                                  _val.shape[0] * _val.shape[2],
                              )
                          )
                          new_out = dot(val, out_seq)
  
                      pos = node.outputs.index(outer_out)
                      old_new = list(
                          zip(node.outputs[:pos], new_outs[:pos])
                      )
                      old = fgraph.clients[node.outputs[pos]][0][
                          0
                      ].outputs[0]
                      old_new.append((old, new_out))
                      old_new += list(
                          zip(node.outputs[pos + 1 :], new_outs[pos:])
                      )
                      replacements = dict(old_new)
                      replacements["remove"] = [node]
                      return replacements
  
      return False
  
imports: import copy
  import dataclasses
  from itertools import chain
  from sys import maxsize
  from typing import Optional, cast
  import numpy
  import pytensor
  from pytensor import scalar
  from pytensor import tensor
  from pytensor.compile import optdb
  from pytensor.compile.function.types import deep_copy_op
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Apply, Constant, NominalVariable, Variable, ancestors, apply_depends_on, equal_computations, graph_inputs, io_toposort
  from pytensor.graph.destroyhandler import DestroyHandler
  from pytensor.graph.features import ReplaceValidate
  from pytensor.graph.fg import FunctionGraph
  from pytensor.graph.op import compute_test_value
  from pytensor.graph.replace import clone_replace
  from pytensor.graph.rewriting.basic import GraphRewriter, copy_stack_trace, in2out, node_rewriter
  from pytensor.graph.rewriting.db import EquilibriumDB, SequenceDB
  from pytensor.graph.rewriting.utils import get_clients_at_depth
  from pytensor.graph.type import HasShape
  from pytensor.graph.utils import InconsistencyError
  from pytensor.raise_op import Assert
  from pytensor.scalar import ScalarConstant
  from pytensor.scan.op import Scan, ScanInfo
  from pytensor.scan.utils import ScanArgs, compress_outs, expand_empty, reconstruct_graph, safe_new, scan_can_remove_outs
  from pytensor.tensor.basic import Alloc, AllocEmpty, get_underlying_scalar_constant_value
  from pytensor.tensor.elemwise import DimShuffle, Elemwise
  from pytensor.tensor.exceptions import NotScalarConstantError
  from pytensor.tensor.math import Dot, dot, maximum, minimum
  from pytensor.tensor.rewriting.basic import constant_folding, local_useless_switch
  from pytensor.tensor.rewriting.elemwise import local_upcast_elemwise_constant_inputs
  from pytensor.tensor.rewriting.math import local_abs_merge, local_mul_switch_sink
  from pytensor.tensor.shape import shape
  from pytensor.tensor.subtensor import IncSubtensor, Subtensor, get_canonical_form_slice, get_idx_list, get_slice_elements, set_subtensor
  from pytensor.tensor.variable import TensorConstant, get_unique_constant_value
label_desc: Use image node_id 17 for calling a global function with example usage: push_out_dot1_scan(fgraph, node) and returns: False, False, replacements