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/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# simple and general slots called by gui.py
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import QLineEdit, QFileDialog, QMessageBox
|
|
4
|
+
|
|
5
|
+
def openFileDialog(fileType, lineEdit):
|
|
6
|
+
"""return a openFile function that opens special type of files
|
|
7
|
+
the openFile dialog is connected with a lineEdit widget
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
fileType (str): the file type for opening
|
|
11
|
+
lineEdit (QLineEdit): the QLineEdit that connects to the QFileDialog
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
function obj: slot function opening special type of files
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def openFile():
|
|
18
|
+
fileName, _ = QFileDialog.getOpenFileName(
|
|
19
|
+
None,
|
|
20
|
+
f'Choose {fileType} file',
|
|
21
|
+
'',
|
|
22
|
+
f'All Files (*)'
|
|
23
|
+
)
|
|
24
|
+
lineEdit.setText(fileName)
|
|
25
|
+
return openFile
|
|
26
|
+
|
|
27
|
+
def openFilesDialog(fileType, listWidget):
|
|
28
|
+
"""return a openFile function that opens a series of files
|
|
29
|
+
the openFile dialog is connected with a QListWidget
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
fileType (str): the file type for opening
|
|
33
|
+
listWidget (QListWidget): the QListWidget that connects to the QFileDialog
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
function obj: slot function opening a series of files
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def openFiles():
|
|
40
|
+
|
|
41
|
+
fileNames, _ = QFileDialog.getOpenFileNames(
|
|
42
|
+
None,
|
|
43
|
+
f'Choose {fileType} files',
|
|
44
|
+
'',
|
|
45
|
+
f'All Files (*)'
|
|
46
|
+
)
|
|
47
|
+
listWidget.addItems(fileNames)
|
|
48
|
+
return openFiles
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# file parser used in bfee
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import MDAnalysis
|
|
9
|
+
import numpy as np
|
|
10
|
+
import parmed
|
|
11
|
+
from MDAnalysis import transformations
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# an runtime error
|
|
15
|
+
# selection corresponding to nothing
|
|
16
|
+
class SelectionError(RuntimeError):
|
|
17
|
+
def __init__(self, arg):
|
|
18
|
+
self.args = arg
|
|
19
|
+
|
|
20
|
+
class fileParser:
|
|
21
|
+
"""topology and coordinate parser,
|
|
22
|
+
this class implements basic method for the topology and
|
|
23
|
+
coordinate file, used by BFEE
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, topFile, coorFile=''):
|
|
27
|
+
"""initialize fileParser, on coorFile is not provided, then
|
|
28
|
+
topFile is considered as a top-coor file, such as mol2
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
topFile (str): path of the topology (psf, parm) file
|
|
32
|
+
coorFile (str): path of the coordinate (pdb, rst) file. Defaults to ''.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
coorPostfix = os.path.splitext(coorFile)[-1]
|
|
36
|
+
if coorPostfix == '.rst7' or coorPostfix == '.rst' or coorPostfix == '.inpcrd':
|
|
37
|
+
coorType = 'inpcrd'
|
|
38
|
+
|
|
39
|
+
if coorFile == '':
|
|
40
|
+
self.uObject = MDAnalysis.Universe(topFile)
|
|
41
|
+
else:
|
|
42
|
+
# MDAnalysis does not well recognize amber file type by postfix
|
|
43
|
+
coorPostfix = os.path.splitext(coorFile)[-1]
|
|
44
|
+
if coorPostfix == '.rst7' or coorPostfix == '.rst' or coorPostfix == '.inpcrd':
|
|
45
|
+
coorType = 'INPCRD'
|
|
46
|
+
self.uObject = MDAnalysis.Universe(topFile, coorFile, format=coorType)
|
|
47
|
+
else:
|
|
48
|
+
self.uObject = MDAnalysis.Universe(topFile, coorFile)
|
|
49
|
+
self.uObject.add_TopologyAttr('tempfactors')
|
|
50
|
+
self.topPath = topFile
|
|
51
|
+
|
|
52
|
+
def saveFile(self, selection, targetPath, targetType, saveTop=False, topPath=''):
|
|
53
|
+
"""save the coordinate file to the target type
|
|
54
|
+
this function cannot really 'save' the topology file,
|
|
55
|
+
it can only copy the original topology to the target path
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
selection (str): selection of atoms to save
|
|
59
|
+
targetPath (str): path for the coor file to be saved
|
|
60
|
+
targetType (str): type of the coor file (pdb, xyz)
|
|
61
|
+
saveTop (bool): whether the topology file will be saved. Defaults to False.
|
|
62
|
+
topPath (str, optional): path for the topology file to be saved. Defaults to ''.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
SelectionError: if the selection corresponds to nothing
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
atoms = self.uObject.select_atoms(selection)
|
|
69
|
+
|
|
70
|
+
if len(atoms) == 0:
|
|
71
|
+
raise SelectionError('Empty selection!')
|
|
72
|
+
|
|
73
|
+
atoms.write(targetPath, targetType, bonds=None)
|
|
74
|
+
if saveTop:
|
|
75
|
+
assert(selection == 'all')
|
|
76
|
+
shutil.copyfile(self.topPath, topPath)
|
|
77
|
+
|
|
78
|
+
def saveNDX(self, selections, names, targetPath, nonHydrogen=True):
|
|
79
|
+
"""save an ndx file, including the selections
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
selections (list of str): the selections for the ndx file
|
|
83
|
+
names (list of str): the name in ndx of each selection
|
|
84
|
+
targetPath (str): path for the file to be saved
|
|
85
|
+
nonHydrogen (bool, optional): whether only non-H atoms are considered. Defaults to True.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
SelectionError: if the selection corresponds to nothing
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
assert(len(selections) == len(names))
|
|
92
|
+
|
|
93
|
+
if nonHydrogen == True:
|
|
94
|
+
HString = 'and not (name H*)'
|
|
95
|
+
else:
|
|
96
|
+
HString = ''
|
|
97
|
+
|
|
98
|
+
for selection, name in zip(selections, names):
|
|
99
|
+
atoms = self.uObject.select_atoms(f'{selection} {HString}')
|
|
100
|
+
|
|
101
|
+
if len(atoms) == 0:
|
|
102
|
+
raise SelectionError('Empty selection!')
|
|
103
|
+
|
|
104
|
+
atoms.write(targetPath, 'ndx', name=name, mode='a')
|
|
105
|
+
|
|
106
|
+
def getResid(self, selection):
|
|
107
|
+
"""return a string listing the resid of the selection
|
|
108
|
+
may be used to generate amber masks
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
selection (str): MDAnalysis selection
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
SelectionError: if the selection corresponds to nothing
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
str: a list of resid, e.g. (4,5,6,7,8,9,10)
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
atoms = self.uObject.select_atoms(selection)
|
|
121
|
+
|
|
122
|
+
if len(atoms) == 0:
|
|
123
|
+
raise SelectionError('Empty selection!')
|
|
124
|
+
|
|
125
|
+
return ','.join([str(num+1) for num in atoms.residues.ix])
|
|
126
|
+
|
|
127
|
+
def measureMinmax(self, selection):
|
|
128
|
+
"""mimic VMD measure minmax
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
selection (str): selection of atoms
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
SelectionError: if the selection corresponds to nothing
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
np.array (2*3, float): ((minX, minY, minZ), (maxX, maxY, maxZ))
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
atoms = self.uObject.select_atoms(selection)
|
|
141
|
+
|
|
142
|
+
if len(atoms) == 0:
|
|
143
|
+
raise SelectionError('Empty selection!')
|
|
144
|
+
|
|
145
|
+
atomPositions = atoms.positions
|
|
146
|
+
xyz_array = np.transpose(atomPositions)
|
|
147
|
+
min_x = np.min(xyz_array[0])
|
|
148
|
+
max_x = np.max(xyz_array[0])
|
|
149
|
+
min_y = np.min(xyz_array[1])
|
|
150
|
+
max_y = np.max(xyz_array[1])
|
|
151
|
+
min_z = np.min(xyz_array[2])
|
|
152
|
+
max_z = np.max(xyz_array[2])
|
|
153
|
+
|
|
154
|
+
return np.array([[min_x, min_y, min_z],[max_x, max_y, max_z]])
|
|
155
|
+
|
|
156
|
+
def measureCenter(self, selection):
|
|
157
|
+
"""mimic vmd measure center
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
selection (str): selection of atoms
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
SelectionError: if the selection corresponds to nothing
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
np.array (3, float): (x, y, z)
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
atoms = self.uObject.select_atoms(selection)
|
|
170
|
+
|
|
171
|
+
if len(atoms) == 0:
|
|
172
|
+
raise SelectionError('Empty selection!')
|
|
173
|
+
|
|
174
|
+
atomPositions = atoms.positions
|
|
175
|
+
xyz_array = np.transpose(atomPositions)
|
|
176
|
+
center_x = np.average(xyz_array[0])
|
|
177
|
+
center_y = np.average(xyz_array[1])
|
|
178
|
+
center_z = np.average(xyz_array[2])
|
|
179
|
+
|
|
180
|
+
return np.array([center_x, center_y, center_z])
|
|
181
|
+
|
|
182
|
+
def measureDistance(self, selection1, selection2):
|
|
183
|
+
"""measure the distance between the center of mass
|
|
184
|
+
of two atom groups
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
selection1 (str): selection of atom group 1
|
|
188
|
+
selection2 (str): selection of atom group 2
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
float: distance between the center of mass of the two atom groups
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
center1 = self.measureCenter(selection1)
|
|
195
|
+
center2 = self.measureCenter(selection2)
|
|
196
|
+
return round(np.linalg.norm(center2 - center1), 1)
|
|
197
|
+
|
|
198
|
+
def measurePBC(self):
|
|
199
|
+
"""measure periodic boundary condition of the file
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
np.array (2*3, float): ((lengthX, lengthY, lengthZ), (centerX, centerY, centerZ))
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
minmaxArray = self.measureMinmax('all')
|
|
206
|
+
vec = minmaxArray[1] - minmaxArray[0]
|
|
207
|
+
center = self.measureCenter('all')
|
|
208
|
+
|
|
209
|
+
return np.array((vec, center))
|
|
210
|
+
|
|
211
|
+
def measurePolarAngles(self, selectionPro, selectionLig):
|
|
212
|
+
"""calculation the polar angles based on selectionPro and selectionLig
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
selectionPro (str): selection of the host molecule
|
|
216
|
+
selectionLig (str): selection of the ligand molecule
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
np.array (2, float): (theta, phi) in degrees
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
vector = self.measureCenter(selectionLig) - self.measureCenter(selectionPro)
|
|
223
|
+
vector /= np.linalg.norm(vector)
|
|
224
|
+
|
|
225
|
+
return (float(int(math.degrees(np.arccos(-vector[1])))), float(int(math.degrees(np.arctan2(vector[2], vector[0])))))
|
|
226
|
+
|
|
227
|
+
def setBeta(self, selection, beta):
|
|
228
|
+
"""set beta for the selected atoms
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
selection (str): selection of atoms to change beta
|
|
232
|
+
beta (int): beta value
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
SelectionError: if the selection corresponds to nothing
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
atoms = self.uObject.select_atoms(selection)
|
|
239
|
+
|
|
240
|
+
if len(atoms) == 0:
|
|
241
|
+
raise SelectionError('Empty selection!')
|
|
242
|
+
|
|
243
|
+
atoms.tempfactors = beta
|
|
244
|
+
|
|
245
|
+
def moveSystem(self, moveVector):
|
|
246
|
+
"""move all the atoms in the loaded file
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
moveVector (np.array, 3, float): the vector of moving
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
allAtoms = self.uObject.select_atoms('all')
|
|
253
|
+
transformations.translate(moveVector)(allAtoms)
|
|
254
|
+
|
|
255
|
+
def rotateSystem(self, axis, degrees):
|
|
256
|
+
"""rotate all the atoms in the loaded file
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
axis (str): 'x' or 'y' or 'z'
|
|
260
|
+
degrees (float): degrees to move by
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
assert(axis == 'x' or axis == 'y' or axis == 'z')
|
|
264
|
+
|
|
265
|
+
allAtoms = self.uObject.select_atoms('all')
|
|
266
|
+
if axis == 'x':
|
|
267
|
+
axisVector = (1,0,0)
|
|
268
|
+
elif axis == 'y':
|
|
269
|
+
axisVector = (0,1,0)
|
|
270
|
+
else:
|
|
271
|
+
axisVector = (0,0,1)
|
|
272
|
+
transformations.rotate.rotateby(degrees, axisVector, ag=allAtoms)(allAtoms)
|
|
273
|
+
|
|
274
|
+
def centerSystem(self):
|
|
275
|
+
"""move all the atoms in the loaded file,
|
|
276
|
+
such that the center of system be (0,0,0)
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
vec = self.measurePBC()[1] * -1.0
|
|
280
|
+
self.moveSystem(vec)
|
|
281
|
+
|
|
282
|
+
def charmmToGromacs(psfFile, pdbFile, prmFiles, PBC, outputPrefix):
|
|
283
|
+
"""convert a set of CHARMM files (psf + pdb + prm + pbc) into the Gromacs format
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
psfFile (str): path of the psf file
|
|
287
|
+
pdbFile (str): path of the pdb file
|
|
288
|
+
prmFiles (list of str): pathes of the prm files
|
|
289
|
+
PBC (list of flost): pbc information
|
|
290
|
+
outputPrefix (str): path + prefix of the output file
|
|
291
|
+
"""
|
|
292
|
+
struct = parmed.load_file(psfFile)
|
|
293
|
+
struct.load_parameters(
|
|
294
|
+
parmed.charmm.CharmmParameterSet(*prmFiles)
|
|
295
|
+
)
|
|
296
|
+
struct.coordinates = parmed.load_file(pdbFile).coordinates
|
|
297
|
+
struct.box = [PBC[0], PBC[1], PBC[2], 90, 90, 90]
|
|
298
|
+
struct.save(f'{outputPrefix}.top', format='gromacs')
|
|
299
|
+
struct.save(f'{outputPrefix}.gro')
|
|
300
|
+
|
|
301
|
+
def amberToGromacs(parmFile, rstFile, PBC, outputPrefix):
|
|
302
|
+
"""convert a set of Amber files (parm7 + rst7) into the Gromacs format
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
parmFile (str): path of the parm7 file
|
|
306
|
+
rstFile (str): path of the rst7 file
|
|
307
|
+
PBC (list of flost): pbc information
|
|
308
|
+
outputPrefix (str): path + prefix of the output file
|
|
309
|
+
"""
|
|
310
|
+
struct = parmed.load_file(parmFile, xyz=rstFile)
|
|
311
|
+
struct.box = [PBC[0], PBC[1], PBC[2], 90, 90, 90]
|
|
312
|
+
struct.save(f'{outputPrefix}.top', format='gromacs')
|
|
313
|
+
struct.save(f'{outputPrefix}.gro')
|
|
314
|
+
|
|
315
|
+
def gromacsToAmber(topFile, groFile, PBC, outputPrefix):
|
|
316
|
+
"""convert a set of Gromacs files (top + gro/pdb) into the Amber format
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
topFile (str): path of the top file
|
|
320
|
+
groFile (str): path of the gro file
|
|
321
|
+
PBC (list of flost): pbc information
|
|
322
|
+
outputPrefix (str): path + prefix of the output file
|
|
323
|
+
"""
|
|
324
|
+
struct = parmed.load_file(topFile, xyz=groFile)
|
|
325
|
+
struct.box = [PBC[0], PBC[1], PBC[2], 90, 90, 90]
|
|
326
|
+
struct.save(f'{outputPrefix}.parm7', format='amber')
|
|
327
|
+
struct.save(f'{outputPrefix}.rst7', format='rst7')
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# plot figures
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
|
|
7
|
+
import matplotlib
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import interpolate
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# an runtime error
|
|
14
|
+
# does not have corresponding correction for a pmf
|
|
15
|
+
class NoCorrectionFileError(RuntimeError):
|
|
16
|
+
def __init__(self, arg):
|
|
17
|
+
self.args = arg
|
|
18
|
+
|
|
19
|
+
def isGaWTM(pmfFiles):
|
|
20
|
+
"""determine whether the input PMFs indicate Gs-WTM simulations
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
pmfFiles (list[str]): path to a set of PMFs (and pmf corrections)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
bool: GaWTM simulation or not
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
for file in pmfFiles:
|
|
30
|
+
if ''.join(pathlib.Path(file).suffixes) == '.reweightamd1.cumulant.pmf':
|
|
31
|
+
return True
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
def correctGaWTM(pmfFile):
|
|
35
|
+
"""read a 1D namd PMF file and correct it using cumulant.pmf file
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
pmfFile (str): path to the pmf File
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
np.array (N*2): 1D PMF
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
pmf = np.loadtxt(pmfFile)
|
|
45
|
+
correction_pmfFile = pmfFile.replace('.czar.pmf', '') + '.reweightamd1.cumulant.pmf'
|
|
46
|
+
|
|
47
|
+
if not os.path.exists(correction_pmfFile):
|
|
48
|
+
raise NoCorrectionFileError(f'{pmfFile} does not have a corresponding correction!')
|
|
49
|
+
|
|
50
|
+
correction_data = np.loadtxt(correction_pmfFile)
|
|
51
|
+
correction_interpolate = interpolate.interp1d(correction_data[:,0], correction_data[:,1], fill_value="extrapolate")
|
|
52
|
+
|
|
53
|
+
pmf[:,1] += correction_interpolate(pmf[:,0])
|
|
54
|
+
|
|
55
|
+
return pmf
|
|
56
|
+
|
|
57
|
+
def readPMF(pmfFile):
|
|
58
|
+
"""read a 1D namd PMF file
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
pmfFile (str): path to the pmf File
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
np.array (N*2): 1D PMF
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
return np.loadtxt(pmfFile)
|
|
68
|
+
|
|
69
|
+
def mergePMF(pmfFiles):
|
|
70
|
+
"""merge several PMF files
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
pmfFiles (list of np.arrays): list of 1D pmfs
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
np.array (N*2): merged PMF if the PMFs overlap, pmfFiles[0] otherwise
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
numPmfs = len(pmfFiles)
|
|
80
|
+
assert(numPmfs > 0)
|
|
81
|
+
|
|
82
|
+
# sort pmfs
|
|
83
|
+
pmfSort = [i for i in range(numPmfs)]
|
|
84
|
+
pmfSort.sort(key=lambda x: pmfFiles[x][0][0])
|
|
85
|
+
|
|
86
|
+
finalPMF = pmfFiles[pmfSort[0]]
|
|
87
|
+
|
|
88
|
+
if len(pmfFiles) > 1:
|
|
89
|
+
for i in range(1, len(pmfFiles)):
|
|
90
|
+
for j in range(len(finalPMF)):
|
|
91
|
+
if finalPMF[j][0] == pmfFiles[pmfSort[i]][0][0]:
|
|
92
|
+
# overlapped region
|
|
93
|
+
avgDifference = np.average(finalPMF[j:,1:] - pmfFiles[pmfSort[i]][0:len(finalPMF)-j,1:])
|
|
94
|
+
pmfFiles[pmfSort[i]][:,1:] += avgDifference
|
|
95
|
+
finalPMF[j:,1:] = (finalPMF[j:,1:] + pmfFiles[pmfSort[i]][0:len(finalPMF)-j,1:]) / 2
|
|
96
|
+
# other region
|
|
97
|
+
finalPMF = np.append(finalPMF, pmfFiles[pmfSort[i]][len(finalPMF)-j:], axis=0)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
finalPMF[:,1] -= finalPMF[:,1].min()
|
|
101
|
+
|
|
102
|
+
return finalPMF
|
|
103
|
+
|
|
104
|
+
def writePMF(pmfFile, pmf):
|
|
105
|
+
"""write a 1D namd PMF file
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
pmfFile (str): path to the pmf File
|
|
109
|
+
pmf (np.array, N*2): pmf to be written
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
np.savetxt(pmfFile, pmf, fmt='%g')
|
|
113
|
+
|
|
114
|
+
def plotPMF(pmf):
|
|
115
|
+
"""plot a pmf
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
pmf (np.array, N*2): pmf to be plotted
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
plt.plot(pmf[:,0], pmf[:,1])
|
|
122
|
+
plt.xlabel('Transition coordinate')
|
|
123
|
+
plt.ylabel('ΔG (kcal/mol)')
|
|
124
|
+
plt.show()
|
|
125
|
+
|
|
126
|
+
def plotHysteresis(forwardProfile, backwardProfile):
|
|
127
|
+
"""plot the profile describing the hysteresis between forward and backward
|
|
128
|
+
simulations
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
forwardProfile (np.array, N*2): forward free-energy profile to be plotted
|
|
132
|
+
backwardProfile (np.array, N*2): backward free-energy profile to be plotted
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
plt.plot(forwardProfile[:,0], forwardProfile[:,1], label='Forward')
|
|
136
|
+
plt.plot(backwardProfile[:,0], backwardProfile[:,1], label='Backward')
|
|
137
|
+
plt.xlabel('Lambda')
|
|
138
|
+
plt.ylabel('ΔG (kcal/mol)')
|
|
139
|
+
plt.legend()
|
|
140
|
+
plt.show()
|
|
141
|
+
|
|
142
|
+
def calcRMSD(inputArray):
|
|
143
|
+
"""calculate RMSD of a np.array with respect to (0,0,0,...0)
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
inputArray (1D np.array): the input array
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
float: RMSD of a np.array with respect to (0,0,0,...0)
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
sumG2 = sum(map(lambda x: x * x, inputArray))
|
|
153
|
+
return math.sqrt(sumG2 / len(inputArray))
|
|
154
|
+
|
|
155
|
+
def readFrame(input):
|
|
156
|
+
"""read a frame of Colvars hist file and calculate its RMSD with respect to zero array
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
input (python file object): input object
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
float: RMSD with respect to zero array
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
G = []
|
|
166
|
+
while True:
|
|
167
|
+
line = input.readline()
|
|
168
|
+
|
|
169
|
+
# end of file
|
|
170
|
+
if not line:
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
splitedLine = line.strip().split()
|
|
174
|
+
if splitedLine == []:
|
|
175
|
+
if G == []:
|
|
176
|
+
continue
|
|
177
|
+
else:
|
|
178
|
+
break
|
|
179
|
+
if splitedLine[0].startswith('#'):
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
G.append(float(splitedLine[1]))
|
|
183
|
+
|
|
184
|
+
if G != []:
|
|
185
|
+
return calcRMSD(G)
|
|
186
|
+
else:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def parseHistFile(histPath):
|
|
190
|
+
"""parse a hist.czar.pmf file and return frame-RMSD list
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
histPath (str): path to a hist.czar.pmf file
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
1D np.array: time evolution of RMSD with respect to zero array
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
rmsd = []
|
|
200
|
+
with open(histPath, 'r') as ifile:
|
|
201
|
+
while True:
|
|
202
|
+
rmsdPerFrame = readFrame(ifile)
|
|
203
|
+
if rmsdPerFrame is False:
|
|
204
|
+
break
|
|
205
|
+
rmsd.append(rmsdPerFrame)
|
|
206
|
+
return rmsd
|
|
207
|
+
|
|
208
|
+
def plotConvergence(rmsdList):
|
|
209
|
+
"""plot the time evolution of PMF rmsd
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
rmsdList (list or 1D np.array, float): time evolution of RMSD with respect to zero array
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
plt.plot(range(1, len(rmsdList) + 1), rmsdList)
|
|
216
|
+
plt.xlabel('Frame')
|
|
217
|
+
plt.ylabel('RMSD (Colvars Unit)')
|
|
218
|
+
plt.show()
|
BFEE2/doc/Doc.pdf
ADDED
|
Binary file
|
BFEE2/doc/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|