def state_by_node2state_by_state(tpm):
    """Convert a state-by-node TPM to a state-by-state TPM.

    .. important::
        A nondeterministic state-by-node TPM can have more than one
        representation as a state-by-state TPM. However, the mapping can be
        made to be one-to-one if we assume the TPMs to be conditionally
        independent. Therefore, **this function returns the corresponding
        conditionally independent state-by-state TPM.**

    .. note::
        The indices of the rows of the state-by-node TPM are assumed to follow
        the little-endian convention, while the indices of the columns follow
        the big-endian convention. The indices of the rows and columns of the
        resulting state-by-state TPM both follow the big-endian convention. See
        the documentation on PyPhi :ref:`tpm-conventions` for more info.

    Args:
        tpm (list[list] or np.ndarray): A state-by-node TPM with row indices
            following the little-endian convention and column indices following
            the big-endian convention.

    Returns:
        np.ndarray: A state-by-state TPM, with both row and column indices
        following the big-endian convention.

    >>> tpm = np.array([[1, 1, 0],
    ...                 [0, 0, 1],
    ...                 [0, 1, 1],
    ...                 [1, 0, 0],
    ...                 [0, 0, 1],
    ...                 [1, 0, 0],
    ...                 [1, 1, 1],
    ...                 [1, 0, 1]])
    >>> state_by_node2state_by_state(tpm)
    array([[0., 0., 0., 1., 0., 0., 0., 0.],
           [0., 0., 0., 0., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 1., 0.],
           [0., 1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 1., 0., 0., 0.],
           [0., 1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 1.],
           [0., 0., 0., 0., 0., 1., 0., 0.]])
    """
    # Cast to np.array.
    tpm = np.array(tpm)
    # Convert to multidimensional form.
    tpm = to_multidimensional(tpm)
    # Get the number of nodes from the last dimension of the TPM.
    N = tpm.shape[-1]
    # Get the number of states.
    S = 2**N
    # Initialize the state-by-state TPM.
    sbs_tpm = np.zeros((S, S))
    if not np.any(np.logical_and(tpm < 1, tpm > 0)):
        # TPM is deterministic.
        for previous_state_index in range(S):
            # Use the little-endian convention to get the row and column
            # indices.
            previous_state = le_index2state(previous_state_index, N)
            current_state_index = state2le_index(tpm[previous_state])
            sbs_tpm[previous_state_index, current_state_index] = 1
    else:
        # TPM is nondeterministic.
        for previous_state_index in range(S):
            # Use the little-endian convention to get the row and column
            # indices.
            previous_state = le_index2state(previous_state_index, N)
            marginal_tpm = tpm[previous_state]
            for current_state_index in range(S):
                current_state = np.array(
                    [i for i in le_index2state(current_state_index, N)])
                sbs_tpm[previous_state_index, current_state_index] = (
                    np.prod(marginal_tpm[current_state == 1]) *
                    np.prod(1 - marginal_tpm[current_state == 0]))
    return sbs_tpm
Convert a state-by-node TPM to a state-by-state TPM.

    .. important::
        A nondeterministic state-by-node TPM can have more than one
        representation as a state-by-state TPM. However, the mapping can be
        made to be one-to-one if we assume the TPMs to be conditionally
        independent. Therefore, **this function returns the corresponding
        conditionally independent state-by-state TPM.**

    .. note::
        The indices of the rows of the state-by-node TPM are assumed to follow
        the little-endian convention, while the indices of the columns follow
        the big-endian convention. The indices of the rows and columns of the
        resulting state-by-state TPM both follow the big-endian convention. See
        the documentation on PyPhi :ref:`tpm-conventions` for more info.

    Args:
        tpm (list[list] or np.ndarray): A state-by-node TPM with row indices
            following the little-endian convention and column indices following
            the big-endian convention.

    Returns:
        np.ndarray: A state-by-state TPM, with both row and column indices
        following the big-endian convention.

    >>> tpm = np.array([[1, 1, 0],
    ...                 [0, 0, 1],
    ...                 [0, 1, 1],
    ...                 [1, 0, 0],
    ...                 [0, 0, 1],
    ...                 [1, 0, 0],
    ...                 [1, 1, 1],
    ...                 [1, 0, 1]])
    >>> state_by_node2state_by_state(tpm)
    array([[0., 0., 0., 1., 0., 0., 0., 0.],
           [0., 0., 0., 0., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 1., 0.],
           [0., 1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 1., 0., 0., 0.],
           [0., 1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 1.],
           [0., 0., 0., 0., 0., 1., 0., 0.]])