code: def push_out_seq_scan(fgraph, node):
      r"""Push out the variables inside the `Scan` that depend only on constants and sequences.
  
      This optimization resembles `push_out_non_seq_scan` but it tries to push--out of
      the inner function--the computation that only relies on sequence and
      non-sequence inputs. The idea behind this optimization is that, when it is
      possible to do so, it is generally more computationally efficient to perform
      a single operation on a large tensor rather then perform that same operation
      many times on many smaller tensors. In many cases, this optimization can
      increase memory usage but, in some specific cases, it can also decrease it.
      """
      if not isinstance(node.op, Scan):
          return False
  
      node_inputs, node_outputs = (
          node.op.inner_inputs,
          node.op.inner_outputs,
      )
  
      local_fgraph_topo = io_toposort(node_inputs, node_outputs)
      local_fgraph_outs_set = set(node_outputs)
      local_fgraph_outs_map = {v: k for k, v in enumerate(node_outputs)}
  
      to_remove_set = set()
      to_replace_set = set()
      to_replace_map = {}
  
      def add_to_replace(y):
          to_replace_set.add(y)
          to_replace_map[y] = add_to_replace.n
          add_to_replace.n += 1
  
      add_to_replace.n = 0
  
      replace_with_in = []
      replace_with_out = []
  
      op = node.op
      # Construct the list of non_sequences to simplify a few things
      inner_non_seqs = op.inner_non_seqs(node_inputs)
      inner_non_seqs_set = set(inner_non_seqs)
      inner_non_seqs_map = {v: k for k, v in enumerate(inner_non_seqs)}
  
      outer_non_seqs = op.outer_non_seqs(node.inputs)
      inner_seqs = op.inner_seqs(node_inputs)
      inner_seqs_set = set(inner_seqs)
      inner_seqs_map = {v: k for k, v in enumerate(inner_seqs)}
  
      outer_seqs = op.outer_seqs(node.inputs)
      assert len(inner_non_seqs) == len(outer_non_seqs)
      assert len(inner_seqs) == len(outer_seqs)
  
      for nd in local_fgraph_topo:
          if (
              nd not in to_remove_set
              and all(
                  (x in inner_non_seqs_set)
                  or (x.owner in to_remove_set)
                  or isinstance(x, Constant)
                  or (x in inner_seqs_set)
                  for x in nd.inputs
              )
              and isinstance(nd.op, Elemwise)
          ):
              outside_ins = []
              depends_on_seqs = False
  
              for x in nd.inputs:
                  if x in inner_non_seqs_set:
                      _idx = inner_non_seqs_map[x]
                      new_input = outer_non_seqs[_idx]
                  elif x in inner_seqs_set:
                      new_input = outer_seqs[inner_seqs_map[x]]
                      depends_on_seqs = True
                  elif x in to_replace_set:
                      new_input = replace_with_out[to_replace_map[x]]
                      depends_on_seqs = True
                  else:
                      assert isinstance(x, Constant)
                      new_input = x
  
                  outside_ins.append(new_input)
  
              if not depends_on_seqs:
                  # Removing this node from the inner graph of scan
                  # should be handled by the PushOutNonSeqScan
                  # optimization. The current optimization only tries
                  # to pull sequence-dependant computation out of
                  # scan.
                  continue
  
              to_remove_set.add(nd)
  
              # Do not call make_node for test_value
              nw_outer_node = nd.op.make_node(*outside_ins)
  
              if config.compute_test_value != "off":
                  compute_test_value(nw_outer_node)
  
              # Step 2. Create variables for replacements
              for idx, y in enumerate(nd.outputs):
                  y_place_holder = safe_new(y, "_replace")
                  add_to_replace(y)
                  replace_with_in.append(y_place_holder)
                  replace_with_out.append(nw_outer_node.outputs[idx])
  
          elif (
              nd not in to_remove_set
              and isinstance(nd.op, DimShuffle)
              and (
                  nd.inputs[0] in inner_seqs_set
                  or nd.inputs[0].owner in to_remove_set
              )
          ):
              to_remove_set.add(nd)
              x = nd.inputs[0]
              if x in inner_seqs_set:
                  outside_ins = outer_seqs[inner_seqs_map[x]]
              elif x in to_replace_set:
                  outside_ins = replace_with_out[to_replace_map[x]]
              new_ord = (0,)
              for old_ord in nd.op.new_order:
                  if old_ord == "x":
                      new_ord += (old_ord,)
                  else:
                      new_ord += (old_ord + 1,)
              new_outer = outside_ins.dimshuffle(new_ord)
              y = nd.outputs[0]
              y_place_holder = safe_new(y, "_replace")
              add_to_replace(y)
              replace_with_in.append(y_place_holder)
              replace_with_out.append(new_outer)
  
              if hasattr(new_outer.tag, "test_value"):
                  new_sh = new_outer.tag.test_value.shape
                  ref_sh = (outside_ins.tag.test_value.shape[0],)
                  ref_sh += nd.outputs[0].tag.test_value.shape
                  assert new_sh == ref_sh
  
      # We need to check all candidate replacements and choose those that
      # make sense for us
      # Step 1. which elements of `to_replace` are used by remaining
      # components of the inner function
      clean_to_replace = []
      clean_replace_with_in = []
      clean_replace_with_out = []
  
      existent_nodes = [
          nd for nd in local_fgraph_topo if nd not in to_remove_set
      ]
      existent_nodes_set = set(existent_nodes)
  
      to_keep_set = set()
      for nd in existent_nodes:
          to_keep_set.update(nd.inputs)
  
      for out, idx in to_replace_map.items():
          if (
              out in to_keep_set
              and out.owner not in existent_nodes_set
              and
              # If types are different, conversion Op will be inserted,
              # and it may trigger an infinite loop.
              out.type.is_super(replace_with_in[idx].type)
          ):
              clean_to_replace.append(out)
              clean_replace_with_in.append(replace_with_in[idx])
              clean_replace_with_out.append(replace_with_out[idx])
  
      if len(clean_to_replace) > 0:
          # We can finally put an end to all this madness
          givens = {}
          nw_outer = []
          nw_inner = []
          for to_repl, repl_in, repl_out in zip(
              clean_to_replace,
              clean_replace_with_in,
              clean_replace_with_out,
          ):
              if isinstance(repl_out, Constant):
                  repl_in = repl_out
              else:
                  nw_inner.append(repl_in)
                  nw_outer.append(repl_out)
  
              givens[to_repl] = repl_in
  
          op_outs = clone_replace(node_outputs, replace=givens)
          op_ins = nw_inner + node_inputs
  
          # Reconstruct node
          nw_info = dataclasses.replace(
              op.info, n_seqs=op.info.n_seqs + len(nw_inner)
          )
          nwScan = Scan(
              op_ins,
              op_outs,
              nw_info,
              mode=op.mode,
              profile=op.profile,
              truncate_gradient=op.truncate_gradient,
              # TODO: This seems questionable
              name=op.name,
              allow_gc=op.allow_gc,
          )
          # Do not call make_node for test_value
          nw_node = nwScan(
              *(node.inputs[:1] + nw_outer + node.inputs[1:]),
              return_list=True,
          )[0].owner
  
          replacements = dict(zip(node.outputs, nw_node.outputs))
          replacements["remove"] = [node]
          return replacements
  
      elif (
          not to_keep_set
          and not op.info.as_while
          and not op.outer_mitmot(node.inputs)
      ):
          # Nothing in the inner graph should be kept
          replace_with = {}
          for out, idx in to_replace_map.items():
              if out in local_fgraph_outs_set:
                  x = node.outputs[local_fgraph_outs_map[out]]
                  _y = replace_with_out[idx]
                  ls = node_outputs
                  if out in op.inner_mitsot_outs(ls):
                      odx = op.inner_mitsot_outs(ls).index(out)
                      inp = op.outer_mitsot(node.inputs)[odx]
                      st = abs(np.min(op.info.mit_sot_in_slices))
                      y = set_subtensor(inp[st:], _y)
                  elif out in op.inner_sitsot_outs(ls):
                      odx = op.inner_sitsot_outs(ls).index(out)
                      inp = op.outer_sitsot(node.inputs)[odx]
                      y = set_subtensor(inp[1:], _y)
                  elif out in op.inner_nitsot_outs(ls):
                      y = _y
                  else:
                      y = _y[-1]
                  replace_with[x] = y
  
          # We need to add one extra dimension to the outputs
          if replace_with and len(replace_with) == len(node.outputs):
              replacements = dict(replace_with.items())
              replacements["remove"] = [node]
              return replacements
      else:
          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 3 for calling a global function with example usage: push_out_seq_scan(fgraph, node) and returns: False, replacements, False, replacements