docs: def fingerprint(P, obs1, obs2=None, p0=None, tau=1, k=None, ncv=None):
      r"""Dynamical fingerprint for equilibrium or relaxation experiment
  
      The dynamical fingerprint is given by the implied time-scale
      spectrum together with the corresponding amplitudes.
  
      Parameters
      ----------
      P : (M, M) scipy.sparse matrix
          Transition matrix
      obs1 : (M,) ndarray
          Observable, represented as vector on state space
      obs2 : (M,) ndarray (optional)
          Second observable, for cross-correlations
      p0 : (M,) ndarray (optional)
          Initial distribution for a relaxation experiment
      tau : int (optional)
          Lag time of given transition matrix, for correct time-scales
      k : int (optional)
          Number of time-scales and amplitudes to compute
      ncv : int (optional)
          The number of Lanczos vectors generated, `ncv` must be greater than k;
          it is recommended that ncv > 2*k
  
  
      Returns
      -------
      timescales : (N,) ndarray
          Time-scales of the transition matrix
      amplitudes : (N,) ndarray
          Amplitudes for the given observable(s)
  
      """
      if obs2 is None:
          obs2 = obs1
      R, D, L = rdl_decomposition(P, k=k, ncv=ncv)
      """Stationary vector"""
      mu = L[0, :]
      """Extract diagonal"""
      w = np.diagonal(D)
      """Compute time-scales"""
      timescales = timescales_from_eigenvalues(w, tau)
      if p0 is None:
          """Use stationary distribution - we can not use only left
          eigenvectors since the system might be non-reversible"""
          amplitudes = np.dot(mu * obs1, R) * np.dot(L, obs2)
      else:
          """Use initial distribution"""
          amplitudes = np.dot(p0 * obs1, R) * np.dot(L, obs2)
      return timescales, amplitudes