예제 #1
0
def test_is_connected(sparse_mode):
    C1 = 1.0 * np.array([[1, 4, 3], [3, 2, 4], [4, 5, 1]])
    C2 = 1.0 * np.array([[0, 1], [1, 0]])
    C3 = 1.0 * np.array([[7]])

    C = scipy.sparse.block_diag((C1, C2, C3))

    C = C.toarray()
    """Forward transition block 1 -> block 2"""
    C[2, 3] = 1
    """Forward transition block 2 -> block 3"""
    C[4, 5] = 1

    T_connected = scipy.sparse.csr_matrix(C1 / C1.sum(axis=1)[:, np.newaxis])
    T_not_connected = scipy.sparse.csr_matrix(C / C.sum(axis=1)[:, np.newaxis])

    if not sparse_mode:
        T_connected = T_connected.toarray()
        T_not_connected = T_not_connected.toarray()
    """Directed"""
    assert_(not is_connected(T_not_connected))
    assert_(is_connected(T_connected))

    """Undirected"""
    assert_(is_connected(T_not_connected, directed=False))
예제 #2
0
    def test_connected_count_matrix(self):
        """Directed"""
        is_c = is_connected(self.T_not_connected)
        self.assertFalse(is_c)

        is_c = is_connected(self.T_connected)
        self.assertTrue(is_c)
        """Undirected"""
        is_c = is_connected(self.T_not_connected, directed=False)
        self.assertTrue(is_c)
예제 #3
0
def test_transition_matrix(oom_msm_scenario):
    for msm in oom_msm_scenario.msms:
        P = msm.transition_matrix
        # should be ndarray by default
        np.testing.assert_(
            isinstance(P, np.ndarray)
            or isinstance(P, scipy.sparse.csr_matrix))
        # shape
        np.testing.assert_equal(P.shape, (msm.n_states, msm.n_states))
        # test transition matrix properties
        import deeptime.markov.tools.analysis as msmana
        np.testing.assert_(msmana.is_transition_matrix(P))
        np.testing.assert_(msmana.is_connected(P))
        # REVERSIBLE
        if msm.reversible:
            np.testing.assert_(msmana.is_reversible(P))
        # Test equality with model:
        from scipy.sparse import issparse
        if issparse(P):
            P = P.toarray()
        if msm.reversible:
            np.testing.assert_allclose(
                P, oom_msm_scenario.rmsmrev.transition_matrix)
        else:
            np.testing.assert_allclose(P,
                                       oom_msm_scenario.rmsm.transition_matrix)
예제 #4
0
 def _transition_matrix(self, msm):
     P = msm.transition_matrix
     # should be ndarray by default
     # assert (isinstance(P, np.ndarray))
     assert (isinstance(P, np.ndarray) or isinstance(P, scipy.sparse.csr_matrix))
     # shape
     assert (np.all(P.shape == (msm.nstates, msm.nstates)))
     # test transition matrix properties
     assert (is_transition_matrix(P))
     assert (is_connected(P))
     # REVERSIBLE
     if msm.is_reversible:
         assert (is_reversible(P))
예제 #5
0
    def _update_transition_matrix(model: _SampleStorage,
                                  transition_matrix_prior,
                                  initial_distribution_prior,
                                  reversible: bool = True,
                                  stationary: bool = False,
                                  n_sampling_steps: int = 1000):
        """ Updates the hidden-state transition matrix and the initial distribution """
        C = _bd_util.count_matrix(model.hidden_trajs, 1,
                                  model.transition_matrix.shape[0])
        model.counts[...] = C
        C = C + transition_matrix_prior

        # check if we work with these options
        if reversible and not is_connected(C, directed=True):
            raise NotImplementedError(
                'Encountered disconnected count matrix with sampling option reversible:\n '
                f'{C}\nUse prior to ensure connectivity or use reversible=False.'
            )
        # ensure consistent sparsity pattern (P0 might have additional zeros because of underflows)
        # TODO: these steps work around a bug in msmtools. Should be fixed there
        P0 = transition_matrix(C,
                               reversible=reversible,
                               maxiter=10000,
                               warn_not_converged=False)
        zeros = np.where(P0 + P0.T == 0)
        C[zeros] = 0
        # run sampler
        Tij = sample_tmatrix(C,
                             nsample=1,
                             nsteps=n_sampling_steps,
                             reversible=reversible)

        # INITIAL DISTRIBUTION
        if stationary:  # p0 is consistent with P
            p0 = stationary_distribution(Tij, C=C)
        else:
            n0 = BayesianHMM._count_init(model.hidden_trajs,
                                         model.transition_matrix.shape[0])
            first_timestep_counts_with_prior = n0 + initial_distribution_prior
            positive = first_timestep_counts_with_prior > 0
            p0 = np.zeros(n0.shape, dtype=model.transition_matrix.dtype)
            p0[positive] = np.random.dirichlet(
                first_timestep_counts_with_prior[positive]
            )  # sample p0 from posterior

        # update HMM with new sample
        model.transition_matrix[...] = Tij
        model.stationary_distribution[...] = p0
    def test_transition_matrix(self, setting):
        scenario = make_double_well(setting)
        msm = scenario.msm
        P = msm.transition_matrix
        # should be ndarray by default
        # assert (isinstance(P, np.ndarray))
        assert_(isinstance(P, np.ndarray) or isinstance(P, scipy.sparse.csr_matrix))
        # shape
        assert_equal(P.shape, (msm.n_states, msm.n_states))
        # test transition matrix properties
        import deeptime.markov.tools.analysis as msmana

        assert_(msmana.is_transition_matrix(P))
        assert_(msmana.is_connected(P))
        # REVERSIBLE
        if msm.reversible:
            assert_(msmana.is_reversible(P))
예제 #7
0
 def check_input(self):
     from deeptime.markov.tools.analysis import is_connected
     if self.C.dtype not in (np.float32, np.float64, np.longdouble):
         raise ValueError(
             "Only supports float32, float64, and longdouble dtype.")
     if not np.all(self.C >= 0):
         raise ValueError("Count matrix contains negative elements")
     if not is_connected(self.C):
         raise ValueError("Count matrix is not connected")
     if not np.all(self.V >= 0.0):
         raise ValueError("P0 contains negative entries")
     if not np.allclose(self.V, self.V.T, atol=1e-6):
         raise ValueError("P0 is not reversible")
     """Check sparsity pattern"""
     iC, jC = np.where((self.C + self.C.T + np.eye(self.C.shape[0])) > 0)
     iV, jV = np.where((self.V + self.V.T + np.eye(self.V.shape[0])) > 0)
     if not np.array_equal(iC, iV):
         raise ValueError('Sparsity patterns of C and X are different.')
     if not np.array_equal(jC, jV):
         raise ValueError('Sparsity patterns of C and X are different.')
예제 #8
0
    def check_input(self):
        from deeptime.markov.tools.analysis import is_connected

        if not np.all(self.C >= 0):
            raise ValueError("Count matrix contains negative elements")
        if not is_connected(self.C, directed=False):
            raise ValueError("Count matrix is not connected")
        if not np.all(self.X >= 0.0):
            raise ValueError("P0 contains negative entries")
        if not np.allclose(self.X, self.X.T):
            raise ValueError("P0 is not reversible")
        """Check sparsity pattern - ignore diagonal"""
        C_sym = self.C + self.C.T
        X_sym = self.X + self.X.T
        ind = np.diag_indices(C_sym.shape[0])
        C_sym[ind] = 0.0
        X_sym[ind] = 0.0
        iC, jC = np.where(C_sym > 0.0)
        iV, jV = np.where(X_sym > 0.0)
        if not np.array_equal(iC, iV):
            raise ValueError('Sparsity patterns of C and X are different.')
        if not np.array_equal(jC, jV):
            raise ValueError('Sparsity patterns of C and X are different.')
예제 #9
0
 def test_is_connected(self):
     self.assertTrue(is_connected(self.T))
     self.assertTrue(is_connected(self.T, directed=False))
예제 #10
0
    def fit(self, data, n_burn_in: int = 0, n_thin: int = 1, **kwargs):
        r""" Sample from the posterior.

        Parameters
        ----------
        data : array_like or list of array_like
            Input time series data.
        n_burn_in : int, optional, default=0
            The number of samples to discard to burn-in, following which :attr:`n_samples` samples will be generated.
        n_thin : int, optional, default=1
            The number of Gibbs sampling updates used to generate each returned sample.
        **kwargs
            Ignored kwargs for scikit-learn compatibility.

        Returns
        -------
        self : BayesianHMM
            Reference to self.
        """
        dtrajs = ensure_dtraj_list(data)

        # fetch priors
        tmat = self.initial_hmm.transition_model.transition_matrix
        transition_matrix_prior = self._transition_matrix_prior_np

        initial_distribution_prior = self._initial_distribution_prior_np

        model = BayesianHMMPosterior()
        # update HMM Model
        model.prior = self.initial_hmm.copy()

        prior = model.prior

        # check if we are strongly connected in the reversible case (plus prior)
        if self.reversible and not is_connected(tmat + transition_matrix_prior,
                                                directed=True):
            raise NotImplementedError(
                'Trying to sample disconnected HMM with option reversible:\n '
                f'{tmat}\n Use prior to connect, select connected subset, '
                f'or use reversible=False.')

        # EVALUATE STRIDE
        dtrajs_lagged_strided = compute_dtrajs_effective(
            dtrajs,
            lagtime=prior.lagtime,
            n_states=prior.n_hidden_states,
            stride=self.stride)
        # if stride is different to init_hmm, check if microstates in lagged-strided trajs are compatible
        if self.stride != self.initial_hmm.stride:
            symbols = np.unique(np.concatenate(dtrajs_lagged_strided))
            if not len(
                    np.intersect1d(self.initial_hmm.observation_symbols,
                                   symbols)) == len(symbols):
                raise ValueError(
                    'Choice of stride has excluded a different set of microstates than in '
                    'init_hmm. Set of observed microstates in time-lagged strided trajectories '
                    'must match to the one used for init_hmm estimation.')

        # here we blow up the output matrix (if needed) to the FULL state space because we want to use dtrajs in the
        # Bayesian HMM sampler. This is just an initialization.
        n_states_full = number_of_states(dtrajs_lagged_strided)

        if prior.n_observation_states < n_states_full:
            eps = 0.01 / n_states_full  # default output probability, in order to avoid zero columns
            # full state space output matrix. make sure there are no zero columns
            full_obs_probabilities = eps * np.ones(
                (prior.n_hidden_states, n_states_full), dtype=np.float64)
            # fill active states
            full_obs_probabilities[:, prior.observation_symbols] = np.maximum(
                eps, prior.output_probabilities)
            # renormalize B to make it row-stochastic
            full_obs_probabilities /= full_obs_probabilities.sum(axis=1)[:,
                                                                         None]
        else:
            full_obs_probabilities = prior.output_probabilities

        maxT = max(len(o) for o in dtrajs_lagged_strided)

        # pre-construct hidden variables
        temp_alpha = np.zeros((maxT, prior.n_hidden_states))

        has_all_obs_symbols = model.prior.n_observation_states == len(
            model.prior.observation_symbols_full)

        try:
            # sample model is basically copy of prior
            sample_model = BayesianHMM._SampleStorage(
                transition_matrix=prior.transition_model.transition_matrix.
                copy(),
                output_model=DiscreteOutputModel(
                    full_obs_probabilities.copy()),
                initial_distribution=prior.initial_distribution.copy(),
                stationary_distribution=prior.transition_model.
                stationary_distribution.copy(),
                counts=prior.count_model.count_matrix.copy(),
                hidden_trajs=[])

            # Run burn-in.
            for _ in range(n_burn_in):
                self._update(sample_model, dtrajs_lagged_strided, temp_alpha,
                             transition_matrix_prior,
                             initial_distribution_prior)

            # Collect data.
            models = []
            for _ in range(self.n_samples):
                # Run a number of Gibbs sampling updates to generate each sample.
                for _ in range(n_thin):
                    self._update(sample_model, dtrajs_lagged_strided,
                                 temp_alpha, transition_matrix_prior,
                                 initial_distribution_prior)
                    sample_model.output_model.normalize()
                self._append_sample(models, prior, sample_model)

            if not has_all_obs_symbols:
                models = [
                    m.submodel(states=None,
                               obs=model.prior.observation_symbols)
                    for m in models
                ]

            model.samples = models
        finally:
            del temp_alpha

        # set new model
        self._model = model

        return self
예제 #11
0
    def _estimate(self, dtrajs):
        # ensure right format
        dtrajs = ensure_dtraj_list(dtrajs)

        if self.init_hmsm is None:  # estimate using maximum-likelihood superclass
            # memorize the observation state for bhmm and reset
            # TODO: more elegant solution is to set Estimator params only temporarily in estimate(X, **kwargs)
            default_connectivity = self.connectivity
            default_mincount_connectivity = self.mincount_connectivity
            default_observe_nonempty = self.observe_nonempty
            self.connectivity = None
            self.observe_nonempty = False
            self.mincount_connectivity = 0
            self.accuracy = 1e-2  # this is sufficient for an initial guess
            super(BayesianHMSM, self)._estimate(dtrajs)
            self.connectivity = default_connectivity
            self.mincount_connectivity = default_mincount_connectivity
            self.observe_nonempty = default_observe_nonempty
        else:  # if given another initialization, must copy its attributes
            copy_attributes = [
                '_nstates', '_reversible', '_pi', '_observable_set',
                'likelihoods', 'likelihood', 'hidden_state_probabilities',
                'hidden_state_trajectories', 'count_matrix', 'initial_count',
                'initial_distribution', '_active_set'
            ]
            check_user_choices = ['lag', '_nstates']

            # check if nstates and lag are compatible
            for attr in check_user_choices:
                if not getattr(self, attr) == getattr(self.init_hmsm, attr):
                    raise UserWarning(
                        'BayesianHMSM cannot be initialized with init_hmsm with '
                        'incompatible lag or nstates.')

            if (len(dtrajs) != len(self.init_hmsm.dtrajs_full) or not all(
                (_np.array_equal(d1, d2)
                 for d1, d2 in zip(dtrajs, self.init_hmsm.dtrajs_full)))):
                raise NotImplementedError(
                    'Bayesian HMM estimation with init_hmsm is currently only implemented '
                    + 'if applied to the same data.')

            # TODO: implement more elegant solution to copy-pasting effective stride evaluation from ML HMM.
            # EVALUATE STRIDE
            if self.stride == 'effective':
                # by default use lag as stride (=lag sampling), because we currently have no better theory for deciding
                # how many uncorrelated counts we can make
                self.stride = self.lag
                # get a quick estimate from the spectral radius of the nonreversible
                from pyemma.msm import estimate_markov_model
                msm_nr = estimate_markov_model(dtrajs,
                                               lag=self.lag,
                                               reversible=False,
                                               sparse=False,
                                               connectivity='largest',
                                               dt_traj=self.timestep_traj)
                # if we have more than nstates timescales in our MSM, we use the next (neglected) timescale as an
                # estimate of the decorrelation time
                if msm_nr.nstates > self.nstates:
                    corrtime = max(1, msm_nr.timescales()[self.nstates - 1])
                    # use the smaller of these two pessimistic estimates
                    self.stride = int(min(self.lag, 2 * corrtime))

            # if stride is different to init_hmsm, check if microstates in lagged-strided trajs are compatible
            if self.stride != self.init_hmsm.stride:
                from deeptime.markov import compute_dtrajs_effective
                dtrajs_lagged_strided = compute_dtrajs_effective(
                    dtrajs, lagtime=self.lag, n_states=-1, stride=self.stride)
                _nstates_obs = number_of_states(dtrajs_lagged_strided,
                                                only_used=True)
                _nstates_obs_full = number_of_states(dtrajs)

                if _np.setxor1d(_np.concatenate(dtrajs_lagged_strided),
                                _np.concatenate(
                                    self.init_hmsm._dtrajs_lagged)).size != 0:
                    raise UserWarning(
                        'Choice of stride has excluded a different set of microstates than in '
                        'init_hmsm. Set of observed microstates in time-lagged strided trajectories '
                        'must match to the one used for init_hmsm estimation.')

                self._dtrajs_full = dtrajs
                self._dtrajs_lagged = dtrajs_lagged_strided
                self._nstates_obs_full = _nstates_obs_full
                self._nstates_obs = _nstates_obs
                self._observable_set = _np.arange(self._nstates_obs)
                self._dtrajs_obs = dtrajs
            else:
                copy_attributes += [
                    '_dtrajs_full', '_dtrajs_lagged', '_nstates_obs_full',
                    '_nstates_obs', '_observable_set', '_dtrajs_obs'
                ]

            # update self with estimates from init_hmsm
            self.__dict__.update({
                k: i
                for k, i in self.init_hmsm.__dict__.items()
                if k in copy_attributes
            })

            # as mentioned in the docstring, take init_hmsm observed set observation probabilities
            self.observe_nonempty = False

            # update HMM Model
            self.update_model_params(
                P=self.init_hmsm.transition_matrix,
                pobs=self.init_hmsm.observation_probabilities,
                dt_model=TimeUnit(self.dt_traj).get_scaled(self.lag))

        # check if we have a valid initial model
        if self.reversible and not is_connected(self.count_matrix):
            raise NotImplementedError(
                'Encountered disconnected count matrix:\n{count_matrix} '
                'with reversible Bayesian HMM sampler using lag={lag}'
                ' and stride={stride}. Consider using shorter lag, '
                'or shorter stride (to use more of the data), '
                'or using a lower value for mincount_connectivity.'.format(
                    count_matrix=self.count_matrix,
                    lag=self.lag,
                    stride=self.stride))

        # here we blow up the output matrix (if needed) to the FULL state space because we want to use dtrajs in the
        # Bayesian HMM sampler. This is just an initialization.
        nstates_full = number_of_states(dtrajs)
        if self.nstates_obs < nstates_full:
            eps = 0.01 / nstates_full  # default output probability, in order to avoid zero columns
            # full state space output matrix. make sure there are no zero columns
            B_init = eps * _np.ones(
                (self.nstates, nstates_full), dtype=_np.float64)
            # fill active states
            B_init[:, self.observable_set] = _np.maximum(
                eps, self.observation_probabilities)
            # renormalize B to make it row-stochastic
            B_init /= B_init.sum(axis=1)[:, None]
        else:
            B_init = self.observation_probabilities

        # HMM sampler
        if self.show_progress:
            self._progress_register(self.nsamples,
                                    description='Sampling HMSMs',
                                    stage=0)

            from deeptime.util.callbacks import ProgressCallback
            outer_self = self

            class BHMMCallback(ProgressCallback):
                def __call__(self, inc=1, *args, **kw):
                    super().__call__(inc, *args, **kw)
                    outer_self._progress_update(1, stage=0)

            progress = BHMMCallback
        else:
            progress = None

        from deeptime.markov.hmm import BayesianHMM

        if self.init_hmsm is not None:
            hmm_mle = self.init_hmsm.hmm
            estimator = BayesianHMM(
                hmm_mle,
                n_samples=self.nsamples,
                stride=self.stride,
                initial_distribution_prior=self.p0_prior,
                transition_matrix_prior=self.transition_matrix_prior,
                store_hidden=self.store_hidden,
                reversible=self.reversible,
                stationary=self.stationary)
        else:
            estimator = BayesianHMM.default(
                dtrajs,
                n_hidden_states=self.nstates,
                lagtime=self.lag,
                n_samples=self.nsamples,
                stride=self.stride,
                initial_distribution_prior=self.p0_prior,
                transition_matrix_prior=self.transition_matrix_prior,
                store_hidden=self.store_hidden,
                reversible=self.reversible,
                stationary=self.stationary,
                prior_submodel=True,
                separate=self.separate)

        estimator.fit(dtrajs, n_burn_in=0, n_thin=1, progress=progress)
        model = estimator.fetch_model()
        if self.show_progress:
            self._progress_force_finish(stage=0)

        # Samples
        sample_inp = [(m.transition_model.transition_matrix,
                       m.transition_model.stationary_distribution,
                       m.output_probabilities) for m in model.samples]

        samples = []
        for P, pi, pobs in sample_inp:  # restrict to observable set if necessary
            Bobs = pobs[:, self.observable_set]
            pobs = Bobs / Bobs.sum(axis=1)[:, None]  # renormalize
            samples.append(_HMSM(P, pobs, pi=pi, dt_model=self.dt_model))

        # store results
        self.sampled_trajs = [
            model.samples[i].hidden_state_trajectories
            for i in range(self.nsamples)
        ]
        self.update_model_params(samples=samples)

        # deal with connectivity
        states_subset = None
        if self.connectivity == 'largest':
            states_subset = 'largest-strong'
        elif self.connectivity == 'populous':
            states_subset = 'populous-strong'
        # OBSERVATION SET
        if self.observe_nonempty:
            observe_subset = 'nonempty'
        else:
            observe_subset = None

        # return submodel (will return self if all None)
        return self.submodel(states=states_subset,
                             obs=observe_subset,
                             mincount_connectivity=self.mincount_connectivity,
                             inplace=True)