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 infer_shape(self, fgraph, node, inputs_shapes):
      # By construction, corresponding then/else pairs have the same number
      # of dimensions
  
      ts_shapes = inputs_shapes[1:][: self.n_outs]
      fs_shapes = inputs_shapes[1:][self.n_outs :]
      # All elements of all shape tuples for the true and false outputs are
      # unpacked into the inputs of a separate ifelse, and then the outputs
      # of that ifelse are packed back into shape tuples.
      new_ts_inputs = []
      for ts_shape in ts_shapes:
          if isinstance(ts_shape, (list, tuple)):
              new_ts_inputs += list(ts_shape)
          else:
              # It can be None for generic objects
              return [None] * self.n_outs
  
      new_fs_inputs = []
      for fs_shape in fs_shapes:
          if isinstance(fs_shape, (list, tuple)):
              new_fs_inputs += list(fs_shape)
          else:
              # It can be None for generic objects
              return [None] * self.n_outs
  
      assert len(new_ts_inputs) == len(new_fs_inputs)
      if len(new_ts_inputs + new_fs_inputs) > 0:
          name_tokens = ["shape"]
          if self.name is not None:
              name_tokens.append(self.name)
  
          new_ifelse = IfElse(
              n_outs=len(new_ts_inputs),
              as_view=False,
              name="_".join(name_tokens),
          )
          new_outs = new_ifelse(
              node.inputs[0],
              *(new_ts_inputs + new_fs_inputs),
              return_list=True,
          )
      else:
          new_outs = []
  
      # generate pairs of shapes
      out_shapes = []
      for out in node.outputs:
          out_shapes.append(tuple(new_outs[: out.ndim]))
          new_outs = new_outs[out.ndim :]
  
      # new_outs should be an empty list after last iteration
      assert len(new_outs) == 0
  
      return out_shapes
  
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 5 for calling the IfElse obj's underlying member method code with example usage: obj.infer_shape(fgraph, node, inputs_shapes) and returns: out_shapes, unknown, unknown