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,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)
|