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
mastermsm/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.1"
|
|
File without changes
|
mastermsm/fewsm/fewsm.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is part of the MasterMSM package.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import copy
|
|
7
|
+
import warnings
|
|
8
|
+
#import random
|
|
9
|
+
from ..msm import msm
|
|
10
|
+
from ..trajectory import traj, traj_lib
|
|
11
|
+
#import msm_lib
|
|
12
|
+
from ..fewsm import fewsm_lib
|
|
13
|
+
|
|
14
|
+
class FEWSM(msm.MSM):
|
|
15
|
+
"""
|
|
16
|
+
A class for doing clustering of MSMs into few-state models
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
keys : dict
|
|
21
|
+
A dictionary containing the clusters formed.
|
|
22
|
+
parent : class
|
|
23
|
+
Instance of the MSM class that we aim to cluster.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, parent, N=2, method="robust"):
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
parent : class
|
|
32
|
+
Instance of the MSM class that we aim to cluster.
|
|
33
|
+
N : int
|
|
34
|
+
The desired number of clusters.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
self.parent = parent
|
|
38
|
+
self.N = N
|
|
39
|
+
self.keys = list(range(N))
|
|
40
|
+
self.lagt = parent.lagt
|
|
41
|
+
self.sliding = parent.sliding
|
|
42
|
+
self.sym = parent.sym
|
|
43
|
+
self.macros = self.eigen_group(N=self.N, method=method)
|
|
44
|
+
|
|
45
|
+
def eigen_group(self, N=2, method="robust"):
|
|
46
|
+
""" Splits microstates into macrostates
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
N : int
|
|
51
|
+
Number of clusters.
|
|
52
|
+
method : str
|
|
53
|
+
The method used for clustering.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
macros : dict
|
|
58
|
+
A dictionary with the membership to macrostates.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# generate eigenvectors in case the MSM does not have them
|
|
63
|
+
if not hasattr(self.parent, 'lvecsT'):
|
|
64
|
+
if not hasattr(self.parent, 'trans'):
|
|
65
|
+
self.parent.calc_trans()
|
|
66
|
+
self.parent.calc_evals(evecs=True)
|
|
67
|
+
lvecs = self.parent.lvecsT
|
|
68
|
+
|
|
69
|
+
# split in desired number of macrostates
|
|
70
|
+
macros = {}
|
|
71
|
+
keep_states = self.parent.keep_states
|
|
72
|
+
macros[0] = list(range(len(keep_states)))
|
|
73
|
+
for n in range(1, N):
|
|
74
|
+
if method == "robust":
|
|
75
|
+
macro_new, _ = fewsm_lib.split_sigma(macros, lvecs[:,n])
|
|
76
|
+
elif method == "sign":
|
|
77
|
+
macro_new, _ = fewsm_lib.split_sign(macros, lvecs[:,n])
|
|
78
|
+
macros = copy.deepcopy(macro_new)
|
|
79
|
+
print ("\n Initial membership of microstates to macrostates:")
|
|
80
|
+
if len(self.parent.keep_keys) < 100:
|
|
81
|
+
for k,v in macros.items():
|
|
82
|
+
print (k, [self.parent.keep_keys[x] for x in v])
|
|
83
|
+
else:
|
|
84
|
+
for k,v in macros.items():
|
|
85
|
+
print (k,":", len(v))
|
|
86
|
+
return macros
|
|
87
|
+
|
|
88
|
+
def map_trajectory(self):
|
|
89
|
+
""" Maps trajectory onto the PCCA clusters
|
|
90
|
+
|
|
91
|
+
Frames in microstates outside the connected set (not in
|
|
92
|
+
``keep_keys``) are removed if they are at the start of a
|
|
93
|
+
trajectory, and otherwise inherit the macrostate of the
|
|
94
|
+
previous frame. A trajectory that never visits the connected
|
|
95
|
+
set is left out, with a warning.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
mappedtraj : list
|
|
100
|
+
One TimeSeries per parent trajectory, stored in
|
|
101
|
+
``self.mappedtraj``.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
print ("\n Mapping trajectory onto macrostates...")
|
|
105
|
+
mappedtraj = []
|
|
106
|
+
keep_keys = self.parent.keep_keys
|
|
107
|
+
# macrostate of each microstate key
|
|
108
|
+
macro_of = {keep_keys[i]: k for k, v in self.macros.items() \
|
|
109
|
+
for i in v}
|
|
110
|
+
for n, data in enumerate(self.parent.data):
|
|
111
|
+
# one mapped trajectory per parent trajectory; -1 marks
|
|
112
|
+
# frames outside the connected set
|
|
113
|
+
mt_states = [macro_of.get(s, -1) for s in data.distraj]
|
|
114
|
+
start = next((i for i, s in enumerate(mt_states) \
|
|
115
|
+
if s >= 0), None)
|
|
116
|
+
if start is None:
|
|
117
|
+
warnings.warn("trajectory %i never visits the "
|
|
118
|
+
"connected set; left out of mappedtraj"%n)
|
|
119
|
+
continue
|
|
120
|
+
mt_states = traj_lib._filter_states(mt_states[start:])
|
|
121
|
+
mt = traj.TimeSeries(distraj=mt_states, dt=data.dt)
|
|
122
|
+
mappedtraj.append(mt)
|
|
123
|
+
self.mappedtraj = mappedtraj
|
|
124
|
+
#super().__init__(mappedtraj, keys=range(self.N), lagt=self.parent.lagt)
|
|
125
|
+
|
|
126
|
+
def calc_count(self, nproc=None, sym=False):
|
|
127
|
+
""" Calculates the macrostate count matrix
|
|
128
|
+
|
|
129
|
+
Lumps the count matrix of the parent MSM according to the
|
|
130
|
+
current membership in self.macros. This is what counting
|
|
131
|
+
transitions on the mapped trajectories gives at the lag time
|
|
132
|
+
of the parent. The arguments are unused and kept only for
|
|
133
|
+
compatibility with MSM.calc_count.
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
self.count = fewsm_lib.map_micro2macro(self.parent.count, \
|
|
137
|
+
self.macros, self.parent.keep_states)
|
|
138
|
+
|
|
139
|
+
def metastability(self):
|
|
140
|
+
""" Calculate metastability according to the definition
|
|
141
|
+
in Chodera et al, J Chem Phys, (2007)
|
|
142
|
+
|
|
143
|
+
The macrostate transition matrix is rebuilt from the current
|
|
144
|
+
membership in self.macros on every call.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
float
|
|
149
|
+
Metastability
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
self.calc_count()
|
|
153
|
+
self.calc_trans()
|
|
154
|
+
return fewsm_lib.metastability(self.trans)
|
|
155
|
+
|
|
156
|
+
# def optim(self, nsteps=1, nwrite=None, fout="mc.dat"):
|
|
157
|
+
# """ MC optimization using the metastability Q as energy.
|
|
158
|
+
#
|
|
159
|
+
# Parameters
|
|
160
|
+
# ----------
|
|
161
|
+
# nsteps : int
|
|
162
|
+
# Number of steps per round of MC and per microstate.
|
|
163
|
+
# nwrite : int
|
|
164
|
+
# Frequency of writing MC output.
|
|
165
|
+
# fout : string
|
|
166
|
+
# File for output of MC progress.
|
|
167
|
+
#
|
|
168
|
+
# Returns
|
|
169
|
+
# -------
|
|
170
|
+
# macro_opt : dict
|
|
171
|
+
# Dictionary with the membership to macrostates.
|
|
172
|
+
#
|
|
173
|
+
# """
|
|
174
|
+
# print "\n Optimizing the lumped MSM\n"
|
|
175
|
+
# out = open(fout, "w")
|
|
176
|
+
# out.write("# iter q \n")
|
|
177
|
+
#
|
|
178
|
+
# nmac = self.N
|
|
179
|
+
# nmic = len(self.parent.keep_keys)
|
|
180
|
+
# mcsteps = len(self.count)*nsteps*nmic # mc steps per block
|
|
181
|
+
# mcsteps_max = nmic*20000 # maximum number of mc steps
|
|
182
|
+
# print self.count
|
|
183
|
+
# print self.trans
|
|
184
|
+
# q = self.metastability()
|
|
185
|
+
# print " initial:", q
|
|
186
|
+
# q_opt = q
|
|
187
|
+
#
|
|
188
|
+
# macro = copy.deepcopy(self.macros)
|
|
189
|
+
# cont = True
|
|
190
|
+
# nmc = 0 # number of mc blocks
|
|
191
|
+
# reject = 0
|
|
192
|
+
# while cont:
|
|
193
|
+
# imc = 0
|
|
194
|
+
# out.write ("%6i %12.10f %10.6e\n"%(imc + nmc*mcsteps,q,1))
|
|
195
|
+
# while imc < mcsteps:
|
|
196
|
+
# # try ramdom insertion of a microstate in a macrostate
|
|
197
|
+
# imac = 0
|
|
198
|
+
# jmac = 0
|
|
199
|
+
# while imc < mcsteps:
|
|
200
|
+
# imc +=1
|
|
201
|
+
# while True:
|
|
202
|
+
# # choose microstate to move around
|
|
203
|
+
# imic = random.choice(range(nmic))
|
|
204
|
+
# imac = int([x for x in range(nmac) if imic in macro[x]][0])
|
|
205
|
+
# if len(macro[imac]) > 1:
|
|
206
|
+
# # choose destination macrostate
|
|
207
|
+
# jmac = random.choice([x for x in range(nmac) if x is not imac])
|
|
208
|
+
# break
|
|
209
|
+
# # move microstate from i to j
|
|
210
|
+
# macro_new = copy.deepcopy(macro)
|
|
211
|
+
# macro_new[imac].remove(imic)
|
|
212
|
+
# macro_new[jmac].append(imic)
|
|
213
|
+
# # calculate transition count matrix for new mapping
|
|
214
|
+
# count_mac_new = fewsm_lib.map_micro2macro(self.parent.count, macro_new, self.parent.keep_states)
|
|
215
|
+
# Tmacro_new = msm_lib.calc_trans(nmac, range(nmac), count_mac_new)
|
|
216
|
+
# # calculate metastability
|
|
217
|
+
# q_new = fewsm_lib.metastability(Tmacro_new)
|
|
218
|
+
# delta = fewsm_lib.beta(imc,mcsteps)*(q - q_new) # calculate increment (Q is a -Energy)
|
|
219
|
+
# if fewsm_lib.metropolis(delta):
|
|
220
|
+
# #print "ACCEPT"
|
|
221
|
+
# macro = copy.deepcopy(macro_new)
|
|
222
|
+
# count_mac = count_mac_new
|
|
223
|
+
# q = q_new
|
|
224
|
+
# if q > q_opt:
|
|
225
|
+
# q_opt = q
|
|
226
|
+
# macro_opt = copy.deepcopy(macro)
|
|
227
|
+
# Tmacro_opt = Tmacro_new
|
|
228
|
+
# self.macro = copy.deepcopy(macro_opt)
|
|
229
|
+
# else:
|
|
230
|
+
# reject+=1
|
|
231
|
+
# #print " REJECT"
|
|
232
|
+
#
|
|
233
|
+
# out.write ("%6i %12.10e %10.6e\n"%(imc + nmc*mcsteps,q,1./fewsm_lib.beta(imc,mcsteps)))
|
|
234
|
+
# imc +=1
|
|
235
|
+
# cont = False
|
|
236
|
+
# print " final :", q
|
|
237
|
+
# print " best :", q_opt
|
|
238
|
+
# print " acceptance:",1.-float(reject)/mcsteps
|
|
239
|
+
#
|
|
240
|
+
# self.map_trajectory()
|
|
241
|
+
# self.do_count()
|
|
242
|
+
# self.do_trans()
|
|
243
|
+
#
|
|
244
|
+
# def write_mapping(self):
|
|
245
|
+
# """
|
|
246
|
+
# Prints files with the mapping between states and clusters
|
|
247
|
+
#
|
|
248
|
+
# """
|
|
249
|
+
# for mtraj in self.mappedtraj:
|
|
250
|
+
# try:
|
|
251
|
+
# idf = mtraj.filename.rfind(".dat")
|
|
252
|
+
# filename = mtraj.filename[:idf] + "_mapped_pcca%g.dat"%self.N
|
|
253
|
+
# except ValueError:
|
|
254
|
+
# filename = mtraj.filename + "_mapped_pcca%g.dat"%self.N
|
|
255
|
+
# print " ...writing mapped trajectory at %s"%filename
|
|
256
|
+
# fout = open(filename, "w")
|
|
257
|
+
# micro_data = [x for x in self.parent.data if x.filename == mtraj.filename][0]
|
|
258
|
+
# for x in zip(micro_data.time, micro_data.states, self.data[0].states):
|
|
259
|
+
# fout.write("%10.3f %s %8i\n"%(x[0], x[1], x[2]))
|
|
260
|
+
# fout.close()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is part of the MasterMSM package.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
import copy, itertools
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
def map_micro2macro(cmic, mac, states):
|
|
9
|
+
""" maps microstates into macrostates """
|
|
10
|
+
m = len(mac)
|
|
11
|
+
cmac = np.zeros((m, m), int)
|
|
12
|
+
for i in range(m):
|
|
13
|
+
for j in range(m):
|
|
14
|
+
cmac[j,i] = sum(cmic[states[x],states[y]] for (x,y) in \
|
|
15
|
+
itertools.product(mac[j],mac[i]))
|
|
16
|
+
return cmac
|
|
17
|
+
|
|
18
|
+
def test_sign(v):
|
|
19
|
+
"""check whether positive and negative signs are present in vector"""
|
|
20
|
+
test = False
|
|
21
|
+
if any(v > 0.) and any(v<0):
|
|
22
|
+
test = True
|
|
23
|
+
return test
|
|
24
|
+
|
|
25
|
+
def split_sign(macro, lvec):
|
|
26
|
+
""" split based on sign structure """
|
|
27
|
+
# calculate spread in eigenvector
|
|
28
|
+
nt = len(macro)
|
|
29
|
+
spread = []
|
|
30
|
+
vals = lvec
|
|
31
|
+
for _, v in macro.items():
|
|
32
|
+
# check that there are positive and negative values in evec
|
|
33
|
+
if test_sign(vals[v]):
|
|
34
|
+
#spread.append(np.sum(vals**2))
|
|
35
|
+
spread.append(np.mean(vals[v]**2))
|
|
36
|
+
else:
|
|
37
|
+
spread.append(0.)
|
|
38
|
+
isplit = np.argsort(-np.array(spread))[0]
|
|
39
|
+
# print " macrostate to split: %i"%isplit,np.array(spread)
|
|
40
|
+
# split
|
|
41
|
+
lvec_split = lvec[macro[isplit]]
|
|
42
|
+
# print lvec_split
|
|
43
|
+
elems = []
|
|
44
|
+
for i in filter(lambda x: lvec_split[x] < 0.,\
|
|
45
|
+
range(len(macro[isplit]))):
|
|
46
|
+
elems.append(macro[isplit][i])
|
|
47
|
+
macro_new = copy.deepcopy(macro)
|
|
48
|
+
macro_new[nt] = elems
|
|
49
|
+
# update old macrostate
|
|
50
|
+
for i in elems:
|
|
51
|
+
macro_new[isplit].remove(i)
|
|
52
|
+
return macro_new, vals
|
|
53
|
+
|
|
54
|
+
def split_sigma(macro, lvec):
|
|
55
|
+
""" split based on distribution """
|
|
56
|
+
nt = len(macro)
|
|
57
|
+
|
|
58
|
+
spread = []
|
|
59
|
+
for i in macro.keys():
|
|
60
|
+
spread.append(np.std(lvec[macro[i]]))
|
|
61
|
+
# split macrostates with maximum spread
|
|
62
|
+
isplit = np.argsort(-np.array(spread))[0]
|
|
63
|
+
#print " macrostate to split: %i"%isplit,spread[isplit]
|
|
64
|
+
# split based on distribution
|
|
65
|
+
elems = []
|
|
66
|
+
keep = []
|
|
67
|
+
val_max = np.max(lvec[macro[isplit]])
|
|
68
|
+
val_min = np.min(lvec[macro[isplit]])
|
|
69
|
+
vals = (lvec[macro[isplit]] - val_min)/(val_max - val_min)
|
|
70
|
+
for i in filter(lambda x: vals[x] < 0.5,range(len(macro[isplit]))):
|
|
71
|
+
elems.append(macro[isplit][i])
|
|
72
|
+
for i in filter(lambda x: vals[x] >= 0.5,range(len(macro[isplit]))):
|
|
73
|
+
keep.append(macro[isplit][i])
|
|
74
|
+
macro_new = copy.deepcopy(macro)
|
|
75
|
+
macro_new[nt] = elems
|
|
76
|
+
#print macro_new
|
|
77
|
+
# update old macrostate
|
|
78
|
+
for i in elems:
|
|
79
|
+
macro_new[isplit].remove(i)
|
|
80
|
+
macro = copy.deepcopy(macro_new)
|
|
81
|
+
return macro, vals
|
|
82
|
+
|
|
83
|
+
def metastability(T):
|
|
84
|
+
return np.sum(np.diag(T))
|
|
85
|
+
|
|
86
|
+
def beta(imc,mcsteps):
|
|
87
|
+
# inverse temperature for MCSA
|
|
88
|
+
x = imc - 1
|
|
89
|
+
a = 4./mcsteps
|
|
90
|
+
temp = (1 + (np.exp(-a*x)-1.)/(1.- np.exp(-a*mcsteps))) # MCSA temperature
|
|
91
|
+
try:
|
|
92
|
+
beta = 1./temp
|
|
93
|
+
except ZeroDivisionError:
|
|
94
|
+
beta = 1e20
|
|
95
|
+
return beta
|
|
96
|
+
|
|
97
|
+
def metropolis(delta):
|
|
98
|
+
if delta < 0:
|
|
99
|
+
return True
|
|
100
|
+
else:
|
|
101
|
+
accept = False
|
|
102
|
+
p = min(1.0,np.exp(-delta))
|
|
103
|
+
rand = np.random.random()
|
|
104
|
+
if (rand < p):
|
|
105
|
+
accept = True
|
|
106
|
+
return accept
|
|
File without changes
|