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 ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2.0.1"
File without changes
@@ -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