docs: def state_by_state2state_by_node(tpm):
      """Convert a state-by-state TPM to a state-by-node TPM.
  
      .. danger::
          Many nondeterministic state-by-state TPMs can be represented by a
          single a state-by-state TPM. However, the mapping can be made to be
          one-to-one if we assume the state-by-state TPM is conditionally
          independent, as this function does. **If the given TPM is not
          conditionally independent, the conditional dependencies will be
          silently lost.**
  
      .. note::
          The indices of the rows and columns of the state-by-state TPM are
          assumed to follow the little-endian convention. The indices of the rows
          of the resulting state-by-node TPM also follow the little-endian
          convention. See the documentation on PyPhi the :ref:`tpm-conventions`
          more information.
  
      Args:
          tpm (list[list] or np.ndarray): A square state-by-state TPM with row
              and column indices following the little-endian convention.
  
      Returns:
          np.ndarray: A state-by-node TPM, with row indices following the
          little-endian convention.
  
      Example:
          >>> tpm = np.array([[0.5, 0.5, 0.0, 0.0],
          ...                 [0.0, 1.0, 0.0, 0.0],
          ...                 [0.0, 0.2, 0.0, 0.8],
          ...                 [0.0, 0.3, 0.7, 0.0]])
          >>> state_by_state2state_by_node(tpm)
          array([[[0.5, 0. ],
                  [1. , 0.8]],
          <BLANKLINE>
                 [[1. , 0. ],
                  [0.3, 0.7]]])
      """
      # Cast to np.array.
      tpm = np.array(tpm)
      # Get the number of states from the length of one side of the TPM.
      S = tpm.shape[-1]
      # Get the number of nodes from the number of states.
      N = int(log2(S))
      # Initialize the new state-by node TPM.
      sbn_tpm = np.zeros(([2] * N + [N]))
      # Map indices to state-tuples with the little-endian convention.
      states = {i: le_index2state(i, N) for i in range(S)}
      # Get an array for each node with 1 in positions that correspond to that
      # node being on in the next state, and a 0 otherwise.
      node_on = np.array([[states[i][n] for i in range(S)] for n in range(N)])
      on_probabilities = [tpm * node_on[n] for n in range(N)]
      for i, state in states.items():
          # Get the probability of each node being on given the previous state i,
          # i.e., a row of the state-by-node TPM.
          # Assign that row to the ith state in the state-by-node TPM.
          sbn_tpm[state] = [np.sum(on_probabilities[n][i]) for n in range(N)]
      return sbn_tpm