code: def local_dot22_to_dot22scalar(fgraph, node):
      """
      Notes
      -----
      Previous attempts to alter this optimization to replace dot22 with
      gemm instead of dot22scalar resulted in some Scan nodes being
      duplicated and the ScanSaveMem optimization never running on them,
      resulting in highly increased memory usage. Until this issue is
      resolved, this optimization should keep using dot22scalar instead of
      gemm.
  
      We upcast the scalar if after the multiplication with the dot this give
      the same type.
  
      We execute this optimizer after the gemm optimizer. This
      allow to give more priority to gemm that give more speed up
      then this optimizer, but allow the gemm optimizer to ignore
      this op.
  
      TODO: support when we can reorder the mul to generate a
      dot22scalar or fix the canonizer to merge them(1 mul with multiple
      inputs)
  
      """
      if node.op != mul:
          return False
      i_dot22 = [x.owner and x.owner.op == _dot22 for x in node.inputs]
      if not any(i_dot22):
          return False  # no dot22
      if i_dot22.count(True) > 1:
          # TODO: try each of them.
          pass
          # return False #TODO fix
      dot22_idx = i_dot22.index(True)
      d = node.inputs[dot22_idx]
      i_scalar = [_as_scalar(x, dtype=d.dtype) for x in node.inputs]
      if not any(i_scalar):
          # Check if we can reorder the graph as this mul have a mul in inputs.
          # We support only 1 additional level of mul.
          # The canonizer should have merged those mul together.
          i_mul = [
              x.owner
              and x.owner.op == mul
              and any(
                  _as_scalar(x_i, dtype=d.dtype)
                  for x_i in x.owner.inputs
              )
              for x in node.inputs
          ]
          if not any(i_mul):
              # no scalar in input and no multiplication
              # if their was a multiplication we couls reorder the graph
              # by the associativity of the graph.
              return False
  
          mul_idx = i_mul.index(
              True
          )  # The first one should always work
          m = node.inputs[mul_idx]
  
          scalar_idx = -1
          for i, x in enumerate(m.owner.inputs):
              if _as_scalar(x, dtype=d.dtype) and (
                  pytensor.scalar.upcast(x.type.dtype, d.type.dtype)
                  == d.type.dtype
              ):
                  scalar_idx = i
                  break
  
          if scalar_idx < 0:
              _logger.info(
                  f"Not optimizing dot22 with inputs {node.inputs} {[x.type for x in node.inputs]}, as the"
                  " type of the scalar cannot be upcasted to the"
                  " matrix type"
              )
              return False
          a = ptb.cast(
              _as_scalar(m.owner.inputs[scalar_idx], dtype=d.dtype),
              d.type.dtype,
          )
          assert not a.type.ndim
          dot = _dot22scalar(d.owner.inputs[0], d.owner.inputs[1], a)
  
          # The other inputs to the original node that were
          # neither part of the dot22 or this mul should be
          # factors in the returned "mul" node.
          assert dot22_idx != mul_idx
          other_factors = [
              inpt
              for i, inpt in enumerate(node.inputs)
              if i not in (dot22_idx, mul_idx)
          ]
          other_m_inputs = [
              inpt
              for i, inpt in enumerate(m.owner.inputs)
              if i != scalar_idx
          ]
  
          return [mul(dot, *(other_factors + other_m_inputs))]
  
      scalar_idx = -1
      for i, x in enumerate(node.inputs):
          if (
              i != dot22_idx
              and i_scalar[i] is not None
              and (
                  pytensor.scalar.upcast(x.type.dtype, d.type.dtype)
                  == d.type.dtype
              )
          ):
              scalar_idx = i
              break
      if scalar_idx < 0:
          _logger.info(
              f"Not optimizing dot22 with inputs {node.inputs} {[x.type for x in node.inputs]}, as the type "
              "of the scalar cannot be upcasted to the matrix type"
          )
          return False
      assert scalar_idx < len(node.inputs)
      s = node.inputs[scalar_idx]
      o = copy.copy(node.inputs)
      o.remove(d)
      o.remove(s)
  
      a = ptb.cast(i_scalar[scalar_idx], d.type.dtype)
      assert not a.type.ndim
      if len(o) == 0:
          return [_dot22scalar(d.owner.inputs[0], d.owner.inputs[1], a)]
      else:
          return [
              mul(
                  _dot22scalar(d.owner.inputs[0], d.owner.inputs[1], a),
                  *o,
              )
          ]
  
imports: import copy
  import logging
  import time
  import numpy
  from pytensor.tensor.rewriting.basic import register_specialize
  import pytensor.scalar
  from pytensor.compile.mode import optdb
  from pytensor.configdefaults import config
  from pytensor.graph.features import ReplacementDidNotRemoveError, ReplaceValidate
  from pytensor.graph.rewriting.basic import EquilibriumGraphRewriter, GraphRewriter, copy_stack_trace, in2out, node_rewriter
  from pytensor.graph.rewriting.db import SequenceDB
  from pytensor.graph.utils import InconsistencyError
  from pytensor.tensor import basic
  from pytensor.tensor.blas import Dot22, _dot22, _dot22scalar, batched_dot, gemm_inplace, gemm_no_inplace, gemv_inplace, gemv_no_inplace, ger, ger_destructive
  from pytensor.tensor.elemwise import DimShuffle, Elemwise
  from pytensor.tensor.exceptions import NotScalarConstantError
  from pytensor.tensor.math import Dot, _matrix_matrix_matmul, add, mul, neg, sub
  from pytensor.tensor.rewriting.elemwise import local_dimshuffle_lift
  from pytensor.tensor.type import DenseTensorType, TensorType, integer_dtypes, values_eq_approx_remove_inf_nan
label_desc: Use image node_id 17 for calling a global function with example usage: local_dot22_to_dot22scalar(fgraph, node) and returns: False, False, list, False, list, list, False, False