docs: def log_likelihood(C, T):
      r"""Log-likelihood of the count matrix given a transition matrix.
  
      Parameters
      ----------
      C : (M, M) ndarray or scipy.sparse matrix
          Count matrix
      T : (M, M) ndarray orscipy.sparse matrix
          Transition matrix
  
      Returns
      -------
      logL : float
          Log-likelihood of the count matrix
  
      Notes
      -----
  
      The likelihood of a set of observed transition counts
      :math:`C=(c_{ij})` for a given matrix of transition counts
      :math:`T=(t_{ij})` is given by
  
      .. math:: L(C|P)=\prod_{i=1}^{M} \left( \prod_{j=1}^{M} p_{ij}^{c_{ij}} \right)
  
      The log-likelihood is given by
  
      .. math:: l(C|P)=\sum_{i,j=1}^{M}c_{ij} \log p_{ij}.
  
      The likelihood describes the probability of making an observation
      :math:`C` for a given model :math:`P`.
  
      Examples
      --------
  
      >>> import numpy as np
      >>> from msmtools.estimation import log_likelihood
  
      >>> T = np.array([[0.9, 0.1, 0.0], [0.5, 0.0, 0.5], [0.0, 0.1, 0.9]])
  
      >>> C = np.array([[58, 7, 0], [6, 0, 4], [0, 3, 21]])
      >>> logL = log_likelihood(C, T)
      >>> logL # doctest: +ELLIPSIS
      -38.2808034725...
  
      >>> C = np.array([[58, 20, 0], [6, 0, 4], [0, 3, 21]])
      >>> logL = log_likelihood(C, T)
      >>> logL # doctest: +ELLIPSIS
      -68.2144096814...
  
      References
      ----------
      .. [1] Prinz, J H, H Wu, M Sarich, B Keller, M Senne, M Held, J D
          Chodera, C Schuette and F Noe. 2011. Markov models of
          molecular kinetics: Generation and validation. J Chem Phys
          134: 174105
  
      """
      if issparse(C) and issparse(T):
          return sparse.likelihood.log_likelihood(C, T)
      else:
          # use the dense likelihood calculator for all other cases
          # if a mix of dense/sparse C/T matrices is used, then both
          # will be converted to ndarrays.
          if not isinstance(C, np.ndarray):
              C = np.array(C)
          if not isinstance(T, np.ndarray):
              T = np.array(T)
          # computation is still efficient, because we only use terms
          # for nonzero elements of T
          nz = np.nonzero(T)
          return np.dot(C[nz], np.log(T[nz]))