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,1051 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import importlib.util
|
|
3
|
+
import os
|
|
4
|
+
import mdtraj as md
|
|
5
|
+
import numpy as np
|
|
6
|
+
from mastermsm.trajectory import traj_lib, traj
|
|
7
|
+
from mastermsm.test.download_data import download_osf_alaTB, download_osf_ala5, _DATA_DIR
|
|
8
|
+
|
|
9
|
+
class TestTimeSeries(unittest.TestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
download_osf_alaTB()
|
|
12
|
+
|
|
13
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
14
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
15
|
+
|
|
16
|
+
self.ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
17
|
+
self.tss = [traj.TimeSeries(xtc=xtc, top=top) \
|
|
18
|
+
for i in range(2)]
|
|
19
|
+
|
|
20
|
+
self.dis = traj.TimeSeries(distraj=[0,1,1,0])
|
|
21
|
+
self.dis_multi = traj.TimeSeries(distraj=[[0,1,1,0], \
|
|
22
|
+
[1,0,0,1]])
|
|
23
|
+
|
|
24
|
+
#class TestMDTrajLib(unittest.TestCase):
|
|
25
|
+
# def setUp(self):
|
|
26
|
+
# download_osf_alaTB()
|
|
27
|
+
# self.ts = traj.TimeSeries(top='test/data/alaTB.gro', \
|
|
28
|
+
# trajs=['test/data/alaTB.xtc'])
|
|
29
|
+
# self.ts_multi = traj.TimeSeries(top='test/data/alaTB.gro', \
|
|
30
|
+
# trajs=['test/data/alaTB.xtc', \
|
|
31
|
+
# 'test/data/alaTB.xtc'])
|
|
32
|
+
# self.ts_dis = traj.TimeSeries(dtrajs=[0,1,1,0])
|
|
33
|
+
#
|
|
34
|
+
#
|
|
35
|
+
# def test_inrange(self):
|
|
36
|
+
# self.inrange = traj_lib._inrange(2, 1, 3)
|
|
37
|
+
# self.assertEqual(self.inrange, 1)
|
|
38
|
+
# self.inrange = traj_lib._inrange(0, 1, 2)
|
|
39
|
+
# self.assertEqual(self.inrange, 0)
|
|
40
|
+
# self.inrange = traj_lib._inrange(1, 1, 2)
|
|
41
|
+
# self.assertEqual(self.inrange, 0)
|
|
42
|
+
#
|
|
43
|
+
# def test_inbounds(self):
|
|
44
|
+
# TBA_bounds = {}
|
|
45
|
+
# TBA_bounds['A'] = [-100., -40., -50., -10.]
|
|
46
|
+
# TBA_bounds['E'] = [-180., -40., 125., 165.]
|
|
47
|
+
# TBA_bounds['L'] = [50., 100., -40., 70.0]
|
|
48
|
+
#
|
|
49
|
+
# # test in alpha helix
|
|
50
|
+
# self.inbounds = traj_lib._inbounds(TBA_bounds['A'], -90, -40)
|
|
51
|
+
# self.assertEqual(self.inbounds, 1)
|
|
52
|
+
# # test in beta-sheet
|
|
53
|
+
# self.inbounds = traj_lib._inbounds(TBA_bounds['E'], -90, 140)
|
|
54
|
+
# self.assertEqual(self.inbounds, 1)
|
|
55
|
+
# # test in left-handed alpha helix
|
|
56
|
+
# self.inbounds = traj_lib._inbounds(TBA_bounds['L'], 70, 30)
|
|
57
|
+
# self.assertEqual(self.inbounds, 1)
|
|
58
|
+
# # test when no conformation
|
|
59
|
+
# self.inbounds = traj_lib._inbounds(TBA_bounds['A'], 0, 0)
|
|
60
|
+
# self.assertEqual(self.inbounds, 0)
|
|
61
|
+
#
|
|
62
|
+
#
|
|
63
|
+
# def test_state(self):
|
|
64
|
+
# psi = [-30, 0, -40, 90, 140, 180]
|
|
65
|
+
# phi = [60., 0, -90, -90, -90, -180]
|
|
66
|
+
# states_test = ['L','O','A','O','E','O']
|
|
67
|
+
# bounds = {}
|
|
68
|
+
# bounds['A'] = [-100., -40., -50., -10.]
|
|
69
|
+
# bounds['E'] = [-180., -40., 125., 165.]
|
|
70
|
+
# bounds['L'] = [50., 100., -40., 70.0]
|
|
71
|
+
#
|
|
72
|
+
# for ind in range(len(phi)):
|
|
73
|
+
# result = traj_lib._state(phi[ind], psi[ind], bounds)
|
|
74
|
+
# state = result[0]
|
|
75
|
+
# self.assertEqual(state, states_test[ind], 'expected state %s but got %s'%(state,states_test[ind]))
|
|
76
|
+
#
|
|
77
|
+
# def test_stategrid(self):
|
|
78
|
+
# self.assertIsNotNone(traj_lib._stategrid(-180, -180, 20))
|
|
79
|
+
# self.assertLess(traj_lib._stategrid(-180, 0, 20),400)
|
|
80
|
+
# self.assertEqual(traj_lib._stategrid(0, 0, 20), 210)
|
|
81
|
+
# self.assertEqual(traj_lib._stategrid(-180, 0, 100), 2186)
|
|
82
|
+
#
|
|
83
|
+
# def test_discreterama(self):
|
|
84
|
+
# mdt_test = self.tr.mdt
|
|
85
|
+
#
|
|
86
|
+
# phi = md.compute_phi(mdt_test)
|
|
87
|
+
# psi = md.compute_psi(mdt_test)
|
|
88
|
+
# # print(psi)
|
|
89
|
+
# # psi = ([ 6, 8, 14, 16], [-30, 0, -40, 90, 140, 180])
|
|
90
|
+
# # phi = ([ 4, 6, 8, 14],[60., 0, -90, -90, -90, -180])
|
|
91
|
+
# states = ['L','A','E']
|
|
92
|
+
# discrete = traj_lib.discrete_rama(phi, psi, states=states)
|
|
93
|
+
# unique_st = set(discrete)
|
|
94
|
+
# for state in unique_st:
|
|
95
|
+
# self.assertIn(state, ['O', 'A', 'E', 'L'])
|
|
96
|
+
#
|
|
97
|
+
# def test_discreteramagrid(self):
|
|
98
|
+
# mdt_test = self.tr.mdt
|
|
99
|
+
#
|
|
100
|
+
# phi = md.compute_phi(mdt_test)
|
|
101
|
+
# psi = md.compute_psi(mdt_test)
|
|
102
|
+
# discrete = traj_lib.discrete_ramagrid(phi, psi, nbins=20)
|
|
103
|
+
# min_ibin = min(discrete)
|
|
104
|
+
# max_ibin = max(discrete)
|
|
105
|
+
# self.assertLess(max_ibin,400)
|
|
106
|
+
# self.assertGreaterEqual(min_ibin,0)
|
|
107
|
+
|
|
108
|
+
class TestMDtraj(unittest.TestCase):
|
|
109
|
+
def setUp(self):
|
|
110
|
+
download_osf_alaTB()
|
|
111
|
+
self.gro = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
112
|
+
self.xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
113
|
+
self.mdtraj = traj_lib.load_mdtraj(xtc=self.xtc, top=self.gro)
|
|
114
|
+
self.mdtrajs = [traj_lib.load_mdtraj(xtc=self.xtc, \
|
|
115
|
+
top=self.gro) for i in range(2)]
|
|
116
|
+
|
|
117
|
+
def test_traj(self):
|
|
118
|
+
self.assertIsNotNone(self.mdtraj)
|
|
119
|
+
self.assertEqual(self.mdtraj.n_atoms, 19)
|
|
120
|
+
self.assertEqual(self.mdtraj.timestep, 5.)
|
|
121
|
+
self.assertEqual(self.mdtraj.n_residues, 3)
|
|
122
|
+
self.assertEqual(self.mdtraj.n_frames, 40001)
|
|
123
|
+
|
|
124
|
+
def test_trajs(self):
|
|
125
|
+
self.assertEqual(len(self.mdtrajs), 2)
|
|
126
|
+
self.assertEqual(self.mdtrajs[0].n_atoms, 19)
|
|
127
|
+
self.assertEqual(self.mdtrajs[0].timestep, 5.)
|
|
128
|
+
self.assertEqual(self.mdtrajs[0].n_residues, 3)
|
|
129
|
+
|
|
130
|
+
def test_load_mdtraj(self):
|
|
131
|
+
self.assertIsNotNone(self.mdtraj)
|
|
132
|
+
self.assertEqual(self.mdtraj.__module__, 'mdtraj.core.trajectory')
|
|
133
|
+
self.assertEqual(hasattr(self.mdtraj, '__class__'), True)
|
|
134
|
+
|
|
135
|
+
# def test_read_distraj(self):
|
|
136
|
+
# self.assertIsNotNone(self.tr._read_distraj)
|
|
137
|
+
# self.assertEqual(callable(self.tr._read_distraj), True)
|
|
138
|
+
# # read distraj from temp file
|
|
139
|
+
# content = "0.0 A\n" \
|
|
140
|
+
# "1.0 E\n" \
|
|
141
|
+
# "2.0 L\n" \
|
|
142
|
+
# "3.0 O"
|
|
143
|
+
# fn = 'temp.txt'
|
|
144
|
+
# fd = open(fn, 'w+')
|
|
145
|
+
#
|
|
146
|
+
# try:
|
|
147
|
+
# fd.write(content)
|
|
148
|
+
# fd.seek(0)
|
|
149
|
+
# cstates, dt = self.tr._read_distraj(distraj=fd.name)
|
|
150
|
+
# self.assertIsInstance(cstates, list)
|
|
151
|
+
# self.assertEqual(len(cstates), len(content.split('\n')))
|
|
152
|
+
# self.assertEqual(dt, 1.0)
|
|
153
|
+
#
|
|
154
|
+
# finally:
|
|
155
|
+
# fd.close()
|
|
156
|
+
# os.remove(fd.name)
|
|
157
|
+
# # read distraj from array and custom timestamp
|
|
158
|
+
# distraj_arr = content.split('\n')
|
|
159
|
+
# cstates, dt = self.tr._read_distraj(distraj=distraj_arr, dt=2.0)
|
|
160
|
+
# self.assertIsInstance(cstates,list)
|
|
161
|
+
# self.assertEqual(len(cstates), len(content.split('\n')))
|
|
162
|
+
# self.assertEqual(dt, 2.0)
|
|
163
|
+
# # read empty 'discrete' trajectory
|
|
164
|
+
# cstates, dt = self.tr._read_distraj(distraj=[])
|
|
165
|
+
# self.assertEqual(len(cstates), 0)
|
|
166
|
+
# self.assertEqual(dt, 1.0)
|
|
167
|
+
#
|
|
168
|
+
# def test_timeseries_init(self):
|
|
169
|
+
# self.assertIsNotNone(self.ts)
|
|
170
|
+
# traj = self.ts.trajs[0]
|
|
171
|
+
# self.assertIsNotNone(traj.mdt)
|
|
172
|
+
# self.assertEqual(hasattr(traj.mdt, '__class__'), True)
|
|
173
|
+
# self.assertEqual(traj.mdt.__module__ , 'mdtraj.core.trajectory')
|
|
174
|
+
# self.assertIsNotNone(self.tr.discretize)
|
|
175
|
+
#
|
|
176
|
+
# def test_ts_discretize(self):
|
|
177
|
+
# self.tr.discretize('rama', states=['A', 'E', 'L'])
|
|
178
|
+
# self.assertIsNotNone(self.tr.distraj)
|
|
179
|
+
# unique_states = sorted(set(self.tr.distraj))
|
|
180
|
+
# self.assertListEqual(unique_states, ['A', 'E', 'L', 'O'])
|
|
181
|
+
#
|
|
182
|
+
# def test_ts_find_keys(self):
|
|
183
|
+
# self.assertIsNotNone(self.tr.find_keys)
|
|
184
|
+
# # test excluding state O (unassigned)
|
|
185
|
+
# self.tr.distraj = ['O']*50000
|
|
186
|
+
# for i in range(len(self.tr.distraj)):
|
|
187
|
+
# self.tr.distraj[i] = np.random.choice(['A', 'E', 'L', 'O'])
|
|
188
|
+
#
|
|
189
|
+
# self.tr.find_keys()
|
|
190
|
+
# keys = self.tr.keys
|
|
191
|
+
# self.assertEqual(len(set(keys)), len(keys))
|
|
192
|
+
# self.assertEqual(len(keys), 3)
|
|
193
|
+
# for key in keys:
|
|
194
|
+
# self.assertIn(key,['A','E','L'])
|
|
195
|
+
#
|
|
196
|
+
# del self.tr.distraj
|
|
197
|
+
# # test excluding state in alpha-h
|
|
198
|
+
# self.tr.distraj = ['O'] * 50000
|
|
199
|
+
# for i in range(len(self.tr.distraj)):
|
|
200
|
+
# self.tr.distraj[i] = np.random.choice(['A', 'E', 'L', 'O'])
|
|
201
|
+
#
|
|
202
|
+
# self.tr.find_keys(exclude=['A'])
|
|
203
|
+
# keys = self.tr.keys
|
|
204
|
+
# self.assertEqual(len(set(keys)),len(keys))
|
|
205
|
+
# self.assertEqual(len(keys), 3)
|
|
206
|
+
# for key in keys:
|
|
207
|
+
# self.assertIn(key,['O','E','L'])
|
|
208
|
+
#
|
|
209
|
+
# def test_gc(self):
|
|
210
|
+
# self.tr.gc()
|
|
211
|
+
# self.assertIs(hasattr(self.tr, 'mdt'), False)
|
|
212
|
+
|
|
213
|
+
class UseMDtraj(unittest.TestCase):
|
|
214
|
+
def setUp(self):
|
|
215
|
+
download_osf_alaTB()
|
|
216
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
217
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
218
|
+
self.traj = traj.TimeSeries(xtc=xtc, top=top)
|
|
219
|
+
self.trajs = [traj.TimeSeries(xtc=xtc, top=top) \
|
|
220
|
+
for i in range(2)]
|
|
221
|
+
|
|
222
|
+
def test_atributes(self):
|
|
223
|
+
tr = self.traj
|
|
224
|
+
self.assertIsNotNone(tr.mdt)
|
|
225
|
+
self.assertEqual(tr.mdt.n_atoms, 19)
|
|
226
|
+
self.assertEqual(tr.mdt.n_frames, 40001)
|
|
227
|
+
self.assertEqual(tr.mdt.n_residues, 3)
|
|
228
|
+
|
|
229
|
+
class TestFeaturizer_alaTB(unittest.TestCase):
|
|
230
|
+
def setUp(self):
|
|
231
|
+
download_osf_alaTB()
|
|
232
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
233
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
234
|
+
self.ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
235
|
+
self.ts2 = [traj.TimeSeries(xtc=xtc, top=top) \
|
|
236
|
+
for i in range(2)]
|
|
237
|
+
|
|
238
|
+
def test_create_featurizer(self):
|
|
239
|
+
feat = traj.Featurizer(self.ts)
|
|
240
|
+
self.assertEqual(feat.n_trajs, 1)
|
|
241
|
+
feat = traj.Featurizer(self.ts2)
|
|
242
|
+
self.assertEqual(feat.n_trajs, 2)
|
|
243
|
+
|
|
244
|
+
def test_torsions(self):
|
|
245
|
+
feat = traj.Featurizer(self.ts)
|
|
246
|
+
feat.add_torsions(shift=False)
|
|
247
|
+
self.assertEqual(np.shape(self.ts.features),\
|
|
248
|
+
(40001, 2))
|
|
249
|
+
|
|
250
|
+
def test_torsions_sincos(self):
|
|
251
|
+
feat = traj.Featurizer(self.ts)
|
|
252
|
+
feat.add_torsions(shift=False, sincos=True)
|
|
253
|
+
self.assertEqual(np.shape(self.ts.features),\
|
|
254
|
+
(40001, 4))
|
|
255
|
+
|
|
256
|
+
def test_torsions_shift(self):
|
|
257
|
+
feat = traj.Featurizer(self.ts)
|
|
258
|
+
feat.add_torsions(shift=True)
|
|
259
|
+
self.assertTrue(np.all(self.ts.features[:,1] > -2))
|
|
260
|
+
self.assertTrue(np.all(self.ts.features[:,0] > -2))
|
|
261
|
+
|
|
262
|
+
class TestFeaturizer_ala5(unittest.TestCase):
|
|
263
|
+
def setUp(self):
|
|
264
|
+
download_osf_ala5()
|
|
265
|
+
top = os.path.join(_DATA_DIR, 'ala5.gro')
|
|
266
|
+
xtc = os.path.join(_DATA_DIR, 'ala5.xtc')
|
|
267
|
+
self.ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
268
|
+
self.ts2 = [traj.TimeSeries(xtc=xtc, top=top) \
|
|
269
|
+
for i in range(2)]
|
|
270
|
+
|
|
271
|
+
def test_create_featurizer(self):
|
|
272
|
+
feat = traj.Featurizer(self.ts)
|
|
273
|
+
self.assertEqual(feat.n_trajs, 1)
|
|
274
|
+
feat = traj.Featurizer(self.ts2)
|
|
275
|
+
self.assertEqual(feat.n_trajs, 2)
|
|
276
|
+
|
|
277
|
+
def test_torsions(self):
|
|
278
|
+
feat = traj.Featurizer(self.ts)
|
|
279
|
+
feat.add_torsions(shift=False)
|
|
280
|
+
self.assertEqual(np.shape(self.ts.features),\
|
|
281
|
+
(50001, 10))
|
|
282
|
+
|
|
283
|
+
def test_torsions_sincos(self):
|
|
284
|
+
feat = traj.Featurizer(self.ts)
|
|
285
|
+
feat.add_torsions(shift=False, sincos=True)
|
|
286
|
+
self.assertEqual(np.shape(self.ts.features),\
|
|
287
|
+
(50001, 20))
|
|
288
|
+
|
|
289
|
+
def test_contacts(self):
|
|
290
|
+
feat = traj.Featurizer(self.ts)
|
|
291
|
+
feat.add_contacts()
|
|
292
|
+
self.assertEqual(np.shape(self.ts.features),\
|
|
293
|
+
(50001, 3))
|
|
294
|
+
|
|
295
|
+
class TestDimRed_alaTB(unittest.TestCase):
|
|
296
|
+
def setUp(self):
|
|
297
|
+
download_osf_alaTB()
|
|
298
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
299
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
300
|
+
self.ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
301
|
+
feat = traj.Featurizer(self.ts)
|
|
302
|
+
feat.add_torsions(shift=False)
|
|
303
|
+
|
|
304
|
+
def test_whiten(self):
|
|
305
|
+
dr = traj.DimRed(self.ts)
|
|
306
|
+
dr.whiten()
|
|
307
|
+
self.assertLess(np.abs(np.mean(self.ts.features[:,0])), 1e-6)
|
|
308
|
+
self.assertLess(np.abs(np.mean(self.ts.features[:,1])), 1e-6)
|
|
309
|
+
|
|
310
|
+
class TestDimRed_TICA(unittest.TestCase):
|
|
311
|
+
""" TICA checked against an analytic AR(1)/OU process """
|
|
312
|
+
def _ou(self, n=200000, a=0.95, seed=0):
|
|
313
|
+
rng = np.random.RandomState(seed)
|
|
314
|
+
x = np.zeros((n, 2))
|
|
315
|
+
s = np.sqrt(1 - a**2)
|
|
316
|
+
for i in range(1, n):
|
|
317
|
+
x[i] = a*x[i-1] + s*rng.randn(2)
|
|
318
|
+
# mix slow and fast coordinates so TICA must unmix them
|
|
319
|
+
return x
|
|
320
|
+
|
|
321
|
+
def _ts(self, x):
|
|
322
|
+
ts = traj.TimeSeries()
|
|
323
|
+
ts.features = x
|
|
324
|
+
ts.dt = 1.0
|
|
325
|
+
return ts
|
|
326
|
+
|
|
327
|
+
def test_timescale(self):
|
|
328
|
+
# two independent AR(1): eigenvalues a1^lag and a2^lag
|
|
329
|
+
rng = np.random.RandomState(1)
|
|
330
|
+
n, a = 200000, np.array([0.99, 0.9])
|
|
331
|
+
x = np.zeros((n, 2))
|
|
332
|
+
e = rng.randn(n, 2)*np.sqrt(1 - a**2)
|
|
333
|
+
for i in range(1, n):
|
|
334
|
+
x[i] = a*x[i-1] + e[i]
|
|
335
|
+
x = x.dot(np.array([[1., 0.5], [0.3, 1.]])) # mix
|
|
336
|
+
ts = self._ts(x)
|
|
337
|
+
dr = traj.DimRed(ts)
|
|
338
|
+
dr.doTICA(lag=5, n=2)
|
|
339
|
+
np.testing.assert_allclose(dr.tica_eigenvalues, a**5, atol=0.02)
|
|
340
|
+
np.testing.assert_allclose(dr.tica_timescales, \
|
|
341
|
+
-5/np.log(a**5), rtol=0.15)
|
|
342
|
+
self.assertEqual(ts.features.shape, (n, 2))
|
|
343
|
+
|
|
344
|
+
def test_whitened_projection(self):
|
|
345
|
+
ts = self._ts(self._ou(n=50000))
|
|
346
|
+
dr = traj.DimRed(ts)
|
|
347
|
+
dr.doTICA(lag=3, n=2)
|
|
348
|
+
self.assertLess(np.abs(ts.features.mean(axis=0)).max(), 0.05)
|
|
349
|
+
|
|
350
|
+
def test_multiple_trajs(self):
|
|
351
|
+
tss = [self._ts(self._ou(n=20000, seed=s)) for s in (2, 3)]
|
|
352
|
+
dr = traj.DimRed(tss)
|
|
353
|
+
dr.doTICA(lag=2, n=1)
|
|
354
|
+
for t in tss:
|
|
355
|
+
self.assertEqual(t.features.shape, (20000, 1))
|
|
356
|
+
|
|
357
|
+
def test_bad_lag(self):
|
|
358
|
+
ts = self._ts(self._ou(n=100))
|
|
359
|
+
dr = traj.DimRed(ts)
|
|
360
|
+
with self.assertRaises(ValueError):
|
|
361
|
+
dr.doTICA(lag=0)
|
|
362
|
+
with self.assertRaises(ValueError):
|
|
363
|
+
dr.doTICA(lag=100)
|
|
364
|
+
|
|
365
|
+
class TestFilterStates(unittest.TestCase):
|
|
366
|
+
def test_fills_with_previous(self):
|
|
367
|
+
out = traj_lib._filter_states([0, -1, -1, 1, -1, 2])
|
|
368
|
+
self.assertEqual(out, [0, 0, 0, 1, 1, 2])
|
|
369
|
+
|
|
370
|
+
def test_leading_unassigned_keeps_length(self):
|
|
371
|
+
# frames before the first assignment inherit the first label
|
|
372
|
+
out = traj_lib._filter_states(np.array([-1, -1, 3, 3, -1, 4]))
|
|
373
|
+
self.assertEqual(out, [3, 3, 3, 3, 3, 4])
|
|
374
|
+
|
|
375
|
+
def test_none_assigned(self):
|
|
376
|
+
with self.assertRaises(ValueError):
|
|
377
|
+
traj_lib._filter_states([-1, -1])
|
|
378
|
+
|
|
379
|
+
class TestProtocol(unittest.TestCase):
|
|
380
|
+
""" Discretization protocols on synthetic and real data """
|
|
381
|
+
def _two_well(self, n=20000, seed=0):
|
|
382
|
+
rng = np.random.RandomState(seed)
|
|
383
|
+
s = np.cumsum(rng.rand(n) < 0.002) % 2 # slow switching
|
|
384
|
+
x = np.column_stack([3.*s - 1.5, np.zeros(n)]) \
|
|
385
|
+
+ 0.3*rng.randn(n, 2)
|
|
386
|
+
ts = traj.TimeSeries()
|
|
387
|
+
ts.features = x
|
|
388
|
+
ts.dt = 1.0
|
|
389
|
+
return ts, s
|
|
390
|
+
|
|
391
|
+
def test_kmeans_recovers_wells_and_is_nondestructive(self):
|
|
392
|
+
ts, s = self._two_well()
|
|
393
|
+
x0 = ts.features.copy()
|
|
394
|
+
spec = {'whiten': False, 'cluster': ('kmeans', {'k': 2, 'dim': 2})}
|
|
395
|
+
res = traj.discretize_protocol(ts, spec, name='km', seed=0)
|
|
396
|
+
np.testing.assert_array_equal(ts.features, x0)
|
|
397
|
+
self.assertFalse(hasattr(ts, 'distraj'))
|
|
398
|
+
self.assertEqual(res.n_states, 2)
|
|
399
|
+
self.assertEqual(res.frac_assigned, 1.0)
|
|
400
|
+
d = np.asarray(res.timeseries[0].distraj)
|
|
401
|
+
agree = max(np.mean(d == s), np.mean(d != s))
|
|
402
|
+
self.assertGreater(agree, 0.99)
|
|
403
|
+
|
|
404
|
+
def test_seed_reproducible(self):
|
|
405
|
+
ts, _ = self._two_well()
|
|
406
|
+
spec = {'cluster': ('kmeans', {'k': 10, 'dim': 2})}
|
|
407
|
+
a = traj.discretize_protocol(ts, spec, seed=3)
|
|
408
|
+
b = traj.discretize_protocol(ts, spec, seed=3)
|
|
409
|
+
np.testing.assert_array_equal(a.timeseries[0].distraj, \
|
|
410
|
+
b.timeseries[0].distraj)
|
|
411
|
+
|
|
412
|
+
def test_tica_then_kmeans_multiple_trajs(self):
|
|
413
|
+
tss = [self._two_well(n=10000, seed=i)[0] for i in (1, 2)]
|
|
414
|
+
spec = {'reduce': ('tica', {'lag': 10, 'n': 1}),
|
|
415
|
+
'cluster': ('kmeans', {'k': 2, 'dim': 1})}
|
|
416
|
+
res = traj.discretize_protocol(tss, spec, seed=0)
|
|
417
|
+
for tr, new in zip(tss, res.timeseries):
|
|
418
|
+
self.assertEqual(tr.features.shape, (10000, 2))
|
|
419
|
+
self.assertEqual(new.features.shape, (10000, 1))
|
|
420
|
+
self.assertEqual(len(new.distraj), 10000)
|
|
421
|
+
|
|
422
|
+
def test_compare_protocols(self):
|
|
423
|
+
ts, _ = self._two_well(n=5000)
|
|
424
|
+
protocols = {
|
|
425
|
+
'pca': {'reduce': ('pca', {'n': 2}),
|
|
426
|
+
'cluster': ('kmeans', {'k': 4, 'dim': 2})},
|
|
427
|
+
'raw': {'whiten': False,
|
|
428
|
+
'cluster': ('kmeans', {'k': 2, 'dim': 2})}}
|
|
429
|
+
out = traj.compare_protocols(ts, protocols, seed=0)
|
|
430
|
+
self.assertEqual(sorted(out), ['pca', 'raw'])
|
|
431
|
+
self.assertEqual(out['pca'].n_states, 4)
|
|
432
|
+
self.assertEqual(out['raw'].n_states, 2)
|
|
433
|
+
|
|
434
|
+
def test_bad_specs(self):
|
|
435
|
+
ts, _ = self._two_well(n=100)
|
|
436
|
+
for spec in [{}, {'rama': ['A'], 'cluster': 'kmeans'},
|
|
437
|
+
{'cluster': 'nope'},
|
|
438
|
+
{'reduce': 'nope', 'cluster': 'kmeans'},
|
|
439
|
+
{'cluster': 'kmeans', 'typo': 1}]:
|
|
440
|
+
with self.assertRaises(ValueError):
|
|
441
|
+
traj.discretize_protocol(ts, spec)
|
|
442
|
+
|
|
443
|
+
def test_no_features(self):
|
|
444
|
+
ts = traj.TimeSeries()
|
|
445
|
+
with self.assertRaises(ValueError):
|
|
446
|
+
traj.discretize_protocol(ts, {'cluster': 'kmeans'})
|
|
447
|
+
|
|
448
|
+
def test_rama_matches_timeseries_discretize(self):
|
|
449
|
+
download_osf_alaTB()
|
|
450
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
451
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
452
|
+
ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
453
|
+
res = traj.discretize_protocol(ts, {'rama': ['A', 'E'],
|
|
454
|
+
'check': False})
|
|
455
|
+
self.assertFalse(hasattr(ts, 'distraj'))
|
|
456
|
+
ts.discretize(states=['A', 'E'], check=False)
|
|
457
|
+
self.assertEqual(res.timeseries[0].distraj, ts.distraj)
|
|
458
|
+
self.assertLessEqual(res.n_states, 2)
|
|
459
|
+
|
|
460
|
+
def test_featurized_protocol_leaves_input_clean(self):
|
|
461
|
+
download_osf_alaTB()
|
|
462
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
463
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
464
|
+
ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
465
|
+
spec = {'features': [('torsions', {'sincos': True})],
|
|
466
|
+
'reduce': ('pca', {'n': 2}),
|
|
467
|
+
'cluster': ('kmeans', {'k': 10, 'dim': 2})}
|
|
468
|
+
res = traj.discretize_protocol(ts, spec, seed=0)
|
|
469
|
+
self.assertFalse(hasattr(ts, 'features'))
|
|
470
|
+
self.assertFalse(hasattr(ts, 'distraj'))
|
|
471
|
+
self.assertEqual(len(res.timeseries[0].distraj), ts.mdt.n_frames)
|
|
472
|
+
self.assertEqual(res.n_states, 10)
|
|
473
|
+
|
|
474
|
+
def test_hdbscan_fraction_assigned(self):
|
|
475
|
+
try:
|
|
476
|
+
import hdbscan
|
|
477
|
+
except ImportError:
|
|
478
|
+
self.skipTest("hdbscan not installed")
|
|
479
|
+
ts, _ = self._two_well(n=5000)
|
|
480
|
+
spec = {'whiten': False,
|
|
481
|
+
'cluster': ('hdbscan', {'mcs': 100, 'dim': 2})}
|
|
482
|
+
res = traj.discretize_protocol(ts, spec)
|
|
483
|
+
self.assertGreater(res.frac_assigned, 0.5)
|
|
484
|
+
self.assertEqual(len(res.timeseries[0].distraj), 5000)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class TestRamaBounds(unittest.TestCase):
|
|
488
|
+
""" Interactive confirmation of Ramachandran bounds. """
|
|
489
|
+
def setUp(self):
|
|
490
|
+
# (phi, psi) in degrees: two A points, one E, one L, one O
|
|
491
|
+
pts = np.array([[-60., -40.], [-70., -30.], [-120., 150.],
|
|
492
|
+
[60., 40.], [0., 0.]])
|
|
493
|
+
self.phi = np.radians(pts[:, :1])
|
|
494
|
+
self.psi = np.radians(pts[:, 1:])
|
|
495
|
+
|
|
496
|
+
@staticmethod
|
|
497
|
+
def _answers(*answers):
|
|
498
|
+
it = iter(answers)
|
|
499
|
+
return lambda prompt: next(it)
|
|
500
|
+
|
|
501
|
+
def test_default_bounds(self):
|
|
502
|
+
b = traj_lib.default_bounds(['A', 'E'])
|
|
503
|
+
self.assertEqual(sorted(b), ['A', 'E'])
|
|
504
|
+
self.assertEqual(b['A'], [-130., -40., -50., 20.])
|
|
505
|
+
b['A'][0] = 0.
|
|
506
|
+
self.assertEqual(traj_lib.default_bounds(['A'])['A'][0], -130.)
|
|
507
|
+
with self.assertRaises(ValueError):
|
|
508
|
+
traj_lib.default_bounds(['X'])
|
|
509
|
+
|
|
510
|
+
def test_validate_bounds(self):
|
|
511
|
+
for bad in [[], [1, 2, 3], [10, -10, 0, 10],
|
|
512
|
+
[-200, 0, 0, 10], ['a', 1, 2, 3]]:
|
|
513
|
+
with self.assertRaises(ValueError):
|
|
514
|
+
traj_lib._validate_bounds(bad)
|
|
515
|
+
self.assertEqual(traj_lib._validate_bounds(['1', 2, 3, 4]),
|
|
516
|
+
[1., 2., 3., 4.])
|
|
517
|
+
|
|
518
|
+
def test_populations(self):
|
|
519
|
+
pops = traj_lib.rama_populations(self.phi, self.psi,
|
|
520
|
+
traj_lib.default_bounds())
|
|
521
|
+
self.assertAlmostEqual(pops['A'], 0.4)
|
|
522
|
+
self.assertAlmostEqual(pops['E'], 0.2)
|
|
523
|
+
self.assertAlmostEqual(pops['L'], 0.2)
|
|
524
|
+
self.assertAlmostEqual(pops['O'], 0.2)
|
|
525
|
+
|
|
526
|
+
def test_overlaps(self):
|
|
527
|
+
b = {'A': [-100, 0, -50, 0], 'B': [-50, 50, -10, 10]}
|
|
528
|
+
self.assertEqual(traj_lib._overlaps(b), [('A', 'B')])
|
|
529
|
+
self.assertEqual(traj_lib._overlaps(traj_lib.default_bounds()),
|
|
530
|
+
[])
|
|
531
|
+
|
|
532
|
+
def test_accept(self):
|
|
533
|
+
b0 = traj_lib.default_bounds(['A', 'E'])
|
|
534
|
+
for ans in ['', 'y', 'YES']:
|
|
535
|
+
b = traj_lib.confirm_bounds(self.phi, self.psi, b0,
|
|
536
|
+
input_fn=self._answers(ans), plot=False)
|
|
537
|
+
self.assertEqual(b, b0)
|
|
538
|
+
|
|
539
|
+
def test_redefine(self):
|
|
540
|
+
b0 = traj_lib.default_bounds(['A', 'E'])
|
|
541
|
+
# reject, give bad then good A, keep E, then accept
|
|
542
|
+
b = traj_lib.confirm_bounds(self.phi, self.psi, b0,
|
|
543
|
+
input_fn=self._answers('maybe', 'n', '10, -10, 0, 1',
|
|
544
|
+
'-90 0 -60 0', '', 'y'),
|
|
545
|
+
plot=False)
|
|
546
|
+
self.assertEqual(b['A'], [-90., 0., -60., 0.])
|
|
547
|
+
self.assertEqual(b['E'], b0['E'])
|
|
548
|
+
self.assertEqual(b0['A'], [-130., -40., -50., 20.])
|
|
549
|
+
|
|
550
|
+
def test_plot(self):
|
|
551
|
+
from unittest import mock
|
|
552
|
+
import matplotlib
|
|
553
|
+
matplotlib.use('Agg')
|
|
554
|
+
with mock.patch.object(traj_lib.plt, 'show') as show:
|
|
555
|
+
b = traj_lib.confirm_bounds(self.phi, self.psi,
|
|
556
|
+
traj_lib.default_bounds(),
|
|
557
|
+
input_fn=self._answers('n', '', '', '', 'y'))
|
|
558
|
+
self.assertEqual(show.call_count, 2)
|
|
559
|
+
self.assertEqual(sorted(b), ['A', 'E', 'L'])
|
|
560
|
+
|
|
561
|
+
def test_noninteractive_warns(self):
|
|
562
|
+
from unittest import mock
|
|
563
|
+
b0 = traj_lib.default_bounds(['A'])
|
|
564
|
+
with mock.patch.object(traj_lib, '_interactive',
|
|
565
|
+
return_value=False):
|
|
566
|
+
with self.assertWarns(UserWarning):
|
|
567
|
+
b = traj_lib.confirm_bounds(self.phi, self.psi, b0,
|
|
568
|
+
plot=False)
|
|
569
|
+
self.assertEqual(b, b0)
|
|
570
|
+
|
|
571
|
+
def test_no_stdin_warns(self):
|
|
572
|
+
def no_stdin(prompt):
|
|
573
|
+
raise EOFError
|
|
574
|
+
with self.assertWarns(UserWarning):
|
|
575
|
+
traj_lib.confirm_bounds(self.phi, self.psi,
|
|
576
|
+
traj_lib.default_bounds(), input_fn=no_stdin,
|
|
577
|
+
plot=False)
|
|
578
|
+
|
|
579
|
+
def test_discretizer_custom_bounds(self):
|
|
580
|
+
download_osf_alaTB()
|
|
581
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
582
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
583
|
+
trs = [traj.TimeSeries(xtc=xtc, top=top) for i in range(2)]
|
|
584
|
+
bounds = {'A': [-180., 0., -100., 50.],
|
|
585
|
+
'E': [-180., 0., 100., 180.]}
|
|
586
|
+
disc = traj.Discretizer(trs)
|
|
587
|
+
disc.rama(bounds=bounds, check=False)
|
|
588
|
+
phi, psi = traj_lib.compute_rama(trs[0])
|
|
589
|
+
ref = traj_lib.discrete_rama(phi, psi, bounds=bounds)
|
|
590
|
+
self.assertEqual(disc.bounds, bounds)
|
|
591
|
+
for tr in trs:
|
|
592
|
+
self.assertEqual(tr.bounds, bounds)
|
|
593
|
+
self.assertEqual(tr.distraj, ref)
|
|
594
|
+
res = traj.discretize_protocol(trs, {'rama': bounds,
|
|
595
|
+
'check': False})
|
|
596
|
+
self.assertEqual(res.bounds, bounds)
|
|
597
|
+
self.assertEqual(res.timeseries[0].distraj, ref)
|
|
598
|
+
with self.assertRaises(ValueError):
|
|
599
|
+
traj.discretize_protocol(trs, {'cluster': 'kmeans',
|
|
600
|
+
'check': False})
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# ---------------------------------------------------------------------
|
|
604
|
+
# Tests for code added on develop since master (TICA, protocols,
|
|
605
|
+
# Ramachandran bounds), covering branches the tests above leave out.
|
|
606
|
+
# ---------------------------------------------------------------------
|
|
607
|
+
import io
|
|
608
|
+
import sys
|
|
609
|
+
import contextlib
|
|
610
|
+
from types import SimpleNamespace
|
|
611
|
+
from unittest import mock
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _quiet(f, *args, **kwargs):
|
|
615
|
+
""" Call f with stdout silenced (doTICA and friends print). """
|
|
616
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
617
|
+
return f(*args, **kwargs)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _alaTB(n=1):
|
|
621
|
+
download_osf_alaTB()
|
|
622
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
623
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
624
|
+
trs = [traj.TimeSeries(xtc=xtc, top=top) for i in range(n)]
|
|
625
|
+
return trs[0] if n == 1 else trs
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class TestDimRed_TICA_extra(unittest.TestCase):
|
|
629
|
+
""" Input validation, estimator and output conventions of doTICA """
|
|
630
|
+
def _ts(self, x, dt=None):
|
|
631
|
+
ts = traj.TimeSeries()
|
|
632
|
+
ts.features = x
|
|
633
|
+
if dt is not None:
|
|
634
|
+
ts.dt = dt
|
|
635
|
+
return ts
|
|
636
|
+
|
|
637
|
+
def _ar1(self, n=50000, a=(0.98, 0.8), seed=0):
|
|
638
|
+
rng = np.random.RandomState(seed)
|
|
639
|
+
a = np.asarray(a)
|
|
640
|
+
x = np.zeros((n, len(a)))
|
|
641
|
+
e = rng.randn(n, len(a))*np.sqrt(1 - a**2)
|
|
642
|
+
for i in range(1, n):
|
|
643
|
+
x[i] = a*x[i-1] + e[i]
|
|
644
|
+
return x.dot(np.array([[1., 0.5], [0.3, 1.]]))
|
|
645
|
+
|
|
646
|
+
def test_bad_lag_values(self):
|
|
647
|
+
dr = traj.DimRed(self._ts(self._ar1(n=200)))
|
|
648
|
+
for lag in [None, -1, 2.5]:
|
|
649
|
+
with self.assertRaises(ValueError):
|
|
650
|
+
_quiet(dr.doTICA, lag=lag)
|
|
651
|
+
|
|
652
|
+
def test_bad_n(self):
|
|
653
|
+
dr = traj.DimRed(self._ts(self._ar1(n=200)))
|
|
654
|
+
for n in [0, 3]:
|
|
655
|
+
with self.assertRaises(ValueError):
|
|
656
|
+
_quiet(dr.doTICA, lag=1, n=n)
|
|
657
|
+
|
|
658
|
+
def test_mismatched_features(self):
|
|
659
|
+
rng = np.random.RandomState(0)
|
|
660
|
+
tss = [self._ts(rng.randn(100, 2)), self._ts(rng.randn(100, 3))]
|
|
661
|
+
with self.assertRaises(ValueError):
|
|
662
|
+
_quiet(traj.DimRed(tss).doTICA, lag=1, n=1)
|
|
663
|
+
|
|
664
|
+
def test_lag_longer_than_one_traj(self):
|
|
665
|
+
rng = np.random.RandomState(0)
|
|
666
|
+
tss = [self._ts(rng.randn(100, 2)), self._ts(rng.randn(10, 2))]
|
|
667
|
+
with self.assertRaises(ValueError):
|
|
668
|
+
_quiet(traj.DimRed(tss).doTICA, lag=10, n=1)
|
|
669
|
+
|
|
670
|
+
def test_constant_feature_raises(self):
|
|
671
|
+
rng = np.random.RandomState(0)
|
|
672
|
+
x = np.column_stack([rng.randn(1000), np.ones(1000)])
|
|
673
|
+
with self.assertRaises(ValueError):
|
|
674
|
+
_quiet(traj.DimRed(self._ts(x)).doTICA, lag=1, n=1)
|
|
675
|
+
|
|
676
|
+
def test_float_constant_feature_raises(self):
|
|
677
|
+
# mean of a non-representable constant leaves a ~1e-17 spread
|
|
678
|
+
rng = np.random.RandomState(0)
|
|
679
|
+
x = np.column_stack([rng.randn(1000), np.full(1000, 0.1)])
|
|
680
|
+
with self.assertRaises(ValueError):
|
|
681
|
+
_quiet(traj.DimRed(self._ts(x)).doTICA, lag=1, n=1)
|
|
682
|
+
|
|
683
|
+
def test_feature_units_do_not_matter(self):
|
|
684
|
+
# very different scales must not look singular
|
|
685
|
+
x = self._ar1()*np.array([1e6, 1e-4])
|
|
686
|
+
dr = traj.DimRed(self._ts(x))
|
|
687
|
+
_quiet(dr.doTICA, lag=5, n=2)
|
|
688
|
+
dr1 = traj.DimRed(self._ts(self._ar1()))
|
|
689
|
+
_quiet(dr1.doTICA, lag=5, n=2)
|
|
690
|
+
np.testing.assert_allclose(dr.tica_eigenvalues, \
|
|
691
|
+
dr1.tica_eigenvalues, rtol=1e-6)
|
|
692
|
+
|
|
693
|
+
def test_collinear_features_raise(self):
|
|
694
|
+
# an exact linear combination makes C(0) singular; roundoff
|
|
695
|
+
# can let the Cholesky step in eigh succeed, so doTICA must
|
|
696
|
+
# check explicitly (it used to pass for some seeds)
|
|
697
|
+
for seed in range(20):
|
|
698
|
+
rng = np.random.RandomState(seed)
|
|
699
|
+
b = rng.randn(5000, 2)
|
|
700
|
+
x = np.column_stack([b, b[:, 0] + 2*b[:, 1]])
|
|
701
|
+
with self.assertRaises(ValueError):
|
|
702
|
+
_quiet(traj.DimRed(self._ts(x)).doTICA, lag=1, n=1)
|
|
703
|
+
|
|
704
|
+
def test_eigh_failure_raises_valueerror(self):
|
|
705
|
+
# backstop if C(0) passes the correlation check but the
|
|
706
|
+
# Cholesky step in eigh still fails
|
|
707
|
+
dr = traj.DimRed(self._ts(self._ar1()))
|
|
708
|
+
err = np.linalg.LinAlgError("not positive definite")
|
|
709
|
+
with mock.patch('scipy.linalg.eigh', side_effect=err):
|
|
710
|
+
with self.assertRaises(ValueError):
|
|
711
|
+
_quiet(dr.doTICA, lag=5, n=1)
|
|
712
|
+
|
|
713
|
+
def test_projection_and_normalization(self):
|
|
714
|
+
x = self._ar1()
|
|
715
|
+
ts = self._ts(x.copy())
|
|
716
|
+
dr = traj.DimRed(ts)
|
|
717
|
+
_quiet(dr.doTICA, lag=5, n=2)
|
|
718
|
+
# stored eigenvectors reproduce the projection
|
|
719
|
+
np.testing.assert_allclose(ts.features, \
|
|
720
|
+
(x - dr.tica_mean).dot(dr.tica_eigenvectors[:, :2]))
|
|
721
|
+
# components are C(0)-orthonormal: unit variance, uncorrelated
|
|
722
|
+
np.testing.assert_allclose(np.cov(ts.features.T), np.eye(2), \
|
|
723
|
+
atol=0.02)
|
|
724
|
+
ev = dr.tica_eigenvalues
|
|
725
|
+
self.assertTrue(np.all(np.diff(ev) <= 0))
|
|
726
|
+
self.assertTrue(np.all(ev <= 1 + 1e-10))
|
|
727
|
+
|
|
728
|
+
def test_timescale_units(self):
|
|
729
|
+
x = self._ar1()
|
|
730
|
+
out = {}
|
|
731
|
+
for dt in [None, 1.0, 2.0]:
|
|
732
|
+
dr = traj.DimRed(self._ts(x.copy(), dt=dt))
|
|
733
|
+
_quiet(dr.doTICA, lag=5, n=2)
|
|
734
|
+
out[dt] = dr.tica_timescales
|
|
735
|
+
# without dt, timescales are in frames (same as dt = 1)
|
|
736
|
+
np.testing.assert_allclose(out[None], out[1.0])
|
|
737
|
+
np.testing.assert_allclose(out[2.0], 2*out[1.0])
|
|
738
|
+
|
|
739
|
+
def test_nonpositive_eigenvalues_give_nan(self):
|
|
740
|
+
# white noise: about half the eigenvalues are negative
|
|
741
|
+
rng = np.random.RandomState(0)
|
|
742
|
+
dr = traj.DimRed(self._ts(rng.randn(5000, 4)))
|
|
743
|
+
_quiet(dr.doTICA, lag=1, n=4)
|
|
744
|
+
ev, ts = dr.tica_eigenvalues, dr.tica_timescales
|
|
745
|
+
self.assertTrue(np.any(ev <= 0))
|
|
746
|
+
self.assertTrue(np.all(np.isnan(ts[ev <= 0])))
|
|
747
|
+
self.assertTrue(np.all(np.isfinite(ts[(ev > 0) & (ev < 1)])))
|
|
748
|
+
|
|
749
|
+
def test_pairs_do_not_straddle_trajectories(self):
|
|
750
|
+
# reference estimator built pair by pair, per trajectory
|
|
751
|
+
rng = np.random.RandomState(4)
|
|
752
|
+
xs = [rng.randn(60, 2) + 3., rng.randn(90, 2) - 3.]
|
|
753
|
+
lag = 3
|
|
754
|
+
x0 = np.array([x[t] for x in xs for t in range(len(x) - lag)])
|
|
755
|
+
xt = np.array([x[t+lag] for x in xs \
|
|
756
|
+
for t in range(len(x) - lag)])
|
|
757
|
+
m = 0.5*(x0.mean(0) + xt.mean(0))
|
|
758
|
+
x0, xt = x0 - m, xt - m
|
|
759
|
+
c0 = 0.5*(x0.T.dot(x0) + xt.T.dot(xt))/len(x0)
|
|
760
|
+
ct = x0.T.dot(xt)/len(x0)
|
|
761
|
+
ct = 0.5*(ct + ct.T)
|
|
762
|
+
ref = np.sort(np.linalg.eigvals(np.linalg.solve(c0, ct)).real)
|
|
763
|
+
dr = traj.DimRed([self._ts(x.copy()) for x in xs])
|
|
764
|
+
_quiet(dr.doTICA, lag=lag, n=2)
|
|
765
|
+
np.testing.assert_allclose(np.sort(dr.tica_eigenvalues), ref)
|
|
766
|
+
# a single concatenated trajectory gives a different answer
|
|
767
|
+
dr1 = traj.DimRed(self._ts(np.vstack(xs)))
|
|
768
|
+
_quiet(dr1.doTICA, lag=lag, n=2)
|
|
769
|
+
self.assertFalse(np.allclose(np.sort(dr1.tica_eigenvalues), ref))
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
class TestDiscretizer_extra(unittest.TestCase):
|
|
773
|
+
def _ts(self, n=3000, seed=0):
|
|
774
|
+
rng = np.random.RandomState(seed)
|
|
775
|
+
ts = traj.TimeSeries()
|
|
776
|
+
ts.features = np.vstack([rng.randn(n//2, 2) - 3, \
|
|
777
|
+
rng.randn(n - n//2, 2) + 3])
|
|
778
|
+
return ts
|
|
779
|
+
|
|
780
|
+
def test_single_timeseries_is_wrapped(self):
|
|
781
|
+
ts = self._ts()
|
|
782
|
+
self.assertEqual(traj.Discretizer(ts).timeseries, [ts])
|
|
783
|
+
|
|
784
|
+
def test_kmeans_random_state(self):
|
|
785
|
+
runs = []
|
|
786
|
+
for i in range(2):
|
|
787
|
+
ts = self._ts()
|
|
788
|
+
disc = traj.Discretizer(ts)
|
|
789
|
+
disc.kmeans(k=8, dim=2, random_state=7)
|
|
790
|
+
runs.append((np.asarray(ts.distraj), disc.k_centers))
|
|
791
|
+
np.testing.assert_array_equal(runs[0][0], runs[1][0])
|
|
792
|
+
# centers agree to roundoff only (threaded reductions)
|
|
793
|
+
np.testing.assert_allclose(runs[0][1], runs[1][1], atol=1e-12)
|
|
794
|
+
|
|
795
|
+
def test_hdbscan_missing(self):
|
|
796
|
+
disc = traj.Discretizer(self._ts())
|
|
797
|
+
with mock.patch.dict(sys.modules, {'hdbscan': None}):
|
|
798
|
+
with self.assertRaises(ImportError):
|
|
799
|
+
disc.hdbscan(mcs=50, dim=2)
|
|
800
|
+
|
|
801
|
+
def test_hdbscan_labels_filled(self):
|
|
802
|
+
try:
|
|
803
|
+
import hdbscan
|
|
804
|
+
except ImportError:
|
|
805
|
+
self.skipTest("hdbscan not installed")
|
|
806
|
+
tss = [self._ts(seed=s) for s in (0, 1)]
|
|
807
|
+
disc = traj.Discretizer(tss)
|
|
808
|
+
disc.hdbscan(mcs=100, dim=2)
|
|
809
|
+
self.assertEqual(disc.hdb_nclusters, 2)
|
|
810
|
+
self.assertTrue(0 < disc.frac_assigned <= 1)
|
|
811
|
+
for ts in tss:
|
|
812
|
+
self.assertEqual(len(ts.distraj), 3000)
|
|
813
|
+
self.assertGreaterEqual(min(ts.distraj), 0)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
class TestProtocol_extra(unittest.TestCase):
|
|
817
|
+
def _ts(self, n=2000, seed=0):
|
|
818
|
+
rng = np.random.RandomState(seed)
|
|
819
|
+
ts = traj.TimeSeries()
|
|
820
|
+
ts.features = rng.randn(n, 2)
|
|
821
|
+
return ts
|
|
822
|
+
|
|
823
|
+
def test_repr_and_default_names(self):
|
|
824
|
+
ts = self._ts()
|
|
825
|
+
res = traj.discretize_protocol(ts, {'cluster': ('kmeans', \
|
|
826
|
+
{'k': 3, 'dim': 2})}, seed=0)
|
|
827
|
+
self.assertEqual(res.name, 'protocol')
|
|
828
|
+
self.assertEqual(repr(res), \
|
|
829
|
+
"Discretization('protocol', n_states=3, "\
|
|
830
|
+
"frac_assigned=1.000)")
|
|
831
|
+
|
|
832
|
+
def test_seed_does_not_mutate_spec(self):
|
|
833
|
+
kw = {'k': 3, 'dim': 2}
|
|
834
|
+
spec = {'cluster': ('kmeans', kw)}
|
|
835
|
+
res = traj.discretize_protocol(self._ts(), spec, seed=1)
|
|
836
|
+
self.assertEqual(kw, {'k': 3, 'dim': 2})
|
|
837
|
+
self.assertEqual(res.spec, spec)
|
|
838
|
+
|
|
839
|
+
def test_unknown_feature(self):
|
|
840
|
+
ts = _alaTB()
|
|
841
|
+
with self.assertRaises(ValueError):
|
|
842
|
+
traj.discretize_protocol(ts, {'features': [('nope', {})], \
|
|
843
|
+
'cluster': 'kmeans'})
|
|
844
|
+
|
|
845
|
+
def test_features_spec_replaces_existing_features(self):
|
|
846
|
+
ts = _alaTB()
|
|
847
|
+
traj.Featurizer(ts).add_torsions(shift=False)
|
|
848
|
+
x0 = ts.features.copy()
|
|
849
|
+
spec = {'features': [('torsions', {'sincos': True})],
|
|
850
|
+
'cluster': ('kmeans', {'k': 5, 'dim': 4})}
|
|
851
|
+
res = traj.discretize_protocol(ts, spec, seed=0)
|
|
852
|
+
self.assertEqual(res.timeseries[0].features.shape, \
|
|
853
|
+
(ts.mdt.n_frames, 4))
|
|
854
|
+
np.testing.assert_array_equal(ts.features, x0)
|
|
855
|
+
|
|
856
|
+
def test_existing_features_are_copied(self):
|
|
857
|
+
ts = self._ts()
|
|
858
|
+
x0 = ts.features.copy()
|
|
859
|
+
res = traj.discretize_protocol(ts, {'whiten': False, \
|
|
860
|
+
'cluster': ('kmeans', {'k': 2, 'dim': 2})}, seed=0)
|
|
861
|
+
new = res.timeseries[0]
|
|
862
|
+
self.assertIsNot(new.features, ts.features)
|
|
863
|
+
np.testing.assert_array_equal(new.features, x0)
|
|
864
|
+
|
|
865
|
+
def test_compare_protocols_independent(self):
|
|
866
|
+
ts = self._ts()
|
|
867
|
+
x0 = ts.features.copy()
|
|
868
|
+
spec = {'cluster': ('kmeans', {'k': 4, 'dim': 2})}
|
|
869
|
+
out = traj.compare_protocols(ts, {'a': spec, 'b': spec}, seed=0)
|
|
870
|
+
self.assertEqual(out['a'].name, 'a')
|
|
871
|
+
self.assertIsNot(out['a'].timeseries[0], out['b'].timeseries[0])
|
|
872
|
+
np.testing.assert_array_equal(out['a'].timeseries[0].distraj, \
|
|
873
|
+
out['b'].timeseries[0].distraj)
|
|
874
|
+
np.testing.assert_array_equal(ts.features, x0)
|
|
875
|
+
self.assertFalse(hasattr(ts, 'distraj'))
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
class TestRama_extra(unittest.TestCase):
|
|
879
|
+
""" State assignment, bounds checks and user interaction """
|
|
880
|
+
def test_discrete_rama_states_and_shapes(self):
|
|
881
|
+
# one dihedral: A, unassigned (inherits A), E
|
|
882
|
+
phi = np.radians([[-60.], [0.], [-120.]])
|
|
883
|
+
psi = np.radians([[-40.], [0.], [150.]])
|
|
884
|
+
self.assertEqual(traj_lib.discrete_rama(phi, psi, \
|
|
885
|
+
states=['A', 'E']), ['A', 'A', 'E'])
|
|
886
|
+
# without E, the last frame is unassigned and inherits A
|
|
887
|
+
self.assertEqual(traj_lib.discrete_rama(phi, psi, \
|
|
888
|
+
states=['A']), ['A', 'A', 'A'])
|
|
889
|
+
with self.assertRaises(ValueError):
|
|
890
|
+
traj_lib.discrete_rama(phi, np.zeros((3, 2)))
|
|
891
|
+
|
|
892
|
+
def test_multi_region_bounds(self):
|
|
893
|
+
b = {'A': [-100., -50., -60., -20., 50., 100., 20., 60.]}
|
|
894
|
+
phi = np.radians([[-70.], [70.], [0.]])
|
|
895
|
+
psi = np.radians([[-40.], [40.], [0.]])
|
|
896
|
+
self.assertEqual(traj_lib.discrete_rama(phi, psi, bounds=b), \
|
|
897
|
+
['A', 'A', 'A'])
|
|
898
|
+
pops = traj_lib.rama_populations(phi, psi, b)
|
|
899
|
+
self.assertAlmostEqual(pops['A'], 2/3.)
|
|
900
|
+
self.assertAlmostEqual(pops['O'], 1/3.)
|
|
901
|
+
|
|
902
|
+
def test_populations_match_state_assignment(self):
|
|
903
|
+
# overlapping and multi-region bounds, order matters
|
|
904
|
+
b = {'A': [-130., -40., -50., 20.],
|
|
905
|
+
'B': [-90., 0., -20., 40., 30., 100., -40., 70.],
|
|
906
|
+
'E': [-180., -40., 125., 180.]}
|
|
907
|
+
rng = np.random.RandomState(0)
|
|
908
|
+
f = rng.uniform(-180, 180, 5000)
|
|
909
|
+
y = rng.uniform(-180, 180, 5000)
|
|
910
|
+
# include points exactly on edges (open intervals)
|
|
911
|
+
f[:3], y[:3] = [-40., -130., 0.], [-30., -50., 0.]
|
|
912
|
+
labels = [traj_lib._state(a, c, b)[0] for a, c in zip(f, y)]
|
|
913
|
+
pops = traj_lib.rama_populations(np.radians(f), \
|
|
914
|
+
np.radians(y), b)
|
|
915
|
+
for k in ['A', 'B', 'E', 'O']:
|
|
916
|
+
self.assertAlmostEqual(pops[k], labels.count(k)/5000.)
|
|
917
|
+
self.assertEqual(labels[:3], ['O', 'O', 'O'])
|
|
918
|
+
|
|
919
|
+
def test_overlaps_multi_region(self):
|
|
920
|
+
# A's two rectangles overlap each other (same state: ignored);
|
|
921
|
+
# A's second rectangle overlaps B
|
|
922
|
+
b = {'A': [-100, 0, -50, 0, -50, 50, -20, 10],
|
|
923
|
+
'B': [40, 90, 0, 50]}
|
|
924
|
+
self.assertEqual(traj_lib._overlaps(b), [('A', 'B')])
|
|
925
|
+
self.assertEqual(traj_lib._overlaps( \
|
|
926
|
+
{'A': [-100, 0, -50, 0, -50, 50, -20, 10]}), [])
|
|
927
|
+
|
|
928
|
+
def test_confirm_reports_overlap(self):
|
|
929
|
+
b = {'A': [-100, 0, -50, 0], 'B': [-50, 50, -10, 10]}
|
|
930
|
+
out = io.StringIO()
|
|
931
|
+
with contextlib.redirect_stdout(out):
|
|
932
|
+
traj_lib.confirm_bounds(np.zeros(3), np.zeros(3), b, \
|
|
933
|
+
input_fn=lambda p: 'y', plot=False)
|
|
934
|
+
self.assertIn("A and B overlap", out.getvalue())
|
|
935
|
+
|
|
936
|
+
def test_interactive_kernel(self):
|
|
937
|
+
try:
|
|
938
|
+
import IPython
|
|
939
|
+
except ImportError:
|
|
940
|
+
self.skipTest("IPython not installed")
|
|
941
|
+
with mock.patch('IPython.get_ipython', \
|
|
942
|
+
return_value=SimpleNamespace(kernel=object())):
|
|
943
|
+
with mock.patch.object(traj_lib.sys, 'stdin', None):
|
|
944
|
+
self.assertTrue(traj_lib._interactive())
|
|
945
|
+
|
|
946
|
+
def test_interactive_stdin(self):
|
|
947
|
+
tty = mock.Mock(isatty=lambda: True)
|
|
948
|
+
pipe = mock.Mock(isatty=lambda: False)
|
|
949
|
+
# no IPython at all, and (if installed) IPython without a kernel
|
|
950
|
+
cases = [{'IPython': None}]
|
|
951
|
+
if importlib.util.find_spec('IPython') is not None:
|
|
952
|
+
cases.append({})
|
|
953
|
+
for mods in cases:
|
|
954
|
+
with mock.patch.dict(sys.modules, mods):
|
|
955
|
+
with mock.patch('IPython.get_ipython', \
|
|
956
|
+
return_value=None, create=True) \
|
|
957
|
+
if not mods else contextlib.nullcontext():
|
|
958
|
+
for stdin, expect in [(tty, True), (pipe, False), \
|
|
959
|
+
(None, False)]:
|
|
960
|
+
with mock.patch.object(traj_lib.sys, 'stdin', \
|
|
961
|
+
stdin):
|
|
962
|
+
self.assertEqual(traj_lib._interactive(), \
|
|
963
|
+
expect)
|
|
964
|
+
|
|
965
|
+
def test_timeseries_discretize_uses_confirmed_bounds(self):
|
|
966
|
+
ts = _alaTB()
|
|
967
|
+
new = {'A': [-180., 0., -100., 50.]}
|
|
968
|
+
with mock.patch.object(traj_lib, 'confirm_bounds', \
|
|
969
|
+
return_value=new) as cb:
|
|
970
|
+
ts.discretize(states=['A', 'E'], check=True)
|
|
971
|
+
cb.assert_called_once()
|
|
972
|
+
self.assertEqual(cb.call_args[0][2], \
|
|
973
|
+
traj_lib.default_bounds(['A', 'E']))
|
|
974
|
+
self.assertEqual(ts.bounds, new)
|
|
975
|
+
phi, psi = traj_lib.compute_rama(ts)
|
|
976
|
+
self.assertEqual(ts.distraj, \
|
|
977
|
+
traj_lib.discrete_rama(phi, psi, bounds=new))
|
|
978
|
+
|
|
979
|
+
def test_discretizer_rama_pools_trajectories(self):
|
|
980
|
+
trs = _alaTB(n=2)
|
|
981
|
+
new = {'E': [-180., 0., 100., 180.]}
|
|
982
|
+
with mock.patch.object(traj_lib, 'confirm_bounds', \
|
|
983
|
+
return_value=new) as cb:
|
|
984
|
+
disc = traj.Discretizer(trs)
|
|
985
|
+
disc.rama(check=True)
|
|
986
|
+
cb.assert_called_once()
|
|
987
|
+
phi, psi, b0 = cb.call_args[0]
|
|
988
|
+
n = sum(tr.mdt.n_frames for tr in trs)
|
|
989
|
+
self.assertEqual(len(phi), n)
|
|
990
|
+
self.assertEqual(len(psi), n)
|
|
991
|
+
self.assertEqual(b0, traj_lib.default_bounds())
|
|
992
|
+
self.assertEqual(disc.bounds, new)
|
|
993
|
+
for tr in trs:
|
|
994
|
+
self.assertEqual(tr.bounds, new)
|
|
995
|
+
self.assertEqual(set(''.join(tr.distraj)) - {'O'}, {'E'})
|
|
996
|
+
|
|
997
|
+
def test_protocol_rama_check(self):
|
|
998
|
+
ts = _alaTB()
|
|
999
|
+
new = {'A': [-180., 0., -100., 50.]}
|
|
1000
|
+
with mock.patch.object(traj_lib, 'confirm_bounds', \
|
|
1001
|
+
return_value=new) as cb:
|
|
1002
|
+
res = traj.discretize_protocol(ts, {'rama': ['A'],
|
|
1003
|
+
'check': True})
|
|
1004
|
+
cb.assert_called_once()
|
|
1005
|
+
self.assertEqual(res.bounds, new)
|
|
1006
|
+
self.assertFalse(hasattr(ts, 'bounds'))
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
class TestDiscreteRama(unittest.TestCase):
|
|
1010
|
+
""" Transition-based assignment on synthetic angles (degrees). """
|
|
1011
|
+
|
|
1012
|
+
def _run(self, phi_deg, psi_deg):
|
|
1013
|
+
phi = np.radians(np.array(phi_deg, dtype=float))
|
|
1014
|
+
psi = np.radians(np.array(psi_deg, dtype=float))
|
|
1015
|
+
return traj_lib.discrete_rama(phi, psi, return_unassigned=True)
|
|
1016
|
+
|
|
1017
|
+
def test_leading_frames_dropped(self):
|
|
1018
|
+
# dihedral 0: O, A, O, E ; dihedral 1: O, O, E, O
|
|
1019
|
+
# A = (-60,-40), E = (-120,150), O = (0,180)
|
|
1020
|
+
phi = [[0, 0], [-60, 0], [0, -120], [-120, 0]]
|
|
1021
|
+
psi = [[180, 180], [-40, 180], [180, 150], [150, 180]]
|
|
1022
|
+
cstates, unassigned, ndrop = self._run(phi, psi)
|
|
1023
|
+
self.assertEqual(ndrop, 2)
|
|
1024
|
+
self.assertEqual(cstates, ['AE', 'EE'])
|
|
1025
|
+
np.testing.assert_allclose(unassigned, [0.5, 0.75])
|
|
1026
|
+
self.assertNotIn('O', ''.join(cstates))
|
|
1027
|
+
|
|
1028
|
+
def test_default_return(self):
|
|
1029
|
+
phi = np.radians([[-60.], [-120.]])
|
|
1030
|
+
psi = np.radians([[-40.], [150.]])
|
|
1031
|
+
self.assertEqual(traj_lib.discrete_rama(phi, psi), ['A', 'E'])
|
|
1032
|
+
|
|
1033
|
+
def test_never_assigned_raises(self):
|
|
1034
|
+
with self.assertRaises(ValueError):
|
|
1035
|
+
self._run([[-60, 0], [-60, 0]], [[-40, 180], [-40, 180]])
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
class TestRamaCheckDefault(unittest.TestCase):
|
|
1039
|
+
""" Bounds confirmation is opt-in, so scripts never block. """
|
|
1040
|
+
|
|
1041
|
+
def test_default_does_not_confirm(self):
|
|
1042
|
+
from unittest import mock
|
|
1043
|
+
download_osf_alaTB()
|
|
1044
|
+
top = os.path.join(_DATA_DIR, 'alaTB.gro')
|
|
1045
|
+
xtc = os.path.join(_DATA_DIR, 'alaTB.xtc')
|
|
1046
|
+
with mock.patch.object(traj_lib, 'confirm_bounds') as cb:
|
|
1047
|
+
ts = traj.TimeSeries(xtc=xtc, top=top)
|
|
1048
|
+
ts.discretize(states=['A', 'E'])
|
|
1049
|
+
traj.Discretizer([ts]).rama(states=['A', 'E'])
|
|
1050
|
+
traj.discretize_protocol([ts], {'rama': ['A', 'E']})
|
|
1051
|
+
cb.assert_not_called()
|