class_docstr: Generalizes a core `Op` to work with batched dimensions.
  
  TODO: Dispatch JAX (should be easy with the vectorize macro)
  TODO: Dispatch Numba
  TODO: C implementation?
  TODO: Fuse Blockwise?
class_docstr_tok: ["Generalizes","a","core","`","Op","`","to","work","with","batched","dimensions",".","TODO",":","Dispatch","JAX","(","should","be","easy","with","the","vectorize","macro",")","TODO",":","Dispatch","Numba","TODO",":","C","implementation","?","TODO",":","Fuse","Blockwise","?"]
code: def L_op(self, inputs, outs, ograds):
      from pytensor.tensor.math import sum as pt_sum
  
      # Compute grad with respect to broadcasted input
      rval = self._bgrad(inputs, outs, ograds)
  
      # TODO: (Borrowed from Elemwise) make sure that zeros are clearly identifiable
      # to the gradient.grad method when the outputs have
      # some integer and some floating point outputs
      if any(out.type.dtype not in continuous_dtypes for out in outs):
          # For integer output, return value may only be zero or undefined
          # We don't bother with trying to check that the scalar ops
          # correctly returned something that evaluates to 0, we just make
          # the return value obviously zero so that gradient.grad can tell
          # this op did the right thing.
          new_rval = []
          for elem, inp in zip(rval, inputs):
              if isinstance(elem.type, (NullType, DisconnectedType)):
                  new_rval.append(elem)
              else:
                  elem = inp.zeros_like()
                  if str(elem.type.dtype) not in continuous_dtypes:
                      elem = elem.astype(config.floatX)
                  assert str(elem.type.dtype) not in discrete_dtypes
                  new_rval.append(elem)
          return new_rval
  
      # Sum out the broadcasted dimensions
      batch_ndims = self.batch_ndim(outs[0].owner)
      batch_shape = outs[0].type.shape[:batch_ndims]
      for i, (inp, sig) in enumerate(zip(inputs, self.inputs_sig)):
          if isinstance(rval[i].type, (NullType, DisconnectedType)):
              continue
  
          assert inp.type.ndim == batch_ndims + len(sig)
  
          to_sum = [
              j
              for j, (inp_s, out_s) in enumerate(
                  zip(inp.type.shape, batch_shape)
              )
              if inp_s == 1 and out_s != 1
          ]
          if to_sum:
              rval[i] = pt_sum(rval[i], axis=to_sum, keepdims=True)
  
      return rval
  
imports: from collections.abc import Sequence
  from copy import copy
  from typing import Any, Optional, cast
  import numpy
  from pytensor import config
  from pytensor.gradient import DisconnectedType
  from pytensor.graph.basic import Apply, Constant, Variable
  from pytensor.graph.null_type import NullType
  from pytensor.graph.op import Op
  from pytensor.graph.replace import _vectorize_node, _vectorize_not_needed, vectorize_graph
  from pytensor.scalar import ScalarType
  from pytensor.tensor import as_tensor_variable
  from pytensor.tensor.shape import shape_padleft
  from pytensor.tensor.type import TensorType, continuous_dtypes, discrete_dtypes, tensor
  from pytensor.tensor.utils import _parse_gufunc_signature, broadcast_static_dim_lengths, import_func_from_string
  from pytensor.tensor.variable import TensorVariable
label_desc: Use image node_id 9 for calling the Blockwise obj's underlying member method code with example usage: obj.L_op(inputs, outs, ograds) and returns: rval, new_rval