code: def _try_get_last_cell_state_in_tf_lstm_block(op: Operation) -> Var:
      """
      Parameters
      ----------
      op: Operation
          Must have op type "tf_lstm_block"
  
      Returns
      -------
      Var, a var representing the last cell state in the lstm. None if check fails.
  
      One of the outputs of the op "tf_lstm_block" is the cell state (cs) which has shape [seq_len, batch, feat].
      That is, it is the cell state tensor of the lstm, which includes all the time steps.
      This, normally, can not be mapped to the MIL lstm op's cell state output, since that op only
      returns the last time step of the cell state, which is a tensor of shape [batch, feat].
      However, if the cell state output of "tf_lstm_block" is being sliced, before being used anywhere else,
      and sliced in such a way that it extracts just the last time step of the seq dimension, then
      it can indeed be mapped to MIL's lstm op.
      This utility function detects this condition. If true, it returns the var
      that corresponds to the rank 2 sliced cell state.
  
      In particular, the following pattern is detected:
  
      Input pattern:
      ..., cs, ... = tf_lstm_block(...) # [seq_len, batch, feat]
      extracted_cell_state = slice_by_index(x=cs, ...) # [batch, feat] or [1, batch, feat], such that seq dim. is sliced at the last time step
      out = op(extracted_cell_state)
  
      The "cs" var can be feeding into multiple "slice_by_index" ops, some of which slice it into [batch, feat] and
      some into [1, batch, feat] shaped tensors. This scenario is handled in the following manner:
  
      step 1: verify that the output "cs" only feeds into slice_by_index ops
      step 2: add a slice_by_index op to the graph, which slices the last time step and creates a
              tensor, "last_cs", of shape [batch, feat]
      step 3: add an expand_dims op to the graph which takes in "last_cs" and expands it to create
              a tensor, "expanded_last_cs", of shape [1, batch, feat]
      step 4: now, iterate over all the child ops of "cs". Each one of these will be of type "slice_by_index".
              Verify that they are slicing only the last time step. If not, exit out of the function by returning None.
              Once verified, replace its output var with either "last_cs" or "expanded_last_cs", depending on its shape.
      step 5: remove all the child ops of "cs". Return "last_cs"
      """
      if op.op_type != "tf_lstm_block":
          raise ValueError(
              "op must have type 'tf_lstm_block'. Got {}".format(
                  op.op_type
              )
          )
  
      cs = op.outputs[1]
      if len(cs.child_ops) == 0 and len(cs.consuming_blocks) == 0:
          return cs
      if len(cs.consuming_blocks) > 1:
          return None
      if not all(
          [
              child_op.op_type == "slice_by_index"
              for child_op in cs.child_ops
          ]
      ):
          return None
      child_ops = cs.child_ops[:]
      block = op.enclosing_block
  
      # extract the last time step of the cell states
      last_cs = mb.slice_by_index(
          x=cs,
          begin=[-1, 0, 0],
          end=[-1, 0, 0],
          begin_mask=[False, True, True],
          end_mask=[False, True, True],
          squeeze_mask=[True, False, False],
          before_op=child_ops[0],
      )  # this is of shape [batch, feat]
      expanded_last_cs = mb.expand_dims(
          x=last_cs, axes=[0], before_op=child_ops[0]
      )  # shape: [1, batch, feat]
  
      # for each child op, which is a "slice_by_index" op, verify the following conditions:
      # - input is a rank 3 tensor, of shape [seq_len, batch, feat]
      # - output is either a rank 2 tensor of shape [batch, feat] or rank 3 of shape [1, batch, feat]
      # - the first dimension is sliced with an index that is the last index,
      #   so if its positive it should be of value, seq-1, or if negative, it should be -1
      for slice_op in child_ops:
          # if any of the input arguments of the slice op is not compile time known, the check fails early
          for input in slice_op.inputs.values():
              if input == slice_op.x:
                  continue
              if input is None or input.val is None:
                  return None
  
          x = slice_op.x
          out = slice_op.outputs[0]
          # check input rank
          if x.rank != 3:
              return None
          # check output rank and shape
          if out.rank not in (2, 3):
              return None
          if out.shape[-2:] != x.shape[-2:]:
              return None
          if out.rank == 3 and out.shape[0] != 1:
              return None
  
          # check that only the last time step is being extracted
          begin = slice_op.begin.val.tolist()
          end = slice_op.end.val.tolist()
          stride = slice_op.stride.val.tolist()
          begin_mask = slice_op.begin_mask.val.tolist()
          end_mask = slice_op.end_mask.val.tolist()
          squeeze_mask = slice_op.squeeze_mask.val.tolist()
  
          # the stride for the first dimension must be 1
          if stride[0] != 1:
              return None
  
          # check if the first dimension is sliced exactly for the last time step
          if is_symbolic(x.shape[0]):
              """
              When the first dimension is symbolic, we check for the following condition to be true:
              - begin[0] == -1 and begin_mask[0] == False
              If this condition is not met, we return None and exit
              """
              if begin[0] != -1 or begin_mask[0]:
                  return None
          else:
              time = x.shape[0]
              begin = [i + time if i < 0 else i for i in begin]
              end = [i + time if i < 0 else i for i in end]
  
              begin_time = 0 if begin_mask[0] else begin[0]
              end_time = time if end_mask[0] else end[0]
              if squeeze_mask[0]:
                  if begin_time != time - 1:
                      return None
              else:
                  if end_time - begin_time != 1:
                      return None
                  if begin_time != time - 1:
                      return None
  
          block.replace_uses_of_var_after_op(
              anchor_op=slice_op,
              old_var=slice_op.outputs[0],
              new_var=last_cs
              if len(out.shape) == 2
              else expanded_last_cs,
          )
  
      block.remove_ops(child_ops)
      return last_cs
  
file_path: coremltools/coremltools/converters/mil/frontend/tensorflow/ssa_passes/tf_lstm_to_core_lstm.py
imports: import numpy
  from coremltools import _logger
  from coremltools.converters.mil.mil import Block, Builder, Operation, Var
  from coremltools.converters.mil.mil.passes.graph_pass import AbstractGraphPass
  from coremltools.converters.mil.mil.passes.helper import block_context_manager
  from coremltools.converters.mil.mil.passes.pass_registry import register_pass
  from coremltools.converters.mil.mil.types.symbolic import is_symbolic
label_desc: Use image node_id 2 for calling a global function with example usage: _try_get_last_cell_state_in_tf_lstm_block(op) and returns: last_cs, cs, None, None, None, None, None, None, None, None, None, None, None, None