Exemple #1
0
    def test_eigenvectors(self):
        P = self.bdc.transition_matrix

        # k==None
        ev = eigvals(P)
        ev = ev[np.argsort(np.abs(ev))[::-1]]
        Dn = np.diag(ev)

        # right eigenvectors
        Rn = eigenvectors(P)
        assert_allclose(np.dot(P, Rn), np.dot(Rn, Dn))
        # left eigenvectors
        Ln = eigenvectors(P, right=False).T
        assert_allclose(np.dot(Ln.T, P), np.dot(Dn, Ln.T))
        # orthogonality
        Xn = np.dot(Ln.T, Rn)
        di = np.diag_indices(Xn.shape[0])
        Xn[di] = 0.0
        assert_allclose(Xn, 0)

        # k!=None
        Dnk = Dn[:, 0:self.k][0:self.k, :]
        # right eigenvectors
        Rn = eigenvectors(P, k=self.k)
        assert_allclose(np.dot(P, Rn), np.dot(Rn, Dnk))
        # left eigenvectors
        Ln = eigenvectors(P, right=False, k=self.k).T
        assert_allclose(np.dot(Ln.T, P), np.dot(Dnk, Ln.T))
        # orthogonality
        Xn = np.dot(Ln.T, Rn)
        di = np.diag_indices(self.k)
        Xn[di] = 0.0
        assert_allclose(Xn, 0)
def test_eigenvectors_reversible(scenario, ncv_values):
    k, bdc = scenario
    P = bdc.transition_matrix

    ev = eigvals(P)
    ev = ev[np.argsort(np.abs(ev))[::-1]]
    Dn = np.diag(ev)
    Dnk = Dn[:, :k][:k, :]
    with assert_raises(ValueError) if bdc.sparse and k is None else nullcontext():
        # right eigenvectors
        Rn = eigenvectors(P, k=k, reversible=True, ncv=ncv_values)
        assert_allclose(P @ Rn, Rn @ Dnk)
        # left eigenvectors
        Ln = eigenvectors(P, right=False, k=k, reversible=True, ncv=ncv_values).T
        assert_allclose(Ln.T @ P, Dnk @ Ln.T)
        # orthogonality
        Xn = Ln.T @ Rn
        di = np.diag_indices(Xn.shape[0] if k is None else k)
        Xn[di] = 0.0
        assert_allclose(Xn, 0)

        Rn = eigenvectors(P, k=k, ncv=ncv_values, reversible=True, mu=bdc.stationary_distribution)
        assert_allclose(ev[:k][np.newaxis, :] * Rn, P.dot(Rn))

        Ln = eigenvectors(P, right=False, k=k, ncv=ncv_values, reversible=True, mu=bdc.stationary_distribution).T
        assert_allclose(P.transpose().dot(Ln), ev[:k][np.newaxis, :] * Ln)
def test_eigenvectors(scenario, ncv_values):
    k, bdc = scenario
    P = bdc.transition_matrix
    ev = eigvals(P)
    ev = ev[np.argsort(np.abs(ev))[::-1]]

    Dn = np.diag(ev)
    Dnk = Dn[:, :k][:k, :]
    with assert_raises(ValueError) if bdc.sparse and k is None else nullcontext():
        # right eigenvectors
        Rn = eigenvectors(P, k=k, ncv=ncv_values)
        assert_allclose(P @ Rn, Rn @ Dnk)
        # left eigenvectors
        Ln = eigenvectors(P, right=False, k=k, ncv=ncv_values).T
        assert_allclose(Ln.T @ P, Dnk @ Ln.T)
        # orthogonality
        Xn = Ln.T @ Rn
        di = np.diag_indices(Xn.shape[0] if k is None else k)
        Xn[di] = 0.0
        assert_allclose(Xn, 0)
Exemple #4
0
    def test_eigenvectors(self):
        P_dense = self.bdc.transition_matrix
        P = self.bdc.transition_matrix_sparse
        ev, L, R = eig(P_dense, left=True, right=True)
        ind = np.argsort(np.abs(ev))[::-1]
        ev = ev[ind]
        R = R[:, ind]
        L = L[:, ind]
        vals = ev[0:self.k]
        """k=None"""
        with self.assertRaises(ValueError):
            Rn = eigenvectors(P)

        with self.assertRaises(ValueError):
            Ln = eigenvectors(P, right=False)
        """k is not None"""
        Rn = eigenvectors(P, k=self.k)
        assert_allclose(vals[np.newaxis, :] * Rn, P.dot(Rn))

        Ln = eigenvectors(P, right=False, k=self.k).T
        assert_allclose(P.transpose().dot(Ln), vals[np.newaxis, :] * Ln)
        """k is not None and ncv is not None"""
        Rn = eigenvectors(P, k=self.k, ncv=self.ncv)
        assert_allclose(vals[np.newaxis, :] * Rn, P.dot(Rn))

        Ln = eigenvectors(P, right=False, k=self.k, ncv=self.ncv).T
        assert_allclose(P.transpose().dot(Ln), vals[np.newaxis, :] * Ln)
Exemple #5
0
def _pcca_connected(P, n, pi=None):
    r"""PCCA+ spectral clustering method with optimized memberships [1]_

    Clusters the first n_cluster eigenvectors of a transition matrix in order to cluster the states.
    This function assumes that the transition matrix is fully connected.

    Parameters
    ----------
    P : ndarray (n,n)
        Transition matrix.
    n : int
        Number of clusters to group to.
    pi: ndarray(n,), optional, default=None
        Stationary distribution if available.

    Returns
    -------
    chi : ndarray (n x m)
        A matrix containing the probability or membership of each state to be assigned to each cluster.
        The rows sum to 1.

    References
    ----------
    [1] S. Roeblitz and M. Weber, Fuzzy spectral clustering by PCCA+:
        application to Markov state models and data classification.
        Adv Data Anal Classif 7, 147-179 (2013).
    """

    # test connectivity
    from deeptime.markov.tools.estimation import connected_sets

    labels = connected_sets(P)
    n_components = len(
        labels
    )  # (n_components, labels) = connected_components(P, connection='strong')
    if n_components > 1:
        raise ValueError(
            "Transition matrix is disconnected. Cannot use pcca_connected.")

    if pi is None:
        from deeptime.markov.tools.analysis import stationary_distribution
        pi = stationary_distribution(P)
    else:
        if pi.shape[0] != P.shape[0]:
            raise ValueError(
                f"Stationary distribution must span entire state space but got {pi.shape[0]} states "
                f"instead of {P.shape[0]}.")
        pi /= pi.sum()  # make sure it is normalized

    from deeptime.markov.tools.analysis import is_reversible

    if not is_reversible(P, mu=pi):
        raise ValueError(
            "Transition matrix does not fulfill detailed balance. "
            "Make sure to call pcca with a reversible transition matrix estimate"
        )
    # TODO: Susanna mentioned that she has a potential fix for nonreversible matrices by replacing each complex conjugate
    #      pair by the real and imaginary components of one of the two vectors. We could use this but would then need to
    #      orthonormalize all eigenvectors e.g. using Gram-Schmidt orthonormalization. Currently there is no theoretical
    #      foundation for this, so I'll skip it for now.

    # right eigenvectors, ordered
    from deeptime.markov.tools.analysis import eigenvectors

    evecs = eigenvectors(P, n)

    # orthonormalize
    for i in range(n):
        evecs[:, i] /= math.sqrt(np.dot(evecs[:, i] * pi, evecs[:, i]))
    # make first eigenvector positive
    evecs[:, 0] = np.abs(evecs[:, 0])

    # Is there a significant complex component?
    if not np.alltrue(np.isreal(evecs)):
        warnings.warn(
            "The given transition matrix has complex eigenvectors, so it doesn't exactly fulfill detailed balance. "
            "Forcing eigenvectors to be real and continuing. Be aware that this is not theoretically solid."
        )
    evecs = np.real(evecs)

    # create initial solution using PCCA+. This could have negative memberships
    chi, rot_matrix = _pcca_connected_isa(evecs, n)

    # optimize the rotation matrix with PCCA++.
    rot_matrix = _opt_soft(evecs, rot_matrix, n)

    # These memberships should be nonnegative
    memberships = np.dot(evecs[:, :], rot_matrix)

    # We might still have numerical errors. Force memberships to be in [0,1]
    memberships = np.clip(memberships, 0., 1.)

    for i in range(0, np.shape(memberships)[0]):
        memberships[i] /= np.sum(memberships[i])

    return memberships