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.
@@ -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
+