docs: def coarsegrain(P, n):
      """
      Coarse-grains transition matrix P to n sets using PCCA
  
      Coarse-grains transition matrix P such that the dominant eigenvalues are preserved, using:
  
      ..math:
          \tilde{P} = M^T P M (M^T M)^{-1}
  
      See [2]_ for the derivation of this form from the coarse-graining method first derived in [1]_.
  
      References
      ----------
      [1] S. Kube and M. Weber
          A coarse graining method for the identification of transition rates between molecular conformations.
          J. Chem. Phys. 126, 024103 (2007)
      [2] F. Noe, H. Wu, J.-H. Prinz and N. Plattner:
          Projected and hidden Markov models for calculating kinetics and metastable states of complex molecules
          J. Chem. Phys. 139, 184114 (2013)
      """
      M = pcca(P, n)
      # coarse-grained transition matrix
      W = np.linalg.inv(np.dot(M.T, M))
      A = np.dot(np.dot(M.T, P), M)
      P_coarse = np.dot(W, A)
  
      # symmetrize and renormalize to eliminate numerical errors
      from msmtools.analysis import stationary_distribution
      pi_coarse = np.dot(M.T, stationary_distribution(P))
      X = np.dot(np.diag(pi_coarse), P_coarse)
      P_coarse = X / X.sum(axis=1)[:, None]
  
      return P_coarse