<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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