MasterMSM 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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()