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
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is part of the MasterMSM package.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
#import h5py
|
|
6
|
+
import copy
|
|
7
|
+
import sys
|
|
8
|
+
import math
|
|
9
|
+
import numpy as np
|
|
10
|
+
import mdtraj as md
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
|
|
13
|
+
def load_mdtraj(top=None, xtc=None, stride=None):
|
|
14
|
+
""" Loads trajectories using mdtraj.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
top: str
|
|
19
|
+
The topology file, may be a PDB or GRO file.
|
|
20
|
+
xtc : str
|
|
21
|
+
The trajectory filename.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
mdtrajs : list
|
|
26
|
+
A list of mdtraj Trajectory objects.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
return md.load(xtc, top=top, stride=stride)
|
|
30
|
+
|
|
31
|
+
def compute_rama(traj, shift=False, sincos=False):
|
|
32
|
+
""" Computes Ramachandran angles
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
traj : md.trajectory
|
|
37
|
+
An MDtraj trajectory object
|
|
38
|
+
shift : bool
|
|
39
|
+
Whether we want to shift the torsions or not
|
|
40
|
+
sincos : bool
|
|
41
|
+
Whether we are calculating the sines and cosines
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
phi : array
|
|
46
|
+
An array with phi torsions
|
|
47
|
+
psi : array
|
|
48
|
+
An array with psi torsions
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
_, phi = md.compute_phi(traj.mdt)
|
|
52
|
+
_, psi = md.compute_psi(traj.mdt)
|
|
53
|
+
if shift:
|
|
54
|
+
return _shift(phi, psi)
|
|
55
|
+
elif sincos:
|
|
56
|
+
return np.column_stack([np.sin(phi), np.cos(phi)]), \
|
|
57
|
+
np.column_stack([np.sin(psi), np.cos(psi)])
|
|
58
|
+
else:
|
|
59
|
+
return phi, psi
|
|
60
|
+
|
|
61
|
+
def compute_contacts(traj, scheme=None, log=False):
|
|
62
|
+
""" Computes inter-residue contacts
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
traj : md.trajectory
|
|
67
|
+
An MDtraj trajectory object
|
|
68
|
+
log : bool
|
|
69
|
+
Whether distances are in log scale
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
distances, pairs = md.compute_contacts(traj, scheme=scheme)
|
|
73
|
+
if not log:
|
|
74
|
+
return distances
|
|
75
|
+
else:
|
|
76
|
+
return np.log(distances)
|
|
77
|
+
|
|
78
|
+
def _filter_states(states):
|
|
79
|
+
"""
|
|
80
|
+
Fills not-assigned frames (negative labels) when using dbscan or
|
|
81
|
+
hdbscan. Each unassigned frame inherits the label of the previous
|
|
82
|
+
assigned frame; unassigned frames at the start of the trajectory,
|
|
83
|
+
which have no previous frame, inherit the first assigned label.
|
|
84
|
+
The output always has the same length as the input.
|
|
85
|
+
|
|
86
|
+
Raises
|
|
87
|
+
------
|
|
88
|
+
ValueError
|
|
89
|
+
If no frame is assigned.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
states = list(states)
|
|
93
|
+
first = next((s for s in states if s >= 0), None)
|
|
94
|
+
if first is None:
|
|
95
|
+
raise ValueError("no frame was assigned to any cluster")
|
|
96
|
+
fs = []
|
|
97
|
+
prev = first
|
|
98
|
+
for s in states:
|
|
99
|
+
if s >= 0:
|
|
100
|
+
prev = s
|
|
101
|
+
fs.append(prev)
|
|
102
|
+
return fs
|
|
103
|
+
|
|
104
|
+
def _shift(phi, psi):
|
|
105
|
+
phi_s, psi_s = copy.deepcopy(phi), copy.deepcopy(psi)
|
|
106
|
+
for i in range(len(phi_s)):
|
|
107
|
+
phi_s[i] = [x + 2*np.pi if x < 0 else x for x in phi_s[i]]
|
|
108
|
+
for i in range(len(psi_s)):
|
|
109
|
+
psi_s[i] = [x + 2*np.pi if (x <-2) else x for x in psi_s[i]]
|
|
110
|
+
return phi_s, psi_s
|
|
111
|
+
|
|
112
|
+
def _inrange(x, lo, hi):
|
|
113
|
+
return 1 if (x > lo and x < hi) else 0
|
|
114
|
+
|
|
115
|
+
def _inbounds(bounds, phi, psi):
|
|
116
|
+
for i in range(0, len(bounds), 4):
|
|
117
|
+
if _inrange(phi, bounds[i], bounds[i+1]) and _inrange(psi, bounds[i+2], bounds[i+3]):
|
|
118
|
+
return 1
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
def _state(phi, psi, bounds):
|
|
122
|
+
""" Assign phi, psi (in degrees) to a coarse state.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
phi : float
|
|
127
|
+
Phi dihedral angle in degrees.
|
|
128
|
+
psi : float
|
|
129
|
+
Psi dihedral angle in degrees.
|
|
130
|
+
bounds : dict
|
|
131
|
+
Dict mapping state labels to [phi_min, phi_max, psi_min, psi_max, ...] bounds.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
k : str
|
|
136
|
+
Assigned state label, or 'O' if unassigned.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
for k, b in bounds.items():
|
|
140
|
+
if _inbounds(b, phi, psi):
|
|
141
|
+
return k, []
|
|
142
|
+
return 'O', [(phi, psi)]
|
|
143
|
+
|
|
144
|
+
_DEFAULT_BOUNDS = {'A': [-130., -40., -50., 20.],
|
|
145
|
+
'E': [-180., -40., 125., 180.],
|
|
146
|
+
'L': [30., 100., -40., 70.]}
|
|
147
|
+
|
|
148
|
+
def default_bounds(states=None):
|
|
149
|
+
""" Default Buchete-Hummer bounds for the requested states.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
states : list, optional
|
|
154
|
+
State labels, from 'A', 'E' and 'L'. Default is all three.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
bounds : dict
|
|
159
|
+
Maps each label to [phi_min, phi_max, psi_min, psi_max]
|
|
160
|
+
in degrees.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
if states is None:
|
|
164
|
+
states = ['A', 'E', 'L']
|
|
165
|
+
unknown = [s for s in states if s not in _DEFAULT_BOUNDS]
|
|
166
|
+
if unknown:
|
|
167
|
+
raise ValueError("no default bounds for states %s"%unknown)
|
|
168
|
+
return {s: list(_DEFAULT_BOUNDS[s]) for s in states}
|
|
169
|
+
|
|
170
|
+
def _validate_bounds(b):
|
|
171
|
+
""" Check one state's bounds list, returning it as floats. """
|
|
172
|
+
b = [float(x) for x in b]
|
|
173
|
+
if len(b) == 0 or len(b) % 4 != 0:
|
|
174
|
+
raise ValueError("bounds need groups of four values "\
|
|
175
|
+
"(phi_min, phi_max, psi_min, psi_max), got %i"%len(b))
|
|
176
|
+
for i in range(0, len(b), 4):
|
|
177
|
+
for lo, hi in [(b[i], b[i+1]), (b[i+2], b[i+3])]:
|
|
178
|
+
if not lo < hi:
|
|
179
|
+
raise ValueError("lower bound %g is not below "\
|
|
180
|
+
"upper bound %g"%(lo, hi))
|
|
181
|
+
if lo < -180. or hi > 180.:
|
|
182
|
+
raise ValueError("bounds must lie within "\
|
|
183
|
+
"[-180, 180] degrees")
|
|
184
|
+
return b
|
|
185
|
+
|
|
186
|
+
def _overlaps(bounds):
|
|
187
|
+
""" Pairs of states whose rectangles overlap. """
|
|
188
|
+
rects = [(k, b[i:i+4]) for k, b in bounds.items() \
|
|
189
|
+
for i in range(0, len(b), 4)]
|
|
190
|
+
pairs = []
|
|
191
|
+
for n, (k1, r1) in enumerate(rects):
|
|
192
|
+
for k2, r2 in rects[n+1:]:
|
|
193
|
+
if k1 == k2:
|
|
194
|
+
continue
|
|
195
|
+
if r1[0] < r2[1] and r2[0] < r1[1] and \
|
|
196
|
+
r1[2] < r2[3] and r2[2] < r1[3]:
|
|
197
|
+
if (k1, k2) not in pairs:
|
|
198
|
+
pairs.append((k1, k2))
|
|
199
|
+
return pairs
|
|
200
|
+
|
|
201
|
+
def rama_populations(phi, psi, bounds):
|
|
202
|
+
""" Fraction of (phi, psi) points falling in each state.
|
|
203
|
+
|
|
204
|
+
Counts direct assignments only, before unassigned points
|
|
205
|
+
inherit the previous state. Overlapping regions go to the
|
|
206
|
+
first state in ``bounds``, as in ``discrete_rama``.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
phi, psi : array
|
|
211
|
+
Angles in radians, any shape (all residues are pooled).
|
|
212
|
+
bounds : dict
|
|
213
|
+
State bounds in degrees.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
pops : dict
|
|
218
|
+
Fraction per state, plus 'O' for unassigned points.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
f = np.degrees(np.ravel(phi))
|
|
222
|
+
y = np.degrees(np.ravel(psi))
|
|
223
|
+
free = np.ones(len(f), dtype=bool)
|
|
224
|
+
pops = {}
|
|
225
|
+
for k, b in bounds.items():
|
|
226
|
+
inside = np.zeros(len(f), dtype=bool)
|
|
227
|
+
for i in range(0, len(b), 4):
|
|
228
|
+
inside |= (f > b[i]) & (f < b[i+1]) & \
|
|
229
|
+
(y > b[i+2]) & (y < b[i+3])
|
|
230
|
+
inside &= free
|
|
231
|
+
pops[k] = inside.mean() if len(f) else 0.
|
|
232
|
+
free &= ~inside
|
|
233
|
+
pops['O'] = free.mean() if len(f) else 0.
|
|
234
|
+
return pops
|
|
235
|
+
|
|
236
|
+
def plot_rama_bounds(phi, psi, bounds, ax=None, bins=90):
|
|
237
|
+
""" Ramachandran histogram with the state bounds drawn on top.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
phi, psi : array
|
|
242
|
+
Angles in radians, any shape (all residues are pooled).
|
|
243
|
+
bounds : dict
|
|
244
|
+
State bounds in degrees.
|
|
245
|
+
ax : matplotlib Axes, optional
|
|
246
|
+
Axes to draw on. A new figure is made by default.
|
|
247
|
+
bins : int
|
|
248
|
+
Number of histogram bins per axis.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
ax : matplotlib Axes
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
from matplotlib.colors import LogNorm
|
|
256
|
+
from matplotlib.patches import Rectangle
|
|
257
|
+
if ax is None:
|
|
258
|
+
_, ax = plt.subplots(figsize=(5, 4.5))
|
|
259
|
+
f = np.degrees(np.ravel(phi))
|
|
260
|
+
y = np.degrees(np.ravel(psi))
|
|
261
|
+
h, xe, ye = np.histogram2d(f, y, bins=bins, \
|
|
262
|
+
range=[[-180, 180], [-180, 180]], density=True)
|
|
263
|
+
h = np.ma.masked_where(h == 0, h)
|
|
264
|
+
ax.pcolormesh(xe, ye, h.T, norm=LogNorm(), cmap='Greys')
|
|
265
|
+
pops = rama_populations(phi, psi, bounds)
|
|
266
|
+
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
|
|
267
|
+
for n, (k, b) in enumerate(bounds.items()):
|
|
268
|
+
c = colors[n % len(colors)]
|
|
269
|
+
for i in range(0, len(b), 4):
|
|
270
|
+
ax.add_patch(Rectangle((b[i], b[i+2]), b[i+1] - b[i], \
|
|
271
|
+
b[i+3] - b[i+2], fill=False, ec=c, lw=2, \
|
|
272
|
+
label="%s (%.1f%%)"%(k, 100*pops[k]) \
|
|
273
|
+
if i == 0 else None))
|
|
274
|
+
ax.set_xlim(-180, 180)
|
|
275
|
+
ax.set_ylim(-180, 180)
|
|
276
|
+
ax.set_xticks(range(-180, 181, 90))
|
|
277
|
+
ax.set_yticks(range(-180, 181, 90))
|
|
278
|
+
ax.set_xlabel(r'$\phi$ (deg)')
|
|
279
|
+
ax.set_ylabel(r'$\psi$ (deg)')
|
|
280
|
+
ax.set_title('unassigned: %.1f%%'%(100*pops['O']))
|
|
281
|
+
ax.legend(loc='upper right', fontsize='small')
|
|
282
|
+
ax.figure.tight_layout()
|
|
283
|
+
return ax
|
|
284
|
+
|
|
285
|
+
def _interactive():
|
|
286
|
+
""" Whether a user can answer input() prompts. """
|
|
287
|
+
try:
|
|
288
|
+
from IPython import get_ipython
|
|
289
|
+
ip = get_ipython()
|
|
290
|
+
if ip is not None and getattr(ip, 'kernel', None) is not None:
|
|
291
|
+
return True
|
|
292
|
+
except ImportError:
|
|
293
|
+
pass
|
|
294
|
+
return sys.stdin is not None and sys.stdin.isatty()
|
|
295
|
+
|
|
296
|
+
def _ask(prompt, input_fn):
|
|
297
|
+
""" Prompt the user; None if nobody can answer. """
|
|
298
|
+
try:
|
|
299
|
+
return input_fn(prompt).strip()
|
|
300
|
+
except (EOFError, OSError, NotImplementedError):
|
|
301
|
+
# pytest capture, closed stdin, or a kernel without stdin
|
|
302
|
+
# (e.g. notebooks executed by nbconvert)
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def confirm_bounds(phi, psi, bounds, input_fn=None, plot=True):
|
|
306
|
+
""" Show the Ramachandran bounds and let the user revise them.
|
|
307
|
+
|
|
308
|
+
Draws the pooled Ramachandran histogram with each state's
|
|
309
|
+
region, reports the populations, and asks whether to accept.
|
|
310
|
+
On refusal, asks for new bounds state by state (blank keeps
|
|
311
|
+
the current ones) and shows the result again. When no user
|
|
312
|
+
can answer (scripts without a terminal, pytest, nbconvert),
|
|
313
|
+
the bounds are accepted with a warning.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
phi, psi : array
|
|
318
|
+
Angles in radians, any shape (all residues are pooled).
|
|
319
|
+
bounds : dict
|
|
320
|
+
Proposed state bounds in degrees.
|
|
321
|
+
input_fn : callable, optional
|
|
322
|
+
Replacement for ``input`` (used for testing). When given,
|
|
323
|
+
the session is assumed to be interactive.
|
|
324
|
+
plot : bool
|
|
325
|
+
Whether to draw the histogram (the populations are always
|
|
326
|
+
printed).
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
bounds : dict
|
|
331
|
+
The accepted bounds.
|
|
332
|
+
|
|
333
|
+
"""
|
|
334
|
+
import warnings
|
|
335
|
+
bounds = {k: _validate_bounds(b) for k, b in bounds.items()}
|
|
336
|
+
if input_fn is None:
|
|
337
|
+
input_fn = input
|
|
338
|
+
interactive = _interactive()
|
|
339
|
+
else:
|
|
340
|
+
interactive = True
|
|
341
|
+
|
|
342
|
+
while True:
|
|
343
|
+
if plot:
|
|
344
|
+
plot_rama_bounds(phi, psi, bounds)
|
|
345
|
+
plt.show()
|
|
346
|
+
plt.close()
|
|
347
|
+
pops = rama_populations(phi, psi, bounds)
|
|
348
|
+
for k, b in bounds.items():
|
|
349
|
+
print(" %s: %s (%.1f%%)"%(k, b, 100*pops[k]))
|
|
350
|
+
print(" unassigned: %.1f%%"%(100*pops['O']))
|
|
351
|
+
for k1, k2 in _overlaps(bounds):
|
|
352
|
+
print(" warning: %s and %s overlap; points in both go "\
|
|
353
|
+
"to %s"%(k1, k2, k1))
|
|
354
|
+
|
|
355
|
+
ans = _ask("Accept these bounds? [Y/n]: ", input_fn) \
|
|
356
|
+
if interactive else None
|
|
357
|
+
if ans is None:
|
|
358
|
+
warnings.warn("no interactive input available; using "\
|
|
359
|
+
"Ramachandran bounds without confirmation: %s"\
|
|
360
|
+
%bounds, stacklevel=2)
|
|
361
|
+
return bounds
|
|
362
|
+
if ans.lower() in ('', 'y', 'yes'):
|
|
363
|
+
return bounds
|
|
364
|
+
if ans.lower() not in ('n', 'no'):
|
|
365
|
+
print("Please answer y or n.")
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
for k in bounds:
|
|
369
|
+
while True:
|
|
370
|
+
new = _ask("%s %s -> phi_min, phi_max, psi_min, "\
|
|
371
|
+
"psi_max [, more groups of 4] (blank keeps): "\
|
|
372
|
+
%(k, bounds[k]), input_fn)
|
|
373
|
+
if not new:
|
|
374
|
+
break
|
|
375
|
+
try:
|
|
376
|
+
bounds[k] = _validate_bounds( \
|
|
377
|
+
new.replace(',', ' ').split())
|
|
378
|
+
break
|
|
379
|
+
except ValueError as e:
|
|
380
|
+
print(" invalid: %s"%e)
|
|
381
|
+
|
|
382
|
+
def discrete_rama(phi, psi, bounds=None, states=None,
|
|
383
|
+
return_unassigned=False):
|
|
384
|
+
""" Assign Ramachandran angles to coarse states.
|
|
385
|
+
|
|
386
|
+
Follows the Buchete-Hummer transition-based assignment: frames
|
|
387
|
+
outside every core inherit the state of the previous frame.
|
|
388
|
+
Leading frames, recorded before every dihedral has visited a core,
|
|
389
|
+
have no previous state and are dropped.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
phi : array
|
|
394
|
+
Phi angles, shape (n_frames, n_dihedrals), in radians.
|
|
395
|
+
psi : array
|
|
396
|
+
Psi angles, shape (n_frames, n_dihedrals), in radians.
|
|
397
|
+
bounds : dict, optional
|
|
398
|
+
Custom bounds dict. Default uses standard A/E/L regions.
|
|
399
|
+
states : list, optional
|
|
400
|
+
State labels to include. Defaults to all defined labels.
|
|
401
|
+
return_unassigned : bool, optional
|
|
402
|
+
Also return the fraction of frames outside every core, per
|
|
403
|
+
dihedral, and the number of leading frames dropped.
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
cstates : list
|
|
408
|
+
Per-frame state string (one character per dihedral pair), with
|
|
409
|
+
the leading unassigned frames removed.
|
|
410
|
+
unassigned : array, optional
|
|
411
|
+
Fraction of frames (over the full input) outside every core,
|
|
412
|
+
one value per dihedral. Only if return_unassigned is True.
|
|
413
|
+
ndrop : int, optional
|
|
414
|
+
Number of leading frames dropped. Only if return_unassigned
|
|
415
|
+
is True.
|
|
416
|
+
|
|
417
|
+
Raises
|
|
418
|
+
------
|
|
419
|
+
ValueError
|
|
420
|
+
If some dihedral never visits any core.
|
|
421
|
+
|
|
422
|
+
References
|
|
423
|
+
----------
|
|
424
|
+
N.-V. Buchete and G. Hummer, J. Phys. Chem. B (2008).
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
if bounds is None:
|
|
428
|
+
bounds = default_bounds(states)
|
|
429
|
+
|
|
430
|
+
ndih = phi.shape[1]
|
|
431
|
+
if psi.shape[1] != ndih:
|
|
432
|
+
raise ValueError("phi and psi have different numbers of dihedrals")
|
|
433
|
+
|
|
434
|
+
raw = [[_state(f[n] * 180. / math.pi, y[n] * 180. / math.pi,
|
|
435
|
+
bounds)[0] for n in range(ndih)]
|
|
436
|
+
for f, y in zip(phi, psi)]
|
|
437
|
+
raw = np.array(raw, dtype='<U1').reshape(len(raw), ndih)
|
|
438
|
+
unassigned = np.mean(raw == 'O', axis=0)
|
|
439
|
+
|
|
440
|
+
# first frame at which each dihedral is in a core
|
|
441
|
+
assigned = raw != 'O'
|
|
442
|
+
if not np.all(assigned.any(axis=0)):
|
|
443
|
+
never = np.where(~assigned.any(axis=0))[0].tolist()
|
|
444
|
+
raise ValueError("dihedral(s) %s never visit any core" % never)
|
|
445
|
+
ndrop = int(np.max(np.argmax(assigned, axis=0)))
|
|
446
|
+
|
|
447
|
+
# inherit over the full history, then drop the leading frames
|
|
448
|
+
cstates = []
|
|
449
|
+
prev_s_string = ['O'] * ndih
|
|
450
|
+
for row in raw:
|
|
451
|
+
s_string = [prev_s_string[n] if s == 'O' else s
|
|
452
|
+
for n, s in enumerate(row)]
|
|
453
|
+
cstates.append(''.join(s_string))
|
|
454
|
+
prev_s_string = s_string
|
|
455
|
+
cstates = cstates[ndrop:]
|
|
456
|
+
|
|
457
|
+
if return_unassigned:
|
|
458
|
+
return cstates, unassigned, ndrop
|
|
459
|
+
return cstates
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: MasterMSM
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Algorithms to construct master equation / Markov state models
|
|
5
|
+
Author-email: David De Sancho <daviddesancho@gmail.com>
|
|
6
|
+
License-Expression: GPL-2.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/BioKT/MasterMSM
|
|
8
|
+
Project-URL: Documentation, https://mastermsm.readthedocs.io
|
|
9
|
+
Project-URL: Changelog, https://github.com/BioKT/MasterMSM/blob/master/CHANGELOG.md
|
|
10
|
+
Keywords: markov state model,master equation,molecular dynamics
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy
|
|
25
|
+
Requires-Dist: scipy
|
|
26
|
+
Requires-Dist: matplotlib
|
|
27
|
+
Requires-Dist: networkx
|
|
28
|
+
Requires-Dist: mdtraj
|
|
29
|
+
Requires-Dist: scikit-learn
|
|
30
|
+
Provides-Extra: hdbscan
|
|
31
|
+
Requires-Dist: hdbscan; extra == "hdbscan"
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest; extra == "test"
|
|
34
|
+
Requires-Dist: ipython; extra == "test"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
[](https://github.com/BioKT/MasterMSM/actions/workflows/ci.yml)
|
|
38
|
+
[](https://codecov.io/gh/BioKT/MasterMSM)
|
|
39
|
+
[](https://mastermsm.readthedocs.io/en/latest/?badge=latest)
|
|
40
|
+
|
|
41
|
+
MasterMSM
|
|
42
|
+
=========
|
|
43
|
+
MasterMSM is a Python package for building Markov state models (MSMs)
|
|
44
|
+
from molecular dynamics trajectories, using a formulation based on the
|
|
45
|
+
chemical master equation. Besides the usual transition matrix, it
|
|
46
|
+
estimates the rate matrix of the model, which gives direct access to
|
|
47
|
+
rate coefficients, committors and reactive fluxes.
|
|
48
|
+
|
|
49
|
+
Full documentation is at [mastermsm.readthedocs.io](https://mastermsm.readthedocs.io).
|
|
50
|
+
|
|
51
|
+
Features
|
|
52
|
+
--------
|
|
53
|
+
* **Trajectory handling**: read MD trajectories through
|
|
54
|
+
[MDTraj](https://mdtraj.org) or discrete state trajectories from
|
|
55
|
+
file.
|
|
56
|
+
* **Featurization and dimensionality reduction**: backbone torsions
|
|
57
|
+
and contacts; PCA and TICA.
|
|
58
|
+
* **Discretization**: assignment to Ramachandran basins with
|
|
59
|
+
transition-based assignment (suited to small peptides), k-means
|
|
60
|
+
and HDBSCAN clustering. Different protocols can be run on the same
|
|
61
|
+
data and compared.
|
|
62
|
+
* **Model estimation**: count and transition matrices at any lag
|
|
63
|
+
time; rate matrices from a Taylor expansion or from the maximum
|
|
64
|
+
likelihood propagator-based (MLPB) method; bootstrap errors for
|
|
65
|
+
relaxation times and equilibrium populations.
|
|
66
|
+
* **Validation**: convergence of relaxation times with lag time,
|
|
67
|
+
Chapman-Kolmogorov test, autocorrelation functions of the
|
|
68
|
+
eigenmodes.
|
|
69
|
+
* **Kinetics**: committors, reactive fluxes and rates between sets of
|
|
70
|
+
states; sensitivity analysis of the network.
|
|
71
|
+
* **Coarse-graining**: few-state models from the eigenvectors of the
|
|
72
|
+
MSM.
|
|
73
|
+
|
|
74
|
+
Installation
|
|
75
|
+
------------
|
|
76
|
+
MasterMSM requires Python >= 3.10 and is installed from
|
|
77
|
+
[PyPI](https://pypi.org/project/MasterMSM/) with `pip`:
|
|
78
|
+
|
|
79
|
+
pip install MasterMSM
|
|
80
|
+
|
|
81
|
+
To work on the code, install it from source instead:
|
|
82
|
+
|
|
83
|
+
git clone https://github.com/BioKT/MasterMSM
|
|
84
|
+
cd MasterMSM
|
|
85
|
+
pip install -e .
|
|
86
|
+
|
|
87
|
+
The dependencies are installed automatically by `pip`: numpy, scipy,
|
|
88
|
+
matplotlib, networkx, [mdtraj](https://mdtraj.org) and scikit-learn.
|
|
89
|
+
If you use conda, you may prefer to install them from conda-forge
|
|
90
|
+
first:
|
|
91
|
+
|
|
92
|
+
conda install -c conda-forge numpy scipy matplotlib \
|
|
93
|
+
networkx mdtraj scikit-learn
|
|
94
|
+
|
|
95
|
+
HDBSCAN clustering additionally needs the optional
|
|
96
|
+
[hdbscan](https://github.com/scikit-learn-contrib/hdbscan) package
|
|
97
|
+
(`pip install "MasterMSM[hdbscan]"`, or `pip install hdbscan`).
|
|
98
|
+
|
|
99
|
+
Quick start
|
|
100
|
+
-----------
|
|
101
|
+
The script below builds an MSM for the alanine dipeptide from a short
|
|
102
|
+
MD trajectory (downloaded from [OSF](https://osf.io/a2vc7) on first
|
|
103
|
+
use).
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from mastermsm.trajectory import traj
|
|
107
|
+
from mastermsm.msm import msm
|
|
108
|
+
from mastermsm.fewsm import fewsm
|
|
109
|
+
from mastermsm.test import download_data as dd
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
# 1. Load the trajectory and assign frames to Ramachandran states
|
|
113
|
+
dd.download_osf_alaTB()
|
|
114
|
+
tr = traj.TimeSeries(top=dd._DATA_DIR + "/alaTB.gro",
|
|
115
|
+
xtc=dd._DATA_DIR + "/alaTB.xtc")
|
|
116
|
+
tr.discretize(states=["A", "E", "L"])
|
|
117
|
+
tr.find_keys()
|
|
118
|
+
|
|
119
|
+
# 2. Build the MSM at a lag time of 10 ps (units of the trajectory)
|
|
120
|
+
smsm = msm.SuperMSM([tr])
|
|
121
|
+
lagt = 10
|
|
122
|
+
smsm.do_msm(lagt)
|
|
123
|
+
model = smsm.msms[lagt]
|
|
124
|
+
model.do_trans() # transition matrix and its eigenvalues
|
|
125
|
+
model.boots() # bootstrap errors
|
|
126
|
+
print(model.keep_keys) # states in the model: ['A', 'E', 'L']
|
|
127
|
+
print(model.tauT) # relaxation times, ~794 and ~55 ps
|
|
128
|
+
print(model.tau_std)
|
|
129
|
+
print(model.peqT) # equilibrium populations
|
|
130
|
+
|
|
131
|
+
# 3. Rate matrix, committors and flux between E and A
|
|
132
|
+
model.do_rate() # method='Taylor' (default) or 'MLPB'
|
|
133
|
+
print(model.rate)
|
|
134
|
+
model.do_pfold(FF=["A"], UU=["E"])
|
|
135
|
+
print(model.pfold, model.kf)
|
|
136
|
+
|
|
137
|
+
# 4. Coarse-grain into a two-state model
|
|
138
|
+
two_state = fewsm.FEWSM(parent=model, N=2)
|
|
139
|
+
print(two_state.macros)
|
|
140
|
+
|
|
141
|
+
# 5. Validate: relaxation times as a function of lag time
|
|
142
|
+
smsm.convergence_test(time=[5, 10, 20, 50, 100], error=True)
|
|
143
|
+
for lt in sorted(smsm.msms):
|
|
144
|
+
print(lt, smsm.msms[lt].tauT)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
MasterMSM uses `multiprocessing`, so scripts need the
|
|
148
|
+
`if __name__ == "__main__":` guard shown above. This is not required
|
|
149
|
+
in a Jupyter notebook.
|
|
150
|
+
|
|
151
|
+
Examples
|
|
152
|
+
--------
|
|
153
|
+
The [examples](examples) folder contains Jupyter notebooks:
|
|
154
|
+
|
|
155
|
+
| Folder | Content |
|
|
156
|
+
|--------|---------|
|
|
157
|
+
| [alanine_dipeptide](examples/alanine_dipeptide) | Complete workflow on the simplest peptide model: discretization, MSM, validation, rate matrix (Taylor and MLPB) |
|
|
158
|
+
| [alanine_pentapeptide](examples/alanine_pentapeptide) | Featurization, TICA and comparison of discretization protocols |
|
|
159
|
+
| [bistable_potential](examples/bistable_potential) | Brownian dynamics on one and two dimensional two-state potentials |
|
|
160
|
+
| [schutte_potential](examples/schutte_potential) | Brownian dynamics on the one dimensional Schütte potential |
|
|
161
|
+
|
|
162
|
+
Most of the data are hosted at [OSF](https://osf.io/a2vc7); each
|
|
163
|
+
notebook says where its data come from. The Brownian dynamics data
|
|
164
|
+
for the bistable and Schütte potentials can be fetched with
|
|
165
|
+
|
|
166
|
+
python examples/download_data.py
|
|
167
|
+
|
|
168
|
+
Testing
|
|
169
|
+
-------
|
|
170
|
+
The tests use `pytest` and download a small amount of MD data on the
|
|
171
|
+
first run:
|
|
172
|
+
|
|
173
|
+
pip install pytest
|
|
174
|
+
pytest --pyargs mastermsm
|
|
175
|
+
|
|
176
|
+
Contributing
|
|
177
|
+
------------
|
|
178
|
+
Development takes place on the `develop` branch; `master` holds the
|
|
179
|
+
releases. Bug reports and pull requests (against `develop`) are welcome
|
|
180
|
+
on [GitHub](https://github.com/BioKT/MasterMSM/issues).
|
|
181
|
+
|
|
182
|
+
Citation
|
|
183
|
+
--------
|
|
184
|
+
If you use MasterMSM in your work, please cite:
|
|
185
|
+
|
|
186
|
+
@article{mastermsm,
|
|
187
|
+
author = "David De Sancho and Anne Aguirre",
|
|
188
|
+
title = "{MasterMSM: A Package for Constructing Master Equation Models of Molecular Dynamics}",
|
|
189
|
+
year = "2019",
|
|
190
|
+
month = "6",
|
|
191
|
+
journal = "J. Chem. Inf. Model.",
|
|
192
|
+
url = "https://doi.org/10.1021/acs.jcim.9b00468",
|
|
193
|
+
doi = "10.1021/acs.jcim.9b00468"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
Contributors
|
|
197
|
+
------------
|
|
198
|
+
* David De Sancho
|
|
199
|
+
* Anne Aguirre
|
|
200
|
+
* Ion Mitxelena
|
|
201
|
+
|
|
202
|
+
License
|
|
203
|
+
-------
|
|
204
|
+
MasterMSM is distributed under the GNU General Public License,
|
|
205
|
+
version 2. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
mastermsm/__init__.py,sha256=wAxkK8w13vqoF47A8iqWdSlIgRRXmZiQ0R4wePZfzhs,22
|
|
2
|
+
mastermsm/fewsm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
mastermsm/fewsm/fewsm.py,sha256=zdO45ccn4RpYIhbJ_uBRH-OdulskoJZU6wylb8qKcoI,9463
|
|
4
|
+
mastermsm/fewsm/fewsm_lib.py,sha256=vB7wzAG7T8aSODEhiJxNobp6OJ1tn_NLn9Wwvz7ELy8,3091
|
|
5
|
+
mastermsm/msm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mastermsm/msm/msm.py,sha256=fUHUeFV5ZWbDYm03P3ZZLo2SndXpU11LUkV4BR4xLhs,38007
|
|
7
|
+
mastermsm/msm/msm_lib.py,sha256=qf5ocQMVLG4J_-Gw8ERnJKjY0JrGOxXSIRwovBR6DvY,36972
|
|
8
|
+
mastermsm/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
mastermsm/test/download_data.py,sha256=Z6gjz4WWeLsf9yAyrSezH36dJYptpsFKPmcYWHbJtO0,2359
|
|
10
|
+
mastermsm/test/test_fewsm.py,sha256=c04T-ox6G18v8mSE4M1HH2WiU1go1-0xpLOeTAlpnP8,3546
|
|
11
|
+
mastermsm/test/test_msm.py,sha256=RiymRaQqQEE59wQ3k9RVEEqp8XruFFqCGl8DV0YhWRs,42292
|
|
12
|
+
mastermsm/test/test_trajectory.py,sha256=oUo-xiwJDxaVNNXNpyeDHGUj6vZU56_L71d8AxFTKSA,42437
|
|
13
|
+
mastermsm/trajectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
mastermsm/trajectory/traj.py,sha256=cZD_eP4jGn-TDNRhpTwiIgD0BaNp_HYNKYn8YM1b81Y,24612
|
|
15
|
+
mastermsm/trajectory/traj_lib.py,sha256=dSoA5Zd1D_i4VjE6lCOIjGmiwBGtIJp6_a-7v0_j5Kg,14539
|
|
16
|
+
mastermsm-2.0.1.dist-info/licenses/LICENSE,sha256=wDzqAntLQORAL6vQhVdzZyfsPVvFStZKtkct5DIZjK0,18047
|
|
17
|
+
mastermsm-2.0.1.dist-info/METADATA,sha256=-Xh4K4gEa3YPpeyrCQSaKKHecb-8f1DBzTiXY4SoIc4,7832
|
|
18
|
+
mastermsm-2.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
19
|
+
mastermsm-2.0.1.dist-info/top_level.txt,sha256=RGqtnrMyaRvEZW8KrTGYFrTBQkmLGi_hg3YW6ZrtsjU,10
|
|
20
|
+
mastermsm-2.0.1.dist-info/RECORD,,
|