def get_averaged_bias_matrix(bias_sequences, dtrajs, nstates=None): from thermotools.util import logsumexp as _logsumexp from thermotools.util import logsumexp_pair as _logsumexp_pair from thermotools.util import kahan_summation as _kahan_summation nmax = int(_np.max([dtraj.max() for dtraj in dtrajs])) if nstates is None: nstates = nmax + 1 elif nstates < nmax + 1: raise ValueError( "nstates is smaller than the number of observed microstates") nthermo = bias_sequences[0].shape[1] bias_matrix = -_np.ones(shape=(nthermo, nstates), dtype=_np.float64) * _np.inf counts = _np.zeros(shape=(nstates, ), dtype=_np.intc) for s in range(len(bias_sequences)): for i in range(nstates): idx = (dtrajs[s] == i) nidx = idx.sum() if nidx == 0: continue counts[i] += nidx selected_bias_sequence = bias_sequences[s][idx, :] for k in range(nthermo): bias_matrix[k, i] = _logsumexp_pair( bias_matrix[k, i], _logsumexp( _np.ascontiguousarray(-selected_bias_sequence[:, k]), inplace=False)) return _np.log(counts)[_np.newaxis, :] - bias_matrix
def set_model_params(self, pi=None, f=None, normalize_f=None, label=None): r"""Call to set all basic model parameters. Parameters ---------- pi : ndarray(n) Stationary distribution. If not already normalized, pi will be scaled to fulfill :math:`\sum_i \pi_i = 1`. The free energies f will then be computed from pi via :math:`f_i = - \log(\pi_i)`. f : ndarray(n) Discrete-state free energies. If normalized_f = True, a constant will be added to normalize the stationary distribution. Otherwise f is left as given. Then, pi will be computed from f via :math:`\pi_i = \exp(-f_i)` and, if necessary, scaled to fulfill :math:`\sum_i \pi_i = 1`. If both (pi and f) are given, f takes precedence over pi. normalize_energy : bool, default=True If parametrized by free energy f, normalize them such that :math:`\sum_i \pi_i = 1`, which is achieved by :math:`\log \sum_i \exp(-f_i) = 0`. label : str, default=None Human-readable description for the thermodynamic state of this model. May contain a temperature description, such as '300 K' or a description of bias energy such as 'unbiased' or 'Umbrella 1'. """ if f is not None: _types.assert_array(f, ndim=1, kind='numeric') f = _np.array(f, dtype=float) if normalize_f: f += _logsumexp( -f ) # normalize on the level on energies to achieve sum_i pi_i = 1 pi = _np.exp(-f) elif pi is not None: # if f is not given, use pi. pi can't be None at this point _types.assert_array(pi, ndim=1, kind='numeric') pi = _np.array(pi, dtype=float) f = -_np.log(pi) f += _logsumexp(-f) # always shift f when set by pi else: raise ValueError( "Trying to initialize model without parameters: both pi (stationary distribution)" \ " and f (free energy) are None. At least one of them needs to be set.") # set parameters (None does not overwrite) self.update_model_params(pi=pi, f=f, normalize_energy=normalize_f, label=label)
def get_averaged_bias_matrix(bias_sequences, dtrajs, nstates=None): r""" Computes a bias matrix via an exponential average of the observed frame wise bias energies. Parameters ---------- bias_sequences : list of numpy.ndarray(T_i, num_therm_states) A single reduced bias energy trajectory or a list of reduced bias energy trajectories. For every simulation frame seen in trajectory i and time step t, btrajs[i][t, k] is the reduced bias energy of that frame evaluated in the k'th thermodynamic state (i.e. at the k'th Umbrella/Hamiltonian/temperature) dtrajs : list of numpy.ndarray(T_i) of int A single discrete trajectory or a list of discrete trajectories. The integers are indexes in 0,...,num_conf_states-1 enumerating the num_conf_states Markov states or the bins the trajectory is in at any time. nstates : int, optional, default=None Number of configuration states. Returns ------- bias_matrix : numpy.ndarray(shape=(num_therm_states, num_conf_states)) object bias_energies_full[j, i] is the bias energy in units of kT for each discrete state i at thermodynamic state j. """ from thermotools.util import logsumexp as _logsumexp from thermotools.util import logsumexp_pair as _logsumexp_pair nmax = int(_np.max([dtraj.max() for dtraj in dtrajs])) if nstates is None: nstates = nmax + 1 elif nstates < nmax + 1: raise ValueError( "nstates is smaller than the number of observed microstates") nthermo = bias_sequences[0].shape[1] bias_matrix = -_np.ones(shape=(nthermo, nstates), dtype=_np.float64) * _np.inf counts = _np.zeros(shape=(nstates, ), dtype=_np.intc) for s in range(len(bias_sequences)): for i in range(nstates): idx = (dtrajs[s] == i) nidx = idx.sum() if nidx == 0: continue counts[i] += nidx selected_bias_sequence = bias_sequences[s][idx, :] for k in range(nthermo): bias_matrix[k, i] = _logsumexp_pair( bias_matrix[k, i], _logsumexp( _np.ascontiguousarray(-selected_bias_sequence[:, k]), inplace=False)) idx = counts.nonzero() log_counts = _np.log(counts[idx]) bias_matrix *= -1.0 bias_matrix[:, idx] += log_counts[_np.newaxis, :] return bias_matrix
def set_model_params(self, pi=None, f=None, normalize_f=True): r""" Parameters ---------- pi : ndarray(n) Stationary distribution. If not already normalized, pi will be scaled to fulfill :math:`\sum_i \pi_i = 1`. The free energies f will be computed from pi via :math:`f_i = - \log(\pi_i)`. Only if normalize_f is True, a constant will be added to ensure consistency with :math:`\sum_i \pi_i = 1`. f : ndarray(n) Discrete-state free energies. If normalized_f = True, a constant will be added to normalize the stationary distribution. Otherwise f is left as given. normalize_f : bool, default=True If parametrized by free energy f, normalize them such that :math:`\sum_i \pi_i = 1`, which is achieved by :math:`\log \sum_i \exp(-f_i) = 0`. label : str, default='ground state' Human-readable description for the thermodynamic state of this model. May contain a temperature description, such as '300 K' or a description of bias energy such as 'unbiased' or 'Umbrella 1' """ # check input if pi is None and f is None: raise ValueError('Trying to initialize model without parameters:' ' Both pi (stationary distribution)' 'and f (free energy) are None.' 'At least one of them needs to be set.') # use f with preference if f is not None: _types.assert_array(f, ndim=1, kind='numeric') f = _np.array(f, dtype=float) if normalize_f: f += _logsumexp( -f ) # normalize on the level on energies to achieve sum_i pi_i = 1 pi = _np.exp(-f) else: # if f is not given, use pi. pi can't be None at this point _types.assert_array(pi, ndim=1, kind='numeric') pi = _np.array(pi, dtype=float) f = -_np.log(pi) pi /= pi.sum() # always normalize pi # set parameters self.update_model_params(pi=pi, f=f, normalize_energy=normalize_f) # set derived quantities self._nstates = len(pi)