bfee2 3.1.1.post1__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.
- BFEE2/__init__.py +0 -0
- BFEE2/commonTools/__init__.py +0 -0
- BFEE2/commonTools/commonSlots.py +48 -0
- BFEE2/commonTools/fileParser.py +327 -0
- BFEE2/commonTools/ploter.py +218 -0
- BFEE2/doc/Doc.pdf +0 -0
- BFEE2/doc/__init__.py +1 -0
- BFEE2/gui.py +2785 -0
- BFEE2/inputGenerator.py +2949 -0
- BFEE2/postTreatment.py +676 -0
- BFEE2/templates_gromacs/000.colvars.template +37 -0
- BFEE2/templates_gromacs/000.generate_tpr_sh.template +31 -0
- BFEE2/templates_gromacs/000.mdp.template +74 -0
- BFEE2/templates_gromacs/001.colvars.template +76 -0
- BFEE2/templates_gromacs/001.generate_tpr_sh.template +31 -0
- BFEE2/templates_gromacs/001.mdp.template +73 -0
- BFEE2/templates_gromacs/001.readme.template +1 -0
- BFEE2/templates_gromacs/002.colvars.template +101 -0
- BFEE2/templates_gromacs/002.generate_tpr_sh.template +31 -0
- BFEE2/templates_gromacs/002.mdp.template +73 -0
- BFEE2/templates_gromacs/003.colvars.template +125 -0
- BFEE2/templates_gromacs/003.generate_tpr_sh.template +36 -0
- BFEE2/templates_gromacs/003.mdp.template +73 -0
- BFEE2/templates_gromacs/004.colvars.template +148 -0
- BFEE2/templates_gromacs/004.generate_tpr_sh.template +37 -0
- BFEE2/templates_gromacs/004.mdp.template +74 -0
- BFEE2/templates_gromacs/005.colvars.template +170 -0
- BFEE2/templates_gromacs/005.generate_tpr_sh.template +38 -0
- BFEE2/templates_gromacs/005.mdp.template +74 -0
- BFEE2/templates_gromacs/006.colvars.template +192 -0
- BFEE2/templates_gromacs/006.generate_tpr_sh.template +39 -0
- BFEE2/templates_gromacs/006.mdp.template +74 -0
- BFEE2/templates_gromacs/007.colvars.template +210 -0
- BFEE2/templates_gromacs/007.generate_tpr_sh.template +40 -0
- BFEE2/templates_gromacs/007.mdp.template +73 -0
- BFEE2/templates_gromacs/007_eq.colvars.template +169 -0
- BFEE2/templates_gromacs/007_eq.generate_tpr_sh.template +64 -0
- BFEE2/templates_gromacs/007_min.mdp.template +62 -0
- BFEE2/templates_gromacs/008.colvars.template +42 -0
- BFEE2/templates_gromacs/008.generate_tpr_sh.template +31 -0
- BFEE2/templates_gromacs/008.mdp.template +74 -0
- BFEE2/templates_gromacs/008_eq.colvars.template +14 -0
- BFEE2/templates_gromacs/008_eq.generate_tpr_sh.template +31 -0
- BFEE2/templates_gromacs/BFEEGromacs.py +1268 -0
- BFEE2/templates_gromacs/__init__.py +0 -0
- BFEE2/templates_gromacs/find_min_max.awk +27 -0
- BFEE2/templates_namd/__init__.py +0 -0
- BFEE2/templates_namd/configTemplate.py +1152 -0
- BFEE2/templates_namd/fep.tcl +299 -0
- BFEE2/templates_namd/fep_lddm.tcl +312 -0
- BFEE2/templates_namd/scriptTemplate.py +304 -0
- BFEE2/templates_namd/solvate.tcl +9 -0
- BFEE2/templates_namd/solvate_mem.tcl +9 -0
- BFEE2/templates_namd/updateCenters.py +312 -0
- BFEE2/templates_readme/Readme_Gromacs_Geometrical.txt +25 -0
- BFEE2/templates_readme/Readme_NAMD_Alchemical.txt +20 -0
- BFEE2/templates_readme/Readme_NAMD_Geometrical.txt +34 -0
- BFEE2/templates_readme/__init__.py +1 -0
- BFEE2/templates_readme/rags.py +187 -0
- BFEE2/third_party/__init__.py +0 -0
- BFEE2/third_party/py_bar.py +585 -0
- BFEE2/version.py +4 -0
- bfee2-3.1.1.post1.data/scripts/BFEE2Gui.py +19 -0
- bfee2-3.1.1.post1.dist-info/METADATA +86 -0
- bfee2-3.1.1.post1.dist-info/RECORD +68 -0
- bfee2-3.1.1.post1.dist-info/WHEEL +5 -0
- bfee2-3.1.1.post1.dist-info/licenses/LICENSE +677 -0
- bfee2-3.1.1.post1.dist-info/top_level.txt +1 -0
BFEE2/postTreatment.py
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
# post-treatment of BFEE
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from BFEE2.third_party import py_bar
|
|
8
|
+
|
|
9
|
+
# Boltazann constant for NAMD unit convention
|
|
10
|
+
BOLTZMANN = 0.0019872041
|
|
11
|
+
BOLTZMANN_GMX = 0.0019872041 * 4.184
|
|
12
|
+
|
|
13
|
+
# standard concentration
|
|
14
|
+
CSTAR = 1661
|
|
15
|
+
CSTAR_GMX = 1.661
|
|
16
|
+
|
|
17
|
+
# an runtime error
|
|
18
|
+
# r* > r(pmf)
|
|
19
|
+
class RStarTooLargeError(RuntimeError):
|
|
20
|
+
def __init__(self, arg):
|
|
21
|
+
self.args = arg
|
|
22
|
+
|
|
23
|
+
class postTreatment:
|
|
24
|
+
"""the post-treatment of BFEE outputs
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, temperature, unit, jobType='geometric'):
|
|
28
|
+
"""do post treatment, internally, all the unit should be converted into
|
|
29
|
+
the NAMD/Colvars unit
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
temperature (float): temperature of the simulation
|
|
33
|
+
unit (str): unit convention used by MD engine, 'namd' or 'gromacs'
|
|
34
|
+
jobType (str): 'geometric' or 'alchemical'. Actually this arg is not used yet. Default to geometric.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if unit == 'namd':
|
|
38
|
+
self.BOLTZMANN = BOLTZMANN
|
|
39
|
+
self.CSTAR = CSTAR
|
|
40
|
+
elif unit == 'gromacs':
|
|
41
|
+
self.BOLTZMANN = BOLTZMANN_GMX
|
|
42
|
+
self.CSTAR = CSTAR_GMX
|
|
43
|
+
|
|
44
|
+
self.unit = unit
|
|
45
|
+
self.beta = 1 / (self.BOLTZMANN * temperature)
|
|
46
|
+
self.temperature = float(temperature)
|
|
47
|
+
|
|
48
|
+
def _readPMF(self, filePath):
|
|
49
|
+
"""read a 1D PMF file
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
filePath (str): the path of the PMF file
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
np.array (float, 2*N): ((x0,x1,x2, ...), (y0, y1, y2, ...))
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
data = np.loadtxt(filePath)
|
|
59
|
+
x = data[:,0]
|
|
60
|
+
y = data[:,1]
|
|
61
|
+
|
|
62
|
+
return np.array((x, y))
|
|
63
|
+
|
|
64
|
+
def _geometricRestraintContribution(self, pmf, forceConstant, rmsd=False, unbound=False):
|
|
65
|
+
"""calculate the contribution of RMSD and angle restraints
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
pmf (np.array, float, 2*N): ((x0,x1,x2, ...), (y0, y1, y2, ...))
|
|
69
|
+
forceConstant (float): the force constant of the restraint
|
|
70
|
+
rmsd (bool): whether the contribution of RMSD is being calculated. Defaults to False.
|
|
71
|
+
unbound (bool, optional): whether unbound-state contribution is being calculated. Defaults to False.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
float: contribution of the geometric restraint
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
width = pmf[0][1] - pmf[0][0]
|
|
78
|
+
|
|
79
|
+
if rmsd:
|
|
80
|
+
# for RMSD, the restraintCenter is zero
|
|
81
|
+
restraintCenter = 0
|
|
82
|
+
else:
|
|
83
|
+
# the minimum of pmf
|
|
84
|
+
restraintCenter = pmf[0][np.argmin(pmf[1])]
|
|
85
|
+
|
|
86
|
+
# integration
|
|
87
|
+
numerator = 0
|
|
88
|
+
denominator = 0
|
|
89
|
+
for x, y in zip(pmf[0], pmf[1]):
|
|
90
|
+
numerator += math.exp(-self.beta * y)
|
|
91
|
+
denominator += math.exp((-self.beta) * (y + 0.5 * forceConstant * ((x - restraintCenter)**2)))
|
|
92
|
+
|
|
93
|
+
contribution = math.log(numerator / denominator) / self.beta
|
|
94
|
+
|
|
95
|
+
if unbound:
|
|
96
|
+
return contribution
|
|
97
|
+
else:
|
|
98
|
+
return -contribution
|
|
99
|
+
|
|
100
|
+
def _geometricRestraintContributionBulk(
|
|
101
|
+
self, theta, forceConstantTheta, forceConstantPhi, forceConstantPsi
|
|
102
|
+
):
|
|
103
|
+
"""contribution of rotational restraints in the unbounded state
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
theta (float): restraining center of the theta angle
|
|
107
|
+
forceConstantTheta (float): restraining force constant for Theta
|
|
108
|
+
forceConstantPhi (float): restraining force constant for Phi
|
|
109
|
+
forceConstantPsi (float): restraining force constant for Psi
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
float: contribution of the geometric restraint in the unbound state
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# all the units in radian
|
|
116
|
+
theta0 = math.radians(theta)
|
|
117
|
+
# periodic CV then the u(phi) and u(psi) should be the same in all cases
|
|
118
|
+
phi0 = math.radians(180)
|
|
119
|
+
psi0 = math.radians(180)
|
|
120
|
+
|
|
121
|
+
forceConstantTheta *= (180 / math.pi)**2
|
|
122
|
+
forceConstantPhi *= (180 / math.pi)**2
|
|
123
|
+
forceConstantPsi *= (180 / math.pi)**2
|
|
124
|
+
|
|
125
|
+
contributionTheta = 0
|
|
126
|
+
contributionPhi = 0
|
|
127
|
+
contributionPsi = 0
|
|
128
|
+
# integration
|
|
129
|
+
for i in range(1000):
|
|
130
|
+
theta = i / 1000.0 * math.pi - math.pi / 2
|
|
131
|
+
contributionTheta += 1.0/1000.0 * math.pi * math.sin(theta + math.pi / 2) * \
|
|
132
|
+
math.exp(-self.beta * 0.5 * forceConstantTheta * ((theta - theta0)**2))
|
|
133
|
+
|
|
134
|
+
phi = i / 1000.0 * 2 * math.pi
|
|
135
|
+
contributionPhi += 1.0/1000.0 * 2 * math.pi * \
|
|
136
|
+
math.exp(-self.beta * 0.5 * forceConstantPhi* ((phi - phi0)**2))
|
|
137
|
+
|
|
138
|
+
psi = i / 1000.0 * 2 * math.pi
|
|
139
|
+
contributionPsi += 1.0/1000.0 * 2 * math.pi * \
|
|
140
|
+
math.exp(-self.beta * 0.5 * forceConstantPsi* ((psi - psi0)**2))
|
|
141
|
+
|
|
142
|
+
return -math.log((contributionTheta * contributionPhi * contributionPsi) / 8 / math.pi**2) / self.beta
|
|
143
|
+
|
|
144
|
+
def _geometricJacobianCorrection(self, pmf):
|
|
145
|
+
"""correct the Jacobian contribution of separation pmf
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
pmf (np.array, float, 2*N): ((x0,x1,x2, ...), (y0, y1, y2, ...)), separation pmf
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
for i in range(len(pmf[0])):
|
|
152
|
+
pmf[1][i] += 2 * self.BOLTZMANN * self.temperature * math.log(pmf[0][i])
|
|
153
|
+
|
|
154
|
+
pmf[1] -= np.min(pmf[1])
|
|
155
|
+
|
|
156
|
+
def _geometricCalculateSI(
|
|
157
|
+
self, rStar, pmf, polarTheta, polarPhi, forceConstantPolarTheta, forceConstantPolarPhi):
|
|
158
|
+
"""calculation the contribution of S* and I* in the separation simulation
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
rStar (float): r* in integration
|
|
162
|
+
pmf (np.array, float, 2*N): ((x0,x1,x2, ...), (y0, y1, y2, ...)), separation pmf
|
|
163
|
+
polarTheta0 (float): restraining center of polarTheta
|
|
164
|
+
polarPhi0 (float): restraining center of polarPhi
|
|
165
|
+
forceConstantPolarTheta (float): restraining force constant for polarTheta
|
|
166
|
+
forceConstantPolarPhi (float): restraining force constant for polarPhi
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
float: contribution of S* and I* in the separation simulation
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
if rStar > pmf[0][-1]:
|
|
173
|
+
raise RStarTooLargeError('r_star cannot be larger than r_max of step 7!')
|
|
174
|
+
|
|
175
|
+
polarTheta0 = math.radians(polarTheta)
|
|
176
|
+
polarPhi0 = math.radians(polarPhi)
|
|
177
|
+
|
|
178
|
+
forceConstantPolarTheta *= (180 / math.pi)**2
|
|
179
|
+
forceConstantPolarPhi *= (180 / math.pi)**2
|
|
180
|
+
|
|
181
|
+
contributionPolarTheta = 0
|
|
182
|
+
contributionPolarPhi = 0
|
|
183
|
+
# integration
|
|
184
|
+
for i in range(1000):
|
|
185
|
+
polarTheta = i / 1000.0 * math.pi
|
|
186
|
+
contributionPolarTheta += 1.0 / 1000.0 * math.pi * math.sin(polarTheta) * \
|
|
187
|
+
math.exp(-self.beta * 0.5 * forceConstantPolarTheta * (polarTheta - polarTheta0)**2)
|
|
188
|
+
|
|
189
|
+
polarPhi = i / 1000.0 * 2 * math.pi - math.pi
|
|
190
|
+
contributionPolarPhi += 1.0 / 1000.0 * 2 * math.pi * \
|
|
191
|
+
math.exp(-self.beta * 0.5 * forceConstantPolarPhi * (polarPhi - polarPhi0)**2)
|
|
192
|
+
|
|
193
|
+
S = rStar**2 * contributionPolarTheta * contributionPolarPhi
|
|
194
|
+
|
|
195
|
+
# w(r*)
|
|
196
|
+
wrStar = pmf[1][0]
|
|
197
|
+
for x, y in zip(pmf[0], pmf[1]):
|
|
198
|
+
if x >= rStar:
|
|
199
|
+
wrStar = y
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
# integration
|
|
203
|
+
width = pmf[0][1] - pmf[0][0]
|
|
204
|
+
I = 0
|
|
205
|
+
for x, y in zip(pmf[0], pmf[1]):
|
|
206
|
+
I += width * math.exp(-self.beta * (y - wrStar))
|
|
207
|
+
if x >= rStar:
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
return -1 / self.beta * math.log(S * I / self.CSTAR)
|
|
211
|
+
|
|
212
|
+
def geometricBindingFreeEnergy(self, filePathes, parameters):
|
|
213
|
+
"""calculate binding free energy for geometric route
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
filePathes (list of strings, 8): pathes of PMF files for step1 - step8.
|
|
217
|
+
PMFs for steps 1 and 8 can be omitted, which
|
|
218
|
+
indicates the investication of a rigid ligand.
|
|
219
|
+
parameters (np.array, floats, 8): (forceConstant1, FC2, FC3, FC4, FC5, FC6, r*, FC8)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
np.array, float, 10: (contributions for step1, 2, 3, 4 ... 8, bulk restraining contribution, free energy)
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
assert len(parameters) == 8
|
|
226
|
+
assert len(filePathes) == 8
|
|
227
|
+
|
|
228
|
+
pmfs = []
|
|
229
|
+
rigid_ligand = False
|
|
230
|
+
for index, path in enumerate(filePathes):
|
|
231
|
+
if (index == 0 or index == 7) and path == '':
|
|
232
|
+
rigid_ligand = True
|
|
233
|
+
pmfs.append(None)
|
|
234
|
+
else:
|
|
235
|
+
pmfs.append(self._readPMF(path))
|
|
236
|
+
self._geometricJacobianCorrection(pmfs[6])
|
|
237
|
+
|
|
238
|
+
contributions = np.zeros(10)
|
|
239
|
+
if not rigid_ligand:
|
|
240
|
+
contributions[0] = self._geometricRestraintContribution(pmfs[0], parameters[0], True, False)
|
|
241
|
+
else:
|
|
242
|
+
contributions[0] = 0.0
|
|
243
|
+
contributions[1] = self._geometricRestraintContribution(pmfs[1], parameters[1], False, False)
|
|
244
|
+
contributions[2] = self._geometricRestraintContribution(pmfs[2], parameters[2], False, False)
|
|
245
|
+
contributions[3] = self._geometricRestraintContribution(pmfs[3], parameters[3], False, False)
|
|
246
|
+
contributions[4] = self._geometricRestraintContribution(pmfs[4], parameters[4], False, False)
|
|
247
|
+
contributions[5] = self._geometricRestraintContribution(pmfs[5], parameters[5], False, False)
|
|
248
|
+
contributions[6] = self._geometricCalculateSI(
|
|
249
|
+
parameters[6], pmfs[6], pmfs[4][0][np.argmin(pmfs[4][1])], pmfs[5][0][np.argmin(pmfs[5][1])],
|
|
250
|
+
parameters[4], parameters[5]
|
|
251
|
+
)
|
|
252
|
+
if not rigid_ligand:
|
|
253
|
+
contributions[7] = self._geometricRestraintContribution(pmfs[7], parameters[7], True, True)
|
|
254
|
+
else:
|
|
255
|
+
contributions[7] = 0.0
|
|
256
|
+
contributions[8] = self._geometricRestraintContributionBulk(
|
|
257
|
+
pmfs[1][0][np.argmin(pmfs[1][1])], parameters[1], parameters[2], parameters[3]
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
contributions[9] = np.sum(contributions[:9])
|
|
261
|
+
|
|
262
|
+
if self.unit == 'namd':
|
|
263
|
+
return contributions
|
|
264
|
+
elif self.unit == 'gromacs':
|
|
265
|
+
return contributions / 4.184
|
|
266
|
+
|
|
267
|
+
def _alchemicalRestraintContributionBulk(
|
|
268
|
+
self, eulerTheta, polarTheta, R,
|
|
269
|
+
forceConstantTheta=0.1, forceConstantPhi=0.1, forceConstantPsi=0.1,
|
|
270
|
+
forceConstanttheta=0.1, forceConstantphi=0.1, forceConstantR=10
|
|
271
|
+
):
|
|
272
|
+
"""contribution of (standard concentration corrected) rotational
|
|
273
|
+
and orienetational restraints in the unbounded state
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
eulerTheta (float): restraining center of the Euler angle theta
|
|
277
|
+
polarTheta (float): restraining center of the polar angle theta
|
|
278
|
+
R (float): restraining center of anger R
|
|
279
|
+
forceConstantTheta (float): restraining force constant for euler Theta. Defaults to 0.1.
|
|
280
|
+
forceConstantPhi (float, optional): restraining force constant for euler Phi. Defaults to 0.1.
|
|
281
|
+
forceConstantPsi (float, optional): restraining force constant for euler Psi. Defaults to 0.1.
|
|
282
|
+
forceConstanttheta (float, optional): restraining force constant for polar theta. Defaults to 0.1.
|
|
283
|
+
forceConstantphi (float, optional): restraining force constant for polar phi. Defaults to 0.1.
|
|
284
|
+
forceConstantR (int, optional): restraining force constant for distance R. Defaults to 10.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
float: contribution of the geometric restraint in the unbound state
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
# degrees to rad
|
|
291
|
+
eulerTheta = math.radians(eulerTheta + 90)
|
|
292
|
+
polarTheta = math.radians(polarTheta)
|
|
293
|
+
forceConstantTheta *= (180 / math.pi)**2
|
|
294
|
+
forceConstantPhi *= (180 / math.pi)**2
|
|
295
|
+
forceConstantPsi *= (180 / math.pi)**2
|
|
296
|
+
forceConstanttheta *= (180 / math.pi)**2
|
|
297
|
+
forceConstantphi *= (180 / math.pi)**2
|
|
298
|
+
|
|
299
|
+
contribution = self.BOLTZMANN * self.temperature * math.log(
|
|
300
|
+
8 * (math.pi**2) * self.CSTAR / ((R**2) * math.sin(eulerTheta) * math.sin(polarTheta)) * \
|
|
301
|
+
math.sqrt(forceConstantTheta * forceConstantPhi * forceConstantPsi * forceConstanttheta * \
|
|
302
|
+
forceConstantphi * forceConstantR ) / ((2 * math.pi * self.BOLTZMANN * self.temperature)**3)
|
|
303
|
+
)
|
|
304
|
+
return contribution
|
|
305
|
+
|
|
306
|
+
def _fepoutFile(self, filePath):
|
|
307
|
+
"""parse a fepout file and return the lambda-free energy relationship
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
filePath (str): path of the fepout file
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
tuple (2D np.array): lambda-free energy relationship
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
Lambda = []
|
|
317
|
+
dA_dLambda = []
|
|
318
|
+
|
|
319
|
+
with open(filePath, 'r', encoding='utf-8') as fepoutFile:
|
|
320
|
+
for line in fepoutFile.readlines():
|
|
321
|
+
if not line.startswith('#Free energy'):
|
|
322
|
+
continue
|
|
323
|
+
splitedLine = line.strip().split()
|
|
324
|
+
Lambda.append((float(splitedLine[7]) + float(splitedLine[8])) / 2)
|
|
325
|
+
dA_dLambda.append(float(splitedLine[11]))
|
|
326
|
+
|
|
327
|
+
if Lambda[0] > Lambda[1]:
|
|
328
|
+
Lambda.reverse()
|
|
329
|
+
dA_dLambda.reverse()
|
|
330
|
+
|
|
331
|
+
return np.array((Lambda, np.cumsum(dA_dLambda)))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _tiLogFile(self, filePath, rigidLigand = False):
|
|
335
|
+
"""parse a ti log file and return the lambda-free energy relationship
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
filePath (str): path of the fepout file
|
|
339
|
+
rigidLigand (bool): whether dealing with a rigid ligand. Default to False.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
tuple (2D np.array): lambda-free energy relationship
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
Lambda = []
|
|
346
|
+
dA_dLambda = []
|
|
347
|
+
|
|
348
|
+
if rigidLigand:
|
|
349
|
+
numCVs = 6
|
|
350
|
+
else:
|
|
351
|
+
numCVs = 7
|
|
352
|
+
|
|
353
|
+
with open(filePath, 'r', encoding='utf-8') as fepoutFile:
|
|
354
|
+
for line in fepoutFile.readlines():
|
|
355
|
+
if not ('dA/dLambda' in line):
|
|
356
|
+
continue
|
|
357
|
+
splitedLine = line.strip().split()
|
|
358
|
+
Lambda.append(float(splitedLine[4]))
|
|
359
|
+
dA_dLambda.append(float(splitedLine[6]))
|
|
360
|
+
|
|
361
|
+
# seven CVs in total with the same Lambda in the step 2
|
|
362
|
+
if Lambda[0] == Lambda[1]:
|
|
363
|
+
correctedLambda = []
|
|
364
|
+
correctedDA_dLambda = []
|
|
365
|
+
|
|
366
|
+
for i in range(0, len(Lambda), numCVs):
|
|
367
|
+
correctedLambda.append(Lambda[i])
|
|
368
|
+
totalDA_dLambda = 0
|
|
369
|
+
for j in range(numCVs):
|
|
370
|
+
totalDA_dLambda += dA_dLambda[i+j]
|
|
371
|
+
correctedDA_dLambda.append(totalDA_dLambda)
|
|
372
|
+
|
|
373
|
+
Lambda = correctedLambda
|
|
374
|
+
dA_dLambda = correctedDA_dLambda
|
|
375
|
+
|
|
376
|
+
if Lambda[0] > Lambda[1]:
|
|
377
|
+
Lambda.reverse()
|
|
378
|
+
dA_dLambda.reverse()
|
|
379
|
+
|
|
380
|
+
for i in range(1, len(Lambda)):
|
|
381
|
+
dA_dLambda[i] = (Lambda[i] - Lambda[i-1]) * dA_dLambda[i]
|
|
382
|
+
|
|
383
|
+
return np.array((Lambda, np.cumsum(dA_dLambda)))
|
|
384
|
+
|
|
385
|
+
def _alchemicalFepoutFile(self, filePath, fileType = 'fepout', rigidLigand = False):
|
|
386
|
+
"""parse a fepout/log file and return the total free energy change
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
filePath (str): path of the fepout file
|
|
390
|
+
fileType (str): 'fepout' (decouping atoms) or 'log' (decoupling restraints). Defaults to 'fepout'.
|
|
391
|
+
rigidLigand (bool): whether dealing with a rigid ligand. Default to False.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
float: free-energy change
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
if fileType == 'fepout':
|
|
398
|
+
_, freeEnergyProfile = self._fepoutFile(filePath)
|
|
399
|
+
|
|
400
|
+
if fileType == 'log':
|
|
401
|
+
_, freeEnergyProfile = self._tiLogFile(filePath, rigidLigand)
|
|
402
|
+
|
|
403
|
+
return freeEnergyProfile[-1]
|
|
404
|
+
|
|
405
|
+
def alchemicalFreeEnergy(self, forwardFilePath, backwardFilePath = '', temperature = 300, jobType = 'fep'):
|
|
406
|
+
""" parse a pair of fepout file, or a single double-wide file using the py_bar library
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
forwardFilePath (str): path to the forward fepout file
|
|
410
|
+
backwardFilePath (str): path to the backward fepout file. Empty string
|
|
411
|
+
corresponds to a double-wide simulation
|
|
412
|
+
temperature (float): temperature of the simulation
|
|
413
|
+
jobType (str, optional): Type of the post-treatment method. 'fep', 'bar' or 'pmf'.
|
|
414
|
+
Defaults to 'fep'.
|
|
415
|
+
Returns:
|
|
416
|
+
tuple[float, float]: free-energy change, error
|
|
417
|
+
"""
|
|
418
|
+
window, deltaU = py_bar.NAMDParser(forwardFilePath, backwardFilePath).get_data()
|
|
419
|
+
analyzer = py_bar.FEPAnalyzer(window, deltaU, temperature)
|
|
420
|
+
|
|
421
|
+
if jobType == 'bar':
|
|
422
|
+
result = analyzer.BAR_free_energy(block_size=50, n_bootstrap=20)
|
|
423
|
+
else:
|
|
424
|
+
result = analyzer.FEP_free_energy()
|
|
425
|
+
|
|
426
|
+
freeEnergy = np.sum(result[1])
|
|
427
|
+
error = np.sqrt(np.sum(np.power(result[2], 2)))
|
|
428
|
+
|
|
429
|
+
return freeEnergy, error
|
|
430
|
+
|
|
431
|
+
def alchemicalFreeEnergyPMF(self, PMFfile):
|
|
432
|
+
""" parse a pmf file, and return the free energy change
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
PMFfile (str): path to the .pmf file
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
tuple[float, float]: free-energy change, error
|
|
439
|
+
"""
|
|
440
|
+
data = np.loadtxt(PMFfile)
|
|
441
|
+
return (data[-1][1] - data[0][1]), 99999 # fictitious error
|
|
442
|
+
|
|
443
|
+
def alchemicalBindingFreeEnergy(self, filePathes, parameters, temperature = 300, jobType = 'fep', rigidLigand = False):
|
|
444
|
+
"""calculate binding free energy for alchemical route
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
filePathes (list of strings, 8): pathes of alchemical output files
|
|
448
|
+
(step1-forward, step1-backward, step2-forward ...)
|
|
449
|
+
parameters (np.array, floats, 9): (eulerTheta, polarTheta, r, forceConstant1, FC2, FC3, FC4, FC5, FC6)
|
|
450
|
+
temperature (float): temperature of the simulation
|
|
451
|
+
jobType (str, optional): Type of the post-treatment method. 'fep', 'bar' or 'pmf'.
|
|
452
|
+
Defaults to 'fep'.
|
|
453
|
+
rigidLigand (bool): whether dealing with a rigid ligand. Default to False.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
tuple:
|
|
457
|
+
np.array, float, 6: (contributions for step1, 2, 3, 4, bulk restraining contribution, free energy)
|
|
458
|
+
np.array, float, 6: errors corresponding each contribution
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
assert len(parameters) == 9
|
|
462
|
+
assert len(filePathes) == 8
|
|
463
|
+
|
|
464
|
+
rigid_ligand = False
|
|
465
|
+
if (filePathes[6] == ''):
|
|
466
|
+
rigid_ligand = True
|
|
467
|
+
|
|
468
|
+
# get free energies from fep outputs
|
|
469
|
+
freeEnergies = []
|
|
470
|
+
for i in range(len(filePathes)):
|
|
471
|
+
if filePathes[i] != '':
|
|
472
|
+
if (i // 2) % 2 == 0:
|
|
473
|
+
# just a dirty solution
|
|
474
|
+
freeEnergies.append(None)
|
|
475
|
+
#freeEnergies.append(self._alchemicalFepoutFile(filePathes[i], 'fepout'))
|
|
476
|
+
else:
|
|
477
|
+
freeEnergies.append(self._alchemicalFepoutFile(filePathes[i], 'log', rigidLigand))
|
|
478
|
+
else:
|
|
479
|
+
# backward file can be empty
|
|
480
|
+
freeEnergies.append(None)
|
|
481
|
+
|
|
482
|
+
contributions = np.zeros(6)
|
|
483
|
+
errors = np.zeros(6)
|
|
484
|
+
|
|
485
|
+
if jobType == 'pmf':
|
|
486
|
+
contributions[0], errors[0] = self.alchemicalFreeEnergyPMF(filePathes[0])
|
|
487
|
+
else:
|
|
488
|
+
contributions[0], errors[0] = self.alchemicalFreeEnergy(filePathes[0], filePathes[1], temperature, jobType)
|
|
489
|
+
|
|
490
|
+
if freeEnergies[3] is not None:
|
|
491
|
+
contributions[1] = -(freeEnergies[2] + freeEnergies[3]) / 2
|
|
492
|
+
errors[1] = abs((freeEnergies[2] - freeEnergies[3]) / 1.414)
|
|
493
|
+
else:
|
|
494
|
+
contributions[1] = -freeEnergies[2]
|
|
495
|
+
errors[1] = 99999
|
|
496
|
+
|
|
497
|
+
if jobType == 'pmf':
|
|
498
|
+
contributions[2], errors[2] = self.alchemicalFreeEnergyPMF(filePathes[4])
|
|
499
|
+
else:
|
|
500
|
+
contributions[2], errors[2] = self.alchemicalFreeEnergy(filePathes[4], filePathes[5], temperature, jobType)
|
|
501
|
+
contributions[2] = -contributions[2]
|
|
502
|
+
|
|
503
|
+
if not rigid_ligand:
|
|
504
|
+
if freeEnergies[7] is not None:
|
|
505
|
+
contributions[3] = (freeEnergies[6] + freeEnergies[7]) / 2
|
|
506
|
+
errors[3] = abs((freeEnergies[6] - freeEnergies[7]) / 1.414)
|
|
507
|
+
else:
|
|
508
|
+
contributions[3] = freeEnergies[6]
|
|
509
|
+
errors[3] = 99999
|
|
510
|
+
else:
|
|
511
|
+
contributions[3] = 0
|
|
512
|
+
errors[3] = 0
|
|
513
|
+
|
|
514
|
+
contributions[4] = self._alchemicalRestraintContributionBulk(*parameters)
|
|
515
|
+
errors[4] = 0
|
|
516
|
+
|
|
517
|
+
contributions[5] = contributions[0] + contributions[1] + contributions[2] + contributions[3] + contributions[4]
|
|
518
|
+
errors[5] = math.sqrt(errors[0]**2 + errors[1]**2 +errors[2]**2 + errors[3]**2 + errors[4]**2)
|
|
519
|
+
|
|
520
|
+
return contributions, errors
|
|
521
|
+
|
|
522
|
+
def _LDDMReadColvarsTmp(self, file_path):
|
|
523
|
+
""" Read an Colvars Tmp file in binding free energy calculations,
|
|
524
|
+
get the force constants and centers constants of restraints for LDDM
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
file_path (str): path to the Colvars.tmp file
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
Tuple[List, List]: lists of force constants and centers
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
force_constants = []
|
|
534
|
+
centers = []
|
|
535
|
+
|
|
536
|
+
with open(file_path, 'r') as colvars_tmp_file:
|
|
537
|
+
center_line = False
|
|
538
|
+
for line in colvars_tmp_file.readlines():
|
|
539
|
+
splited_line = line.strip().split()
|
|
540
|
+
|
|
541
|
+
if len(splited_line) < 2:
|
|
542
|
+
center_line = False
|
|
543
|
+
continue
|
|
544
|
+
|
|
545
|
+
if splited_line[1].startswith('$afc_'):
|
|
546
|
+
force_constants.append(float(splited_line[1].replace('$afc_', '')))
|
|
547
|
+
center_line = True
|
|
548
|
+
continue
|
|
549
|
+
|
|
550
|
+
if center_line:
|
|
551
|
+
centers.append(float(splited_line[1]))
|
|
552
|
+
|
|
553
|
+
continue
|
|
554
|
+
|
|
555
|
+
return force_constants, centers
|
|
556
|
+
|
|
557
|
+
def _LDDMBoundStateFreeEnergy(
|
|
558
|
+
self,
|
|
559
|
+
colvars_tmp_path,
|
|
560
|
+
cvtrj_path,
|
|
561
|
+
fepout_path,
|
|
562
|
+
steps_per_window,
|
|
563
|
+
equilbration_steps_per_window,
|
|
564
|
+
num_windows,
|
|
565
|
+
temperature = 300,
|
|
566
|
+
jobtype = 'fep'
|
|
567
|
+
):
|
|
568
|
+
""" Calculate bound state free-energy contribution and error
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
colvars_tmp_path (str): path to colvars tmp file
|
|
572
|
+
cvtrj_path (str): path to colvars.traj file
|
|
573
|
+
fepout_path (str): path to fepout file
|
|
574
|
+
steps_per_window (int): steps per FEP window
|
|
575
|
+
equilbration_steps_per_window (int): equilibration steps per FEP window
|
|
576
|
+
num_windows (int): number of windows
|
|
577
|
+
temperature (float): temperature of the simulation, defaults to 300
|
|
578
|
+
jobType (str, optional): Type of the post-treatment method. 'fep' or 'bar'.
|
|
579
|
+
Defaults to 'fep'.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Tuple[float, float, float]: free-energy contribution of decoupling the molecule,
|
|
583
|
+
error, and contribution of the restraints
|
|
584
|
+
"""
|
|
585
|
+
force_contants, centers = self._LDDMReadColvarsTmp(colvars_tmp_path)
|
|
586
|
+
colvars_parser = py_bar.ColvarsParser(cvtrj_path, steps_per_window, equilbration_steps_per_window,
|
|
587
|
+
force_contants, centers,
|
|
588
|
+
np.linspace(0, 1, num_windows))
|
|
589
|
+
window, deltaU = colvars_parser.get_data()
|
|
590
|
+
b = py_bar.FEPAnalyzer(window, deltaU, temperature)
|
|
591
|
+
|
|
592
|
+
window2, deltaU2 = py_bar.NAMDParser(fepout_path).get_data()
|
|
593
|
+
success = b.MergeData(window2, deltaU2)
|
|
594
|
+
if not success:
|
|
595
|
+
raise RuntimeError('Failed in merging fepout and colvars.traj! Probably wrong number of windows or crupted simulations!')
|
|
596
|
+
|
|
597
|
+
if jobtype == 'fep':
|
|
598
|
+
result = b.FEP_free_energy()
|
|
599
|
+
else:
|
|
600
|
+
result = b.BAR_free_energy(block_size=50, n_bootstrap=20)
|
|
601
|
+
|
|
602
|
+
#windows = b.Window_boundaries()
|
|
603
|
+
#with open(fepout_path + ".convergence.data", "w") as convergence_file:
|
|
604
|
+
# convergence_file.write(f" lambda dA stdev_A \n")
|
|
605
|
+
# for window, dA, stdA in zip(windows, result[1], resul[2]):
|
|
606
|
+
# convergence_file.write(f" {window} {dA:.4f} {stdA:.4f} \n")
|
|
607
|
+
|
|
608
|
+
return -np.sum(result[1]), np.sqrt(np.sum(np.power(result[2], 2))), colvars_parser.get_restraint_contribution()
|
|
609
|
+
|
|
610
|
+
def _LDDMFreeStateFreeEnergy(self, fepout_path, temperature = 300, jobtype = 'fep'):
|
|
611
|
+
""" Calculate free state free-energy contribution and error
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
fepout_path (str): path to fepout file
|
|
615
|
+
temperature (float, optional): temperature of the simulation. Defaults to 300.
|
|
616
|
+
jobType (str, optional): Type of the post-treatment method. 'fep' or 'bar'.
|
|
617
|
+
Defaults to 'fep'.
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
Tuple[float, float]: free-energy contribution and the error of decoupling the molecule
|
|
621
|
+
"""
|
|
622
|
+
window, deltaU = py_bar.NAMDParser(fepout_path).get_data()
|
|
623
|
+
b = py_bar.FEPAnalyzer(window, deltaU, temperature)
|
|
624
|
+
|
|
625
|
+
if jobtype == 'fep':
|
|
626
|
+
result = b.FEP_free_energy()
|
|
627
|
+
else:
|
|
628
|
+
result = b.BAR_free_energy(block_size=50, n_bootstrap=20)
|
|
629
|
+
|
|
630
|
+
# convergence file
|
|
631
|
+
#windows = b.Window_boundaries()
|
|
632
|
+
#with open(fepout_path + "convergence.data", "w") as convergence_file:
|
|
633
|
+
# convergence_file.write(f" lambda dA stdev_A \n")
|
|
634
|
+
# for window, dA, stdA in zip(windows, result_fep[1], result_fep[2]):
|
|
635
|
+
# convergence_file.write(f" {window} {dA:.4f} {stdA:.4f} \n")
|
|
636
|
+
|
|
637
|
+
return np.sum(result[1]), np.sqrt(np.sum(np.power(result[2], 2)))
|
|
638
|
+
|
|
639
|
+
def LDDMBindingFreeEnergy(
|
|
640
|
+
self,
|
|
641
|
+
colvars_tmp_path,
|
|
642
|
+
cvtrj_path,
|
|
643
|
+
step1_fepout_path,
|
|
644
|
+
steps_per_window,
|
|
645
|
+
equilbration_steps_per_window,
|
|
646
|
+
num_windows,
|
|
647
|
+
step3_fepout_path,
|
|
648
|
+
temperature = 300,
|
|
649
|
+
jobType = 'fep'):
|
|
650
|
+
"""calculate binding free energy for LDDM
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
colvars_tmp_path (str): path to colvars tmp file of step 1
|
|
654
|
+
cvtrj_path (str): path to colvars.traj file of step 1
|
|
655
|
+
step1_fepout_path (str): path to fepout file of step 1
|
|
656
|
+
steps_per_window (int): steps per FEP window of step 1
|
|
657
|
+
equilbration_steps_per_window (int): equilibration steps per FEP window of step 1
|
|
658
|
+
num_windows (int): number of windows of step 1
|
|
659
|
+
step3_fepout_path (str): path to fepout file of step 3
|
|
660
|
+
temperature (float): temperature of the simulation, defaults to 300
|
|
661
|
+
jobType (str, optional): Type of the post-treatment method. 'fep' or 'bar'.
|
|
662
|
+
Defaults to 'fep'.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
tuple:
|
|
666
|
+
np.array, float, 2: (contributions for step1, and step 3)
|
|
667
|
+
np.array, float, 2: errors corresponding each contribution
|
|
668
|
+
"""
|
|
669
|
+
|
|
670
|
+
step1_dG, step1_error, step3_dG_restraint = self._LDDMBoundStateFreeEnergy(
|
|
671
|
+
colvars_tmp_path, cvtrj_path, step1_fepout_path, steps_per_window, equilbration_steps_per_window,
|
|
672
|
+
num_windows, temperature, jobType
|
|
673
|
+
)
|
|
674
|
+
step3_dG_molecule, step3_error = self._LDDMFreeStateFreeEnergy(step3_fepout_path, temperature, jobType)
|
|
675
|
+
|
|
676
|
+
return np.array([step1_dG, step1_error]), np.array([step3_dG_molecule + step3_dG_restraint, step3_error])
|