class_docstr: An `Op` that provides conditional graph evaluation.
  
  According to a scalar condition, this `Op` evaluates and then
  returns all the tensors provided on the "then"-branch, otherwise it
  evaluates and returns the tensors provided on the "else"-branch. The `Op`
  supports multiple tensors on each branch, with the condition that the same
  number of tensors are on the "then"-branch as on the "else"-branch and
  there is a one to one correspondence between their dtypes and numbers of
  dimensions.
  
  The "then"-branch is defined as the first ``N`` tensors (after the
  condition), while the "else"-branch is defined as the last ``N`` tensors.
  
  Example usage:
  
  .. code-block::
  
      rval = ifelse(condition,
                    rval_if_true_1, ..., rval_if_true_N,
                    rval_if_false_1, ..., rval_if_false_N)
  
  .. note:
  
      `Linker`\s other than `CVM`, and some other `VM` subclasses, are
      incompatible with this `Op`, and will ignore its lazy characteristic,
      computing both the true and false branches before returning one.
code: def grad(self, ins, grads):
      condition = ins[0]
      inputs_true_branch = ins[1:][: self.n_outs]
      inputs_false_branch = ins[1:][self.n_outs :]
  
      if self.name is not None:
          nw_name_t = self.name + "_grad_t"
          nw_name_f = self.name + "_grad_f"
      else:
          nw_name_t = None
          nw_name_f = None
  
      if_true_op = IfElse(
          n_outs=self.n_outs, as_view=self.as_view, name=nw_name_t
      )
      if_false_op = IfElse(
          n_outs=self.n_outs, as_view=self.as_view, name=nw_name_f
      )
  
      # The `grads` can have different dtypes than the `inputs`.
      # Since input true/false entries must have the same dtypes, we need to
      # cast the zeros to the corresponding `grads` dtypes and not the input
      # dtypes.
      inputs_true_grad = (
          [condition]
          + grads
          + [
              pt.basic.zeros_like(t, dtype=grads[i].dtype)
              for i, t in enumerate(inputs_true_branch)
          ]
      )
      inputs_false_grad = (
          [condition]
          + [
              pt.basic.zeros_like(f, dtype=grads[i].dtype)
              for i, f in enumerate(inputs_false_branch)
          ]
          + grads
      )
  
      # `condition` does affect the elements of the output so it is connected.
      # For the sake of making the gradient convenient we assume that
      # condition + epsilon always triggers the same branch as condition
      condition_grad = condition.zeros_like().astype(config.floatX)
  
      return (
          [condition_grad]
          + if_true_op(*inputs_true_grad, return_list=True)
          + if_false_op(*inputs_false_grad, return_list=True)
      )
  
imports: from collections.abc import Sequence
  from copy import deepcopy
  from typing import TYPE_CHECKING, Any, Optional, Union
  import numpy
  import pytensor.tensor
  from pytensor import as_symbolic
  from pytensor.compile import optdb
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Apply, Variable, apply_depends_on
  from pytensor.graph.op import _NoPythonOp
  from pytensor.graph.replace import clone_replace
  from pytensor.graph.rewriting.basic import GraphRewriter, in2out, node_rewriter
  from pytensor.graph.type import HasDataType, HasShape
  from pytensor.tensor.shape import Reshape, Shape, SpecifyShape, Unbroadcast
label_desc: Use image node_id 8 for calling the IfElse obj's underlying member method code with example usage: obj.grad(ins, grads) and returns: unknown