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

    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"
    
coremltools/converters/mil/frontend/tensorflow/ssa_passes/tf_lstm_to_core_lstm.py