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,1039 @@
1
+ import unittest
2
+ import mdtraj as md
3
+ import numpy as np
4
+ from mastermsm.trajectory import traj_lib, traj
5
+ from mastermsm.msm import msm, msm_lib
6
+ from mastermsm.test.download_data import download_osf_alaTB
7
+ import os, pickle, io, contextlib, warnings, copy
8
+ from scipy.linalg import expm, logm
9
+
10
+ # thermal energy (kJ/mol)
11
+ beta = 1./(8.314e-3*300)
12
+
13
+ class TestMSMLib(unittest.TestCase):
14
+ def test_esort(self):
15
+ self.assertTrue(hasattr(msm_lib, 'esort'))
16
+ self.assertTrue(callable(msm_lib.esort))
17
+ self.esort = msm_lib.esort([0,float(1)], [1,float(2)])
18
+ self.assertEqual(self.esort, 1)
19
+ self.esort = msm_lib.esort([0,float(100)], [1,float(2)])
20
+ self.assertEqual(self.esort, -1)
21
+ self.esort = msm_lib.esort([100,float(1)], [1,float(1)])
22
+ self.assertEqual(self.esort, 0)
23
+
24
+ def test_mat_mul_v(self):
25
+ self.assertTrue(hasattr(msm_lib,'mat_mul_v'))
26
+ self.assertTrue(callable(msm_lib.mat_mul_v))
27
+ self.matrix = np.array([
28
+ [1, 2, 3],
29
+ [4, 5, 6]
30
+ ])
31
+ self.vector = np.array(
32
+ [1, 0, 1]
33
+ )
34
+ self.assertEqual(msm_lib.mat_mul_v(self.matrix, self.vector), [4, 10])
35
+ self.matrix = np.array([
36
+ [-5, -4, 2],
37
+ [1, 6, -3],
38
+ [3, 5.5, -4]
39
+ ])
40
+ self.vector = np.array(
41
+ [1, 2, -3]
42
+ )
43
+ self.assertEqual(msm_lib.mat_mul_v(self.matrix, self.vector), [-19, 22, 26])
44
+
45
+ def test_rand_rate(self):
46
+ testT = np.array([
47
+ [10, 2, 1],
48
+ [1, 1, 1],
49
+ [0, 1, 0]
50
+ ])
51
+ self.random1 = msm_lib.rand_rate(nkeep= 3, count= testT)
52
+ self.random2 = msm_lib.rand_rate(nkeep= 3, count= testT)
53
+ self.assertEqual(self.random1.shape, (3, 3))
54
+ self.assertFalse((self.random1 == self.random2).all())
55
+
56
+ def test_traj_split(self):
57
+ traj1 = traj.TimeSeries(distraj=[1, 2, 3], dt=1.)
58
+ traj2 = traj.TimeSeries(distraj=[3, 2, 1], dt=2.)
59
+ trajs = [traj1, traj2]
60
+ self.filepath, nfrag = msm_lib.traj_split(data=trajs, lagt=10)
61
+ self.assertIsInstance(self.filepath, str)
62
+ self.assertTrue(os.path.exists(self.filepath))
63
+ # both trajectories are shorter than ten lag times: kept whole
64
+ self.assertEqual(nfrag, 2)
65
+ os.remove(self.filepath) # clean temp file
66
+
67
+ def test_traj_split_lengths(self):
68
+ """ Equal fragments, at least tmin and ten lag times long,
69
+ never across trajectories. """
70
+ trajs = [traj.TimeSeries(distraj=list(range(1000)), dt=1.),
71
+ traj.TimeSeries(distraj=list(range(250)), dt=2.)]
72
+ # second trajectory: 250 frames of dt=2, i.e. 500 time units
73
+ for tmin, expected in [(0., [10]*100 + [5]*50),
74
+ (100., [100]*10 + [50]*5),
75
+ (300., [333, 333, 334] + [250]),
76
+ (2000., [1000, 250])]:
77
+ ftmp, nfrag = msm_lib.traj_split(data=trajs, lagt=1, tmin=tmin)
78
+ with open(ftmp, 'rb') as f:
79
+ frags = pickle.load(f)
80
+ os.remove(ftmp)
81
+ self.assertEqual([len(x[0]) for x in frags], expected)
82
+ self.assertEqual(nfrag, len(expected))
83
+ # contiguous pieces that rebuild each trajectory
84
+ self.assertEqual(list(np.concatenate([x[0] for x in frags])),
85
+ list(range(1000)) + list(range(250)))
86
+
87
+ def test_calc_rate(self):
88
+ self.testT = np.array([
89
+ [1, 2, 3],
90
+ [0, 0, 0],
91
+ [10, 10, 10]
92
+
93
+ ])
94
+ self.rate = msm_lib.calc_rate(nkeep=3, trans=self.testT, lagt=10)
95
+ self.assertIsInstance(self.rate, np.ndarray)
96
+ self.assertEqual(self.rate.shape, (3, 3))
97
+
98
+ def test_calc_lifetime(self):
99
+ distraj = [1, 1, 1, 2]
100
+ dt = 1.
101
+ keys = [1, 2]
102
+ data = [distraj, dt, keys]
103
+ self.life = msm_lib.calc_lifetime(data)
104
+ self.assertIsInstance(self.life, dict)
105
+
106
+ def test_partial_rate(self):
107
+ test_nstates = 3
108
+ test_K = np.random.rand(test_nstates,test_nstates)
109
+ d_K_1 = msm_lib.partial_rate(test_K, 1)
110
+ for i in range(test_nstates):
111
+ if i != 1:
112
+ self.assertAlmostEqual(d_K_1[i,1] / test_K[i,1], beta/2)
113
+ self.assertAlmostEqual(d_K_1[1, i] / test_K[1, i], -beta / 2)
114
+ self.assertEqual(d_K_1.shape, (test_nstates, test_nstates))
115
+
116
+ def test_partial_peq(self):
117
+ test_nstates = 3
118
+ test_peq = np.random.rand(3)
119
+ d_peq_1 = msm_lib.partial_peq(test_peq,1)
120
+ self.assertEqual(len(d_peq_1), test_nstates)
121
+ for elem in range(test_nstates):
122
+ d_peq_elem = msm_lib.partial_peq(test_peq, elem)
123
+ for i in range(test_nstates):
124
+ if i != elem:
125
+ self.assertAlmostEqual(d_peq_elem[i] / (test_peq[elem] * test_peq[i]), beta)
126
+ else:
127
+ self.assertAlmostEqual(d_peq_elem[i] / (test_peq[i] * (1. - test_peq[i])), -beta)
128
+
129
+ def test_partial_pfold(self):
130
+ states = range(3)
131
+ K = np.random.rand(2, 2)
132
+ d_K = np.random.rand(2, 2)
133
+ FF = [0]
134
+ UU = [2]
135
+ res_dpfold = msm_lib.partial_pfold(states, K, d_K, FF, UU,
136
+ np.random.randint(0, 2)) # the last int parameter is not used
137
+ self.assertEqual(len(res_dpfold), len(states))
138
+ self.assertIsInstance(res_dpfold, np.ndarray)
139
+ self.assertIsInstance(res_dpfold[0], float)
140
+
141
+ def test_partial_flux(self):
142
+ nstates = np.random.randint(2,50)
143
+ states = range(nstates)
144
+ peq = np.random.rand(nstates)
145
+ K = np.random.rand(nstates,nstates)
146
+ pfold = np.random.rand(nstates)
147
+ d_peq = np.random.rand(nstates)
148
+ d_K = np.random.rand(nstates,nstates)
149
+ d_pfold = np.random.rand(nstates)
150
+ target = [0]
151
+
152
+ res_sum_d_flux = msm_lib.partial_flux(states, peq, K, pfold,d_peq, d_K, d_pfold, target)
153
+
154
+ self.assertIsNotNone(res_sum_d_flux)
155
+ self.assertIsInstance(res_sum_d_flux, float)
156
+
157
+ def test_tau_averages(self):
158
+ tau_boots_test = np.random.rand(2, 2).tolist()
159
+ res_tau_ave, res_tau_std, res_n, res_ci = \
160
+ msm_lib.tau_averages(tau_boots_test)
161
+ self.assertIsInstance(res_tau_std, list)
162
+ self.assertIsInstance(res_tau_ave, list)
163
+ self.assertIsInstance(res_tau_ave[0], float)
164
+ self.assertIsInstance(res_tau_std[0], float)
165
+ self.assertEqual(res_n, [2, 2])
166
+
167
+ def test_tau_averages_short_samples(self):
168
+ """ A sample that lost a state has fewer relaxation times;
169
+ each time is averaged over the samples that have it. """
170
+ boots = [[10., 4., 1.], [12., 6.], [14., 5., 3.]]
171
+ ave, std, n, ci = msm_lib.tau_averages(boots)
172
+ self.assertEqual(n, [3, 3, 2])
173
+ np.testing.assert_allclose(ci[0], np.percentile([10., 12., 14.],
174
+ [16, 84]))
175
+ np.testing.assert_allclose(ci[2], np.percentile([1., 3.], [16, 84]))
176
+ np.testing.assert_allclose(ave, [12., 5., 2.])
177
+ np.testing.assert_allclose(std, [np.std([10., 12., 14.]),
178
+ np.std([4., 6., 5.]), 1.])
179
+ # the short sample first must not truncate the result
180
+ ave2, std2, n2, _ = msm_lib.tau_averages(boots[1:] + boots[:1])
181
+ np.testing.assert_allclose(ave2, ave)
182
+ self.assertEqual(n2, n)
183
+
184
+ def test_tau_averages_ntau(self):
185
+ """ Relaxation times no sample has are reported as nan;
186
+ nan entries are skipped. """
187
+ ave, std, n, ci = msm_lib.tau_averages([[10., np.nan], [12., 6.]],
188
+ ntau=3)
189
+ self.assertEqual(n, [2, 1, 0])
190
+ np.testing.assert_allclose(ave[:2], [11., 6.])
191
+ self.assertTrue(np.isnan(ave[2]) and np.isnan(std[2]))
192
+ self.assertTrue(np.all(np.isnan(ci[2])))
193
+
194
+ def test_peq_averages(self):
195
+ peq_boots_test = np.random.rand(2,3)
196
+ keep_keys_boots_test = [['A','E','O'],['A','E','O']]
197
+ keys = ['A','E','O']
198
+ res_peq_ave, res_peq_std = msm_lib.peq_averages(peq_boots_test, keep_keys_boots_test, keys)
199
+ self.assertEqual(len(res_peq_ave),len(keys))
200
+ self.assertEqual(len(res_peq_std),len(keys))
201
+ self.assertIsInstance(res_peq_ave, list)
202
+ self.assertIsInstance(res_peq_std, list)
203
+ self.assertIsInstance(res_peq_ave[0], float)
204
+ self.assertIsInstance(res_peq_std[0], float)
205
+
206
+ def test_propagate_worker(self):
207
+ t = 0
208
+ rate = np.random.rand(2,2)
209
+ pini = np.random.rand(2,2)
210
+ x_test = [rate, t, pini]
211
+ res_popul = msm_lib.propagate_worker(x_test)
212
+ self.assertIsInstance(res_popul, list)
213
+ self.assertIsInstance(res_popul[0], np.ndarray)
214
+ self.assertIsInstance(res_popul[0][0], float)
215
+
216
+ def test_propagateT_worker(self):
217
+ t = 0
218
+ rate = np.random.rand(2,2)
219
+ pini = np.random.rand(2,2)
220
+ x_test = [rate, t, pini]
221
+ res_popul = msm_lib.propagateT_worker(x_test)
222
+ self.assertIsInstance(res_popul, list)
223
+ self.assertIsInstance(res_popul[0], np.ndarray)
224
+ self.assertIsInstance(res_popul[0][0], float)
225
+
226
+ def test_detailed_balance(self):
227
+ nkeep_test = 2
228
+ rate = np.array(np.random.rand(nkeep_test,nkeep_test))
229
+ peq = np.random.rand(nkeep_test)
230
+ res_rate = msm_lib.detailed_balance(nkeep_test, rate, peq)
231
+ self.assertEqual(res_rate.shape, (nkeep_test,nkeep_test))
232
+ self.assertIsInstance(res_rate,np.ndarray)
233
+ self.assertIsInstance(res_rate[0][0],float)
234
+
235
+ def test_likelihood(self):
236
+ nkeep_test = 2
237
+ rate = np.array(np.random.rand(nkeep_test,nkeep_test))
238
+ count = np.array(np.random.randint(0, 10**5, size=(nkeep_test,nkeep_test)))
239
+ lagt = np.random.randint(1,1000)
240
+ res_mlog_like = msm_lib.likelihood(nkeep_test,rate,count,lagt)
241
+ self.assertIsInstance(res_mlog_like, float)
242
+ self.assertIsNotNone(res_mlog_like)
243
+ self.assertGreater(res_mlog_like, 0)
244
+
245
+ def test_calc_mlrate(self):
246
+ nkeep_test = 2
247
+ rate_init = np.array(np.random.rand(nkeep_test, nkeep_test))
248
+ count = np.array(np.random.randint(0, 10 ** 5, size=(nkeep_test, nkeep_test)))
249
+ lagt = np.random.randint(1, 1000)
250
+ res_rate, res_ml, res_beta = msm_lib.calc_mlrate(nkeep_test, count, lagt, rate_init)
251
+ self.assertIsInstance(res_rate, np.ndarray)
252
+ self.assertIsNotNone(res_rate)
253
+ self.assertIsNotNone(res_ml)
254
+ self.assertIsNotNone(res_beta)
255
+
256
+ def test_mc_move(self):
257
+ nkeep_test = np.random.randint(2,100)
258
+ rate = np.random.rand(nkeep_test,nkeep_test)
259
+ peq_test = np.random.rand(nkeep_test)
260
+ db_rate = msm_lib.detailed_balance(nkeep_test,rate,peq_test)
261
+ new_rate, new_peq = msm_lib.mc_move(nkeep_test, db_rate, peq_test)
262
+ # a move changes either the rate matrix or peq, never both
263
+ rate_changed = not np.array_equal(db_rate, new_rate)
264
+ peq_changed = not np.array_equal(peq_test, new_peq)
265
+ self.assertTrue(rate_changed != peq_changed)
266
+ self.assertEqual(db_rate.shape, new_rate.shape)
267
+ self.assertEqual(peq_test.shape, new_peq.shape)
268
+
269
+ def test_mc_move_all_rates(self):
270
+ """ Every independent (lower triangle) rate can be perturbed. """
271
+ np.random.seed(0)
272
+ n = 4
273
+ peq = np.array([0.4, 0.3, 0.2, 0.1])
274
+ rate = np.zeros((n, n))
275
+ rate[np.tril_indices(n, -1)] = 1.
276
+ rate = msm_lib.detailed_balance(n, rate, peq)
277
+ moved = set()
278
+ for _ in range(2000):
279
+ new_rate, _ = msm_lib.mc_move(n, rate, peq)
280
+ for j, i in np.argwhere(new_rate != rate):
281
+ moved.add((int(j), int(i)))
282
+ self.assertEqual(moved, set(zip(*map(list, np.tril_indices(n, -1)))))
283
+
284
+ def test_mc_move_rate_step(self):
285
+ """ Rate steps follow the magnitude (units) of the rates. """
286
+ n = 4
287
+ peq = np.array([0.4, 0.3, 0.2, 0.1])
288
+ for k in (1e-3, 1., 1e3):
289
+ np.random.seed(0)
290
+ rate = np.zeros((n, n))
291
+ rate[np.tril_indices(n, -1)] = k
292
+ rate = msm_lib.detailed_balance(n, rate, peq)
293
+ scale = 0.1*np.mean(rate[rate > 0.])
294
+ steps = []
295
+ for _ in range(500):
296
+ new_rate, _ = msm_lib.mc_move(n, rate, peq)
297
+ if not np.array_equal(new_rate, rate):
298
+ steps.append(np.abs(new_rate - rate).max())
299
+ self.assertLessEqual(np.max(steps), 0.5*scale)
300
+ self.assertGreater(np.max(steps), 0.4*scale)
301
+ with self.assertRaises(ValueError):
302
+ msm_lib.mc_move(n, np.zeros((n, n)), peq)
303
+
304
+ def test_calc_mlrate_known_model(self):
305
+ """ MLPB recovers a known 3-state rate matrix from exact counts. """
306
+ np.random.seed(0)
307
+ n, lagt = 3, 10.
308
+ peq = np.array([0.5, 0.3, 0.2])
309
+ rate = np.zeros((n, n))
310
+ rate[np.tril_indices(n, -1)] = [0.02, 0.005, 0.01]
311
+ rate = msm_lib.detailed_balance(n, rate, peq)
312
+ trans = expm(rate*lagt)
313
+ count = np.rint(trans*peq*1e6).astype(int)
314
+ rate_init = msm_lib.calc_rate(n, trans.copy(), lagt)
315
+ ml_init = msm_lib.likelihood(n,
316
+ msm_lib.detailed_balance(n, rate_init.copy(), peq), count, lagt)
317
+ with warnings.catch_warnings():
318
+ warnings.simplefilter("error") # no divide by zero in annealing
319
+ with contextlib.redirect_stdout(io.StringIO()):
320
+ res_rate, res_ml, res_temp = msm_lib.calc_mlrate(n, count,
321
+ lagt, rate_init)
322
+ np.testing.assert_allclose(res_rate.sum(axis=0), 0., atol=1e-12)
323
+ # detailed balance: K[j,i] p[i] = K[i,j] p[j]
324
+ flux = res_rate*peq
325
+ np.testing.assert_allclose(flux, flux.T, rtol=0.05)
326
+ np.testing.assert_allclose(res_rate, rate, rtol=0.1)
327
+ ml_true = msm_lib.likelihood(n, rate, count, lagt)
328
+ ml_res = msm_lib.likelihood(n, res_rate, count, lagt)
329
+ self.assertLess(ml_res, ml_init)
330
+ self.assertLess((ml_res - ml_true)/ml_true, 1e-4)
331
+ # annealing temperatures stay within [0, 1]
332
+ self.assertEqual(len(res_ml), len(res_temp))
333
+ self.assertTrue(np.all(np.isfinite(res_temp)))
334
+ self.assertGreaterEqual(np.min(res_temp), 0.)
335
+ self.assertLessEqual(np.max(res_temp), 1.)
336
+
337
+ def test_calc_eigsK(self):
338
+ # detailed-balance rate matrix (k_ji = s_ji p_j, s symmetric):
339
+ # real spectrum, known equilibrium populations
340
+ rng = np.random.default_rng(42)
341
+ for nstates in (2, 3, 10, 50):
342
+ p = rng.random(nstates)
343
+ p /= p.sum()
344
+ s = rng.random((nstates, nstates))
345
+ rate_test = (s + s.T)*p[:,None]
346
+ np.fill_diagonal(rate_test, 0.)
347
+ rate_test -= np.diag(rate_test.sum(axis=0))
348
+ res_tauK, res_peqK = msm_lib.calc_eigsK(rate_test)
349
+ self.assertIsInstance(res_tauK, list)
350
+ self.assertEqual(len(res_tauK), nstates - 1)
351
+ self.assertIsInstance(res_tauK[0], float)
352
+ self.assertTrue(all(t > 0 for t in res_tauK))
353
+ self.assertEqual(len(res_peqK), nstates)
354
+ np.testing.assert_allclose(np.imag(res_peqK), 0., atol=1e-12)
355
+ np.testing.assert_allclose(np.real(res_peqK), p, rtol=1e-8)
356
+
357
+ res_tauK, res_peqK, res_rvecsK, res_lvecsK = msm_lib.calc_eigsK(rate_test, evecs=True)
358
+ self.assertIsNotNone(res_rvecsK)
359
+ self.assertIsNotNone(res_lvecsK)
360
+ self.assertIsInstance(res_lvecsK, np.ndarray)
361
+ self.assertIsInstance(res_rvecsK, np.ndarray)
362
+
363
+ def test_run_commit(self):
364
+ nstates = np.random.randint(3,20)
365
+ states = range(nstates)
366
+ K = np.random.rand(nstates, nstates)
367
+ peq = np.random.rand(nstates)
368
+ FF = [0]
369
+ UU = [nstates - 1]
370
+ J, pfold, sum_flux, kf = msm_lib.run_commit(states, K, peq, FF, UU)
371
+ self.assertIsNotNone(J)
372
+ self.assertIsNotNone(pfold)
373
+ self.assertIsNotNone(sum_flux)
374
+ self.assertIsNotNone(kf)
375
+ self.assertIsInstance(kf, float)
376
+ self.assertGreater(kf, 0)
377
+ self.assertEqual(J.shape, K.shape)
378
+ self.assertEqual(len(pfold), nstates)
379
+ self.assertIsInstance(pfold[0], float)
380
+ self.assertIsInstance(J[0][0], float)
381
+
382
+
383
+ class TestSuperMSM(unittest.TestCase):
384
+ def setUp(self):
385
+ distraj = [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0]
386
+ self.ts = traj.TimeSeries(distraj=distraj, dt=1.)
387
+ self.supermsm = msm.SuperMSM([self.ts])
388
+
389
+ def test_init(self):
390
+ self.assertIsNotNone(self.supermsm)
391
+ self.assertTrue(hasattr(self.supermsm, 'data'))
392
+ self.assertTrue(hasattr(self.supermsm, 'keys'))
393
+ self.assertEqual(sorted(self.supermsm.keys), [0, 1])
394
+
395
+ def test_do_msm(self):
396
+ self.supermsm.do_msm([1])
397
+ self.assertIn(1, self.supermsm.msms)
398
+ self.assertIsInstance(self.supermsm.msms[1], msm.MSM)
399
+
400
+ def test_do_lbrate(self):
401
+ self.supermsm.do_msm([1])
402
+ self.supermsm.do_lbrate()
403
+ self.assertIsNotNone(self.supermsm.lbrate)
404
+ nkeys = len(self.supermsm.keys)
405
+ self.assertEqual(self.supermsm.lbrate.shape, (nkeys, nkeys))
406
+ self.assertIsNotNone(self.supermsm.tauK)
407
+ self.assertIsNotNone(self.supermsm.peqK)
408
+
409
+
410
+ class TestMSM(unittest.TestCase):
411
+ def setUp(self):
412
+ self.nstates = 3
413
+ self.trajectory = [0, 0, 0, 1, 1, 1, 0, 0, 0, 2]
414
+ self.traj_sim = self.trajectory + \
415
+ [x for x in reversed(self.trajectory)]
416
+
417
+ def test_count_lib(self):
418
+ keys = [0, 1, 2]
419
+ C = msm_lib.calc_count_worker([self.trajectory, 1, keys,\
420
+ 1, True])
421
+ self.assertTrue(np.array_equal(C, \
422
+ np.array([[4, 1, 0], [1, 2, 0], [1, 0, 0]])))
423
+
424
+ keys = [0, 1]
425
+ C = msm_lib.calc_count_worker([self.trajectory, 1, keys,\
426
+ 1, True])
427
+ self.assertTrue(np.array_equal(C, \
428
+ np.array([[5, 1], [1, 2]])))
429
+
430
+ keys = [0, 1, 2]
431
+ C = msm_lib.calc_count_worker([self.traj_sim, 1, keys,\
432
+ 1, True])
433
+ self.assertTrue(np.array_equal(C, \
434
+ np.array([[8, 2, 1], [2, 4, 0], [1, 0, 1]])))
435
+
436
+ keys = [0, 2]
437
+ C = msm_lib.calc_count_worker([self.traj_sim, 1, keys,\
438
+ 1, True])
439
+ self.assertTrue(np.array_equal(C, \
440
+ np.array([[16, 1], [1, 1]])))
441
+
442
+ # def setUp(self):
443
+ # download_test_data()
444
+ # self.nstates = np.random.randint(3,100)
445
+ # distraj_1 = np.random.randint(1,self.nstates+1, size=1000).tolist()
446
+ # traj_1 = traj.TimeSeries(distraj= distraj_1, dt=1.)
447
+ # distraj_2 = np.random.randint(1,self.nstates+1, size=1000).tolist()
448
+ # traj_2 = traj.TimeSeries(distraj= distraj_2, dt=2.)
449
+ # self.data = np.array([
450
+ # traj_1,
451
+ # traj_2
452
+ # ])
453
+ # self.lagt = 10
454
+ # self.keys = [i for i in range(1,self.nstates+1)]
455
+ # msm_obj = msm.MSM(data=self.data, lagt=self.lagt, keys=self.keys, sym=True)
456
+ # self.msm = msm_obj
457
+ #
458
+ #
459
+ # def test_init(self):
460
+ # self.msm_empty = msm.MSM()
461
+ # self.assertIsNotNone(self.msm_empty)
462
+ # self.assertIsNone(self.msm_empty.data)
463
+ # self.assertIsNone(self.msm_empty.lagt)
464
+ # self.assertIsNone(self.msm_empty.keys)
465
+ # self.assertFalse(self.msm_empty.sym)
466
+ #
467
+ # self.assertIsNotNone(self.msm)
468
+ # self.assertIsNotNone(self.msm.data)
469
+ # self.assertIsNotNone(self.msm.keys)
470
+ # self.assertIsNotNone(self.msm.lagt)
471
+ # self.assertTrue(self.msm.sym)
472
+ # self.assertTrue(np.array_equal(self.data, self.msm.data))
473
+ # self.assertEqual(self.msm.lagt, self.lagt)
474
+ # self.assertTrue(np.array_equal(self.keys, self.msm.keys))
475
+ #
476
+ # def test_do_count(self):
477
+ # self.msm.do_count()
478
+ # self.assertIsNotNone(self.msm.keep_states)
479
+ # self.assertIsNotNone(self.msm.keep_keys)
480
+ #
481
+ # def test_calc_count_multi(self):
482
+ # count = self.msm.calc_count_multi()
483
+ # self.assertIsNotNone(count)
484
+ # self.assertIsInstance(count, np.ndarray)
485
+ # self.assertEqual(count.shape, (self.nstates, self.nstates))
486
+ #
487
+ # def test_check_connect(self):
488
+ # self.msm.do_count()
489
+ # keep_states, keep_keys = self.msm.check_connect()
490
+ # self.assertEqual(len(keep_keys), len(keep_states))
491
+ # self.assertEqual(self.msm.keep_keys, self.keys)
492
+ #
493
+ # def test_do_trans(self):
494
+ # self.msm.do_count()
495
+ # self.msm.do_trans(evecs=False)
496
+ # self.assertIsNotNone(self.msm.tauT)
497
+ # self.assertIsNotNone(self.msm.trans)
498
+ # self.assertIsNotNone(self.msm.peqT)
499
+ # self.assertFalse(hasattr(self.msm, "rvecsT"))
500
+ # self.assertFalse(hasattr(self.msm, "lvecsT"))
501
+ # self.assertEqual(len(self.msm.tauT), self.nstates - 1)
502
+ # self.assertEqual(len(self.msm.peqT), self.nstates)
503
+ # self.assertEqual(self.msm.trans.shape, (self.nstates, self.nstates))
504
+ # self.msm.do_trans(evecs=True)
505
+ # self.assertTrue(hasattr(self.msm, "rvecsT"))
506
+ # self.assertTrue(hasattr(self.msm, "lvecsT"))
507
+ # self.assertEqual(len(self.msm.rvecsT), self.nstates)
508
+ # self.assertEqual(len(self.msm.lvecsT), self.nstates)
509
+ #
510
+ # def test_do_rate(self):
511
+ # self.msm.do_count()
512
+ # self.msm.do_trans()
513
+ # self.msm.do_rate(evecs=False)
514
+ # self.assertIsNotNone(self.msm.rate)
515
+ # self.assertIsNotNone(self.msm.tauK)
516
+ # self.assertIsNotNone(self.msm.peqK)
517
+ # self.assertEqual(len(self.msm.tauK), self.nstates - 1)
518
+ # self.assertEqual(len(self.msm.peqK), self.nstates)
519
+ # self.msm.do_rate(evecs=True)
520
+ # self.assertIsNotNone(self.msm.rvecsK)
521
+ # self.assertIsNotNone(self.msm.lvecsK)
522
+ #
523
+ # def test_calc_eigsT(self):
524
+ # self.msm.do_count()
525
+ # self.msm.do_trans()
526
+ # tauT, peqT, rvecsT_sorted, lvecsT_sorted = self.msm.calc_eigsT(evecs=True)
527
+ # self.assertIsNotNone(tauT)
528
+ # self.assertIsNotNone(peqT)
529
+ # self.assertEqual(len(tauT), self.nstates - 1)
530
+ # self.assertEqual(len(peqT), self.nstates)
531
+ # self.assertIsNotNone(rvecsT_sorted)
532
+ # self.assertIsNotNone(lvecsT_sorted)
533
+ #
534
+ # def test_calc_eigsK(self):
535
+ # self.msm.do_count()
536
+ # self.msm.do_trans()
537
+ # tauK, peqK, rvecsK_sorted, lvecsK_sorted = self.msm.calc_eigsT(evecs=True)
538
+ # self.assertIsNotNone(tauK)
539
+ # self.assertIsNotNone(peqK)
540
+ # self.assertEqual(len(tauK), self.nstates - 1)
541
+ # self.assertEqual(len(peqK), self.nstates)
542
+ # self.assertIsNotNone(rvecsK_sorted)
543
+ # self.assertIsNotNone(lvecsK_sorted)
544
+ #
545
+ # def test_boots(self):
546
+ # self.msm.do_count()
547
+ # self.msm.do_trans()
548
+ # self.msm.boots()
549
+ # self.assertIsNotNone(self.msm.tau_ave)
550
+ # self.assertIsNotNone(self.msm.tau_std)
551
+ # self.assertIsNotNone(self.msm.peq_ave)
552
+ # self.assertIsNotNone(self.msm.peq_std)
553
+ # self.assertEqual(len(self.msm.tau_ave), self.nstates - 1)
554
+ # self.assertEqual(len(self.msm.tau_std), self.nstates - 1)
555
+ # self.assertEqual(len(self.msm.peq_std), self.nstates)
556
+ # self.assertEqual(len(self.msm.peq_ave), self.nstates)
557
+ #
558
+ # def test_sensitivity(self):
559
+ # self.msm.do_count()
560
+ # self.msm.do_trans()
561
+ # self.msm.do_rate()
562
+ # FF = [np.random.randint(1, self.nstates + 1)]
563
+ #
564
+ # UU = [np.random.randint(1, self.nstates + 1)]
565
+ # self.msm.sensitivity(FF=FF, UU=UU)
566
+ # self.assertIsNotNone(self.msm.kf)
567
+ # self.assertIsNotNone(self.msm.d_pu)
568
+ # self.assertIsNotNone(self.msm.d_lnkf)
569
+ # self.assertIsNotNone(self.msm.dJ)
570
+ # self.assertIsInstance(self.msm.kf, float)
571
+ # self.assertEqual(len(self.msm.d_pu), self.nstates)
572
+ # self.assertEqual(len(self.msm.d_lnkf), self.nstates)
573
+ # self.assertEqual(len(self.msm.dJ),self.nstates)
574
+ # self.assertIsInstance(self.msm.d_pu[0], float)
575
+ # self.assertIsInstance(self.msm.dJ[0], float)
576
+ # self.assertIsInstance(self.msm.d_lnkf[0], float)
577
+ #
578
+ # def test_propagateK(self):
579
+ # # p0_fn = "p0.txt"
580
+ # # new_file = open(p0_fn, "w")
581
+ # random_p0 = np.random.rand(self.nstates)
582
+ # # random_pini = np.random.randint(1, self.nstates + 1, size = 2)
583
+ # # new_file.write(np.array2string(random_p0))
584
+ # # new_file.close()
585
+ # self.msm.do_count()
586
+ # self.msm.do_trans()
587
+ # self.msm.do_rate()
588
+ # time, popul = self.msm.propagateK(p0=random_p0)
589
+ # self.assertIsNotNone(time)
590
+ # self.assertIsInstance(time, np.ndarray)
591
+ # self.assertIsInstance(popul, list)
592
+ # self.assertEqual(len(time), 20)
593
+ # self.assertEqual(len(popul), 20)
594
+ # self.assertEqual(len(popul[0]), self.nstates)
595
+ #
596
+ # for ind, t in enumerate(time):
597
+ # if ind != 0:
598
+ # self.assertGreater(t, time[ind - 1])
599
+ #
600
+ # def test_propagateT(self):
601
+ # random_p0 = np.random.rand(self.nstates)
602
+ # self.msm.do_count()
603
+ # self.msm.do_trans()
604
+ # self.msm.do_rate()
605
+ # tcum, popul = self.msm.propagateT(p0=random_p0)
606
+ # self.assertIsNotNone(tcum)
607
+ # self.assertIsInstance(tcum, list)
608
+ # self.assertIsInstance(popul, list)
609
+ # self.assertEqual(len(tcum), 20)
610
+ # self.assertEqual(len(popul), 20)
611
+ # self.assertEqual(len(popul[0]), self.nstates)
612
+ #
613
+ # def test_acf_mode(self):
614
+ # self.msm.do_count()
615
+ # self.msm.do_trans(evecs=True)
616
+ # self.msm.do_rate()
617
+ # acf_ave = self.msm.acf_mode()
618
+ # self.assertIsInstance(acf_ave, dict)
619
+ # self.assertEqual(len(acf_ave.keys()), len(self.msm.keep_keys) - 1)
620
+ # modes = [key for key in acf_ave.keys()]
621
+ #
622
+ # self.assertIsInstance(acf_ave[modes[0]][0], float)
623
+
624
+
625
+ class TestEigsTau(unittest.TestCase):
626
+ """ Eigenvalue sorting and relaxation times from T. """
627
+
628
+ def test_complex_pair(self):
629
+ # cyclic, non-reversible column-stochastic T
630
+ T = np.array([[.8, 0., .2], [.2, .8, 0.], [0., .2, .8]])
631
+ evals, _, _ = msm_lib.calc_eigsT(T)
632
+ self.assertAlmostEqual(evals[0].real, 1.)
633
+ tau = msm_lib.calc_tauT(evals, 2.)
634
+ self.assertEqual(len(tau), 2)
635
+ expected = -2. / np.log(abs(0.7 + 0.1j * np.sqrt(3.)))
636
+ np.testing.assert_allclose(tau, [expected, expected])
637
+ self.assertTrue(np.isrealobj(tau))
638
+
639
+ def test_negative_eigenvalue(self):
640
+ T = np.array([[.1, .9], [.9, .1]])
641
+ evals, _, _ = msm_lib.calc_eigsT(T)
642
+ tau = msm_lib.calc_tauT(evals, 1.)
643
+ np.testing.assert_allclose(tau, [-1. / np.log(.8)])
644
+
645
+ def test_sorted_by_modulus(self):
646
+ # reversible T with eigenvalues 1, 0.5, -0.9
647
+ evals_in = np.array([1., 0.5, -0.9])
648
+ rng = np.random.default_rng(0)
649
+ Q, _ = np.linalg.qr(rng.normal(size=(3, 3)))
650
+ A = Q @ np.diag(evals_in) @ Q.T
651
+ evals, _, _ = msm_lib.calc_eigsT(A)
652
+ np.testing.assert_allclose(np.real(evals), [1., -0.9, 0.5])
653
+
654
+
655
+ def _markov_chain(T, n, seed):
656
+ """ Sample n frames from column-stochastic T, starting in state 0. """
657
+ rng = np.random.default_rng(seed)
658
+ cum = np.cumsum(T, axis=0)
659
+ u = rng.random(n - 1)
660
+ s = [0]
661
+ for x in u:
662
+ s.append(int(np.searchsorted(cum[:, s[-1]], x)))
663
+ return s
664
+
665
+
666
+ class TestMSMWorkflow(unittest.TestCase):
667
+ """ MSM object on a two-state chain with known kinetics. """
668
+
669
+ p, q = 0.05, 0.10 # rates 0->1 and 1->0 per step
670
+
671
+ @classmethod
672
+ def setUpClass(cls):
673
+ p, q = cls.p, cls.q
674
+ cls.T = np.array([[1 - p, q], [p, 1 - q]])
675
+ cls.lam = 1 - p - q
676
+ cls.trajs = [traj.TimeSeries(distraj=_markov_chain(cls.T, 20000, s),
677
+ dt=1.) for s in (1, 2)]
678
+ cls.msm = msm.MSM(cls.trajs, lagt=1)
679
+ cls.msm.calc_count()
680
+ cls.msm.calc_trans()
681
+ cls.msm.calc_evals()
682
+
683
+ def test_trans(self):
684
+ np.testing.assert_allclose(self.msm.trans.sum(axis=0), 1.)
685
+ np.testing.assert_allclose(self.msm.trans, self.T, atol=0.01)
686
+
687
+ def test_calc_evals(self):
688
+ m = self.msm
689
+ self.assertEqual(len(m.tauT), 1)
690
+ # tauT consistent with the estimated T ...
691
+ lam_est = np.sort(np.linalg.eigvals(m.trans))[0]
692
+ np.testing.assert_allclose(m.tauT, [-1. / np.log(lam_est)])
693
+ # ... and close to the generating model
694
+ np.testing.assert_allclose(m.tauT, [-1. / np.log(self.lam)],
695
+ rtol=0.1)
696
+ np.testing.assert_allclose(m.peqT, [self.q, self.p] /
697
+ np.float64(self.p + self.q), atol=0.02)
698
+ self.assertAlmostEqual(np.sum(m.peqT), 1.)
699
+ self.assertEqual(m.lvecsT.shape, (2, 2))
700
+
701
+ def test_calc_evals_neigs(self):
702
+ m = msm.MSM(self.trajs, lagt=1)
703
+ m.count = self.msm.count
704
+ m.calc_trans()
705
+ m.calc_evals(neigs=1, evecs=False)
706
+ self.assertEqual(len(m.tauT), 1)
707
+ self.assertFalse(hasattr(m, 'lvecsT'))
708
+
709
+ def test_boots(self):
710
+ m = self.msm
711
+ m.boots()
712
+ self.assertEqual(len(m.tau_ave), 1)
713
+ self.assertTrue(np.all(np.isfinite(m.tau_std)))
714
+ self.assertGreater(m.tau_std[0], 0.)
715
+ np.testing.assert_allclose(m.tau_ave, m.tauT, rtol=0.1)
716
+ self.assertAlmostEqual(np.sum(m.peq_ave), 1.)
717
+ np.testing.assert_allclose(m.peq_ave, m.peqT, atol=0.02)
718
+ self.assertEqual(m.tau_nboots, [20])
719
+ lo, hi = m.tau_ci[0]
720
+ self.assertLess(lo, m.tauT[0])
721
+ self.assertGreater(hi, m.tauT[0])
722
+ # 40000 steps, tau1 ~ 6 steps, fragments of >= 5 tau1
723
+ self.assertEqual(m.nfrag, 2*int(20000//(5*m.tauT[0])))
724
+
725
+ def test_boots_nonfinite_tau(self):
726
+ # fragment length is set by tau1, so it must be finite
727
+ m = msm.MSM(self.trajs, lagt=1)
728
+ m.count = self.msm.count
729
+ m.do_trans(evecs=False)
730
+ m.tauT = np.array([np.inf])
731
+ with self.assertRaises(ValueError):
732
+ m.boots(seed=0, nboots=4)
733
+
734
+ def test_boots_nboots(self):
735
+ m = self.msm
736
+ m.boots(seed=0, nboots=7)
737
+ self.assertEqual(m.tau_nboots, [7])
738
+
739
+ def test_boots_block_warning(self):
740
+ """ Fragments of at least block x tau1; a warning when they
741
+ are too few for a reliable error. """
742
+ m = self.msm
743
+ with warnings.catch_warnings():
744
+ warnings.simplefilter("error")
745
+ m.boots(seed=0, nboots=4)
746
+ nfrag5 = m.nfrag
747
+ m.boots(seed=0, nboots=4, block=10.)
748
+ self.assertEqual(m.nfrag, 2*int(20000//(10*m.tauT[0])))
749
+ self.assertLess(m.nfrag, nfrag5)
750
+ with self.assertWarns(UserWarning):
751
+ m.boots(seed=0, nboots=4, block=20000/m.tauT[0]/2.5)
752
+ self.assertEqual(m.nfrag, 4)
753
+
754
+ def test_boots_seed(self):
755
+ """ The bootstrap is reproducible with a seed, either passed
756
+ explicitly or set in numpy's global generator. """
757
+ m = self.msm
758
+ m.boots(seed=1)
759
+ ref = (list(m.tau_std), list(m.peq_std))
760
+ m.boots(seed=1)
761
+ self.assertEqual((list(m.tau_std), list(m.peq_std)), ref)
762
+ m.boots(seed=2)
763
+ self.assertNotEqual(list(m.tau_std), ref[0])
764
+ np.random.seed(5)
765
+ m.boots()
766
+ ref = list(m.tau_std)
767
+ np.random.seed(5)
768
+ m.boots()
769
+ self.assertEqual(list(m.tau_std), ref)
770
+
771
+ def test_do_rate_taylor(self):
772
+ m = self.msm
773
+ m.do_rate(method='Taylor')
774
+ np.testing.assert_allclose(m.rate.sum(axis=0), 0., atol=1e-12)
775
+ np.testing.assert_allclose(m.rate, m.trans - np.eye(2))
776
+ lam_est = np.sort(np.linalg.eigvals(m.trans))[0]
777
+ np.testing.assert_allclose(m.tauK, [1. / (1. - lam_est)])
778
+ np.testing.assert_allclose(m.peqK, m.peqT)
779
+
780
+ def test_do_rate_mlpb(self):
781
+ np.random.seed(0)
782
+ m = msm.MSM(self.trajs, lagt=1)
783
+ m.count = self.msm.count
784
+ m.calc_trans()
785
+ m.calc_evals()
786
+ with warnings.catch_warnings():
787
+ warnings.simplefilter("error")
788
+ with contextlib.redirect_stdout(io.StringIO()):
789
+ m.do_rate(method='MLPB')
790
+ np.testing.assert_allclose(m.rate.sum(axis=0), 0., atol=1e-12)
791
+ # for two states the ML generator is the matrix log of T
792
+ np.testing.assert_allclose(m.rate, logm(m.trans).real, rtol=0.05)
793
+ np.testing.assert_allclose(m.peqK, m.peqT, atol=0.01)
794
+ np.testing.assert_allclose(m.tauK, m.tauT, rtol=0.05)
795
+
796
+ def test_do_rate_mlpb_disconnected(self):
797
+ """ MLPB uses only the counts of the connected set. """
798
+ np.random.seed(0)
799
+ m = msm.MSM(self.trajs, keys=[2, 0, 1], lagt=1)
800
+ count = np.zeros((3, 3), int) # state 2 is never visited
801
+ count[1:, 1:] = self.msm.count
802
+ m.count = count
803
+ m.calc_trans()
804
+ self.assertEqual(m.keep_keys, [0, 1])
805
+ with contextlib.redirect_stdout(io.StringIO()):
806
+ m.do_rate(method='MLPB')
807
+ self.assertEqual(m.rate.shape, (2, 2))
808
+ np.testing.assert_allclose(m.rate, logm(m.trans).real, rtol=0.05)
809
+
810
+ def test_propagate(self):
811
+ m = self.msm
812
+ m.do_rate()
813
+ for prop in (m.propagateT, m.propagateK):
814
+ t, pop = prop(init=[0], time=[1, 10, 1000])
815
+ pop = np.array(pop)
816
+ self.assertEqual(pop.shape, (3, 2))
817
+ np.testing.assert_allclose(pop.sum(axis=1), 1.)
818
+ np.testing.assert_allclose(pop[-1], m.peqT, atol=1e-6)
819
+ # one step of T from state 0 is the first column of T
820
+ _, pop = m.propagateT(init=[0], time=[1])
821
+ np.testing.assert_allclose(pop[0], m.trans[:, 0])
822
+
823
+ def test_acf_mode(self):
824
+ acf = self.msm.acf_mode()
825
+ self.assertEqual(list(acf.keys()), [1])
826
+ lag = np.arange(1, 6)
827
+ np.testing.assert_allclose(acf[1][:5], self.lam ** lag, atol=0.03)
828
+
829
+
830
+ class TestBootsSym(unittest.TestCase):
831
+ """ The bootstrap uses the same estimator as the model: counts
832
+ are symmetrized when the model is. """
833
+
834
+ @classmethod
835
+ def setUpClass(cls):
836
+ # cyclic, non-reversible chain
837
+ T = np.array([[.8, 0., .2], [.2, .8, 0.], [0., .2, .8]])
838
+ trajs = [traj.TimeSeries(distraj=_markov_chain(T, 5000, s), dt=1.)
839
+ for s in (1, 2)]
840
+ cls.msm = msm.MSM(trajs, lagt=1, sym=True)
841
+ cls.msm.calc_count()
842
+ cls.msm.calc_trans()
843
+ cls.msm.calc_evals()
844
+
845
+ def test_worker(self):
846
+ m = self.msm
847
+ ftmp, _ = msm_lib.traj_split(data=m.data, lagt=1)
848
+ ncount = np.sum(m.count)/2.
849
+ try:
850
+ imag = {}
851
+ for sym in (False, True):
852
+ trans, keep = msm_lib.do_boots_worker(
853
+ [ftmp, m.keys, 1, ncount, True, 2, 0, sym])
854
+ np.testing.assert_allclose(trans.sum(axis=0), 1.)
855
+ imag[sym] = np.max(np.abs(np.linalg.eigvals(trans).imag))
856
+ finally:
857
+ os.remove(ftmp)
858
+ self.assertGreater(imag[False], 0.05)
859
+ self.assertLess(imag[True], 1e-10)
860
+
861
+ def test_boots(self):
862
+ """ Errors of the symmetrized model: the bootstrap average is
863
+ close to the (real) relaxation times of the model itself. """
864
+ m = self.msm
865
+ m.boots(seed=0)
866
+ self.assertEqual(m.tau_nboots, [20, 20])
867
+ np.testing.assert_allclose(m.tau_ave, m.tauT, rtol=0.1)
868
+ self.assertTrue(np.all(np.array(m.tau_std) > 0.))
869
+
870
+
871
+ class TestMSMCommittor(unittest.TestCase):
872
+ """ Committors and sensitivity on a symmetric linear chain 0-1-2. """
873
+
874
+ @classmethod
875
+ def setUpClass(cls):
876
+ T = np.array([[.9, .05, 0.], [.1, .9, .1], [0., .05, .9]])
877
+ cls.trajs = [traj.TimeSeries(distraj=_markov_chain(T, 20000, 3),
878
+ dt=1.)]
879
+ cls.msm = msm.MSM(cls.trajs, lagt=1)
880
+ cls.msm.calc_count()
881
+ cls.msm.do_trans()
882
+ cls.msm.do_rate()
883
+
884
+ def test_do_pfold(self):
885
+ m = self.msm
886
+ m.do_pfold(FF=[2], UU=[0])
887
+ self.assertAlmostEqual(m.pfold[0], 0.)
888
+ self.assertAlmostEqual(m.pfold[2], 1.)
889
+ self.assertAlmostEqual(m.pfold[1], 0.5, delta=0.05)
890
+ self.assertGreater(m.kf, 0.)
891
+ self.assertGreater(m.sum_flux, 0.)
892
+
893
+ def test_do_pfold_scalar_states(self):
894
+ m = self.msm
895
+ m.do_pfold(FF=[2], UU=[0])
896
+ pf_list = m.pfold.copy()
897
+ m.do_pfold(FF=2, UU=0)
898
+ np.testing.assert_allclose(m.pfold, pf_list)
899
+
900
+ def test_sensitivity(self):
901
+ m = self.msm
902
+ m.sensitivity(FF=[2], UU=[0])
903
+ self.assertEqual(len(m.dJ), 3)
904
+ self.assertEqual(len(m.d_lnkf), 3)
905
+ self.assertTrue(np.all(np.isfinite(m.d_lnkf)))
906
+
907
+
908
+ class TestSuperMSMValidation(unittest.TestCase):
909
+ """ Implied timescales and Chapman-Kolmogorov test. """
910
+
911
+ @classmethod
912
+ def setUpClass(cls):
913
+ T = np.array([[.95, .10], [.05, .90]])
914
+ cls.trajs = [traj.TimeSeries(distraj=_markov_chain(T, 20000, s),
915
+ dt=1.) for s in (1, 2)]
916
+
917
+ def test_convergence_test(self):
918
+ sm = msm.SuperMSM(self.trajs)
919
+ sm.convergence_test(time=[1, 2, 5])
920
+ self.assertEqual(sorted(sm.msms.keys()), [1, 2, 5])
921
+ tau = [sm.msms[lt].tauT[0] for lt in (1, 2, 5)]
922
+ # Markovian data: implied timescale is lag-independent
923
+ np.testing.assert_allclose(tau, -1. / np.log(0.85), rtol=0.1)
924
+
925
+ def test_convergence_test_error(self):
926
+ sm = msm.SuperMSM(self.trajs)
927
+ sm.convergence_test(time=[1, 2], error=True, seed=0, nboots=5)
928
+ for lt in (1, 2):
929
+ m = sm.msms[lt]
930
+ self.assertEqual(m.tau_nboots, [5])
931
+ self.assertTrue(np.all(np.isfinite(m.tau_std)))
932
+
933
+ def test_ck_test(self):
934
+ sm = msm.SuperMSM(self.trajs)
935
+ pMSM, pMD, epMD = sm.ck_test(time=[1, 2, 5], init=[0])
936
+ self.assertEqual(len(pMSM), 3)
937
+ self.assertEqual(pMD.shape, (3, 2))
938
+ self.assertEqual(epMD.shape, (3,))
939
+ np.testing.assert_array_equal(pMD[:, 0], [1, 2, 5])
940
+ # lag-1 MSM prediction at t = 2, 5 agrees with direct counts
941
+ t1, p1 = pMSM[0]
942
+ for lt, pmd, err in zip(pMD[1:, 0], pMD[1:, 1], epMD[1:]):
943
+ k = list(t1).index(lt)
944
+ self.assertLess(abs(p1[k] - pmd), 5 * err + 0.01)
945
+ # at the lag time itself the prediction is the MD value
946
+ self.assertAlmostEqual(p1[0], pMD[0, 1])
947
+
948
+
949
+ class TestFEWSM(unittest.TestCase):
950
+ """ Few-state clustering of a 3-state chain with a slow 0,1 | 2 split. """
951
+
952
+ @classmethod
953
+ def setUpClass(cls):
954
+ from mastermsm.fewsm import fewsm
955
+ cls.fewsm = fewsm
956
+ T = np.array([[.90, .10, 0.], [.10, .89, .01], [0., .01, .99]])
957
+ cls.trajs = [traj.TimeSeries(distraj=_markov_chain(T, n, s), dt=1.)
958
+ for n, s in ((20000, 4), (10000, 5))]
959
+ cls.msm = msm.MSM(cls.trajs, lagt=1)
960
+ cls.msm.calc_count()
961
+ cls.msm.do_trans()
962
+
963
+ def test_eigen_group(self):
964
+ for method in ('robust', 'sign'):
965
+ f = self.fewsm.FEWSM(self.msm, N=2, method=method)
966
+ groups = sorted(sorted(self.msm.keep_keys[i] for i in v)
967
+ for v in f.macros.values())
968
+ self.assertEqual(groups, [[0, 1], [2]])
969
+
970
+ def test_map_trajectory_states(self):
971
+ f = self.fewsm.FEWSM(self.msm, N=2)
972
+ f.map_trajectory()
973
+ self.assertEqual(len(f.mappedtraj), len(self.trajs))
974
+ kk = self.msm.keep_keys
975
+ macro_of = {kk[i]: k for k, v in f.macros.items() for i in v}
976
+ mt = f.mappedtraj[0].distraj
977
+ ref = self.trajs[0].distraj
978
+ self.assertEqual(mt[:len(ref)], [macro_of[s] for s in ref])
979
+
980
+ def test_map_trajectory_lengths(self):
981
+ f = self.fewsm.FEWSM(self.msm, N=2)
982
+ f.map_trajectory()
983
+ self.assertEqual([len(x.distraj) for x in f.mappedtraj],
984
+ [len(x.distraj) for x in self.trajs])
985
+
986
+ def test_eigen_group_without_evecs(self):
987
+ m = msm.MSM(self.trajs, lagt=1)
988
+ m.count = self.msm.count
989
+ m.calc_trans()
990
+ f = self.fewsm.FEWSM(m, N=2)
991
+ self.assertEqual(len(f.macros), 2)
992
+
993
+ def _fewsm_with_outsider(self, distraj):
994
+ """ FEWSM on the 3-state model, mapping a trajectory that
995
+ visits state 9, which is outside the connected set. """
996
+ f = self.fewsm.FEWSM(self.msm, N=2)
997
+ f.parent = copy.copy(self.msm)
998
+ f.parent.data = [traj.TimeSeries(distraj=distraj, dt=1.)]
999
+ kk = self.msm.keep_keys
1000
+ macro_of = {kk[i]: k for k, v in f.macros.items() for i in v}
1001
+ return f, macro_of
1002
+
1003
+ def test_map_trajectory_disconnected(self):
1004
+ # leading outsiders removed; middle and trailing ones
1005
+ # inherit the macrostate of the previous frame
1006
+ f, m = self._fewsm_with_outsider([9, 9, 0, 9, 2, 9, 9, 1, 9])
1007
+ f.map_trajectory()
1008
+ self.assertEqual(f.mappedtraj[0].distraj,
1009
+ [m[0], m[0], m[2], m[2], m[2], m[1], m[1]])
1010
+
1011
+ def test_map_trajectory_never_connected(self):
1012
+ f, _ = self._fewsm_with_outsider([9, 9, 9])
1013
+ with self.assertWarns(UserWarning):
1014
+ f.map_trajectory()
1015
+ self.assertEqual(f.mappedtraj, [])
1016
+
1017
+ def test_eigen_group_without_trans(self):
1018
+ # FEWSM builds the transition matrix itself if missing
1019
+ m = msm.MSM(self.trajs, lagt=1)
1020
+ m.count = self.msm.count
1021
+ f = self.fewsm.FEWSM(m, N=2)
1022
+ self.assertTrue(hasattr(m, 'trans'))
1023
+ groups = sorted(sorted(m.keep_keys[i] for i in v)
1024
+ for v in f.macros.values())
1025
+ self.assertEqual(groups, [[0, 1], [2]])
1026
+
1027
+ def test_metastability(self):
1028
+ f = self.fewsm.FEWSM(self.msm, N=2)
1029
+ Q = f.metastability()
1030
+ self.assertTrue(0. < Q <= 2.)
1031
+ # lumped counts conserve the total and match the mapped trajectory
1032
+ self.assertEqual(f.count.sum(), self.msm.count.sum())
1033
+ f.map_trajectory()
1034
+ m = msm.MSM(f.mappedtraj, keys=[0, 1], lagt=1)
1035
+ m.calc_count()
1036
+ np.testing.assert_array_equal(f.count, m.count)
1037
+ np.testing.assert_allclose(Q, np.trace(f.trans))
1038
+ # 0,1 | 2 split: Q = T(01->01) + T(2->2), close to 0.995 + 0.99
1039
+ self.assertAlmostEqual(Q, 1.985, delta=0.01)