MasterMSM 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mastermsm/__init__.py +1 -0
- mastermsm/fewsm/__init__.py +0 -0
- mastermsm/fewsm/fewsm.py +260 -0
- mastermsm/fewsm/fewsm_lib.py +106 -0
- mastermsm/msm/__init__.py +0 -0
- mastermsm/msm/msm.py +1059 -0
- mastermsm/msm/msm_lib.py +1347 -0
- mastermsm/test/__init__.py +0 -0
- mastermsm/test/download_data.py +72 -0
- mastermsm/test/test_fewsm.py +103 -0
- mastermsm/test/test_msm.py +1039 -0
- mastermsm/test/test_trajectory.py +1051 -0
- mastermsm/trajectory/__init__.py +0 -0
- mastermsm/trajectory/traj.py +704 -0
- mastermsm/trajectory/traj_lib.py +459 -0
- mastermsm-2.0.1.dist-info/METADATA +205 -0
- mastermsm-2.0.1.dist-info/RECORD +20 -0
- mastermsm-2.0.1.dist-info/WHEEL +5 -0
- mastermsm-2.0.1.dist-info/licenses/LICENSE +340 -0
- mastermsm-2.0.1.dist-info/top_level.txt +1 -0
mastermsm/msm/msm.py
ADDED
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is part of the MasterMSM package.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
import warnings, sys, os
|
|
6
|
+
import math
|
|
7
|
+
from functools import reduce, cmp_to_key
|
|
8
|
+
import itertools
|
|
9
|
+
import numpy as np
|
|
10
|
+
import scipy.linalg as spla
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
import multiprocessing as mp
|
|
13
|
+
from ..msm import msm_lib
|
|
14
|
+
|
|
15
|
+
class SuperMSM(object):
|
|
16
|
+
"""
|
|
17
|
+
A class for constructing MSMs at multiple lag times
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, trajs, sym=False, sliding=True):
|
|
21
|
+
"""
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
trajs : list
|
|
25
|
+
A list of TimeSeries objects from the trajectory module
|
|
26
|
+
sym : bool
|
|
27
|
+
Whether to enforze detailed balance
|
|
28
|
+
sliding : bool
|
|
29
|
+
Whether a sliding window is used in counts
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
self.data = trajs
|
|
33
|
+
self.dt = msm_lib.max_dt(self.data)
|
|
34
|
+
self._out()
|
|
35
|
+
self.sym = sym
|
|
36
|
+
self.sliding = sliding
|
|
37
|
+
self.msms = {}
|
|
38
|
+
self.keys = msm_lib.merge_trajs(self.data)
|
|
39
|
+
|
|
40
|
+
def _out(self):
|
|
41
|
+
""" Output description to user """
|
|
42
|
+
try:
|
|
43
|
+
print("\n Building SuperMSM from \n", [x.file_name for x \
|
|
44
|
+
in self.data])
|
|
45
|
+
except AttributeError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def do_msm(self, lagts):
|
|
49
|
+
""" Construct MSM for a set of values of the lag time
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
-----------
|
|
53
|
+
lagts : int or list
|
|
54
|
+
A single lag time or a list of lag times
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(lagts, (int, float)):
|
|
58
|
+
lagts = [lagts]
|
|
59
|
+
self.lagts = lagts
|
|
60
|
+
for lagt in lagts:
|
|
61
|
+
self.msms[lagt] = MSM(self.data, lagt=lagt, sym=self.sym,\
|
|
62
|
+
sliding=self.sliding)
|
|
63
|
+
|
|
64
|
+
# estimate count matrix
|
|
65
|
+
self.msms[lagt].calc_count()
|
|
66
|
+
|
|
67
|
+
# estimate transition matrix
|
|
68
|
+
self.msms[lagt].calc_trans()
|
|
69
|
+
|
|
70
|
+
def calc_evals(self, neigs=None, evecs=True, errors=False):
|
|
71
|
+
""" Calculates eigenvalues and optionally eigenvectors
|
|
72
|
+
of the transition matrix
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
neigs : int
|
|
77
|
+
Number of eigenvalues to calculate
|
|
78
|
+
evects : bool
|
|
79
|
+
Whether eigenvectors are desired or not
|
|
80
|
+
evals : bool
|
|
81
|
+
Whether we want bootstrap errors
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
for lagt in self.lagts:
|
|
85
|
+
self.msms[lagt].calc_evals(neigs=neigs, evecs=evecs, errors=errors)
|
|
86
|
+
|
|
87
|
+
def convergence_test(self, time=None, error=False, seed=None, \
|
|
88
|
+
nboots=20, block=5.):
|
|
89
|
+
""" Build MSMs at multiple lag times for an implied timescales test.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
time : list
|
|
94
|
+
Lag times at which to build MSMs.
|
|
95
|
+
error : bool
|
|
96
|
+
Whether to bootstrap errors on timescales and populations.
|
|
97
|
+
seed : int
|
|
98
|
+
Seed for the bootstrap (see ``MSM.boots``).
|
|
99
|
+
nboots : int
|
|
100
|
+
Number of bootstrap samples (see ``MSM.boots``).
|
|
101
|
+
block : float
|
|
102
|
+
Minimum fragment length for the bootstrap, in units of
|
|
103
|
+
the slowest relaxation time (see ``MSM.boots``).
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
for lt in time:
|
|
107
|
+
self.do_msm(lt)
|
|
108
|
+
self.msms[lt].do_trans(evecs=False)
|
|
109
|
+
if error:
|
|
110
|
+
self.msms[lt].boots(seed=seed, nboots=nboots, block=block)
|
|
111
|
+
|
|
112
|
+
def ck_test(self, time=None, init=None):
|
|
113
|
+
""" Chapman-Kolmogorov test comparing MSM predictions with direct MD.
|
|
114
|
+
|
|
115
|
+
Builds MSMs at each lag time in `time`, propagates from `init` states,
|
|
116
|
+
and compares the predicted conditional return probability with the
|
|
117
|
+
value directly measured from the count matrices.
|
|
118
|
+
|
|
119
|
+
Follows the procedure described by Prinz et al. [#]_
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
time : list
|
|
124
|
+
Lag times at which to build MSMs for validation.
|
|
125
|
+
init : list
|
|
126
|
+
State labels used as initial conditions (e.g. ['E']).
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
pMSM : list
|
|
131
|
+
List of (time_array, pop_array) tuples, one per lag time in `time`.
|
|
132
|
+
pMD : ndarray, shape (n, 2)
|
|
133
|
+
Direct MD reference: columns are [lag_time, P(init->init)].
|
|
134
|
+
epMD : ndarray, shape (n,)
|
|
135
|
+
Binomial standard errors on pMD.
|
|
136
|
+
|
|
137
|
+
References
|
|
138
|
+
----------
|
|
139
|
+
.. [#] J.-H. Prinz et al, "Markov models of molecular kinetics: Generation
|
|
140
|
+
and validation", J. Chem. Phys. (2011).
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
maxlag = np.max(time)
|
|
144
|
+
|
|
145
|
+
# Ensure MSMs exist and have transition matrices
|
|
146
|
+
for lt in time:
|
|
147
|
+
if lt not in self.msms or not hasattr(self.msms[lt], 'tauT'):
|
|
148
|
+
self.do_msm(lt)
|
|
149
|
+
self.msms[lt].do_trans(evecs=False)
|
|
150
|
+
|
|
151
|
+
# MSM predictions: propagate T from init states, one curve per lag time
|
|
152
|
+
pMSM = []
|
|
153
|
+
for lt in time:
|
|
154
|
+
msm = self.msms[lt]
|
|
155
|
+
logs = np.linspace(np.log10(lt), np.log10(maxlag * 10), 15)
|
|
156
|
+
t_prop = sorted(set([int(lt * math.floor(x / float(lt)))
|
|
157
|
+
for x in 10 ** logs if x >= lt]))
|
|
158
|
+
tcum, popul = msm.propagateT(init=init, time=t_prop)
|
|
159
|
+
pop_init = np.zeros(len(tcum))
|
|
160
|
+
for k, p in enumerate(popul):
|
|
161
|
+
for s in init:
|
|
162
|
+
if s in msm.keep_keys:
|
|
163
|
+
pop_init[k] += p[msm.keep_keys.index(s)]
|
|
164
|
+
pMSM.append((np.array(tcum), pop_init))
|
|
165
|
+
|
|
166
|
+
# MD reference: conditional return probability from count matrices
|
|
167
|
+
pMD_list = []
|
|
168
|
+
epMD_list = []
|
|
169
|
+
for lt in time:
|
|
170
|
+
msm = self.msms[lt]
|
|
171
|
+
init_idx = [msm.keys.index(s) for s in init if s in msm.keys]
|
|
172
|
+
if not init_idx:
|
|
173
|
+
continue
|
|
174
|
+
num = float(np.sum([msm.count[j, i]
|
|
175
|
+
for (j, i) in itertools.product(init_idx, init_idx)]))
|
|
176
|
+
den = float(np.sum([msm.count[:, i] for i in init_idx]))
|
|
177
|
+
if den > 0:
|
|
178
|
+
p_val = num / den
|
|
179
|
+
err = np.sqrt(p_val * (1.0 - p_val) / den) if den > 1 else 0.0
|
|
180
|
+
pMD_list.append([lt, p_val])
|
|
181
|
+
epMD_list.append(err)
|
|
182
|
+
|
|
183
|
+
return pMSM, np.array(pMD_list), np.array(epMD_list)
|
|
184
|
+
|
|
185
|
+
def do_lbrate(self, evecs=False, error=False):
|
|
186
|
+
""" Calculates the rate matrix using the lifetime based method.
|
|
187
|
+
|
|
188
|
+
We use the method described by Buchete and Hummer [#]_.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
evecs : bool
|
|
193
|
+
Whether we want the left eigenvectors of the rate matrix.
|
|
194
|
+
error : bool
|
|
195
|
+
Whether to include errors or not.
|
|
196
|
+
|
|
197
|
+
References
|
|
198
|
+
----------
|
|
199
|
+
.. [#] N.-V. Buchete and G. Hummer, "Coarse master equations for
|
|
200
|
+
peptide folding dynamics", J. Phys. Chem. B (2008).
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
nkeys = len(self.keys)
|
|
204
|
+
|
|
205
|
+
# define multiprocessing options
|
|
206
|
+
nproc = mp.cpu_count()
|
|
207
|
+
if len(self.data) < nproc:
|
|
208
|
+
nproc = len(self.data)
|
|
209
|
+
pool = mp.Pool(processes=nproc)
|
|
210
|
+
|
|
211
|
+
# generate multiprocessing input
|
|
212
|
+
sliding = True
|
|
213
|
+
mpinput = [[x.distraj, x.dt, self.keys, x.dt, sliding]
|
|
214
|
+
for x in self.data]
|
|
215
|
+
|
|
216
|
+
# run counting using multiprocessing
|
|
217
|
+
result = pool.map(msm_lib.calc_count_worker, mpinput)
|
|
218
|
+
pool.close()
|
|
219
|
+
pool.join()
|
|
220
|
+
|
|
221
|
+
# add up all independent counts
|
|
222
|
+
#count = reduce(lambda x, y: np.matrix(x) + np.matrix(y), result)
|
|
223
|
+
count = np.sum(result, 0)
|
|
224
|
+
|
|
225
|
+
# calculate length of visits
|
|
226
|
+
mpinput = [[x.distraj, x.dt, self.keys] for x in self.data]
|
|
227
|
+
# run counting using multiprocessing
|
|
228
|
+
pool = mp.Pool(processes=nproc)
|
|
229
|
+
result = pool.map(msm_lib.calc_lifetime, mpinput)
|
|
230
|
+
pool.close()
|
|
231
|
+
pool.join()
|
|
232
|
+
# reduce
|
|
233
|
+
life = np.zeros((nkeys), float)
|
|
234
|
+
for k in range(nkeys):
|
|
235
|
+
kk = self.keys[k]
|
|
236
|
+
visits = list(itertools.chain([x[kk] for x in result if kk in x.keys()]))
|
|
237
|
+
if len(visits) > 0:
|
|
238
|
+
life[k] = np.mean([item for lst in visits for item in lst])
|
|
239
|
+
self.life = life
|
|
240
|
+
|
|
241
|
+
# calculate lifetime based rates
|
|
242
|
+
lbrate = np.zeros((nkeys, nkeys), float)
|
|
243
|
+
for i in range(nkeys):
|
|
244
|
+
kk = self.keys[i]
|
|
245
|
+
ni = np.sum([count[x, i] for x in range(nkeys) if x != i])
|
|
246
|
+
if ni > 0:
|
|
247
|
+
for j in range(nkeys):
|
|
248
|
+
lbrate[j, i] = count[j, i] / (ni * life[i])
|
|
249
|
+
lbrate[i, i] = 0.
|
|
250
|
+
lbrate[i, i] = -np.sum(lbrate[:, i])
|
|
251
|
+
|
|
252
|
+
self.lbrate = lbrate
|
|
253
|
+
self.tauK, self.peqK, self.lvecsK, self.rvecsK = \
|
|
254
|
+
msm_lib.calc_eigsK(self.lbrate, evecs=True)
|
|
255
|
+
if error:
|
|
256
|
+
self.lbrate_error = self.lbrate / np.sqrt(count)
|
|
257
|
+
|
|
258
|
+
class MSM(object):
|
|
259
|
+
"""
|
|
260
|
+
A class for constructing an MSM at a specific lag time.
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
def __init__(self, data, keys=None, lagt=None, sliding=True,\
|
|
264
|
+
sym=False):
|
|
265
|
+
"""
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
data : list
|
|
269
|
+
Set of trajectories used for the construction
|
|
270
|
+
keys : list of str
|
|
271
|
+
Set of states in the model
|
|
272
|
+
lag : float
|
|
273
|
+
Lag time for building the MSM
|
|
274
|
+
sym : bool
|
|
275
|
+
Whether we want to enforce symmetrization
|
|
276
|
+
sliding : bool
|
|
277
|
+
Whether a sliding window is used in counts
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
self.data = data
|
|
281
|
+
if not keys:
|
|
282
|
+
self.keys = msm_lib.merge_trajs(self.data)
|
|
283
|
+
else:
|
|
284
|
+
self.keys = keys
|
|
285
|
+
self.lagt = lagt
|
|
286
|
+
self.sliding = sliding
|
|
287
|
+
self.sym = sym
|
|
288
|
+
self._out()
|
|
289
|
+
|
|
290
|
+
def _out(self):
|
|
291
|
+
""" Output description to user """
|
|
292
|
+
try:
|
|
293
|
+
print("\n Building MSM at lag time %g"%self.lagt)
|
|
294
|
+
except AttributeError:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
def calc_count(self, nproc=None, sym=False):
|
|
298
|
+
""" Calculate transition count matrix in parallel
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
nproc : int
|
|
303
|
+
Number of processors to be used
|
|
304
|
+
sym : bool
|
|
305
|
+
Whether we enforce detailed balance
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
# define multiprocessing options
|
|
309
|
+
if not nproc:
|
|
310
|
+
nproc = mp.cpu_count()
|
|
311
|
+
if len(self.data) < nproc:
|
|
312
|
+
nproc = len(self.data)
|
|
313
|
+
# print "\n ...running on %g processors"%nproc
|
|
314
|
+
elif nproc > mp.cpu_count():
|
|
315
|
+
nproc = mp.cpu_count()
|
|
316
|
+
pool = mp.Pool(processes=nproc)
|
|
317
|
+
|
|
318
|
+
# generate multiprocessing input
|
|
319
|
+
mpinput = [[x.distraj, x.dt, self.keys, self.lagt, self.sliding] \
|
|
320
|
+
for x in self.data]
|
|
321
|
+
|
|
322
|
+
# run counting using multiprocessing
|
|
323
|
+
result = pool.map(msm_lib.calc_count_worker, mpinput)
|
|
324
|
+
|
|
325
|
+
pool.close()
|
|
326
|
+
pool.join()
|
|
327
|
+
|
|
328
|
+
# add up all independent counts
|
|
329
|
+
count = np.sum(result, 0)
|
|
330
|
+
|
|
331
|
+
# symmetrize if needed
|
|
332
|
+
if self.sym:
|
|
333
|
+
print(" symmetrizing")
|
|
334
|
+
count += count.transpose()
|
|
335
|
+
self.count = count
|
|
336
|
+
|
|
337
|
+
def calc_trans(self):
|
|
338
|
+
""" Calculates transition matrix
|
|
339
|
+
|
|
340
|
+
"""
|
|
341
|
+
# check connectivity
|
|
342
|
+
keep_states, keep_keys = msm_lib.check_connect(self.count, self.keys)
|
|
343
|
+
self.keep_states, self.keep_keys = keep_states, keep_keys
|
|
344
|
+
|
|
345
|
+
nkeep = len(self.keep_states)
|
|
346
|
+
self.trans = msm_lib.calc_trans(nkeep, keep_states, self.count)
|
|
347
|
+
|
|
348
|
+
def do_trans(self, evecs=True):
|
|
349
|
+
""" Calculate transition matrix and eigendecomposition.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
evecs : bool
|
|
354
|
+
Whether to store eigenvectors.
|
|
355
|
+
|
|
356
|
+
"""
|
|
357
|
+
self.calc_trans()
|
|
358
|
+
self.calc_evals(evecs=evecs)
|
|
359
|
+
|
|
360
|
+
def do_rate(self, method='Taylor', init=False, report=False):
|
|
361
|
+
""" Calculates the rate matrix from the transition matrix.
|
|
362
|
+
|
|
363
|
+
We use a method based on a Taylor expansion [#]_ or the maximum likelihood
|
|
364
|
+
propagator based (MLPB) method [2]_.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
method : str
|
|
369
|
+
Which method one wants to use. Acceptable values are 'Taylor'
|
|
370
|
+
and 'MLPB'.
|
|
371
|
+
init : array
|
|
372
|
+
Rate matrix to start optimization from.
|
|
373
|
+
report : bool
|
|
374
|
+
Whether to report the results from MC in MLPB.
|
|
375
|
+
|
|
376
|
+
References
|
|
377
|
+
----------
|
|
378
|
+
.. [#] D. De Sancho, J. Mittal and R. B. Best, "Folding kinetics
|
|
379
|
+
and unfolded state dynamics of the GB1 hairpin from molecular
|
|
380
|
+
simulation", J. Chem. Theory Comput. (2013).
|
|
381
|
+
.. [2] N.-V. Buchete and G. Hummer, "Coarse master equations for
|
|
382
|
+
peptide folding dynamics", J. Phys. Chem. B (2008).
|
|
383
|
+
|
|
384
|
+
"""
|
|
385
|
+
# print "\n Calculating rate matrix ..."
|
|
386
|
+
nkeep = len(self.keep_states)
|
|
387
|
+
if method == 'Taylor':
|
|
388
|
+
self.rate = msm_lib.calc_rate(nkeep, self.trans, self.lagt)
|
|
389
|
+
elif method == 'MLPB':
|
|
390
|
+
if isinstance(init, np.ndarray):
|
|
391
|
+
rate_init = init
|
|
392
|
+
elif init == 'random':
|
|
393
|
+
rate_init = np.random.rand(nkeep, nkeep) * 1.e-2
|
|
394
|
+
else:
|
|
395
|
+
rate_init = msm_lib.calc_rate(nkeep, self.trans, self.lagt)
|
|
396
|
+
# counts restricted to the connected set, as in calc_trans
|
|
397
|
+
keep = self.keep_states
|
|
398
|
+
count = np.asarray(self.count)[np.ix_(keep, keep)]
|
|
399
|
+
self.rate, ml, beta = msm_lib.calc_mlrate(nkeep, count, self.lagt,\
|
|
400
|
+
rate_init)
|
|
401
|
+
if report:
|
|
402
|
+
_, ax = plt.subplots(2, 1, sharex=True)
|
|
403
|
+
ax[0].plot(ml)
|
|
404
|
+
ax[0].plot(np.ones(len(ml)) * ml[0], '--')
|
|
405
|
+
ax[0].set_ylabel('-ln($L$)')
|
|
406
|
+
ax[1].plot(beta)
|
|
407
|
+
# ax[1].set_ylim(0,1.05)
|
|
408
|
+
ax[1].set_xlim(0, len(ml))
|
|
409
|
+
ax[1].set_xlabel('MC steps x n$_{freq}$')
|
|
410
|
+
ax[1].set_ylabel(r'1/$\beta$')
|
|
411
|
+
|
|
412
|
+
self.tauK, self.peqK, self.rvecsK, self.lvecsK = \
|
|
413
|
+
msm_lib.calc_eigsK(self.rate, evecs=True)
|
|
414
|
+
|
|
415
|
+
def calc_evals(self, neigs=None, evecs=True, errors=False):
|
|
416
|
+
""" Calculates eigenvalues and optionally eigenvectors
|
|
417
|
+
of the transition matrix
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
neigs : int
|
|
422
|
+
Number of eigenvalues to calculate
|
|
423
|
+
evects : bool
|
|
424
|
+
Whether eigenvectors are desired or not
|
|
425
|
+
evals : bool
|
|
426
|
+
Whether we want bootstrap errors
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
evals, lvecs, rvecs = msm_lib.calc_eigsT(self.trans)
|
|
430
|
+
|
|
431
|
+
# relaxation times
|
|
432
|
+
if not neigs:
|
|
433
|
+
neigs = len(evals) - 1
|
|
434
|
+
self.tauT = msm_lib.calc_tauT(evals, self.lagt)[:neigs]
|
|
435
|
+
|
|
436
|
+
# equilibrium probabilities
|
|
437
|
+
self.peqT = np.real(rvecs[:,0]/np.sum(rvecs[:,0]))
|
|
438
|
+
|
|
439
|
+
if evecs:
|
|
440
|
+
self.lvecsT = lvecs
|
|
441
|
+
self.rvecsT = rvecs
|
|
442
|
+
|
|
443
|
+
if errors:
|
|
444
|
+
self.boots(neigs=neigs)
|
|
445
|
+
|
|
446
|
+
def boots(self, neigs=None, seed=None, nboots=20, block=5.):
|
|
447
|
+
""" Bootstrap the simulation data to calculate errors.
|
|
448
|
+
|
|
449
|
+
The trajectories are cut into fragments, which are resampled
|
|
450
|
+
with replacement until the count matrix has as many
|
|
451
|
+
transitions as the original. The resampled counts are
|
|
452
|
+
symmetrized if the model is (``sym``), so that the errors
|
|
453
|
+
correspond to the same estimator.
|
|
454
|
+
|
|
455
|
+
Fragments must be long compared to the slowest relaxation
|
|
456
|
+
time, so that they are approximately independent: each is at
|
|
457
|
+
least ``block`` times the slowest relaxation time of the
|
|
458
|
+
model (and at least ten lag times). Long relaxation times
|
|
459
|
+
then leave few fragments; with fewer than eight, the errors
|
|
460
|
+
are unreliable and a warning is issued. The number of
|
|
461
|
+
fragments is stored in ``nfrag``.
|
|
462
|
+
|
|
463
|
+
Each relaxation time is averaged over the bootstrap samples
|
|
464
|
+
that have it (samples can lose states, and hence relaxation
|
|
465
|
+
times, when the resampled counts are not fully connected).
|
|
466
|
+
The number of samples behind each value is stored in
|
|
467
|
+
``tau_nboots``. As the distributions can be skewed, the
|
|
468
|
+
16th and 84th percentiles are stored in ``tau_ci`` besides
|
|
469
|
+
the mean (``tau_ave``) and standard deviation (``tau_std``).
|
|
470
|
+
|
|
471
|
+
Parameters
|
|
472
|
+
----------
|
|
473
|
+
neigs : int
|
|
474
|
+
Number of eigenvalues to calculate
|
|
475
|
+
seed : int
|
|
476
|
+
Seed for the bootstrap, for reproducible errors. Each
|
|
477
|
+
sample gets its own independent stream. If None, the
|
|
478
|
+
seeds are drawn from numpy's global generator, so that
|
|
479
|
+
``np.random.seed`` in the calling code also makes the
|
|
480
|
+
errors reproducible.
|
|
481
|
+
nboots : int
|
|
482
|
+
Number of bootstrap samples.
|
|
483
|
+
block : float
|
|
484
|
+
Minimum fragment length, in units of the slowest
|
|
485
|
+
relaxation time of the model.
|
|
486
|
+
|
|
487
|
+
"""
|
|
488
|
+
if neigs is None:
|
|
489
|
+
neigs = len(self.keep_states) - 1
|
|
490
|
+
|
|
491
|
+
# fragments long compared to the slowest relaxation time
|
|
492
|
+
tau1 = self.tauT[0] if len(self.tauT) > 0 else 0.
|
|
493
|
+
if not np.isfinite(tau1):
|
|
494
|
+
raise ValueError("slowest relaxation time is not finite")
|
|
495
|
+
filetmp, self.nfrag = msm_lib.traj_split(data=self.data, \
|
|
496
|
+
lagt=self.lagt, tmin=block*tau1)
|
|
497
|
+
if self.nfrag < 8:
|
|
498
|
+
warnings.warn("bootstrap at lag time %g uses only %i "
|
|
499
|
+
"fragments of at least %g x the slowest relaxation "
|
|
500
|
+
"time (%g); errors are unreliable"%(self.lagt, \
|
|
501
|
+
self.nfrag, block, tau1))
|
|
502
|
+
|
|
503
|
+
# one independent random stream per bootstrap sample
|
|
504
|
+
if seed is None:
|
|
505
|
+
seed = int(np.random.randint(2**31 - 1))
|
|
506
|
+
seeds = np.random.SeedSequence(seed).spawn(nboots)
|
|
507
|
+
|
|
508
|
+
# number of transitions in the data; a symmetrized count
|
|
509
|
+
# matrix holds every transition twice
|
|
510
|
+
ncount = np.sum(self.count)
|
|
511
|
+
if self.sym:
|
|
512
|
+
ncount = ncount/2.
|
|
513
|
+
multi_boots_input = [[filetmp, self.keys, self.lagt, ncount, \
|
|
514
|
+
self.sliding, neigs, seeds[x], self.sym] \
|
|
515
|
+
for x in range(nboots)]
|
|
516
|
+
|
|
517
|
+
# multiprocessing options
|
|
518
|
+
nproc = min(mp.cpu_count(), nboots)
|
|
519
|
+
pool = mp.Pool(processes=nproc)
|
|
520
|
+
try:
|
|
521
|
+
result = pool.map(msm_lib.do_boots_worker, multi_boots_input)
|
|
522
|
+
finally:
|
|
523
|
+
pool.close()
|
|
524
|
+
pool.join()
|
|
525
|
+
os.remove(filetmp)
|
|
526
|
+
|
|
527
|
+
tauT_boots = []
|
|
528
|
+
peqT_boots = []
|
|
529
|
+
keep_keys_boots = []
|
|
530
|
+
for r in result:
|
|
531
|
+
trans_boots, keep_keys = r[0], r[1]
|
|
532
|
+
evals, lvecs, rvecs = msm_lib.calc_eigsT(trans_boots)
|
|
533
|
+
tauT_boots.append(
|
|
534
|
+
msm_lib.calc_tauT(evals, self.lagt)[:neigs])
|
|
535
|
+
peqT_boots.append(rvecs[:,0]/np.sum(rvecs[:,0]))
|
|
536
|
+
keep_keys_boots.append(keep_keys)
|
|
537
|
+
|
|
538
|
+
self.peq_ave, self.peq_std = msm_lib.peq_averages(peqT_boots, \
|
|
539
|
+
keep_keys_boots, self.keys)
|
|
540
|
+
self.tau_ave, self.tau_std, self.tau_nboots, self.tau_ci = \
|
|
541
|
+
msm_lib.tau_averages(tauT_boots, ntau=neigs)
|
|
542
|
+
|
|
543
|
+
def do_pfold(self, FF=None, UU=None, dot=False):
|
|
544
|
+
""" Wrapper to calculate reactive fluxes and committors
|
|
545
|
+
|
|
546
|
+
We use the Berzhkovskii-Hummer-Szabo method, J. Chem. Phys. (2009)
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
FF : list
|
|
551
|
+
Folded states.
|
|
552
|
+
UU : list
|
|
553
|
+
Unfolded states.
|
|
554
|
+
dot : string
|
|
555
|
+
Filename to output dot graph.
|
|
556
|
+
|
|
557
|
+
"""
|
|
558
|
+
# print "\n Calculating commitment probabilities and fluxes..."
|
|
559
|
+
_states = range(len(self.keep_states))
|
|
560
|
+
if isinstance(FF, list):
|
|
561
|
+
_FF = [self.keep_keys.index(x) for x in FF]
|
|
562
|
+
else:
|
|
563
|
+
_FF = [self.keep_keys.index(FF)]
|
|
564
|
+
if isinstance(UU, list):
|
|
565
|
+
_UU = [self.keep_keys.index(x) for x in UU]
|
|
566
|
+
else:
|
|
567
|
+
_UU = [self.keep_keys.index(UU)]
|
|
568
|
+
|
|
569
|
+
# do the calculation
|
|
570
|
+
self.J, self.pfold, self.sum_flux, self.kf = \
|
|
571
|
+
msm_lib.run_commit(_states, self.rate, self.peqK, _FF, _UU)
|
|
572
|
+
|
|
573
|
+
# def all_paths(self, FF=None, UU=None, out="graph.dot", cut=1):
|
|
574
|
+
# """ Enumerate all paths in network.
|
|
575
|
+
#
|
|
576
|
+
# Parameters
|
|
577
|
+
# ----------
|
|
578
|
+
# FF : list
|
|
579
|
+
# Folded states, currently limited to just one.
|
|
580
|
+
# UU : list
|
|
581
|
+
# Unfolded states, currently limited to just one.
|
|
582
|
+
#
|
|
583
|
+
# """
|
|
584
|
+
# nkeys = len(self.keep_keys)
|
|
585
|
+
# pfold = self.pfold
|
|
586
|
+
# J = self.J
|
|
587
|
+
# flux = self.sum_flux
|
|
588
|
+
#
|
|
589
|
+
# print "\n Enumerating all paths :\n"
|
|
590
|
+
# print " Total flux", flux
|
|
591
|
+
# if isinstance(FF, list):
|
|
592
|
+
# _FF = [self.keep_keys.index(x) for x in FF]
|
|
593
|
+
# else:
|
|
594
|
+
# _FF = [self.keep_keys.index(FF)]
|
|
595
|
+
# if isinstance(UU, list):
|
|
596
|
+
# _UU = [self.keep_keys.index(x) for x in UU]
|
|
597
|
+
# else:
|
|
598
|
+
# _UU = [self.keep_keys.index(UU)]
|
|
599
|
+
#
|
|
600
|
+
# # generate graph from flux matrix
|
|
601
|
+
# Jnode, Jpath = msm_lib.gen_path_lengths(range(nkeys), J, pfold, \
|
|
602
|
+
# flux, _FF, _UU)
|
|
603
|
+
# JpathG = nx.DiGraph(Jpath.transpose())
|
|
604
|
+
#
|
|
605
|
+
# # enumerate paths
|
|
606
|
+
# tot_flux = 0
|
|
607
|
+
# paths = []
|
|
608
|
+
# Jcum = np.zeros((nkeys, nkeys), float)
|
|
609
|
+
# paths_cum = {}
|
|
610
|
+
# p = 0
|
|
611
|
+
# for (j,i) in itertools.product(_FF,_UU):
|
|
612
|
+
# try:
|
|
613
|
+
# for path in nx.all_simple_paths(JpathG, i, j):
|
|
614
|
+
# print " Path :",path
|
|
615
|
+
# f = J[path[1],path[0]]
|
|
616
|
+
# #print " %2i -> %2i: %10.4e "%(path[0], path[1], J[path[1],path[0]])
|
|
617
|
+
# for i in range(2, len(path)):
|
|
618
|
+
# #print " %2i -> %2i: %10.4e %10.4e"%\
|
|
619
|
+
# # (path[i-1], path[i], J[path[i],path[i-1]], Jnode[path[i-1]])
|
|
620
|
+
# f *= J[path[i],path[i-1]]/Jnode[path[i-1]]
|
|
621
|
+
# tot_flux +=f
|
|
622
|
+
# #print " J(path) = %10.4e, %10.4e"%(f,tot_flux)
|
|
623
|
+
# paths_cum[p] = {}
|
|
624
|
+
# paths_cum[p]['path'] = path
|
|
625
|
+
# paths_cum[p]['flux'] = f
|
|
626
|
+
# p +=1
|
|
627
|
+
#
|
|
628
|
+
# except nx.NetworkXNoPath:
|
|
629
|
+
# #print " No path for %g -> %g\n Stopping here"%(i, j)
|
|
630
|
+
# pass
|
|
631
|
+
# # store flux in matrix
|
|
632
|
+
#
|
|
633
|
+
# print " Commulative flux: %10.4e"%tot_flux
|
|
634
|
+
#
|
|
635
|
+
# # sort paths based on flux
|
|
636
|
+
# sorted_paths = sorted(paths_cum.items(), key=operator.itemgetter(1))
|
|
637
|
+
# sorted_paths.reverse()
|
|
638
|
+
#
|
|
639
|
+
# # print paths up to a flux threshold
|
|
640
|
+
# cum = 0
|
|
641
|
+
# for k,v in sorted_paths:
|
|
642
|
+
# path = v['path']
|
|
643
|
+
# f = v['flux']
|
|
644
|
+
# cum += f
|
|
645
|
+
# for j in range(1, len(path)):
|
|
646
|
+
# i = j - 1
|
|
647
|
+
# Jcum[path[j],path[i]] = J[path[j],path[i]]
|
|
648
|
+
# if cum/tot_flux > cut:
|
|
649
|
+
# break
|
|
650
|
+
# print Jcum
|
|
651
|
+
# visual_lib.write_dot(Jcum, nodeweight=self.peqK, \
|
|
652
|
+
# rank=pfold, out=out)
|
|
653
|
+
#
|
|
654
|
+
# def do_dijkstra(self, FF=None, UU=None, cut=None, npath=None, out="graph.dot"):
|
|
655
|
+
# """ Obtaining the maximum flux path wrapping NetworkX's Dikjstra algorithm.
|
|
656
|
+
#
|
|
657
|
+
# Parameters
|
|
658
|
+
# ----------
|
|
659
|
+
# FF : list
|
|
660
|
+
# Folded states, currently limited to just one.
|
|
661
|
+
# UU : list
|
|
662
|
+
# Unfolded states, currently limited to just one.
|
|
663
|
+
# cut : float
|
|
664
|
+
# Percentage of flux to account for.
|
|
665
|
+
#
|
|
666
|
+
# """
|
|
667
|
+
# nkeys = len(self.keep_keys)
|
|
668
|
+
# pfold = self.pfold
|
|
669
|
+
# J = copy.deepcopy(self.J)
|
|
670
|
+
# print J
|
|
671
|
+
# flux = self.sum_flux
|
|
672
|
+
# print "\n Finding shortest path :\n"
|
|
673
|
+
# print " Total flux", flux
|
|
674
|
+
# if isinstance(FF, list):
|
|
675
|
+
# _FF = [self.keep_keys.index(x) for x in FF]
|
|
676
|
+
# else:
|
|
677
|
+
# _FF = [self.keep_keys.index(FF)]
|
|
678
|
+
# if isinstance(UU, list):
|
|
679
|
+
# _UU = [self.keep_keys.index(x) for x in UU]
|
|
680
|
+
# else:
|
|
681
|
+
# _UU = [self.keep_keys.index(UU)]
|
|
682
|
+
#
|
|
683
|
+
# # generate graph from flux matrix
|
|
684
|
+
# Jnode, Jpath = msm_lib.gen_path_lengths(range(nkeys), J, pfold, \
|
|
685
|
+
# flux, _FF, _UU)
|
|
686
|
+
# Jnode_init = Jnode
|
|
687
|
+
# JpathG = nx.DiGraph(Jpath.transpose())
|
|
688
|
+
#
|
|
689
|
+
# # find shortest paths
|
|
690
|
+
# Jcum = np.zeros((nkeys, nkeys), float)
|
|
691
|
+
# cum_paths = []
|
|
692
|
+
# n = 0
|
|
693
|
+
# tot_flux = 0
|
|
694
|
+
# while True:
|
|
695
|
+
# n +=1
|
|
696
|
+
# Jnode, Jpath = msm_lib.gen_path_lengths(range(nkeys), J, pfold, \
|
|
697
|
+
# flux, _FF, _UU)
|
|
698
|
+
# # generate nx graph from matrix
|
|
699
|
+
# JpathG = nx.DiGraph(Jpath.transpose())
|
|
700
|
+
# # find shortest path for sets of end states
|
|
701
|
+
# paths = []
|
|
702
|
+
# for (j,i) in itertools.product(_FF,_UU):
|
|
703
|
+
# try:
|
|
704
|
+
# path = nx.dijkstra_path(JpathG, i, j)
|
|
705
|
+
# pathlength = nx.dijkstra_path_length(JpathG, i, j)
|
|
706
|
+
# #print " shortest path:", path, pathlength
|
|
707
|
+
# paths.append(((j,i), path, pathlength))
|
|
708
|
+
# except nx.NetworkXNoPath:
|
|
709
|
+
# #print " No path for %g -> %g\n Stopping here"%(i, j)
|
|
710
|
+
# pass
|
|
711
|
+
#
|
|
712
|
+
# # sort maximum flux paths
|
|
713
|
+
# try:
|
|
714
|
+
# shortest_path = sorted(paths, key=itemgetter(2))[0]
|
|
715
|
+
# except IndexError:
|
|
716
|
+
# print " No paths"
|
|
717
|
+
# break
|
|
718
|
+
#
|
|
719
|
+
# # calculate contribution to flux
|
|
720
|
+
# sp = shortest_path[1]
|
|
721
|
+
# f = self.J[sp[1],sp[0]]
|
|
722
|
+
# path_fluxes = [f]
|
|
723
|
+
# print ' Path :', sp
|
|
724
|
+
# print "%2i -> %2i: %10.4e "%(sp[0], sp[1], self.J[sp[1],sp[0]])
|
|
725
|
+
# for j in range(2, len(sp)):
|
|
726
|
+
# i = j - 1
|
|
727
|
+
# print "%2i -> %2i: %10.4e %10.4e"%(sp[i], sp[j], \
|
|
728
|
+
# self.J[sp[j],sp[i]], Jnode_init[sp[i]])
|
|
729
|
+
# f *= self.J[sp[j],sp[i]]/Jnode_init[sp[i]]
|
|
730
|
+
# path_fluxes.append(J[sp[j],sp[i]])
|
|
731
|
+
#
|
|
732
|
+
# # find bottleneck
|
|
733
|
+
# ib = np.argmin(path_fluxes)
|
|
734
|
+
#
|
|
735
|
+
# # remove flux from edges
|
|
736
|
+
# for j in range(1,len(sp)):
|
|
737
|
+
# i = j - 1
|
|
738
|
+
# J[sp[j],sp[i]] -= f
|
|
739
|
+
# if J[sp[j],sp[i]] < 0:
|
|
740
|
+
# J[sp[j],sp[i]] = 0.
|
|
741
|
+
# J[sp[ib+1],sp[ib]] = 0. # bottleneck leftover flux = 0.
|
|
742
|
+
#
|
|
743
|
+
# # store flux in matrix
|
|
744
|
+
# for j in range(1, len(sp)):
|
|
745
|
+
# i = j - 1
|
|
746
|
+
# Jcum[sp[j],sp[i]] += f
|
|
747
|
+
#
|
|
748
|
+
# flux -= f
|
|
749
|
+
# tot_flux +=f
|
|
750
|
+
# cum_paths.append((shortest_path, f))
|
|
751
|
+
# print ' flux: %4.2e; ratio: %4.2e; leftover: %4.2e'%(f, f/self.sum_flux,flux/self.sum_flux)
|
|
752
|
+
# #print ' leftover flux: %10.4e\n'%flux
|
|
753
|
+
# if cut is not None:
|
|
754
|
+
# if flux/self.sum_flux < cut:
|
|
755
|
+
# break
|
|
756
|
+
#
|
|
757
|
+
# if npath is not None:
|
|
758
|
+
# if n == npath:
|
|
759
|
+
# break
|
|
760
|
+
#
|
|
761
|
+
# print "\n Commulative flux: %10.4e"%tot_flux
|
|
762
|
+
# print " Fraction: %10.4e"%(tot_flux/self.sum_flux)
|
|
763
|
+
## visual_lib.write_dot(Jcum, nodeweight=self.peqK, \
|
|
764
|
+
## rank=pfold, out=out)
|
|
765
|
+
#
|
|
766
|
+
# return cum_paths
|
|
767
|
+
|
|
768
|
+
def sensitivity(self, FF=None, UU=None, dot=False):
|
|
769
|
+
""" Sensitivity analysis of the states in the network.
|
|
770
|
+
|
|
771
|
+
We use the procedure described by De Sancho, Kubas,
|
|
772
|
+
Blumberger and Best [#]_.
|
|
773
|
+
|
|
774
|
+
Parameters
|
|
775
|
+
----------
|
|
776
|
+
FF : list
|
|
777
|
+
Folded states.
|
|
778
|
+
UU : list
|
|
779
|
+
Unfolded states.
|
|
780
|
+
|
|
781
|
+
Returns
|
|
782
|
+
-------
|
|
783
|
+
dJ : list
|
|
784
|
+
Derivative of flux.
|
|
785
|
+
d_peq : list
|
|
786
|
+
Derivative of equilibrium populations.
|
|
787
|
+
d_kf : list
|
|
788
|
+
Derivative of global rate.
|
|
789
|
+
kf : float
|
|
790
|
+
Global rate.
|
|
791
|
+
|
|
792
|
+
References
|
|
793
|
+
----------
|
|
794
|
+
.. [#] D. De Sancho, A. Kubas, P.-H. Wang, J. Blumberger, R. B.
|
|
795
|
+
Best "Identification of mutational hot spots for substrate diffusion:
|
|
796
|
+
Application to myoglobin", J. Chem. Theory Comput. (2015).
|
|
797
|
+
|
|
798
|
+
"""
|
|
799
|
+
nkeep = len(self.keep_states)
|
|
800
|
+
K = self.rate
|
|
801
|
+
peqK = self.peqK
|
|
802
|
+
|
|
803
|
+
# calculate pfold
|
|
804
|
+
self.do_pfold(FF=FF, UU=UU)
|
|
805
|
+
pfold = self.pfold
|
|
806
|
+
sum_flux = self.sum_flux
|
|
807
|
+
kf = self.kf
|
|
808
|
+
|
|
809
|
+
if isinstance(FF, list):
|
|
810
|
+
_FF = [self.keep_keys.index(x) for x in FF]
|
|
811
|
+
else:
|
|
812
|
+
_FF = [self.keep_keys.index(FF)]
|
|
813
|
+
if isinstance(UU, list):
|
|
814
|
+
_UU = [self.keep_keys.index(x) for x in UU]
|
|
815
|
+
else:
|
|
816
|
+
_UU = [self.keep_keys.index(UU)]
|
|
817
|
+
|
|
818
|
+
pu = np.sum([peqK[x] for x in range(nkeep) if pfold[x] < 0.5])
|
|
819
|
+
dJ = []
|
|
820
|
+
d_pu = []
|
|
821
|
+
d_lnkf = []
|
|
822
|
+
for s in range(nkeep):
|
|
823
|
+
d_K = msm_lib.partial_rate(K, s)
|
|
824
|
+
d_peq = msm_lib.partial_peq(peqK, s)
|
|
825
|
+
d_pfold = msm_lib.partial_pfold(range(nkeep), K, d_K, _FF, _UU, s)
|
|
826
|
+
dJ.append(msm_lib.partial_flux(range(nkeep), peqK, K, pfold, \
|
|
827
|
+
d_peq, d_K, d_pfold, _FF))
|
|
828
|
+
d_pu.append(np.sum([d_peq[x] for x in range(nkeep) \
|
|
829
|
+
if pfold[x] < 0.5]))
|
|
830
|
+
d_lnkf.append((dJ[-1] * pu - sum_flux * d_pu[-1]) / pu ** 2)
|
|
831
|
+
self.kf = kf
|
|
832
|
+
self.d_pu = d_pu
|
|
833
|
+
self.dJ = dJ
|
|
834
|
+
self.d_lnkf = d_lnkf / kf
|
|
835
|
+
|
|
836
|
+
def propagateK(self, p0=None, init=None, time=None):
|
|
837
|
+
""" Propagation of rate matrix using matrix exponential
|
|
838
|
+
|
|
839
|
+
Parameters
|
|
840
|
+
----------
|
|
841
|
+
p0 : string
|
|
842
|
+
Filename with initial population.
|
|
843
|
+
init : string
|
|
844
|
+
State(s) where the population is initialized.
|
|
845
|
+
time : list, array
|
|
846
|
+
User defined range of temperatures for propagating the dynamics.
|
|
847
|
+
|
|
848
|
+
Returns
|
|
849
|
+
-------
|
|
850
|
+
popul : array
|
|
851
|
+
Population of all states as a function of time.
|
|
852
|
+
pnorm : array
|
|
853
|
+
Population of all states as a function of time - normalized.
|
|
854
|
+
|
|
855
|
+
"""
|
|
856
|
+
# defining times for relaxation
|
|
857
|
+
try:
|
|
858
|
+
assert (time is None)
|
|
859
|
+
tmin = self.lagt
|
|
860
|
+
tmax = 1e4 * self.lagt
|
|
861
|
+
logt = np.arange(np.log10(tmin), np.log10(tmax), 0.2)
|
|
862
|
+
time = 10 ** logt
|
|
863
|
+
except:
|
|
864
|
+
time = np.array(time)
|
|
865
|
+
nkeep = len(self.keep_states)
|
|
866
|
+
if p0 is not None:
|
|
867
|
+
try:
|
|
868
|
+
pini = np.array(p0)
|
|
869
|
+
except TypeError:
|
|
870
|
+
try:
|
|
871
|
+
# print " reading initial population from file: %s"%p0
|
|
872
|
+
pini = [float(y) for y in \
|
|
873
|
+
filter(lambda x: x.split()[0] not in ["#", "@"],
|
|
874
|
+
open(p0, "r").readlines())]
|
|
875
|
+
except TypeError:
|
|
876
|
+
print(" p0 is not file")
|
|
877
|
+
print(" exiting here")
|
|
878
|
+
return
|
|
879
|
+
elif init is not None:
|
|
880
|
+
# print " initializing all population in states"
|
|
881
|
+
# print init
|
|
882
|
+
pini = [self.peqT[x] if self.keep_keys[x] in init else 0. for x in range(nkeep)]
|
|
883
|
+
# check normalization and size
|
|
884
|
+
if len(pini) != nkeep:
|
|
885
|
+
print(" initial population vector and state space have different sizes")
|
|
886
|
+
print(" stopping here")
|
|
887
|
+
return
|
|
888
|
+
else:
|
|
889
|
+
sum_pini = np.sum(pini)
|
|
890
|
+
pini_norm = [float(x) / sum_pini for x in pini]
|
|
891
|
+
|
|
892
|
+
# propagate rate matrix : parallel version
|
|
893
|
+
popul = []
|
|
894
|
+
for t in time:
|
|
895
|
+
x = [self.rate, t, pini_norm]
|
|
896
|
+
popul.append(msm_lib.propagate_worker(x))
|
|
897
|
+
# nproc = mp.cpu_count()
|
|
898
|
+
# pool = mp.Pool(processes=nproc)
|
|
899
|
+
# pool_input = [(self.rate, t, pini_norm) for t in time]
|
|
900
|
+
# popul = pool.map(msm_lib.propagate_worker, tuple(pool_input))
|
|
901
|
+
# pool.close()
|
|
902
|
+
# pool.join()
|
|
903
|
+
|
|
904
|
+
## normalize relaxation
|
|
905
|
+
# imax = np.argmax(ptot)
|
|
906
|
+
# maxpop = ptot[imax]
|
|
907
|
+
# imin = np.argmin(ptot)
|
|
908
|
+
# minpop = ptot[imin]
|
|
909
|
+
# if imax < imin:
|
|
910
|
+
# pnorm = map(lambda x: (x-minpop)/(maxpop-minpop), ptot)
|
|
911
|
+
# else:
|
|
912
|
+
# pnorm = map(lambda x: 1 - (x-minpop)/(maxpop-minpop), ptot)
|
|
913
|
+
return time, popul # popul #, pnorm
|
|
914
|
+
|
|
915
|
+
def propagateT(self, p0=None, init=None, time=None):
|
|
916
|
+
"""
|
|
917
|
+
Propagation of transition matrix
|
|
918
|
+
|
|
919
|
+
Parameters
|
|
920
|
+
----------
|
|
921
|
+
p0 : string
|
|
922
|
+
Filename with initial population.
|
|
923
|
+
init : string
|
|
924
|
+
State(s) where the population is initialized.
|
|
925
|
+
time : list, int
|
|
926
|
+
User defined range of temperatures for propagating the dynamics.
|
|
927
|
+
|
|
928
|
+
Returns
|
|
929
|
+
-------
|
|
930
|
+
popul : array
|
|
931
|
+
Population of all states as a function of time.
|
|
932
|
+
pnorm : array
|
|
933
|
+
Population of all states as a function of time - normalized.
|
|
934
|
+
|
|
935
|
+
Notes
|
|
936
|
+
-----
|
|
937
|
+
There is probably just one essential difference between propagateT
|
|
938
|
+
and propagateK. We are obtaining the time evolution of the population
|
|
939
|
+
towards the equilibrium distribution. Using K, we can obtain the
|
|
940
|
+
population at any given time (P(t) = exp(Kt)P0), while here we are
|
|
941
|
+
limited to powers of the transition matrix (hence,
|
|
942
|
+
P(nt) = [T(t)**n]*P0).
|
|
943
|
+
|
|
944
|
+
"""
|
|
945
|
+
# defining times for relaxation
|
|
946
|
+
try:
|
|
947
|
+
assert (time is None)
|
|
948
|
+
tmin = self.lagt
|
|
949
|
+
tmax = 1e4 * self.lagt
|
|
950
|
+
logt = np.arange(np.log10(tmin), np.log10(tmax), 0.2)
|
|
951
|
+
time = 10 ** logt
|
|
952
|
+
except:
|
|
953
|
+
time = np.array(time)
|
|
954
|
+
nkeep = len(self.keep_states)
|
|
955
|
+
if p0 is not None:
|
|
956
|
+
try:
|
|
957
|
+
pini = np.array(p0)
|
|
958
|
+
except TypeError:
|
|
959
|
+
try:
|
|
960
|
+
# print " reading initial population from file: %s"%p0
|
|
961
|
+
pini = [float(y) for y in \
|
|
962
|
+
filter(lambda x: x.split()[0] not in ["#", "@"],
|
|
963
|
+
open(p0, "r").readlines())]
|
|
964
|
+
except TypeError:
|
|
965
|
+
print(" p0 is not file")
|
|
966
|
+
print(" exiting here")
|
|
967
|
+
return
|
|
968
|
+
elif init is not None:
|
|
969
|
+
# print " initializing all population in states"
|
|
970
|
+
# print init
|
|
971
|
+
pini = [self.peqT[x] if self.keep_keys[x] in init else 0. for x in range(nkeep)]
|
|
972
|
+
# check normalization and size
|
|
973
|
+
if len(pini) != nkeep:
|
|
974
|
+
print(" initial population vector and state space have different sizes")
|
|
975
|
+
print(" stopping here")
|
|
976
|
+
return
|
|
977
|
+
else:
|
|
978
|
+
sum_pini = np.sum(pini)
|
|
979
|
+
pini_norm = [float(x) / sum_pini for x in pini]
|
|
980
|
+
|
|
981
|
+
# propagate transition matrix : parallel version
|
|
982
|
+
popul = []
|
|
983
|
+
tcum = []
|
|
984
|
+
for t in time:
|
|
985
|
+
power = int(t / self.lagt)
|
|
986
|
+
tcum.append(self.lagt * power)
|
|
987
|
+
x = [self.trans, power, pini_norm]
|
|
988
|
+
popul.append(msm_lib.propagateT_worker(x))
|
|
989
|
+
# nproc = mp.cpu_count()
|
|
990
|
+
# pool = mp.Pool(processes=nproc)
|
|
991
|
+
# pool_input = [(self.rate, t, pini_norm) for t in time]
|
|
992
|
+
# popul = pool.map(msm_lib.propagate_worker, tuple(pool_input))
|
|
993
|
+
# pool.close()
|
|
994
|
+
# pool.join()
|
|
995
|
+
|
|
996
|
+
## normalize relaxation
|
|
997
|
+
# imax = np.argmax(ptot)
|
|
998
|
+
# maxpop = ptot[imax]
|
|
999
|
+
# imin = np.argmin(ptot)
|
|
1000
|
+
# minpop = ptot[imin]
|
|
1001
|
+
# if imax < imin:
|
|
1002
|
+
# pnorm = map(lambda x: (x-minpop)/(maxpop-minpop), ptot)
|
|
1003
|
+
# else:
|
|
1004
|
+
# pnorm = map(lambda x: 1 - (x-minpop)/(maxpop-minpop), ptot)
|
|
1005
|
+
return tcum, popul # popul #, pnorm
|
|
1006
|
+
|
|
1007
|
+
def acf_mode(self, modes=None):
|
|
1008
|
+
""" Calculate mode autocorrelation functions.
|
|
1009
|
+
|
|
1010
|
+
We use the procedure described by Buchete and Hummer [#]_.
|
|
1011
|
+
|
|
1012
|
+
Parameters
|
|
1013
|
+
----------
|
|
1014
|
+
mode : int, list
|
|
1015
|
+
Index(es) for sorted autocorrelation functions.
|
|
1016
|
+
time : list, int
|
|
1017
|
+
User defined range of temperatures for ACF.
|
|
1018
|
+
|
|
1019
|
+
Returns
|
|
1020
|
+
-------
|
|
1021
|
+
corr : array
|
|
1022
|
+
The mode(s) autocorrelation function.
|
|
1023
|
+
|
|
1024
|
+
References
|
|
1025
|
+
----------
|
|
1026
|
+
.. [#] N.-V. Buchete and G. Hummer, "Coarse master equations for
|
|
1027
|
+
peptide folding dynamics", J. Phys. Chem. B (2008).
|
|
1028
|
+
|
|
1029
|
+
"""
|
|
1030
|
+
if not modes:
|
|
1031
|
+
modes = range(1, len(self.keep_keys))
|
|
1032
|
+
|
|
1033
|
+
kk = self.keep_keys
|
|
1034
|
+
trajs = [x.distraj for x in self.data]
|
|
1035
|
+
|
|
1036
|
+
acf_ave = {}
|
|
1037
|
+
for m in modes:
|
|
1038
|
+
x2 = []
|
|
1039
|
+
acf_cum = []
|
|
1040
|
+
for tr in trajs:
|
|
1041
|
+
projected = []
|
|
1042
|
+
for s in tr:
|
|
1043
|
+
try:
|
|
1044
|
+
projected.append(self.lvecsT[kk.index(s), m])
|
|
1045
|
+
except ValueError:
|
|
1046
|
+
pass
|
|
1047
|
+
x2.extend([x ** 2 for x in projected])
|
|
1048
|
+
acf = np.correlate(projected, projected, mode='full')
|
|
1049
|
+
acf_half = acf[int(acf.size / 2):]
|
|
1050
|
+
acf_cum.append(acf_half)
|
|
1051
|
+
lmin = np.min([len(x) for x in acf_cum])
|
|
1052
|
+
|
|
1053
|
+
acf_ave[m] = []
|
|
1054
|
+
for l in range(1, lmin): # skip zero-lag (trivially 1 after normalisation)
|
|
1055
|
+
num = np.sum([x[l] for x in acf_cum])
|
|
1056
|
+
denom = np.sum([len(x) - l for x in acf_cum])
|
|
1057
|
+
acf_ave[m].append(num / denom)
|
|
1058
|
+
acf_ave[m] /= np.mean(x2)
|
|
1059
|
+
return acf_ave
|