class_docstr: Make `Scan`s perform in-place.
  
  This optimization attempts to make `Scan` compute its recurrent outputs inplace
  on the input tensors that contain their initial states. This optimization can
  improve runtime performance as well as reduce memory usage.
class_docstr_tok: ["Make","`","Scan","`","s","perform","in-place",".","This","optimization","attempts","to","make","`","Scan","`","compute","its","recurrent","outputs","inplace","on","the","input","tensors","that","contain","their","initial","states",".","This","optimization","can","improve","runtime","performance","as","well","as","reduce","memory","usage","."]
code: def apply(self, fgraph):
      for scan_idx, original_node in enumerate(
          reversed(fgraph.toposort())
      ):
          if not isinstance(original_node.op, Scan):
              continue
  
          # First attempt to make the Scan compute inplace every recurrent
          # output that seems like it could be computed inplace. If that
          # fails, go through these outputs individually, trying each of
          # them.
          op = original_node.op
          n_outs = (
              op.info.n_mit_mot + op.info.n_mit_sot + op.info.n_sit_sot
          )
  
          # Generate a list of outputs on which the node could potentially
          # operate inplace.
          out_indices = []
          for out_idx in range(n_outs):
              inp_idx = 1 + op.info.n_seqs + out_idx
              inp = original_node.inputs[inp_idx]
  
              # If the input is from an eligible allocation node, attempt to
              # be inplace on it, even if other nodes are modifying it
              # inplace.
              if inp.owner and isinstance(inp.owner.op, self.alloc_ops):
                  out_indices.append(out_idx)
                  continue
  
              # If the input is not from an eligible allocation node, only
              # attempt to be inplace on it if nothing else is currently
              # inplace on it.
              input_used_inplace = False
              for c in fgraph.clients[original_node.inputs[inp_idx]]:
                  client = c[0]
  
                  # Get the indices of this client's inputs on which it
                  # operates inplace
                  if client.op.destroy_map:
                      # This flattens the content of destroy_map.values()
                      # which is a list of lists
                      inplace_inp_indices = sum(
                          client.op.destroy_map.values(), []
                      )
  
                      inplace_inps = [
                          client.inputs[i] for i in inplace_inp_indices
                      ]
                      if original_node.inputs[inp_idx] in inplace_inps:
                          input_used_inplace = True
                          break
  
              if not input_used_inplace:
                  out_indices.append(out_idx)
  
          if len(out_indices) == 0:
              continue
  
          new_node = self.attempt_scan_inplace(
              fgraph, original_node, out_indices
          )
  
          if new_node is None:
              # Making the scan compute all plausible recurrent outputs
              # inplace has failed. Attempt all plausible recurrent outputs
              # individually.
  
              new_node = original_node
              for pos in out_indices:
                  new_node = (
                      self.attempt_scan_inplace(fgraph, new_node, [pos])
                      or new_node
                  )
  
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 the ScanInplaceOptimizer obj's underlying member method code with example usage: obj.apply(fgraph) without return types