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.
Files changed (68) hide show
  1. BFEE2/__init__.py +0 -0
  2. BFEE2/commonTools/__init__.py +0 -0
  3. BFEE2/commonTools/commonSlots.py +48 -0
  4. BFEE2/commonTools/fileParser.py +327 -0
  5. BFEE2/commonTools/ploter.py +218 -0
  6. BFEE2/doc/Doc.pdf +0 -0
  7. BFEE2/doc/__init__.py +1 -0
  8. BFEE2/gui.py +2785 -0
  9. BFEE2/inputGenerator.py +2949 -0
  10. BFEE2/postTreatment.py +676 -0
  11. BFEE2/templates_gromacs/000.colvars.template +37 -0
  12. BFEE2/templates_gromacs/000.generate_tpr_sh.template +31 -0
  13. BFEE2/templates_gromacs/000.mdp.template +74 -0
  14. BFEE2/templates_gromacs/001.colvars.template +76 -0
  15. BFEE2/templates_gromacs/001.generate_tpr_sh.template +31 -0
  16. BFEE2/templates_gromacs/001.mdp.template +73 -0
  17. BFEE2/templates_gromacs/001.readme.template +1 -0
  18. BFEE2/templates_gromacs/002.colvars.template +101 -0
  19. BFEE2/templates_gromacs/002.generate_tpr_sh.template +31 -0
  20. BFEE2/templates_gromacs/002.mdp.template +73 -0
  21. BFEE2/templates_gromacs/003.colvars.template +125 -0
  22. BFEE2/templates_gromacs/003.generate_tpr_sh.template +36 -0
  23. BFEE2/templates_gromacs/003.mdp.template +73 -0
  24. BFEE2/templates_gromacs/004.colvars.template +148 -0
  25. BFEE2/templates_gromacs/004.generate_tpr_sh.template +37 -0
  26. BFEE2/templates_gromacs/004.mdp.template +74 -0
  27. BFEE2/templates_gromacs/005.colvars.template +170 -0
  28. BFEE2/templates_gromacs/005.generate_tpr_sh.template +38 -0
  29. BFEE2/templates_gromacs/005.mdp.template +74 -0
  30. BFEE2/templates_gromacs/006.colvars.template +192 -0
  31. BFEE2/templates_gromacs/006.generate_tpr_sh.template +39 -0
  32. BFEE2/templates_gromacs/006.mdp.template +74 -0
  33. BFEE2/templates_gromacs/007.colvars.template +210 -0
  34. BFEE2/templates_gromacs/007.generate_tpr_sh.template +40 -0
  35. BFEE2/templates_gromacs/007.mdp.template +73 -0
  36. BFEE2/templates_gromacs/007_eq.colvars.template +169 -0
  37. BFEE2/templates_gromacs/007_eq.generate_tpr_sh.template +64 -0
  38. BFEE2/templates_gromacs/007_min.mdp.template +62 -0
  39. BFEE2/templates_gromacs/008.colvars.template +42 -0
  40. BFEE2/templates_gromacs/008.generate_tpr_sh.template +31 -0
  41. BFEE2/templates_gromacs/008.mdp.template +74 -0
  42. BFEE2/templates_gromacs/008_eq.colvars.template +14 -0
  43. BFEE2/templates_gromacs/008_eq.generate_tpr_sh.template +31 -0
  44. BFEE2/templates_gromacs/BFEEGromacs.py +1268 -0
  45. BFEE2/templates_gromacs/__init__.py +0 -0
  46. BFEE2/templates_gromacs/find_min_max.awk +27 -0
  47. BFEE2/templates_namd/__init__.py +0 -0
  48. BFEE2/templates_namd/configTemplate.py +1152 -0
  49. BFEE2/templates_namd/fep.tcl +299 -0
  50. BFEE2/templates_namd/fep_lddm.tcl +312 -0
  51. BFEE2/templates_namd/scriptTemplate.py +304 -0
  52. BFEE2/templates_namd/solvate.tcl +9 -0
  53. BFEE2/templates_namd/solvate_mem.tcl +9 -0
  54. BFEE2/templates_namd/updateCenters.py +312 -0
  55. BFEE2/templates_readme/Readme_Gromacs_Geometrical.txt +25 -0
  56. BFEE2/templates_readme/Readme_NAMD_Alchemical.txt +20 -0
  57. BFEE2/templates_readme/Readme_NAMD_Geometrical.txt +34 -0
  58. BFEE2/templates_readme/__init__.py +1 -0
  59. BFEE2/templates_readme/rags.py +187 -0
  60. BFEE2/third_party/__init__.py +0 -0
  61. BFEE2/third_party/py_bar.py +585 -0
  62. BFEE2/version.py +4 -0
  63. bfee2-3.1.1.post1.data/scripts/BFEE2Gui.py +19 -0
  64. bfee2-3.1.1.post1.dist-info/METADATA +86 -0
  65. bfee2-3.1.1.post1.dist-info/RECORD +68 -0
  66. bfee2-3.1.1.post1.dist-info/WHEEL +5 -0
  67. bfee2-3.1.1.post1.dist-info/licenses/LICENSE +677 -0
  68. 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])