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/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