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_lib.py
ADDED
|
@@ -0,0 +1,1347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is part of the MasterMSM package.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
import copy
|
|
6
|
+
import numpy as np
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import os #, math
|
|
9
|
+
import tempfile
|
|
10
|
+
from functools import reduce, cmp_to_key
|
|
11
|
+
#import operator
|
|
12
|
+
from scipy import linalg as spla
|
|
13
|
+
#import multiprocessing as mp
|
|
14
|
+
import pickle
|
|
15
|
+
|
|
16
|
+
# thermal energy (kJ/mol)
|
|
17
|
+
beta = 1./(8.314e-3*300)
|
|
18
|
+
|
|
19
|
+
#def difference(k1, k2):
|
|
20
|
+
# l = len(k1)
|
|
21
|
+
# diff = 0
|
|
22
|
+
# for i in range(l):
|
|
23
|
+
# if k1[i] != k2[i]:
|
|
24
|
+
# diff+=1
|
|
25
|
+
# return diff
|
|
26
|
+
|
|
27
|
+
def calc_eigsT(trans):
|
|
28
|
+
"""
|
|
29
|
+
Calculate eigenvalues and eigenvectors of transition matrix T.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
trans : np.array
|
|
34
|
+
The transition matrix
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
evals: np.array
|
|
39
|
+
Eigenvalues, sorted by decreasing modulus (complex in general).
|
|
40
|
+
lvecs : numpy array
|
|
41
|
+
Left eigenvectors of T, in the same order.
|
|
42
|
+
rvecs : numpy array
|
|
43
|
+
Right eigenvectors of T, in the same order.
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
evals, lvecs, rvecs = spla.eig(trans, left=True)
|
|
47
|
+
|
|
48
|
+
# sort by decreasing modulus; ties (conjugate pairs) by real part
|
|
49
|
+
ievals = np.lexsort((-np.real(evals), -np.abs(evals)))
|
|
50
|
+
evals = evals[ievals]
|
|
51
|
+
lvecs = lvecs[:,ievals]
|
|
52
|
+
rvecs = rvecs[:,ievals]
|
|
53
|
+
|
|
54
|
+
return evals, lvecs, rvecs
|
|
55
|
+
|
|
56
|
+
def calc_tauT(evals, lagt):
|
|
57
|
+
"""
|
|
58
|
+
Relaxation times from the eigenvalues of a transition matrix.
|
|
59
|
+
|
|
60
|
+
Uses tau = -lagt/ln|lambda|. Complex-conjugate pairs (from
|
|
61
|
+
non-reversible estimates) and negative eigenvalues decay with
|
|
62
|
+
rate ln|lambda|; both members of a pair are kept, so the output
|
|
63
|
+
has len(evals) - 1 entries. The stationary eigenvalue (first)
|
|
64
|
+
is skipped.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
evals : np.array
|
|
69
|
+
Eigenvalues sorted by decreasing modulus, as from calc_eigsT.
|
|
70
|
+
lagt : float
|
|
71
|
+
Lag time.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
tau : np.array
|
|
76
|
+
Relaxation times (real).
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
mod = np.abs(np.asarray(evals)[1:])
|
|
80
|
+
with np.errstate(divide='ignore'):
|
|
81
|
+
return -lagt / np.log(mod)
|
|
82
|
+
|
|
83
|
+
def calc_eigsK(rate, evecs=False):
|
|
84
|
+
"""
|
|
85
|
+
Calculate eigenvalues and eigenvectors of rate matrix K
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
-----------
|
|
89
|
+
rate : array
|
|
90
|
+
The rate matrix to use.
|
|
91
|
+
evecs : bool
|
|
92
|
+
Whether we want the eigenvectors of the rate matrix.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
-------
|
|
96
|
+
tauK : numpy array
|
|
97
|
+
Relaxation times from K.
|
|
98
|
+
peqK : numpy array
|
|
99
|
+
Equilibrium probabilities from K.
|
|
100
|
+
rvecsK : numpy array, optional
|
|
101
|
+
Right eigenvectors of K, sorted.
|
|
102
|
+
lvecsK : numpy array, optional
|
|
103
|
+
Left eigenvectors of K, sorted.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
evalsK, lvecsK, rvecsK = \
|
|
107
|
+
spla.eig(rate, left=True)
|
|
108
|
+
|
|
109
|
+
# sort modes
|
|
110
|
+
nkeys = len(rate)
|
|
111
|
+
elistK = []
|
|
112
|
+
for i in range(nkeys):
|
|
113
|
+
elistK.append([i,np.real(evalsK[i])])
|
|
114
|
+
elistK.sort(key=cmp_to_key(esort))
|
|
115
|
+
|
|
116
|
+
# calculate relaxation times from K and T
|
|
117
|
+
tauK = []
|
|
118
|
+
for i in range(nkeys):
|
|
119
|
+
if np.abs(elistK[i][1]) > 1e-10:
|
|
120
|
+
iiK, lamK = elistK[i]
|
|
121
|
+
tauK.append(-1./lamK)
|
|
122
|
+
if len(tauK) == 1:
|
|
123
|
+
ieqK = iiK
|
|
124
|
+
|
|
125
|
+
# equilibrium probabilities
|
|
126
|
+
ieqK, _ = elistK[0]
|
|
127
|
+
peqK_sum = reduce(lambda x, y: x + y, map(lambda x: rvecsK[x,ieqK],
|
|
128
|
+
range(nkeys)))
|
|
129
|
+
peqK = rvecsK[:,ieqK]/peqK_sum
|
|
130
|
+
|
|
131
|
+
if not evecs:
|
|
132
|
+
return tauK, peqK
|
|
133
|
+
else:
|
|
134
|
+
# sort eigenvectors
|
|
135
|
+
rvecsK_sorted = np.zeros((nkeys, nkeys), complex)
|
|
136
|
+
lvecsK_sorted = np.zeros((nkeys, nkeys), complex)
|
|
137
|
+
for i in range(nkeys):
|
|
138
|
+
iiK, lamK = elistK[i]
|
|
139
|
+
rvecsK_sorted[:,i] = rvecsK[:,iiK]
|
|
140
|
+
lvecsK_sorted[:,i] = lvecsK[:,iiK]
|
|
141
|
+
return tauK, peqK, rvecsK_sorted, lvecsK_sorted
|
|
142
|
+
|
|
143
|
+
def esort(ei, ej):
|
|
144
|
+
""" Sorts eigenvalues.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
ei : float
|
|
149
|
+
Eigenvalue i
|
|
150
|
+
ej : float
|
|
151
|
+
Eigenvalue j
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
bool :
|
|
156
|
+
Whether the first value is larger than the second.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
_, eval_i = ei
|
|
160
|
+
_, eval_j = ej
|
|
161
|
+
|
|
162
|
+
if eval_j.real > eval_i.real:
|
|
163
|
+
return 1
|
|
164
|
+
elif eval_j.real < eval_i.real:
|
|
165
|
+
return -1
|
|
166
|
+
else:
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
#def find_keys(state_keys, trans, manually_remove):
|
|
170
|
+
# """ eliminate dead ends """
|
|
171
|
+
# keep_states = []
|
|
172
|
+
# keep_keys = []
|
|
173
|
+
# # eliminate dead ends
|
|
174
|
+
# nstate = len(state_keys)
|
|
175
|
+
# for i in range(nstate):
|
|
176
|
+
# key = state_keys[i]
|
|
177
|
+
# summ = 0
|
|
178
|
+
# sumx = 0
|
|
179
|
+
# for j in range(nstate):
|
|
180
|
+
# if j!=i:
|
|
181
|
+
# summ += trans[j][i] # sources
|
|
182
|
+
# sumx += trans[i][j] # sinks
|
|
183
|
+
# if summ > 0 and sumx > 0 and trans[i][i] > 0 and key not in manually_remove:
|
|
184
|
+
# keep_states.append(i)
|
|
185
|
+
# keep_keys.append(state_keys[i])
|
|
186
|
+
# return keep_states,keep_keys
|
|
187
|
+
#
|
|
188
|
+
#def connect_groups(keep_states, trans):
|
|
189
|
+
# """ check for connected groups """
|
|
190
|
+
# connected_groups = []
|
|
191
|
+
# leftover = copy.deepcopy(keep_states)
|
|
192
|
+
# while len(leftover) > 0:
|
|
193
|
+
# #print leftover
|
|
194
|
+
# leftover_new = []
|
|
195
|
+
# n_old_new_net = 0
|
|
196
|
+
# new_net = [ leftover[0] ]
|
|
197
|
+
# n_new_net = len(new_net)
|
|
198
|
+
# while n_new_net != n_old_new_net:
|
|
199
|
+
# for i in range(len(leftover)):
|
|
200
|
+
# l = leftover[i]
|
|
201
|
+
# if l in new_net:
|
|
202
|
+
# continue
|
|
203
|
+
# summ = 0
|
|
204
|
+
# for g in new_net:
|
|
205
|
+
# summ += trans[l][g]+trans[g][l]
|
|
206
|
+
# if summ > 0:
|
|
207
|
+
# new_net.append(l)
|
|
208
|
+
# n_old_new_net = n_new_net
|
|
209
|
+
# n_new_net = len(new_net)
|
|
210
|
+
# #print " added %i new members" % (n_new_net-n_old_new_net)
|
|
211
|
+
# leftover_new = filter(lambda x: x not in new_net, leftover)
|
|
212
|
+
# connected_groups.append(new_net)
|
|
213
|
+
# leftover = copy.deepcopy(leftover_new)
|
|
214
|
+
# return connected_groups
|
|
215
|
+
#
|
|
216
|
+
#def isnative(native_string, string):
|
|
217
|
+
# s = ""
|
|
218
|
+
# for i in range(len(string)):
|
|
219
|
+
# if string[i]==native_string[i]:
|
|
220
|
+
# s+="1"
|
|
221
|
+
# else:
|
|
222
|
+
# s+="0"
|
|
223
|
+
# return s
|
|
224
|
+
|
|
225
|
+
def mat_mul_v(m, v):
|
|
226
|
+
""" Multiplies matrix and vector
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
m : np.array
|
|
231
|
+
The matrix.
|
|
232
|
+
v : np.array
|
|
233
|
+
The vector.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
w : np.array
|
|
238
|
+
The result
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
rows = len(m)
|
|
242
|
+
w = [0]*rows
|
|
243
|
+
irange = range(len(v))
|
|
244
|
+
summ = 0
|
|
245
|
+
for j in range(rows):
|
|
246
|
+
r = m[j]
|
|
247
|
+
for i in irange:
|
|
248
|
+
summ += r[i]*v[i]
|
|
249
|
+
w[j], summ = summ,0
|
|
250
|
+
return w
|
|
251
|
+
|
|
252
|
+
#def dotproduct(v1, v2, sum=sum, imap=itertools.imap, mul=operator.mul):
|
|
253
|
+
# return sum(imap(mul,v1,v2))
|
|
254
|
+
#
|
|
255
|
+
##def rate_analyze(rate):
|
|
256
|
+
## # calculates eigenvalues and eigenvectors from rate matrix
|
|
257
|
+
## # calculate symmetrized matrix
|
|
258
|
+
## kjisym = kji*(kji.transpose())
|
|
259
|
+
## kjisym = sqrt(kjisym)
|
|
260
|
+
## for j in arange(nstates):
|
|
261
|
+
## kjisym[j,j] = -kjisym[j,j]
|
|
262
|
+
## # calculate eigenvalues and eigenvectors
|
|
263
|
+
## eigvalsym,eigvectsym = linalg.eig(kjisym)
|
|
264
|
+
## # index the solutions
|
|
265
|
+
## index = argsort(-eigvalsym)
|
|
266
|
+
## ieq = index[0]
|
|
267
|
+
## # equilibrium population
|
|
268
|
+
## peq = eigvectsym[:,ieq]**2
|
|
269
|
+
## # order eigenvalues and calculate left and right eigenvectors
|
|
270
|
+
## eigval = zeros((nstates),float)
|
|
271
|
+
## PsiR = zeros((nstates,nstates),float)
|
|
272
|
+
## PsiL = zeros((nstates,nstates),float)
|
|
273
|
+
## for i in arange(nstates):
|
|
274
|
+
## eigval[i] = eigvalsym[index[i]]
|
|
275
|
+
## PsiR[:,i] = eigvectsym[:,index[i]]*eigvectsym[:,ieq]
|
|
276
|
+
## PsiL[:,i] = eigvectsym[:,index[i]]/eigvectsym[:,ieq]
|
|
277
|
+
## return eigval,PsiR,PsiL,eigvectsym,peq
|
|
278
|
+
#
|
|
279
|
+
#def propagate(rate, t, pini):
|
|
280
|
+
# # propagate dynamics using rate matrix exponential
|
|
281
|
+
# expkt = spla.expm2(rate*t)
|
|
282
|
+
# return mat_mul_v(expkt,pini)
|
|
283
|
+
#
|
|
284
|
+
#def propagate_eig(elist, rvecs, lvecs, t, pini):
|
|
285
|
+
# # propagate dynamics using rate matrix exponential using eigenvalues and eigenvectors
|
|
286
|
+
# nstates = len(pini)
|
|
287
|
+
# p = np.zeros((nstates),float)
|
|
288
|
+
# for n in range(nstates):
|
|
289
|
+
# #print np.exp(-elist[n][1]*t)
|
|
290
|
+
# i,e = elist[n]
|
|
291
|
+
# p = p + rvecs[:,i]*(np.dot(lvecs[:,i],pini)*\
|
|
292
|
+
# np.exp(-abs(e*t)))
|
|
293
|
+
# return p
|
|
294
|
+
#
|
|
295
|
+
#def bootsfiles(traj_list_dt):
|
|
296
|
+
# n = len(traj_list_dt)
|
|
297
|
+
# traj_list_dt_new = []
|
|
298
|
+
# i = 0
|
|
299
|
+
# while i < n:
|
|
300
|
+
# k = int(np.random.random()*n)
|
|
301
|
+
# traj_list_dt_new.append(traj_list_dt[k])
|
|
302
|
+
# i += 1
|
|
303
|
+
# return traj_list_dt_new
|
|
304
|
+
#
|
|
305
|
+
#def boots_pick(filename, blocksize):
|
|
306
|
+
# raw = open(filename).readlines()
|
|
307
|
+
# lraw = len(raw)
|
|
308
|
+
# nblocks = int(lraw/blocksize)
|
|
309
|
+
# lblock = int(lraw/nblocks)
|
|
310
|
+
# try:
|
|
311
|
+
# ib = np.random.randint(nblocks-1)
|
|
312
|
+
# except ValueError:
|
|
313
|
+
# ib = 0
|
|
314
|
+
# return raw[ib*lblock:(ib+1)*lblock]
|
|
315
|
+
#
|
|
316
|
+
#def onrate(states, target, K, peq):
|
|
317
|
+
# # steady state rate
|
|
318
|
+
# kon = 0.
|
|
319
|
+
# for i in states:
|
|
320
|
+
# if i != target:
|
|
321
|
+
# if K[target,i] > 0:
|
|
322
|
+
# kon += K[target,i]*peq[i]
|
|
323
|
+
# return kon
|
|
324
|
+
|
|
325
|
+
def run_commit(states, K, peq, FF, UU):
|
|
326
|
+
""" Calculate committors and reactive flux
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
states : list
|
|
331
|
+
States in the MSM.
|
|
332
|
+
K : np.array
|
|
333
|
+
Rate matrix.
|
|
334
|
+
peq : np.array
|
|
335
|
+
Equilibrium distribution.
|
|
336
|
+
FF : list
|
|
337
|
+
Definitely folded states.
|
|
338
|
+
UU : list
|
|
339
|
+
Definitely unfolded states.
|
|
340
|
+
|
|
341
|
+
Returns
|
|
342
|
+
-------
|
|
343
|
+
J : np.array
|
|
344
|
+
Reactive flux matrix.
|
|
345
|
+
pfold : np.array
|
|
346
|
+
Values of the committor.
|
|
347
|
+
sum_flux : float
|
|
348
|
+
Sum of reactive fluxes.
|
|
349
|
+
kf : float
|
|
350
|
+
Folding rate from flux over population relationship.
|
|
351
|
+
|
|
352
|
+
"""
|
|
353
|
+
nstates = len(states)
|
|
354
|
+
# define end-states
|
|
355
|
+
UUFF = UU + FF
|
|
356
|
+
print (" definitely FF and UU states", UUFF)
|
|
357
|
+
I = list(filter(lambda x: x not in UU+FF, states))
|
|
358
|
+
NI = len(I)
|
|
359
|
+
|
|
360
|
+
# calculate committors
|
|
361
|
+
b = np.zeros([NI], float)
|
|
362
|
+
A = np.zeros([NI,NI], float)
|
|
363
|
+
for j_ind in range(NI):
|
|
364
|
+
j = I[j_ind]
|
|
365
|
+
summ = 0.
|
|
366
|
+
for i in FF:
|
|
367
|
+
summ += K[i][j]
|
|
368
|
+
b[j_ind] = -summ
|
|
369
|
+
for i_ind in range(NI):
|
|
370
|
+
i = I[i_ind]
|
|
371
|
+
A[j_ind][i_ind] = K[i][j]
|
|
372
|
+
# solve Ax=b
|
|
373
|
+
Ainv = np.linalg.inv(A)
|
|
374
|
+
x = np.dot(Ainv,b)
|
|
375
|
+
#XX = np.dot(Ainv,A)
|
|
376
|
+
|
|
377
|
+
pfold = np.zeros(nstates,float)
|
|
378
|
+
for i in range(nstates):
|
|
379
|
+
if i in UU:
|
|
380
|
+
pfold[i] = 0.0
|
|
381
|
+
elif i in FF:
|
|
382
|
+
pfold[i] = 1.0
|
|
383
|
+
else:
|
|
384
|
+
ii = I.index(i)
|
|
385
|
+
pfold[i] = x[ii]
|
|
386
|
+
|
|
387
|
+
# stationary distribution
|
|
388
|
+
pss = np.zeros(nstates,float)
|
|
389
|
+
for i in range(nstates):
|
|
390
|
+
pss[i] = (1-pfold[i])*peq[i]
|
|
391
|
+
|
|
392
|
+
# flux matrix and reactive flux
|
|
393
|
+
J = np.zeros([nstates,nstates],float)
|
|
394
|
+
for i in range(nstates):
|
|
395
|
+
for j in range(nstates):
|
|
396
|
+
J[j][i] = K[j][i]*peq[i]*(pfold[j]-pfold[i])
|
|
397
|
+
|
|
398
|
+
# dividing line is committor = 0.5
|
|
399
|
+
sum_flux = 0
|
|
400
|
+
left = [x for x in range(nstates) if pfold[x] < 0.5]
|
|
401
|
+
right = [x for x in range(nstates) if pfold[x] > 0.5]
|
|
402
|
+
for i in left:
|
|
403
|
+
for j in right:
|
|
404
|
+
sum_flux += J[j][i]
|
|
405
|
+
|
|
406
|
+
#sum of populations for all reactant states
|
|
407
|
+
pU = np.sum([peq[x] for x in range(nstates) if pfold[x] < 0.5])
|
|
408
|
+
# pU = np.sum(peq[filter(lambda x: x in UU, range(nstates))])
|
|
409
|
+
kf = sum_flux/pU
|
|
410
|
+
return J, pfold, sum_flux, kf
|
|
411
|
+
|
|
412
|
+
def calc_count_worker(x):
|
|
413
|
+
""" mp worker that calculates the count matrix from a trajectory
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
x : list
|
|
418
|
+
List containing input for each parallel worker. Includes:
|
|
419
|
+
distraj : the time series of states
|
|
420
|
+
dt : the timestep for that trajectory
|
|
421
|
+
keys : the keys used in the assignment
|
|
422
|
+
lagt : the lag time for construction
|
|
423
|
+
sliding : sliding windows
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
count : array
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
# parse input from multiprocessing
|
|
431
|
+
distraj = x[0]
|
|
432
|
+
dt = x[1]
|
|
433
|
+
keys = x[2]
|
|
434
|
+
nkeys = len(keys)
|
|
435
|
+
lagt = x[3]
|
|
436
|
+
sliding = x[4]
|
|
437
|
+
|
|
438
|
+
ltraj = len(distraj)
|
|
439
|
+
lag = int(lagt/dt) # number of frames per lag time
|
|
440
|
+
if lag < 1:
|
|
441
|
+
raise ValueError("lag time shorter than timestep")
|
|
442
|
+
if sliding:
|
|
443
|
+
slider = 1 # every state is initial state
|
|
444
|
+
else:
|
|
445
|
+
slider = lag
|
|
446
|
+
|
|
447
|
+
count = np.zeros([nkeys,nkeys], np.int32)
|
|
448
|
+
for i in range(0, ltraj-lag, slider):
|
|
449
|
+
j = i + lag
|
|
450
|
+
state_i = distraj[i]
|
|
451
|
+
state_j = distraj[j]
|
|
452
|
+
if state_i in keys:
|
|
453
|
+
idx_i = keys.index(state_i)
|
|
454
|
+
if state_j in keys:
|
|
455
|
+
idx_j = keys.index(state_j)
|
|
456
|
+
try:
|
|
457
|
+
count[idx_j,idx_i] += 1
|
|
458
|
+
except UnboundLocalError:
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
return count
|
|
462
|
+
|
|
463
|
+
def calc_lifetime(x):
|
|
464
|
+
""" mp worker that calculates the count matrix from a trajectory
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
x : list
|
|
469
|
+
List containing input for each mp worker. Includes:
|
|
470
|
+
distraj :the time series of states
|
|
471
|
+
dt : the timestep for that trajectory
|
|
472
|
+
keys : the keys used in the assignment
|
|
473
|
+
|
|
474
|
+
Returns
|
|
475
|
+
-------
|
|
476
|
+
life : dict
|
|
477
|
+
|
|
478
|
+
"""
|
|
479
|
+
# parse input from multiprocessing
|
|
480
|
+
distraj = x[0]
|
|
481
|
+
dt = x[1]
|
|
482
|
+
keys = x[2]
|
|
483
|
+
ltraj = len(distraj)
|
|
484
|
+
|
|
485
|
+
life = {}
|
|
486
|
+
l = 0
|
|
487
|
+
for j in range(1, ltraj):
|
|
488
|
+
i = j - 1
|
|
489
|
+
state_i = distraj[i]
|
|
490
|
+
state_j = distraj[j]
|
|
491
|
+
if state_i == state_j:
|
|
492
|
+
l += 1
|
|
493
|
+
elif state_j not in keys:
|
|
494
|
+
l += 1
|
|
495
|
+
else:
|
|
496
|
+
try:
|
|
497
|
+
life[state_i].append(l*dt)
|
|
498
|
+
except KeyError:
|
|
499
|
+
life[state_i] = [l*dt]
|
|
500
|
+
l = 1
|
|
501
|
+
#try:
|
|
502
|
+
# life[state_i].append(l*dt)
|
|
503
|
+
#except KeyError:
|
|
504
|
+
# life[state_i] = [l*dt]
|
|
505
|
+
return life
|
|
506
|
+
|
|
507
|
+
def traj_split(data=None, lagt=None, tmin=0.):
|
|
508
|
+
""" Splits trajectories into fragments for bootstrapping
|
|
509
|
+
|
|
510
|
+
Each trajectory is cut into pieces of equal length, as many as
|
|
511
|
+
fit while every piece is at least ``tmin`` and at least ten lag
|
|
512
|
+
times long. A trajectory shorter than that is kept whole.
|
|
513
|
+
|
|
514
|
+
Parameters
|
|
515
|
+
----------
|
|
516
|
+
data : list
|
|
517
|
+
Set of trajectories used for building the MSM.
|
|
518
|
+
lagt : float
|
|
519
|
+
Lag time for building the MSM.
|
|
520
|
+
tmin : float
|
|
521
|
+
Minimum length of a fragment, in the time units of the
|
|
522
|
+
trajectories (typically a multiple of the slowest relaxation
|
|
523
|
+
time, see ``MSM.boots``).
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
-------
|
|
527
|
+
filetmp : str
|
|
528
|
+
Name of a temporary file with the pickled fragments.
|
|
529
|
+
nfrag : int
|
|
530
|
+
Number of fragments.
|
|
531
|
+
|
|
532
|
+
"""
|
|
533
|
+
lmin = max(tmin, 10.*lagt)
|
|
534
|
+
trajs = []
|
|
535
|
+
for x in data:
|
|
536
|
+
s, dt = x.distraj, x.dt
|
|
537
|
+
nfr = int((len(s)*dt)//lmin) if lmin > 0 else len(s)
|
|
538
|
+
nfr = max(1, min(nfr, len(s)))
|
|
539
|
+
edges = np.linspace(0, len(s), nfr + 1).astype(int)
|
|
540
|
+
trajs += [[s[a:b], dt] for a, b in zip(edges[:-1], edges[1:])]
|
|
541
|
+
|
|
542
|
+
# save trajs
|
|
543
|
+
fd, filetmp = tempfile.mkstemp()
|
|
544
|
+
file = os.fdopen(fd, 'wb')
|
|
545
|
+
pickle.dump(trajs, file, protocol=pickle.HIGHEST_PROTOCOL)
|
|
546
|
+
file.close()
|
|
547
|
+
return filetmp, len(trajs)
|
|
548
|
+
|
|
549
|
+
def do_boots_worker(x):
|
|
550
|
+
""" Worker function for parallel bootstrapping.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
x : list
|
|
555
|
+
A list containing the trajectory filename, the states, the lag time,
|
|
556
|
+
the total number of transitions, the sliding window option, the
|
|
557
|
+
number of eigenvalues, the seed for the random number generator
|
|
558
|
+
(an int, a numpy SeedSequence, or None for fresh entropy) and
|
|
559
|
+
whether the count matrix is symmetrized, as in MSM.calc_count.
|
|
560
|
+
The number of transitions is that before symmetrizing.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
filetmp, keys, lagt, ncount, slider, neigs, seed, sym = x
|
|
564
|
+
nkeys = len(keys)
|
|
565
|
+
|
|
566
|
+
finp = open(filetmp, 'rb')
|
|
567
|
+
trans = pickle.load(finp)
|
|
568
|
+
finp.close()
|
|
569
|
+
|
|
570
|
+
ltrans = len(trans)
|
|
571
|
+
rng = np.random.default_rng(seed)
|
|
572
|
+
ncount_boots = 0
|
|
573
|
+
count = np.zeros([nkeys, nkeys], np.int32)
|
|
574
|
+
while ncount_boots < ncount:
|
|
575
|
+
itrans = rng.integers(ltrans)
|
|
576
|
+
count_inp = [trans[itrans][0], trans[itrans][1], keys, lagt, slider]
|
|
577
|
+
c = calc_count_worker(count_inp)
|
|
578
|
+
count += c
|
|
579
|
+
ncount_boots += np.sum(c)
|
|
580
|
+
if sym:
|
|
581
|
+
count += count.transpose()
|
|
582
|
+
D = nx.DiGraph(count)
|
|
583
|
+
keep_states = list(sorted(list(nx.strongly_connected_components(D)),
|
|
584
|
+
key=len, reverse=True)[0])
|
|
585
|
+
keep_states.sort()
|
|
586
|
+
keep_keys = [keys[x] for x in keep_states]
|
|
587
|
+
nkeep = len(keep_keys)
|
|
588
|
+
|
|
589
|
+
trans = calc_trans(nkeep, keep_states, count)
|
|
590
|
+
return trans, keep_keys
|
|
591
|
+
|
|
592
|
+
def calc_trans(nkeep=None, keep_states=None, count=None):
|
|
593
|
+
""" Calculates transition matrix.
|
|
594
|
+
|
|
595
|
+
Uses the maximum likelihood expression by Prinz et al. [#]_
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
lagt : float
|
|
600
|
+
Lag time for construction of MSM.
|
|
601
|
+
|
|
602
|
+
Returns
|
|
603
|
+
-------
|
|
604
|
+
trans : array
|
|
605
|
+
The transition probability matrix.
|
|
606
|
+
|
|
607
|
+
Notes
|
|
608
|
+
-----
|
|
609
|
+
.. [#] J. H. Prinz, H. Wu, M. Sarich, B. Keller, M. Senne, M. Held,
|
|
610
|
+
J. D. Chodera, C. Schutte and F. Noe, "Markov state models:
|
|
611
|
+
Generation and validation", J. Chem. Phys. (2011).
|
|
612
|
+
"""
|
|
613
|
+
trans = np.zeros([nkeep, nkeep], float)
|
|
614
|
+
for i in range(nkeep):
|
|
615
|
+
ni = reduce(lambda x, y: x + y, map(lambda x:
|
|
616
|
+
count[keep_states[x]][keep_states[i]], range(nkeep)))
|
|
617
|
+
for j in range(nkeep):
|
|
618
|
+
trans[j][i] = float(count[keep_states[j]][keep_states[i]])/float(ni)
|
|
619
|
+
return trans
|
|
620
|
+
|
|
621
|
+
def calc_rate(nkeep, trans, lagt):
|
|
622
|
+
""" Calculate rate matrix from transition matrix.
|
|
623
|
+
|
|
624
|
+
We use a method based on a Taylor expansion [#]_.
|
|
625
|
+
|
|
626
|
+
Parameters
|
|
627
|
+
----------
|
|
628
|
+
nkeep : int
|
|
629
|
+
Number of states in transition matrix.
|
|
630
|
+
trans: np.array
|
|
631
|
+
Transition matrix.
|
|
632
|
+
lagt : float
|
|
633
|
+
The lag time.
|
|
634
|
+
|
|
635
|
+
Returns
|
|
636
|
+
-------
|
|
637
|
+
rate : np.array
|
|
638
|
+
The rate matrix.
|
|
639
|
+
|
|
640
|
+
Notes
|
|
641
|
+
-----
|
|
642
|
+
.. [#] D. De Sancho, J. Mittal and R. B. Best, "Folding kinetics
|
|
643
|
+
and unfolded state dynamics of the GB1 hairpin from molecular
|
|
644
|
+
simulation", J. Chem. Theory Comput. (2013).
|
|
645
|
+
|
|
646
|
+
"""
|
|
647
|
+
rate = trans/lagt
|
|
648
|
+
|
|
649
|
+
# enforce mass conservation
|
|
650
|
+
for i in range(nkeep):
|
|
651
|
+
rate[i][i] = -(np.sum(rate[:i,i]) + np.sum(rate[i+1:,i]))
|
|
652
|
+
return rate
|
|
653
|
+
|
|
654
|
+
def rand_rate(nkeep, count):
|
|
655
|
+
""" Randomly generate initial matrix.
|
|
656
|
+
|
|
657
|
+
Parameters
|
|
658
|
+
----------
|
|
659
|
+
nkeep : int
|
|
660
|
+
Number of states in transition matrix.
|
|
661
|
+
|
|
662
|
+
count : np.array
|
|
663
|
+
Transition matrix.
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
-------
|
|
667
|
+
rand_rate : np.array
|
|
668
|
+
The random rate matrix.
|
|
669
|
+
|
|
670
|
+
"""
|
|
671
|
+
nkeys = len(count)
|
|
672
|
+
|
|
673
|
+
rand_rate = np.zeros((nkeys, nkeys), float)
|
|
674
|
+
for i in range(nkeys):
|
|
675
|
+
for j in range(nkeys):
|
|
676
|
+
if i != j:
|
|
677
|
+
if (count[i,j] !=0) and (count[j,i] != 0):
|
|
678
|
+
rand_rate[j,i] = np.exp(np.random.randn()*-3)
|
|
679
|
+
rand_rate[i,i] = -np.sum(rand_rate[:,i] )
|
|
680
|
+
return rand_rate
|
|
681
|
+
|
|
682
|
+
def calc_mlrate(nkeep, count, lagt, rate_init):
|
|
683
|
+
""" Calculate rate matrix using maximum likelihood Bayesian method.
|
|
684
|
+
|
|
685
|
+
We use a the MLPB method described by Buchete and Hummer [#]_.
|
|
686
|
+
|
|
687
|
+
Parameters
|
|
688
|
+
----------
|
|
689
|
+
nkeep : int
|
|
690
|
+
Number of states in transition matrix.
|
|
691
|
+
count : np.array
|
|
692
|
+
Transition matrix.
|
|
693
|
+
lagt : float
|
|
694
|
+
The lag time.
|
|
695
|
+
|
|
696
|
+
Returns
|
|
697
|
+
-------
|
|
698
|
+
rate : np.array
|
|
699
|
+
The rate matrix.
|
|
700
|
+
|
|
701
|
+
Notes
|
|
702
|
+
-----
|
|
703
|
+
.. [#] N.-V. Buchete and G. Hummer, "Coarse master equations for
|
|
704
|
+
peptide folding dynamics", J. Phys. Chem. B (2008).
|
|
705
|
+
|
|
706
|
+
"""
|
|
707
|
+
# initialize rate matrix and equilibrium distribution enforcing detailed balance
|
|
708
|
+
p_prev = np.sum(count, axis=0)/float(np.sum(count))
|
|
709
|
+
rate_prev = detailed_balance(nkeep, rate_init, p_prev)
|
|
710
|
+
ml_prev = likelihood(nkeep, rate_prev, count, lagt)
|
|
711
|
+
|
|
712
|
+
# initialize MC sampling
|
|
713
|
+
print ("MLPB optimization of rate matrix:\n START")
|
|
714
|
+
#print rate_prev,"\n", p_prev, ml_prev
|
|
715
|
+
ml_ref = ml_prev
|
|
716
|
+
ml_cum = [ml_prev]
|
|
717
|
+
temp_cum = [1.]
|
|
718
|
+
nstep = 0
|
|
719
|
+
nsteps = 1000*nkeep**2
|
|
720
|
+
k = -3./nsteps
|
|
721
|
+
nfreq = 10
|
|
722
|
+
ncycle = 0
|
|
723
|
+
accept = 0
|
|
724
|
+
rate_best = rate_prev
|
|
725
|
+
ml_best = ml_prev
|
|
726
|
+
while True:
|
|
727
|
+
# annealing temperature: 1 throughout the first cycle, then
|
|
728
|
+
# decaying exponentially from 1 (nstep = 0) to 0 (nstep = nsteps)
|
|
729
|
+
if ncycle > 0:
|
|
730
|
+
temp = (np.exp(k*nstep) - np.exp(k*nsteps))/(1 - np.exp(k*nsteps))
|
|
731
|
+
else:
|
|
732
|
+
temp = 1.
|
|
733
|
+
|
|
734
|
+
# random choice of MC move
|
|
735
|
+
rate, p = mc_move(nkeep, rate_prev, p_prev)
|
|
736
|
+
rate = detailed_balance(nkeep, rate, p)
|
|
737
|
+
|
|
738
|
+
# calculate likelihood
|
|
739
|
+
ml = likelihood(nkeep, rate, count, lagt)
|
|
740
|
+
|
|
741
|
+
# Boltzmann acceptance / rejection
|
|
742
|
+
if ml < ml_prev:
|
|
743
|
+
#print " ACCEPT\n"
|
|
744
|
+
rate_prev = rate
|
|
745
|
+
p_prev = p
|
|
746
|
+
ml_prev = ml
|
|
747
|
+
accept +=1
|
|
748
|
+
if ml < ml_best:
|
|
749
|
+
ml_best = ml
|
|
750
|
+
rate_best = rate
|
|
751
|
+
else:
|
|
752
|
+
delta_ml = ml - ml_prev
|
|
753
|
+
# at zero temperature uphill moves are always rejected
|
|
754
|
+
if temp > 0 and np.random.random() < np.exp(-delta_ml/temp):
|
|
755
|
+
#print " ACCEPT BOLTZMANN\n"
|
|
756
|
+
rate_prev = rate
|
|
757
|
+
p_prev = p
|
|
758
|
+
ml_prev = ml
|
|
759
|
+
accept +=1
|
|
760
|
+
nstep +=1
|
|
761
|
+
|
|
762
|
+
if nstep > nsteps:
|
|
763
|
+
ncycle +=1
|
|
764
|
+
ml_cum.append(ml_prev)
|
|
765
|
+
temp_cum.append(temp)
|
|
766
|
+
print ("\n END of cycle %g"%ncycle)
|
|
767
|
+
print (" acceptance :%g"%(float(accept)/nsteps))
|
|
768
|
+
accept = 0
|
|
769
|
+
print (rate_prev)
|
|
770
|
+
print (" L old =", ml_ref,"; L new:", ml_prev)
|
|
771
|
+
improvement = (ml_ref - ml_cum[-1])/ml_ref
|
|
772
|
+
print (" improvement :%g"%improvement)
|
|
773
|
+
if improvement > 0.001 or ncycle < 3:
|
|
774
|
+
nstep = 0
|
|
775
|
+
ml_ref = np.mean(ml_cum[-nsteps:])
|
|
776
|
+
else:
|
|
777
|
+
break
|
|
778
|
+
elif nstep % nfreq == 0:
|
|
779
|
+
ml_cum.append(ml_prev)
|
|
780
|
+
temp_cum.append(temp)
|
|
781
|
+
|
|
782
|
+
return rate_best, ml_cum, temp_cum
|
|
783
|
+
|
|
784
|
+
def mc_move(nkeep, rate, peq):
|
|
785
|
+
""" Make MC move in either rate or equilibrium probability.
|
|
786
|
+
|
|
787
|
+
Changes in equilibrium probabilities are introduced so that the new value
|
|
788
|
+
is drawn from a normal distribution centered at the current value.
|
|
789
|
+
|
|
790
|
+
Parameters
|
|
791
|
+
----------
|
|
792
|
+
nkeep : int
|
|
793
|
+
The number of states.
|
|
794
|
+
rate : array
|
|
795
|
+
The rate matrix obeying detailed balance.
|
|
796
|
+
peq : array
|
|
797
|
+
The equilibrium probability
|
|
798
|
+
|
|
799
|
+
"""
|
|
800
|
+
npeq = nkeep - 1
|
|
801
|
+
# independent rates: lower triangle, rate[j,i] with j > i
|
|
802
|
+
rows, cols = np.tril_indices(nkeep, -1)
|
|
803
|
+
nparam = npeq + len(rows)
|
|
804
|
+
if not np.any(rate > 0.):
|
|
805
|
+
raise ValueError("rate matrix has no positive elements")
|
|
806
|
+
|
|
807
|
+
while True:
|
|
808
|
+
i = np.random.randint(0, nparam)
|
|
809
|
+
#print i
|
|
810
|
+
rate_new = copy.deepcopy(rate)
|
|
811
|
+
peq_new = copy.deepcopy(peq)
|
|
812
|
+
if i < npeq:
|
|
813
|
+
#print " Peq"
|
|
814
|
+
scale = np.mean(peq)*0.1
|
|
815
|
+
# peq_new[i] = np.random.normal(loc=peq[i], scale=scale)
|
|
816
|
+
peq_new[i] = peq[i] + (np.random.random() - 0.5)*scale
|
|
817
|
+
peq_new = peq_new/np.sum(peq_new)
|
|
818
|
+
if np.all(peq_new > 0):
|
|
819
|
+
break
|
|
820
|
+
else:
|
|
821
|
+
#print " Rate"
|
|
822
|
+
j, i = rows[i - npeq], cols[i - npeq]
|
|
823
|
+
try:
|
|
824
|
+
# step relative to the mean rate, as for peq above
|
|
825
|
+
scale = np.mean(rate[rate > 0.])*0.1
|
|
826
|
+
#rate_new[j,i] = np.random.normal(loc=rate[j,i], scale=scale)
|
|
827
|
+
rate_new[j,i] = rate[j,i] + (np.random.random() - 0.5)*scale
|
|
828
|
+
if np.all((rate_new - np.diag(np.diag(rate_new))) >= 0):
|
|
829
|
+
break
|
|
830
|
+
except ValueError:
|
|
831
|
+
pass
|
|
832
|
+
#else:
|
|
833
|
+
# print rate_new - np.diag(np.diag(rate_new))
|
|
834
|
+
|
|
835
|
+
return rate_new, peq_new
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def detailed_balance(nkeep, rate, peq):
|
|
839
|
+
""" Enforce detailed balance in rate matrix.
|
|
840
|
+
|
|
841
|
+
Parameters
|
|
842
|
+
----------
|
|
843
|
+
nkeep : int
|
|
844
|
+
The number of states.
|
|
845
|
+
rate : array
|
|
846
|
+
The rate matrix obeying detailed balance.
|
|
847
|
+
peq : array
|
|
848
|
+
The equilibrium probability
|
|
849
|
+
|
|
850
|
+
"""
|
|
851
|
+
for i in range(nkeep):
|
|
852
|
+
for j in range(i):
|
|
853
|
+
rate[j,i] = rate[i,j]*peq[j]/peq[i]
|
|
854
|
+
rate[i,i] = 0
|
|
855
|
+
rate[i,i] = -np.sum(rate[:,i])
|
|
856
|
+
return rate
|
|
857
|
+
|
|
858
|
+
def likelihood(nkeep, rate, count, lagt):
|
|
859
|
+
""" Likelihood of a rate matrix given a count matrix
|
|
860
|
+
|
|
861
|
+
We use the procedure described by Buchete and Hummer [#]_.
|
|
862
|
+
|
|
863
|
+
Parameters
|
|
864
|
+
----------
|
|
865
|
+
nkeep : int
|
|
866
|
+
Number of states in transition matrix.
|
|
867
|
+
count : np.array
|
|
868
|
+
Transition matrix.
|
|
869
|
+
lagt : float
|
|
870
|
+
The lag time.
|
|
871
|
+
|
|
872
|
+
Returns
|
|
873
|
+
-------
|
|
874
|
+
mlog_like : float
|
|
875
|
+
The log likelihood
|
|
876
|
+
|
|
877
|
+
Notes
|
|
878
|
+
-----
|
|
879
|
+
.. [#] N.-V. Buchete and G. Hummer, "Coarse master equations for
|
|
880
|
+
peptide folding dynamics", J. Phys. Chem. B (2008).
|
|
881
|
+
|
|
882
|
+
"""
|
|
883
|
+
# calculate symmetrized rate matrix
|
|
884
|
+
ratesym = np.multiply(rate,rate.transpose())
|
|
885
|
+
ratesym = np.sqrt(ratesym)
|
|
886
|
+
for i in range(nkeep):
|
|
887
|
+
ratesym[i,i] = -ratesym[i,i]
|
|
888
|
+
|
|
889
|
+
# ratesym is symmetric by construction: eigh gives real output
|
|
890
|
+
evalsym, evectsym = np.linalg.eigh(ratesym)
|
|
891
|
+
|
|
892
|
+
# index the solutions
|
|
893
|
+
indx_eig = np.argsort(-evalsym)
|
|
894
|
+
|
|
895
|
+
# equilibrium population
|
|
896
|
+
ieq = indx_eig[0]
|
|
897
|
+
|
|
898
|
+
# calculate left and right eigenvectors
|
|
899
|
+
phiR = np.zeros((nkeep, nkeep))
|
|
900
|
+
phiL = np.zeros((nkeep, nkeep))
|
|
901
|
+
for i in range(nkeep):
|
|
902
|
+
phiR[:,i] = evectsym[:,i]*evectsym[:,ieq]
|
|
903
|
+
phiL[:,i] = evectsym[:,i]/evectsym[:,ieq]
|
|
904
|
+
|
|
905
|
+
# calculate propagators
|
|
906
|
+
prop = np.zeros((nkeep, nkeep), float)
|
|
907
|
+
for i in range(nkeep):
|
|
908
|
+
for j in range(nkeep):
|
|
909
|
+
for n in range(nkeep):
|
|
910
|
+
prop[j,i] = prop[j,i] + \
|
|
911
|
+
phiR[j,n]*phiL[i,n]*np.exp(-abs(evalsym[n])*lagt)
|
|
912
|
+
|
|
913
|
+
# calculate likelihood using matrix of transitions
|
|
914
|
+
log_like = 0.
|
|
915
|
+
for i in range(nkeep):
|
|
916
|
+
for j in range(nkeep):
|
|
917
|
+
if count[j,i] > 0:
|
|
918
|
+
log_like = log_like + float(count[j,i])*np.log(prop[j,i])
|
|
919
|
+
|
|
920
|
+
return -log_like
|
|
921
|
+
|
|
922
|
+
def partial_rate(K, elem):
|
|
923
|
+
""" Calculates the derivative of the rate matrix
|
|
924
|
+
|
|
925
|
+
Parameters
|
|
926
|
+
----------
|
|
927
|
+
K : np.array
|
|
928
|
+
The rate matrix.
|
|
929
|
+
elem : int
|
|
930
|
+
Integer corresponding to which we calculate the
|
|
931
|
+
partial derivative.
|
|
932
|
+
|
|
933
|
+
Returns
|
|
934
|
+
-------
|
|
935
|
+
d_K : np.array
|
|
936
|
+
Partial derivative of rate matrix.
|
|
937
|
+
|
|
938
|
+
"""
|
|
939
|
+
nstates = len(K[0])
|
|
940
|
+
d_K = np.zeros((nstates,nstates), float)
|
|
941
|
+
for i in range(nstates):
|
|
942
|
+
if i != elem:
|
|
943
|
+
d_K[i,elem] = beta/2.*K[i,elem];
|
|
944
|
+
d_K[elem,i] = -beta/2.*K[elem,i];
|
|
945
|
+
for i in range(nstates):
|
|
946
|
+
d_K[i,i] = -np.sum(d_K[:,i])
|
|
947
|
+
return d_K
|
|
948
|
+
|
|
949
|
+
def partial_peq(peq, elem):
|
|
950
|
+
""" Calculates derivative of equilibrium distribution
|
|
951
|
+
|
|
952
|
+
Parameters
|
|
953
|
+
----------
|
|
954
|
+
peq : np.array
|
|
955
|
+
Equilibrium probabilities.
|
|
956
|
+
|
|
957
|
+
"""
|
|
958
|
+
nstates = len(peq)
|
|
959
|
+
d_peq = []
|
|
960
|
+
for i in range(nstates):
|
|
961
|
+
if i != elem:
|
|
962
|
+
d_peq.append(beta*peq[i]*peq[elem])
|
|
963
|
+
else:
|
|
964
|
+
d_peq.append(-beta*peq[i]*(1. - peq[i]))
|
|
965
|
+
return d_peq
|
|
966
|
+
|
|
967
|
+
def partial_pfold(states, K, d_K, FF, UU, elem):
|
|
968
|
+
""" Calculates derivative of pfold """
|
|
969
|
+
nstates = len(states)
|
|
970
|
+
# define end-states
|
|
971
|
+
I = list(filter(lambda x: x not in UU+FF, range(nstates)))
|
|
972
|
+
NI = len(I)
|
|
973
|
+
# calculate committors
|
|
974
|
+
b = np.zeros([NI], float)
|
|
975
|
+
A = np.zeros([NI,NI], float)
|
|
976
|
+
db = np.zeros([NI], float)
|
|
977
|
+
dA = np.zeros([NI,NI], float)
|
|
978
|
+
for j_ind in range(NI):
|
|
979
|
+
j = I[j_ind]
|
|
980
|
+
summ = 0.
|
|
981
|
+
sumd = 0.
|
|
982
|
+
for i in FF:
|
|
983
|
+
summ += K[i][j]
|
|
984
|
+
sumd += d_K[i][j]
|
|
985
|
+
b[j_ind] = -summ
|
|
986
|
+
db[j_ind] = -sumd
|
|
987
|
+
for i_ind in range(NI):
|
|
988
|
+
i = I[i_ind]
|
|
989
|
+
A[j_ind][i_ind] = K[i][j]
|
|
990
|
+
dA[j_ind][i_ind] = d_K[i][j]
|
|
991
|
+
|
|
992
|
+
# solve Ax + Bd(x) = c
|
|
993
|
+
Ainv = np.linalg.inv(A)
|
|
994
|
+
pfold = np.dot(Ainv,b)
|
|
995
|
+
x = np.dot(Ainv,db - np.dot(dA,pfold))
|
|
996
|
+
|
|
997
|
+
dpfold = np.zeros(nstates,float)
|
|
998
|
+
for i in range(nstates):
|
|
999
|
+
if i in UU:
|
|
1000
|
+
dpfold[i] = 0.0
|
|
1001
|
+
elif i in FF:
|
|
1002
|
+
dpfold[i] = 0.0
|
|
1003
|
+
else:
|
|
1004
|
+
ii = I.index(i)
|
|
1005
|
+
dpfold[i] = x[ii]
|
|
1006
|
+
return dpfold
|
|
1007
|
+
|
|
1008
|
+
def partial_flux(states, peq, K, pfold, d_peq, d_K, d_pfold, target):
|
|
1009
|
+
""" Calculates derivative of reactive flux """
|
|
1010
|
+
# flux matrix and reactive flux
|
|
1011
|
+
nstates = len(states)
|
|
1012
|
+
sum_d_flux = 0
|
|
1013
|
+
d_J = np.zeros((nstates,nstates),float)
|
|
1014
|
+
for i in range(nstates):
|
|
1015
|
+
for j in range(nstates):
|
|
1016
|
+
d_J[j][i] = d_K[j][i]*peq[i]*(pfold[j]-pfold[i]) + \
|
|
1017
|
+
K[j][i]*d_peq[i]*(pfold[j]-pfold[i]) + \
|
|
1018
|
+
K[j][i]*peq[i]*(d_pfold[j]-d_pfold[i])
|
|
1019
|
+
if j in target and K[j][i]>0: # dividing line corresponds to I to F transitions
|
|
1020
|
+
sum_d_flux += d_J[j][i]
|
|
1021
|
+
return sum_d_flux
|
|
1022
|
+
|
|
1023
|
+
def propagate_worker(x):
|
|
1024
|
+
""" Propagate dynamics using rate matrix exponential
|
|
1025
|
+
|
|
1026
|
+
Parameters
|
|
1027
|
+
----------
|
|
1028
|
+
x : list
|
|
1029
|
+
Contains K, the time and the initial population
|
|
1030
|
+
|
|
1031
|
+
Returns
|
|
1032
|
+
-------
|
|
1033
|
+
popul : np.array
|
|
1034
|
+
The propagated population
|
|
1035
|
+
|
|
1036
|
+
"""
|
|
1037
|
+
rate, t, pini = x
|
|
1038
|
+
expkt = spla.expm(rate*t)
|
|
1039
|
+
popul = mat_mul_v(expkt, pini)
|
|
1040
|
+
return popul
|
|
1041
|
+
|
|
1042
|
+
def propagateT_worker(x):
|
|
1043
|
+
""" Propagate dynamics using power of transition matrix
|
|
1044
|
+
|
|
1045
|
+
Parameters
|
|
1046
|
+
----------
|
|
1047
|
+
x : list
|
|
1048
|
+
Contains T, the power and initial population
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
Returns
|
|
1052
|
+
-------
|
|
1053
|
+
popul : np.array
|
|
1054
|
+
The propagated population
|
|
1055
|
+
|
|
1056
|
+
"""
|
|
1057
|
+
trans, power, pini = x
|
|
1058
|
+
trans_pow = np.linalg.matrix_power(trans,power)
|
|
1059
|
+
popul = mat_mul_v(trans_pow, pini)
|
|
1060
|
+
return popul
|
|
1061
|
+
|
|
1062
|
+
#def gen_path_lengths(keys, J, pfold, flux, FF, UU):
|
|
1063
|
+
# """ use BHS prescription for defining path lenghts """
|
|
1064
|
+
# nkeys = len(keys)
|
|
1065
|
+
# I = [x for x in range(nkeys) if x not in FF+UU]
|
|
1066
|
+
# Jnode = []
|
|
1067
|
+
# # calculate flux going through nodes
|
|
1068
|
+
# for i in range(nkeys):
|
|
1069
|
+
# Jnode.append(np.sum([J[i,x] for x in range(nkeys) \
|
|
1070
|
+
# if pfold[x] < pfold[i]]))
|
|
1071
|
+
# # define matrix with edge lengths
|
|
1072
|
+
# Jpath = np.zeros((nkeys, nkeys), float)
|
|
1073
|
+
# for i in UU:
|
|
1074
|
+
# for j in I + FF:
|
|
1075
|
+
# if J[j,i] > 0:
|
|
1076
|
+
# Jpath[j,i] = np.log(flux/J[j,i]) + 1
|
|
1077
|
+
# for i in I:
|
|
1078
|
+
# for j in [x for x in FF+I if pfold[x] > pfold[i]]:
|
|
1079
|
+
# if J[j,i] > 0:
|
|
1080
|
+
# Jpath[j,i] = np.log(Jnode[j]/J[j,i]) + 1
|
|
1081
|
+
# return Jnode, Jpath
|
|
1082
|
+
|
|
1083
|
+
#def calc_acf(x):
|
|
1084
|
+
# """ mp worker that calculates the ACF for a given mode
|
|
1085
|
+
#
|
|
1086
|
+
# Parameters
|
|
1087
|
+
# ----------
|
|
1088
|
+
# x : list
|
|
1089
|
+
# List containing input for each mp worker. Includes:
|
|
1090
|
+
# distraj :the time series of states
|
|
1091
|
+
# dt : the timestep for that trajectory
|
|
1092
|
+
# keys : the keys used in the assignment
|
|
1093
|
+
# lagt : the lag time for construction
|
|
1094
|
+
#
|
|
1095
|
+
# Returns
|
|
1096
|
+
# -------
|
|
1097
|
+
# acf : array
|
|
1098
|
+
# The autocorrelation function from that trajectory.
|
|
1099
|
+
#
|
|
1100
|
+
# """
|
|
1101
|
+
# # parse input from multiprocessing
|
|
1102
|
+
# distraj = x[0]
|
|
1103
|
+
# dt = x[1]
|
|
1104
|
+
# keys = x[2]
|
|
1105
|
+
# nkeys = len(keys)
|
|
1106
|
+
# lagt = x[3]
|
|
1107
|
+
## time =
|
|
1108
|
+
## sliding = x[4]
|
|
1109
|
+
#
|
|
1110
|
+
## ltraj = len(distraj)
|
|
1111
|
+
## lag = int(lagt/dt) # number of frames per lag time
|
|
1112
|
+
## if sliding:
|
|
1113
|
+
## slider = 1 # every state is initial state
|
|
1114
|
+
## else:
|
|
1115
|
+
## slider = lag
|
|
1116
|
+
##
|
|
1117
|
+
## count = np.zeros([nkeys,nkeys], np.int32)
|
|
1118
|
+
## for i in range(0, ltraj-lag, slider):
|
|
1119
|
+
## j = i + lag
|
|
1120
|
+
## state_i = distraj[i]
|
|
1121
|
+
## state_j = distraj[j]
|
|
1122
|
+
## if state_i in keys:
|
|
1123
|
+
## idx_i = keys.index(state_i)
|
|
1124
|
+
## if state_j in keys:
|
|
1125
|
+
## idx_j = keys.index(state_j)
|
|
1126
|
+
## try:
|
|
1127
|
+
## count[idx_j][idx_i] += 1
|
|
1128
|
+
## except UnboundLocalError:
|
|
1129
|
+
## pass
|
|
1130
|
+
# return acf
|
|
1131
|
+
|
|
1132
|
+
#def project_worker(x):
|
|
1133
|
+
# """ project simulation trajectories on eigenmodes"""
|
|
1134
|
+
# trans, power, pini = x
|
|
1135
|
+
# trans_pow = np.linalg.matrix_power(trans,power)
|
|
1136
|
+
# popul = mat_mul_v(trans_pow, pini)
|
|
1137
|
+
# return popul
|
|
1138
|
+
#
|
|
1139
|
+
|
|
1140
|
+
def peq_averages(peq_boots, keep_keys_boots, keys):
|
|
1141
|
+
""" Return averages from bootstrap results
|
|
1142
|
+
|
|
1143
|
+
Parameters
|
|
1144
|
+
----------
|
|
1145
|
+
peq_boots : list
|
|
1146
|
+
List of Peq arrays
|
|
1147
|
+
keep_keys_boots : list
|
|
1148
|
+
List of key lists
|
|
1149
|
+
keys : list
|
|
1150
|
+
List of keys
|
|
1151
|
+
|
|
1152
|
+
Returns:
|
|
1153
|
+
-------
|
|
1154
|
+
peq_ave : array
|
|
1155
|
+
Peq averages
|
|
1156
|
+
peq_std : array
|
|
1157
|
+
Peq std
|
|
1158
|
+
|
|
1159
|
+
"""
|
|
1160
|
+
peq_ave = []
|
|
1161
|
+
peq_std = []
|
|
1162
|
+
peq_indexes = []
|
|
1163
|
+
peq_keep = []
|
|
1164
|
+
for k in keys:
|
|
1165
|
+
peq_indexes.append([x.index(k) if k in x else None for x in keep_keys_boots])
|
|
1166
|
+
nboots = len(peq_boots)
|
|
1167
|
+
for k in keys:
|
|
1168
|
+
l = keys.index(k)
|
|
1169
|
+
data = []
|
|
1170
|
+
for n in range(nboots):
|
|
1171
|
+
if peq_indexes[l][n] is not None:
|
|
1172
|
+
data.append(peq_boots[n][peq_indexes[l][n]])
|
|
1173
|
+
try:
|
|
1174
|
+
peq_ave.append(np.mean(data))
|
|
1175
|
+
peq_std.append(np.std(data))
|
|
1176
|
+
peq_keep.append(data)
|
|
1177
|
+
except RuntimeWarning:
|
|
1178
|
+
peq_ave.append(0.)
|
|
1179
|
+
peq_std.append(0.)
|
|
1180
|
+
return peq_ave, peq_std
|
|
1181
|
+
|
|
1182
|
+
def tau_averages(tau_boots, ntau=None, q=(16, 84)):
|
|
1183
|
+
""" Return averages from bootstrap results
|
|
1184
|
+
|
|
1185
|
+
Bootstrap samples can have fewer relaxation times than the
|
|
1186
|
+
original model, when states drop out of the connected set of
|
|
1187
|
+
the resampled count matrix. Each relaxation time is averaged
|
|
1188
|
+
over the samples that have it; times are matched by rank
|
|
1189
|
+
(slowest first).
|
|
1190
|
+
|
|
1191
|
+
Parameters
|
|
1192
|
+
----------
|
|
1193
|
+
tau_boots : list
|
|
1194
|
+
List of Tau arrays
|
|
1195
|
+
ntau : int
|
|
1196
|
+
Number of relaxation times to report. Defaults to the
|
|
1197
|
+
largest number found in a sample.
|
|
1198
|
+
q : tuple
|
|
1199
|
+
Percentiles for the confidence interval.
|
|
1200
|
+
|
|
1201
|
+
Returns:
|
|
1202
|
+
-------
|
|
1203
|
+
tau_ave : list
|
|
1204
|
+
Tau averages (nan if no sample has that relaxation time)
|
|
1205
|
+
tau_std : list
|
|
1206
|
+
Tau std
|
|
1207
|
+
tau_nboots : list
|
|
1208
|
+
Number of samples that contributed to each average
|
|
1209
|
+
tau_ci : list
|
|
1210
|
+
(lower, upper) percentiles of each relaxation time
|
|
1211
|
+
|
|
1212
|
+
"""
|
|
1213
|
+
if ntau is None:
|
|
1214
|
+
ntau = max(len(x) for x in tau_boots)
|
|
1215
|
+
tau_ave = []
|
|
1216
|
+
tau_std = []
|
|
1217
|
+
tau_nboots = []
|
|
1218
|
+
tau_ci = []
|
|
1219
|
+
for n in range(ntau):
|
|
1220
|
+
data = [x[n] for x in tau_boots if len(x) > n and not np.isnan(x[n])]
|
|
1221
|
+
tau_nboots.append(len(data))
|
|
1222
|
+
if data:
|
|
1223
|
+
tau_ave.append(float(np.mean(data)))
|
|
1224
|
+
tau_std.append(float(np.std(data)))
|
|
1225
|
+
tau_ci.append(tuple(float(v) for v in np.percentile(data, q)))
|
|
1226
|
+
else:
|
|
1227
|
+
tau_ave.append(float('nan'))
|
|
1228
|
+
tau_std.append(float('nan'))
|
|
1229
|
+
tau_ci.append((float('nan'), float('nan')))
|
|
1230
|
+
return tau_ave, tau_std, tau_nboots, tau_ci
|
|
1231
|
+
|
|
1232
|
+
def matrix_ave(mat_boots, keep_keys_boots, keys):
|
|
1233
|
+
""" Return averages from bootstrap results
|
|
1234
|
+
|
|
1235
|
+
Parameters
|
|
1236
|
+
----------
|
|
1237
|
+
mat_boots : list
|
|
1238
|
+
List of matrix arrays
|
|
1239
|
+
keep_keys_boots : list
|
|
1240
|
+
List of key lists
|
|
1241
|
+
keys : list
|
|
1242
|
+
List of keys
|
|
1243
|
+
|
|
1244
|
+
Returns:
|
|
1245
|
+
-------
|
|
1246
|
+
mat_ave : array
|
|
1247
|
+
Matrix averages
|
|
1248
|
+
mat_std : array
|
|
1249
|
+
Matrix std
|
|
1250
|
+
|
|
1251
|
+
"""
|
|
1252
|
+
mat_ave = []
|
|
1253
|
+
mat_std = []
|
|
1254
|
+
nboots = len(keep_keys_boots)
|
|
1255
|
+
for k in keys:
|
|
1256
|
+
mat_ave_keep = []
|
|
1257
|
+
mat_std_keep = []
|
|
1258
|
+
for kk in keys:
|
|
1259
|
+
data = []
|
|
1260
|
+
for n in range(nboots):
|
|
1261
|
+
try:
|
|
1262
|
+
l = keep_keys_boots[n].index(k)
|
|
1263
|
+
ll = keep_keys_boots[n].index(kk)
|
|
1264
|
+
data.append(mat_boots[n][l,ll])
|
|
1265
|
+
except IndexError:
|
|
1266
|
+
data.append(0.)
|
|
1267
|
+
try:
|
|
1268
|
+
mat_ave_keep.append(np.mean(data))
|
|
1269
|
+
mat_std_keep.append(np.std(data))
|
|
1270
|
+
except RuntimeWarning:
|
|
1271
|
+
mat_ave_keep.append(0.)
|
|
1272
|
+
mat_std_keep.append(0.)
|
|
1273
|
+
mat_ave.append(mat_ave_keep)
|
|
1274
|
+
mat_std.append(mat_std_keep)
|
|
1275
|
+
return mat_ave, mat_std
|
|
1276
|
+
|
|
1277
|
+
def merge_trajs(data):
|
|
1278
|
+
"""
|
|
1279
|
+
Merge all trajectories into a consistent set.
|
|
1280
|
+
|
|
1281
|
+
Returns
|
|
1282
|
+
-------
|
|
1283
|
+
new_keys : list
|
|
1284
|
+
Combination of keys from all trajectories.
|
|
1285
|
+
|
|
1286
|
+
"""
|
|
1287
|
+
for tr in data:
|
|
1288
|
+
if not hasattr(tr, 'keys'):
|
|
1289
|
+
tr.find_keys()
|
|
1290
|
+
|
|
1291
|
+
new_keys = []
|
|
1292
|
+
for traj in data:
|
|
1293
|
+
new_keys += filter(lambda x: x not in new_keys, \
|
|
1294
|
+
traj.keys)
|
|
1295
|
+
|
|
1296
|
+
return sorted(new_keys)
|
|
1297
|
+
|
|
1298
|
+
def max_dt(data):
|
|
1299
|
+
"""
|
|
1300
|
+
Find maximum dt in trajectories.
|
|
1301
|
+
|
|
1302
|
+
Parameters
|
|
1303
|
+
----------
|
|
1304
|
+
data : list
|
|
1305
|
+
List of trajectory objects.
|
|
1306
|
+
|
|
1307
|
+
Returns
|
|
1308
|
+
-------
|
|
1309
|
+
float
|
|
1310
|
+
The maximum value of the time-step within the trajectories.
|
|
1311
|
+
|
|
1312
|
+
"""
|
|
1313
|
+
return np.max([x.dt for x in data])
|
|
1314
|
+
|
|
1315
|
+
def check_connect(count, keys):
|
|
1316
|
+
""" Check connectivity of count matrix.
|
|
1317
|
+
|
|
1318
|
+
Parameters
|
|
1319
|
+
----------
|
|
1320
|
+
count : np.array
|
|
1321
|
+
Count matrix
|
|
1322
|
+
keys : list
|
|
1323
|
+
Name of states.
|
|
1324
|
+
|
|
1325
|
+
Returns
|
|
1326
|
+
-------
|
|
1327
|
+
keep_states : list
|
|
1328
|
+
Indexes of states from the largest strongly connected set.
|
|
1329
|
+
keep_keys : list
|
|
1330
|
+
Keys for the largest strongly connected set.
|
|
1331
|
+
|
|
1332
|
+
References
|
|
1333
|
+
-----
|
|
1334
|
+
We use the Tarjan algorithm as implemented in NetworkX. [#]_
|
|
1335
|
+
|
|
1336
|
+
.. [#] R. Tarjan, "Depth-first search and linear graph algorithms",
|
|
1337
|
+
SIAM Journal of Computing (1972).
|
|
1338
|
+
|
|
1339
|
+
"""
|
|
1340
|
+
D = nx.DiGraph(count)
|
|
1341
|
+
keep_states = list(sorted(list(nx.strongly_connected_components(D)),
|
|
1342
|
+
key=len, reverse=True)[0])
|
|
1343
|
+
keep_states.sort()
|
|
1344
|
+
keep_keys = [keys[x] for x in keep_states]
|
|
1345
|
+
return keep_states, keep_keys
|
|
1346
|
+
|
|
1347
|
+
|