gromorg 0.3__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.
- gromorg/__init__.py +309 -0
- gromorg/cache.py +99 -0
- gromorg/capture.py +23 -0
- gromorg/data_structure.py +51 -0
- gromorg/setparam.py +103 -0
- gromorg/swisparam.py +225 -0
- gromorg/tools.py +98 -0
- gromorg/utils.py +167 -0
- gromorg-0.3.dist-info/METADATA +112 -0
- gromorg-0.3.dist-info/RECORD +13 -0
- gromorg-0.3.dist-info/WHEEL +5 -0
- gromorg-0.3.dist-info/licenses/LICENSE +21 -0
- gromorg-0.3.dist-info/top_level.txt +1 -0
gromorg/__init__.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
__version__ = '0.3'
|
|
2
|
+
|
|
3
|
+
from gromorg.swisparam import SwissParams
|
|
4
|
+
from gromorg.utils import extract_energy, extract_forces
|
|
5
|
+
from gromorg.capture import captured_stdout
|
|
6
|
+
from gromorg.data_structure import DataStructure
|
|
7
|
+
from gromorg.utils import commandline_operation
|
|
8
|
+
import gmxapi as gmx
|
|
9
|
+
import numpy as np
|
|
10
|
+
import mdtraj
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GromOrg:
|
|
17
|
+
def __init__(self, structure,
|
|
18
|
+
params,
|
|
19
|
+
box=(10, 10, 10),
|
|
20
|
+
angles=(90, 90, 90),
|
|
21
|
+
supercell=(1, 1, 1),
|
|
22
|
+
solvent=None,
|
|
23
|
+
solvent_scale=0.57,
|
|
24
|
+
maxwarn=0,
|
|
25
|
+
omp_num_threads=1,
|
|
26
|
+
silent=False,
|
|
27
|
+
delete_scratch=True):
|
|
28
|
+
|
|
29
|
+
self._structure = structure
|
|
30
|
+
self._filename = 'test'
|
|
31
|
+
self._box = np.array(box)/10.0 # from Angs to nm
|
|
32
|
+
self._supercell = supercell
|
|
33
|
+
self._angles = angles
|
|
34
|
+
self._silent = silent
|
|
35
|
+
self._delete_scratch = delete_scratch
|
|
36
|
+
self._solvent = solvent
|
|
37
|
+
self._solvent_scale = solvent_scale
|
|
38
|
+
self._maxwarn = maxwarn
|
|
39
|
+
|
|
40
|
+
os.putenv('GMX_MAXBACKUP', '-1')
|
|
41
|
+
os.putenv('OMP_NUM_THREADS', '{}'.format(omp_num_threads))
|
|
42
|
+
|
|
43
|
+
folder = os.getcwd()
|
|
44
|
+
|
|
45
|
+
self._work_dir = folder + 'gromorg_{}/'.format(os.getpid())
|
|
46
|
+
# os.mkdir(self._work_dir)
|
|
47
|
+
try:
|
|
48
|
+
os.mkdir(self._work_dir)
|
|
49
|
+
except FileExistsError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
self._filename_dir = self._work_dir + self._filename
|
|
53
|
+
|
|
54
|
+
# Default parameters
|
|
55
|
+
self._params = {# Run paramters
|
|
56
|
+
'integrator': 'md-vv', # Verlet integrator
|
|
57
|
+
'nsteps': 5000, # 0.001 * 5000 = 50 ps
|
|
58
|
+
'dt': 0.001, # ps
|
|
59
|
+
# Output control
|
|
60
|
+
'nstxout': 1, # save coordinates every 0.001 ps
|
|
61
|
+
'nstvout': 1, # save velocities every 0.001 ps
|
|
62
|
+
'nstenergy': 1, # save energies every 0.001 ps
|
|
63
|
+
'nstlog': 100, # update log file every 0.1 ps
|
|
64
|
+
# Bond parameters
|
|
65
|
+
'continuation': 'no', # first dynamics run
|
|
66
|
+
'cutoff-scheme': 'Verlet', # Buffered neighbor searching
|
|
67
|
+
'verlet-buffer-tolerance': 3.3e-03,
|
|
68
|
+
# 'ns_type': 'grid', # search neighboring grid cells
|
|
69
|
+
'nstlist': 10, # 20 fs, largely irrelevant with Verlet
|
|
70
|
+
'rcoulomb': 1.0, # short-range electrostatic cutoff (in nm)
|
|
71
|
+
'rvdw': 1.0, # short-range van der Waals cutoff (in nm)
|
|
72
|
+
'DispCorr': 'EnerPres', # account for cut-off vdW scheme
|
|
73
|
+
# Electrostatics
|
|
74
|
+
'coulombtype': 'PME', # Particle Mesh Ewald for long-range electrostatics
|
|
75
|
+
'pme_order': 4, # cubic interpolation
|
|
76
|
+
'fourierspacing': 0.16, # grid spacing for FFT
|
|
77
|
+
# Temperature coupling is on
|
|
78
|
+
'tcoupl': 'nose-hoover', # Nose-Hoover thermostat
|
|
79
|
+
'tc-grps': 'system', # one coupling group
|
|
80
|
+
'tau_t': 0.3, # time constant, in ps
|
|
81
|
+
'ref_t': 100, # reference temperature, one for each group, in K
|
|
82
|
+
# Pressure coupling is off
|
|
83
|
+
'pcoupl': 'no', # no pressure coupling in NVT
|
|
84
|
+
# Periodic boundary conditions
|
|
85
|
+
'pbc': 'xyz', # 3-D PBC
|
|
86
|
+
# Velocity generation
|
|
87
|
+
'gen_vel': 'yes', # assign velocities from Maxwell distributio
|
|
88
|
+
'gen_temp': 10, # temperature for Maxwell distribution
|
|
89
|
+
'gen_seed': -1, # generate a random seed
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if params is not None:
|
|
93
|
+
self._params.update(params)
|
|
94
|
+
|
|
95
|
+
def get_mdp(self):
|
|
96
|
+
file = ';Autogenerated MDP\n'
|
|
97
|
+
for keys, values in self._params.items():
|
|
98
|
+
file += '{:30} = {}\n'.format(keys, values)
|
|
99
|
+
|
|
100
|
+
return file
|
|
101
|
+
|
|
102
|
+
def get_topology(self):
|
|
103
|
+
|
|
104
|
+
num_mol = np.prod(self._supercell)
|
|
105
|
+
|
|
106
|
+
topology_data = {'':['; Autogenerated Topology',
|
|
107
|
+
'#include "charmm27.ff/forcefield.itp"',
|
|
108
|
+
'#include "{}.itp"'.format(self._filename)],
|
|
109
|
+
'system': ['molecular system name'],
|
|
110
|
+
'molecules': ['{} {}\n'.format('test', num_mol)]}
|
|
111
|
+
|
|
112
|
+
return DataStructure(topology_data)
|
|
113
|
+
|
|
114
|
+
def get_tpr(self):
|
|
115
|
+
|
|
116
|
+
# write mdp file on disk
|
|
117
|
+
with open('{}.mdp'.format(self._filename_dir), 'w') as f:
|
|
118
|
+
f.write(self.get_mdp())
|
|
119
|
+
|
|
120
|
+
# get parameters for main system
|
|
121
|
+
sw = SwissParams(self._structure, silent=self._silent)
|
|
122
|
+
|
|
123
|
+
with open('{}.pdb'.format(self._filename_dir), 'w') as f:
|
|
124
|
+
f.write(sw.get_pdb_data())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# define unit cell and create gro file
|
|
128
|
+
if self._angles is None:
|
|
129
|
+
|
|
130
|
+
commandline_operation('gmx',
|
|
131
|
+
arguments=['editconf',
|
|
132
|
+
'-box', '{} {} {}'.format(*self._box)],
|
|
133
|
+
input_files={'-f': self._filename_dir + '.pdb'},
|
|
134
|
+
output_files={'-o': self._filename_dir + '.gro'})
|
|
135
|
+
else:
|
|
136
|
+
commandline_operation('gmx',
|
|
137
|
+
arguments=['editconf', #'-noc'
|
|
138
|
+
# '-f', self._filename_dir + '.pdb',
|
|
139
|
+
'-box {} {} {}'.format(*self._box),
|
|
140
|
+
'-bt triclinic',
|
|
141
|
+
'-angles {} {} {}'.format(*self._angles)],
|
|
142
|
+
input_files={'-f': self._filename_dir + '.pdb'},
|
|
143
|
+
output_files={'-o': self._filename_dir + '.gro'})
|
|
144
|
+
|
|
145
|
+
# create supercell from unitcell
|
|
146
|
+
commandline_operation('gmx',
|
|
147
|
+
arguments=['genconf',
|
|
148
|
+
'-nbox {} {} {}'.format(*self._supercell)],
|
|
149
|
+
input_files={'-f': self._filename_dir + '.gro'},
|
|
150
|
+
output_files={'-o': self._filename_dir + '.gro'})
|
|
151
|
+
|
|
152
|
+
# get itp and topology data
|
|
153
|
+
itp = DataStructure(sw.get_itp_data())
|
|
154
|
+
top = self.get_topology()
|
|
155
|
+
|
|
156
|
+
# Add solvent to itp and top files
|
|
157
|
+
if self._solvent is not None:
|
|
158
|
+
top, itp = self._add_solvent(top, itp)
|
|
159
|
+
|
|
160
|
+
# write topology and itp files on disk
|
|
161
|
+
with open('{}.top'.format(self._filename_dir), 'w') as f:
|
|
162
|
+
f.write(top.get_txt())
|
|
163
|
+
|
|
164
|
+
with open('{}.itp'.format(self._filename_dir), 'w') as f:
|
|
165
|
+
f.write(itp.get_txt())
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
commandline_operation('gmx',
|
|
169
|
+
arguments= ['grompp',
|
|
170
|
+
'-maxwarn {}'.format(self._maxwarn)],
|
|
171
|
+
input_files={'-f': self._filename_dir + '.mdp',
|
|
172
|
+
'-c': self._filename_dir + '.gro',
|
|
173
|
+
'-p': self._filename_dir + '.top',
|
|
174
|
+
'-po': self._filename_dir + '_log.mdp'},
|
|
175
|
+
output_files={'-o': self._filename_dir + '.tpr'})
|
|
176
|
+
|
|
177
|
+
tpr_data = gmx.read_tpr(self._filename_dir + '.tpr')
|
|
178
|
+
|
|
179
|
+
return tpr_data
|
|
180
|
+
|
|
181
|
+
def _add_solvent(self, top, itp):
|
|
182
|
+
|
|
183
|
+
sw_sol = SwissParams(self._solvent, silent=self._silent)
|
|
184
|
+
|
|
185
|
+
# solvent pdb
|
|
186
|
+
pdb_solvent = sw_sol.get_pdb_data().replace('LIG', 'SOL')
|
|
187
|
+
|
|
188
|
+
# get box that fits solvent
|
|
189
|
+
coords = []
|
|
190
|
+
for line in pdb_solvent.split('\n'):
|
|
191
|
+
if line.strip().startswith('ATOM'):
|
|
192
|
+
coords.append(line.split()[5:8])
|
|
193
|
+
|
|
194
|
+
s_box = np.max(np.array(coords, dtype=float), axis=0) - np.min(np.array(coords, dtype=float), axis=0)
|
|
195
|
+
s_box = np.maximum([1.0, 1.0, 1.0], s_box)
|
|
196
|
+
|
|
197
|
+
with open('{}_sol.pdb'.format(self._filename_dir), 'w') as f:
|
|
198
|
+
f.write(pdb_solvent)
|
|
199
|
+
|
|
200
|
+
# pdb to gro + box
|
|
201
|
+
commandline_operation('gmx',
|
|
202
|
+
arguments=['editconf',
|
|
203
|
+
'-box {} {} {}'.format(*s_box)],
|
|
204
|
+
input_files={'-f': self._filename_dir + '_sol.pdb'},
|
|
205
|
+
output_files={'-o': self._filename_dir + '_sol.gro'})
|
|
206
|
+
|
|
207
|
+
# get solvent itp
|
|
208
|
+
itp_solvent = DataStructure(sw_sol.get_itp_data().replace('LIG', 'SOL').replace('test', 'test_sol'))
|
|
209
|
+
itp.append_data('atomtypes', itp_solvent.get_data('atomtypes'))
|
|
210
|
+
itp_solvent.remove_data('')
|
|
211
|
+
itp_solvent.remove_data('atomtypes')
|
|
212
|
+
itp_solvent.remove_data('pairtypes')
|
|
213
|
+
|
|
214
|
+
# store solvent itp file
|
|
215
|
+
with open('{}_sol.itp'.format(self._filename_dir), 'w') as f:
|
|
216
|
+
f.write(itp_solvent.get_txt())
|
|
217
|
+
|
|
218
|
+
# append itp file reference to topology
|
|
219
|
+
top.append_data('', ['#include "{}_sol.itp"'.format(self._filename)])
|
|
220
|
+
|
|
221
|
+
open(self._filename_dir + '_sol.top', 'w').close() # temp file
|
|
222
|
+
|
|
223
|
+
# solvate system with solvent
|
|
224
|
+
commandline_operation('gmx',
|
|
225
|
+
arguments=['solvate',
|
|
226
|
+
'-scale {}'.format(self._solvent_scale)],
|
|
227
|
+
input_files={'-cp': self._filename_dir + '.gro',
|
|
228
|
+
'-cs': self._filename_dir + '_sol.gro',
|
|
229
|
+
'-p': self._filename_dir + '_sol.top'},
|
|
230
|
+
output_files={'-o': self._filename_dir + '.gro'})
|
|
231
|
+
|
|
232
|
+
with open(self._filename_dir + '_sol.top', 'rb') as f:
|
|
233
|
+
line_sol = f.read().decode('utf-8')
|
|
234
|
+
|
|
235
|
+
os.remove(self._filename_dir + '_sol.top') # delete temp file
|
|
236
|
+
|
|
237
|
+
# append solvent molecule info to topology
|
|
238
|
+
try:
|
|
239
|
+
n_solvent_mol = int(line_sol.split()[1])
|
|
240
|
+
top.append_data('molecules', ['test_sol {}'.format(n_solvent_mol)])
|
|
241
|
+
|
|
242
|
+
except IndexError:
|
|
243
|
+
warnings.warn('Solvent molecules do not fit in the supercell. '
|
|
244
|
+
'Decrease the solvent size or increase solvent density using solvent_scale')
|
|
245
|
+
|
|
246
|
+
return top, itp
|
|
247
|
+
|
|
248
|
+
def run_md(self, whole=True):
|
|
249
|
+
|
|
250
|
+
md = gmx.mdrun(input=self.get_tpr())
|
|
251
|
+
|
|
252
|
+
if self._silent:
|
|
253
|
+
with captured_stdout(self._filename_dir + '.log'):
|
|
254
|
+
md.run()
|
|
255
|
+
else:
|
|
256
|
+
md.run()
|
|
257
|
+
|
|
258
|
+
trajectory_file = md.output.trajectory.result()
|
|
259
|
+
md_data_dir = md.output.directory.result()
|
|
260
|
+
|
|
261
|
+
if whole:
|
|
262
|
+
commandline_operation('gmx',
|
|
263
|
+
arguments=['trjconv',
|
|
264
|
+
'-pbc whole'],
|
|
265
|
+
stdin='0',
|
|
266
|
+
input_files={'-f': trajectory_file,
|
|
267
|
+
'-s': self._filename_dir + '.tpr'},
|
|
268
|
+
output_files={'-o': md_data_dir + '/{}.trr'.format(self._filename)})
|
|
269
|
+
|
|
270
|
+
trajectory_file = md_data_dir + '/{}.trr'.format(self._filename)
|
|
271
|
+
|
|
272
|
+
trajectory = mdtraj.load_trr(trajectory_file, top=md_data_dir + '/confout.gro')
|
|
273
|
+
energy = extract_energy(md_data_dir + '/ener.edr', initial=0)
|
|
274
|
+
|
|
275
|
+
if self._delete_scratch:
|
|
276
|
+
shutil.rmtree(md.output.directory.result())
|
|
277
|
+
# shutil.rmtree(self._work_dir)
|
|
278
|
+
|
|
279
|
+
return trajectory, energy
|
|
280
|
+
|
|
281
|
+
def get_forces(self):
|
|
282
|
+
|
|
283
|
+
mod_input = gmx.modify_input(input=self.get_tpr(), parameters={'nsteps': 1, 'nstfout': 1})
|
|
284
|
+
md = gmx.mdrun(input=mod_input)
|
|
285
|
+
|
|
286
|
+
if self._silent:
|
|
287
|
+
with captured_stdout(self._filename_dir + '.log'):
|
|
288
|
+
md.run()
|
|
289
|
+
else:
|
|
290
|
+
md.run()
|
|
291
|
+
|
|
292
|
+
trajectory_file = md.output.trajectory.result()
|
|
293
|
+
md_data_dir = md.output._work_dir.result()
|
|
294
|
+
|
|
295
|
+
self._trajectory = mdtraj.load_trr(trajectory_file, top=md_data_dir + '/confout.gro')
|
|
296
|
+
forces = extract_forces(trajectory_file, self._filename_dir + '.tpr', step=0)
|
|
297
|
+
|
|
298
|
+
if self._delete_scratch:
|
|
299
|
+
shutil.rmtree(md.output._work_dir.result())
|
|
300
|
+
# shutil.rmtree(self._work_dir)
|
|
301
|
+
|
|
302
|
+
return forces
|
|
303
|
+
|
|
304
|
+
def __del__(self):
|
|
305
|
+
if os.path.isdir(self._work_dir) and self._delete_scratch:
|
|
306
|
+
shutil.rmtree(self._work_dir)
|
|
307
|
+
|
|
308
|
+
if __name__ == '__main__':
|
|
309
|
+
pass
|
gromorg/cache.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import sys, pickle, time, fcntl
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Singleton class to handle cache
|
|
6
|
+
class SimpleCache:
|
|
7
|
+
__instance__ = None
|
|
8
|
+
|
|
9
|
+
def __new__(cls, *args, **kwargs):
|
|
10
|
+
if cls.__instance__ is not None:
|
|
11
|
+
return cls.__instance__
|
|
12
|
+
|
|
13
|
+
# Py2 compatibility
|
|
14
|
+
if sys.version_info[0] < 3:
|
|
15
|
+
BlockingIOError = IOError
|
|
16
|
+
|
|
17
|
+
cls._calculation_data_filename = '.parameters.pkl'
|
|
18
|
+
cls._pickle_protocol = pickle.HIGHEST_PROTOCOL
|
|
19
|
+
|
|
20
|
+
cls.__instance__ = super(SimpleCache, cls, ).__new__(cls)
|
|
21
|
+
return cls.__instance__
|
|
22
|
+
|
|
23
|
+
def __init__(self, filename=None):
|
|
24
|
+
"""
|
|
25
|
+
Constructor
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
if filename is not None:
|
|
29
|
+
self._calculation_data_filename = filename
|
|
30
|
+
|
|
31
|
+
# python 2 compatibility
|
|
32
|
+
if not '_calculation_data_filename' in dir(self):
|
|
33
|
+
self._calculation_data_filename = '.parameters.pkl'
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
with open(self._calculation_data_filename, 'rb') as input:
|
|
37
|
+
self._calculation_data = pickle.load(input)
|
|
38
|
+
# print('Loaded data from {}'.format(self._calculation_data_filename))
|
|
39
|
+
except (IOError, EOFError, BlockingIOError):
|
|
40
|
+
# print('Creating new calculation data file {}'.format(self._calculation_data_filename))
|
|
41
|
+
self._calculation_data = {}
|
|
42
|
+
except (UnicodeDecodeError):
|
|
43
|
+
warnings.warn('Warning: Calculation data file is corrupted and will be overwritten')
|
|
44
|
+
self._calculation_data = {}
|
|
45
|
+
|
|
46
|
+
def redefine_calculation_data_filename(self, filename):
|
|
47
|
+
|
|
48
|
+
self._calculation_data_filename = filename
|
|
49
|
+
# print('Set data file to {}'.format(self._calculation_data_filename))
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
with open(self._calculation_data_filename, 'rb') as input:
|
|
53
|
+
self._calculation_data = pickle.load(input)
|
|
54
|
+
# print('Loaded data from {}'.format(self._calculation_data_filename))
|
|
55
|
+
except (IOError, EOFError):
|
|
56
|
+
# print('Creating new calculation data file {}'.format(self._calculation_data_filename))
|
|
57
|
+
self._calculation_data = {}
|
|
58
|
+
|
|
59
|
+
def store_calculation_data(self, structure, keyword, data, timeout=60):
|
|
60
|
+
|
|
61
|
+
for iter in range(100):
|
|
62
|
+
try:
|
|
63
|
+
with open(self._calculation_data_filename, 'rb') as input:
|
|
64
|
+
self._calculation_data = pickle.load(input)
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
self._calculation_data = {}
|
|
67
|
+
continue
|
|
68
|
+
except (UnicodeDecodeError):
|
|
69
|
+
warnings.warn('Warning: {} file is corrupted and will be overwritten'.format(self._calculation_data_filename))
|
|
70
|
+
self._calculation_data = {}
|
|
71
|
+
except (BlockingIOError, IOError, EOFError):
|
|
72
|
+
# print('read_try: {}'.format(iter))
|
|
73
|
+
time.sleep(timeout/100)
|
|
74
|
+
continue
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
self._calculation_data[(hash(structure), keyword)] = data
|
|
78
|
+
|
|
79
|
+
for iter in range(100):
|
|
80
|
+
try:
|
|
81
|
+
with open(self._calculation_data_filename, 'wb') as f:
|
|
82
|
+
fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
83
|
+
pickle.dump(self._calculation_data, f, self._pickle_protocol)
|
|
84
|
+
except BlockingIOError:
|
|
85
|
+
# print('read_try: {}'.format(iter))
|
|
86
|
+
time.sleep(timeout/100)
|
|
87
|
+
continue
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
def retrieve_calculation_data(self, input_qchem, keyword):
|
|
91
|
+
return self._calculation_data[(hash(input_qchem), keyword)] if (hash(input_qchem), keyword) in self._calculation_data else None
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def calculation_data(self):
|
|
95
|
+
return self._calculation_data
|
|
96
|
+
|
|
97
|
+
@calculation_data.setter
|
|
98
|
+
def calculation_data(self, calculation_data):
|
|
99
|
+
self._calculation_data = calculation_data
|
gromorg/capture.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sys, os, io
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class captured_stdout:
|
|
5
|
+
def __init__(self, filename):
|
|
6
|
+
self.old_stdout = None
|
|
7
|
+
self.fnull = None
|
|
8
|
+
self._filename = filename
|
|
9
|
+
|
|
10
|
+
def __enter__(self):
|
|
11
|
+
self.F = open(self._filename, 'w')
|
|
12
|
+
try:
|
|
13
|
+
self.old_error = os.dup(sys.stderr.fileno())
|
|
14
|
+
os.dup2(self.F.fileno(), sys.stderr.fileno())
|
|
15
|
+
except (AttributeError, io.UnsupportedOperation):
|
|
16
|
+
self.old_error = None
|
|
17
|
+
return self.F
|
|
18
|
+
|
|
19
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
20
|
+
if self.old_error is not None:
|
|
21
|
+
os.dup2(self.old_error, sys.stderr.fileno())
|
|
22
|
+
|
|
23
|
+
self.F.close()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class DataStructure:
|
|
4
|
+
def __init__(self, itp_data):
|
|
5
|
+
|
|
6
|
+
if isinstance(itp_data, str):
|
|
7
|
+
self._data = {}
|
|
8
|
+
label = ''
|
|
9
|
+
self._data[label] = []
|
|
10
|
+
for lines in itp_data.split('\n'):
|
|
11
|
+
if len(lines.strip()) > 0: # and ';' not in lines:
|
|
12
|
+
if '[' in lines and ']' in lines:
|
|
13
|
+
label = lines.split('[')[1].split(']')[0].strip()
|
|
14
|
+
self._data[label] = []
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
self._data[label].append(lines)
|
|
18
|
+
elif isinstance(itp_data, dict):
|
|
19
|
+
self._data = itp_data
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
raise TypeError('itp_data must be str or dict')
|
|
23
|
+
|
|
24
|
+
def get_txt(self):
|
|
25
|
+
itp_txt = ''
|
|
26
|
+
for label in self._data:
|
|
27
|
+
if len(label) > 0:
|
|
28
|
+
itp_txt += '\n[ {} ]\n'.format(label)
|
|
29
|
+
for line in self._data[label]:
|
|
30
|
+
itp_txt += line + '\n'
|
|
31
|
+
|
|
32
|
+
return itp_txt
|
|
33
|
+
|
|
34
|
+
def append_line(self, label, line):
|
|
35
|
+
self._data[label].append(line)
|
|
36
|
+
|
|
37
|
+
def append_data(self, label, data):
|
|
38
|
+
self._data[label] += list(data)
|
|
39
|
+
|
|
40
|
+
def get_data(self, label):
|
|
41
|
+
return self._data[label]
|
|
42
|
+
|
|
43
|
+
def remove_data(self, label):
|
|
44
|
+
del self._data[label]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
topology = DataStructure(open("../gromorg_50962/test.itp", 'r').read())
|
|
49
|
+
topology.append_line('atomtypes', 'CO 1 fr')
|
|
50
|
+
print(topology.get_txt())
|
|
51
|
+
print(topology.get_data('atomtypes'))
|
gromorg/setparam.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import openbabel
|
|
2
|
+
from gromorg.cache import SimpleCache
|
|
3
|
+
from gromorg.utils import pdb_to_xyz
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SetParams:
|
|
8
|
+
|
|
9
|
+
def __init__(self, filename='.parameters.pkl'):
|
|
10
|
+
self._basename = 'test'
|
|
11
|
+
|
|
12
|
+
self._cache = SimpleCache(filename=filename)
|
|
13
|
+
|
|
14
|
+
def get_hashable_connectivity(self, xyz_txt):
|
|
15
|
+
|
|
16
|
+
def sort_pairs(test):
|
|
17
|
+
sorted_1 = np.sort(test, axis=1)
|
|
18
|
+
|
|
19
|
+
sorted_2 = sorted(sorted_1, key=lambda x: x[1])
|
|
20
|
+
sorted_3 = sorted(sorted_2, key=lambda x: x[0])
|
|
21
|
+
|
|
22
|
+
return np.array(sorted_3)
|
|
23
|
+
|
|
24
|
+
class Connectivity():
|
|
25
|
+
def __init__(self, xyz_txt, use_types=False):
|
|
26
|
+
|
|
27
|
+
obConversion = openbabel.OBConversion()
|
|
28
|
+
obConversion.SetInAndOutFormats("xyz", "mol2")
|
|
29
|
+
|
|
30
|
+
mol = openbabel.OBMol()
|
|
31
|
+
obConversion.ReadString(mol, xyz_txt)
|
|
32
|
+
|
|
33
|
+
# mol.AddHydrogens()
|
|
34
|
+
|
|
35
|
+
atomic_data = []
|
|
36
|
+
for i in range(mol.NumAtoms()):
|
|
37
|
+
if use_types:
|
|
38
|
+
atomic_data.append((mol.GetAtom(i+1).GetFormalCharge(),
|
|
39
|
+
np.product([ord(c) for c in mol.GetAtom(i+1).GetType()]),
|
|
40
|
+
))
|
|
41
|
+
else:
|
|
42
|
+
atomic_data.append((mol.GetAtom(i + 1).GetFormalCharge(),
|
|
43
|
+
mol.GetAtom(i + 1).GetAtomicNum()
|
|
44
|
+
))
|
|
45
|
+
|
|
46
|
+
conn_index = []
|
|
47
|
+
for i in range(mol.NumBonds()):
|
|
48
|
+
conn_index.append((mol.GetBond(i).GetBeginAtomIdx()-1, mol.GetBond(i).GetEndAtomIdx()-1))
|
|
49
|
+
|
|
50
|
+
conn_index = sort_pairs(conn_index)
|
|
51
|
+
|
|
52
|
+
self._connectivity = []
|
|
53
|
+
for i, j in conn_index:
|
|
54
|
+
self._connectivity.append((atomic_data[i], atomic_data[j]))
|
|
55
|
+
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
def __hash__(self):
|
|
59
|
+
return hash(tuple(self._connectivity))
|
|
60
|
+
|
|
61
|
+
return Connectivity(xyz_txt, use_types=False)
|
|
62
|
+
|
|
63
|
+
def add_data(self, itp_file, pdb_file):
|
|
64
|
+
|
|
65
|
+
with open(itp_file, 'r') as f:
|
|
66
|
+
itp_txt = f.read()
|
|
67
|
+
|
|
68
|
+
with open(pdb_file, 'r') as f:
|
|
69
|
+
pdb_txt = f.read()
|
|
70
|
+
|
|
71
|
+
files_dict = {self._basename + '.itp': itp_txt.encode(), self._basename + '.pdb': pdb_txt.encode()}
|
|
72
|
+
xyz_txt = pdb_to_xyz(pdb_txt)
|
|
73
|
+
|
|
74
|
+
self._cache.store_calculation_data(self.get_hashable_connectivity(xyz_txt), 'zip_files', files_dict)
|
|
75
|
+
|
|
76
|
+
def get_data(self, structure):
|
|
77
|
+
|
|
78
|
+
xyz_txt = structure.get_xyz()
|
|
79
|
+
files_dict = self._cache.retrieve_calculation_data(self.get_hashable_connectivity(xyz_txt), 'zip_files')
|
|
80
|
+
|
|
81
|
+
return files_dict
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == '__main__':
|
|
85
|
+
from pyqchem.structure import Structure
|
|
86
|
+
|
|
87
|
+
data = SetParams(filename='.parameter.pkl')
|
|
88
|
+
|
|
89
|
+
data.add_data('test_param.itp', 'test_param.pdb')
|
|
90
|
+
|
|
91
|
+
structure = Structure(coordinates=[[ 0.6952, 0.0000, 0.0000],
|
|
92
|
+
[-0.6695, 0.0000, 0.0000],
|
|
93
|
+
[ 1.2321, 0.9289, 0.0000],
|
|
94
|
+
[ 1.2321, -0.9289, 0.0000],
|
|
95
|
+
[-1.2321, 0.9289, 0.0000],
|
|
96
|
+
[-1.2321, -0.9289, 0.0000]],
|
|
97
|
+
symbols=['C', 'C', 'H', 'H', 'H', 'H'],
|
|
98
|
+
charge=0,
|
|
99
|
+
multiplicity=1)
|
|
100
|
+
|
|
101
|
+
a = data.get_data(structure)
|
|
102
|
+
print(a['test.itp'])
|
|
103
|
+
print(a['test.pdb'])
|
gromorg/swisparam.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import requests as req
|
|
2
|
+
import time
|
|
3
|
+
import io
|
|
4
|
+
import tarfile
|
|
5
|
+
from openbabel import openbabel
|
|
6
|
+
from gromorg.cache import SimpleCache
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SwissParams:
|
|
11
|
+
|
|
12
|
+
BASE_URL = 'https://www.swissparam.ch:8443'
|
|
13
|
+
|
|
14
|
+
def __init__(self, structure, silent=False, approach='both'):
|
|
15
|
+
"""
|
|
16
|
+
:param structure: molecular structure object
|
|
17
|
+
:param silent: suppress progress output
|
|
18
|
+
:param approach: parameterization approach — 'both' (default), 'mmff-based', or 'match'
|
|
19
|
+
"""
|
|
20
|
+
self._structure = structure
|
|
21
|
+
self._filename = 'test'
|
|
22
|
+
self._silent = silent
|
|
23
|
+
self._approach = approach
|
|
24
|
+
|
|
25
|
+
self._tar_data = None
|
|
26
|
+
self._session_number = None
|
|
27
|
+
|
|
28
|
+
self._cache = SimpleCache()
|
|
29
|
+
|
|
30
|
+
def get_mol2(self):
|
|
31
|
+
obConversion = openbabel.OBConversion()
|
|
32
|
+
obConversion.SetInAndOutFormats("xyz", "mol2")
|
|
33
|
+
|
|
34
|
+
mol = openbabel.OBMol()
|
|
35
|
+
obConversion.ReadString(mol, self._structure.get_xyz())
|
|
36
|
+
|
|
37
|
+
return obConversion.WriteString(mol).replace('UNL1', 'test') # change the molname
|
|
38
|
+
|
|
39
|
+
def get_hashable_connectivity(self):
|
|
40
|
+
|
|
41
|
+
def sort_pairs(test):
|
|
42
|
+
sorted_1 = np.sort(test, axis=1)
|
|
43
|
+
sorted_2 = sorted(sorted_1, key=lambda x: x[1])
|
|
44
|
+
sorted_3 = sorted(sorted_2, key=lambda x: x[0])
|
|
45
|
+
return np.array(sorted_3)
|
|
46
|
+
|
|
47
|
+
class Connectivity():
|
|
48
|
+
def __init__(self, structure, use_types=False):
|
|
49
|
+
obConversion = openbabel.OBConversion()
|
|
50
|
+
obConversion.SetInAndOutFormats("xyz", "mol2")
|
|
51
|
+
|
|
52
|
+
mol = openbabel.OBMol()
|
|
53
|
+
obConversion.ReadString(mol, structure.get_xyz())
|
|
54
|
+
|
|
55
|
+
atomic_data = []
|
|
56
|
+
for i in range(mol.NumAtoms()):
|
|
57
|
+
if use_types:
|
|
58
|
+
atomic_data.append((mol.GetAtom(i+1).GetFormalCharge(),
|
|
59
|
+
np.product([ord(c) for c in mol.GetAtom(i+1).GetType()]),
|
|
60
|
+
))
|
|
61
|
+
else:
|
|
62
|
+
atomic_data.append((mol.GetAtom(i + 1).GetFormalCharge(),
|
|
63
|
+
mol.GetAtom(i + 1).GetAtomicNum()
|
|
64
|
+
))
|
|
65
|
+
|
|
66
|
+
conn_index = []
|
|
67
|
+
for i in range(mol.NumBonds()):
|
|
68
|
+
conn_index.append((mol.GetBond(i).GetBeginAtomIdx()-1, mol.GetBond(i).GetEndAtomIdx()-1))
|
|
69
|
+
|
|
70
|
+
conn_index = sort_pairs(conn_index)
|
|
71
|
+
|
|
72
|
+
self._connectivity = []
|
|
73
|
+
for i, j in conn_index:
|
|
74
|
+
self._connectivity.append((atomic_data[i], atomic_data[j]))
|
|
75
|
+
|
|
76
|
+
def __hash__(self):
|
|
77
|
+
return hash(tuple(self._connectivity))
|
|
78
|
+
|
|
79
|
+
return Connectivity(self._structure, use_types=False)
|
|
80
|
+
|
|
81
|
+
def submit_file(self):
|
|
82
|
+
if self._session_number is not None:
|
|
83
|
+
return self._session_number
|
|
84
|
+
|
|
85
|
+
url = f'{self.BASE_URL}/startparam?approach={self._approach}'
|
|
86
|
+
|
|
87
|
+
files = {'myMol2': (self._filename + '.mol2',
|
|
88
|
+
io.BytesIO(self.get_mol2().encode('utf-8')),
|
|
89
|
+
'chemical/x-mol2')}
|
|
90
|
+
|
|
91
|
+
r = req.post(url, files=files)
|
|
92
|
+
|
|
93
|
+
if not self._silent or not r.ok:
|
|
94
|
+
print(f'Server response ({r.status_code}): {r.text}')
|
|
95
|
+
|
|
96
|
+
r.raise_for_status()
|
|
97
|
+
|
|
98
|
+
for line in r.text.splitlines():
|
|
99
|
+
if 'Session number' in line:
|
|
100
|
+
self._session_number = line.split(':')[1].strip()
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
if self._session_number is None:
|
|
104
|
+
raise Exception(f'Failed to get session number. Response:\n{r.text}')
|
|
105
|
+
|
|
106
|
+
if not self._silent:
|
|
107
|
+
print(f'Submitted to SwissParam. Session number: {self._session_number}')
|
|
108
|
+
|
|
109
|
+
r.close()
|
|
110
|
+
return self._session_number
|
|
111
|
+
|
|
112
|
+
def _check_session(self, session_number):
|
|
113
|
+
"""Return the status text for a session."""
|
|
114
|
+
url = f'{self.BASE_URL}/checksession?sessionNumber={session_number}'
|
|
115
|
+
r = req.get(url)
|
|
116
|
+
r.raise_for_status()
|
|
117
|
+
text = r.text
|
|
118
|
+
r.close()
|
|
119
|
+
return text
|
|
120
|
+
|
|
121
|
+
def get_tar_file(self, wait_time=10):
|
|
122
|
+
"""Poll until the job is done, then return the raw tar.gz bytes."""
|
|
123
|
+
|
|
124
|
+
if self._tar_data is not None:
|
|
125
|
+
return self._tar_data
|
|
126
|
+
|
|
127
|
+
session_number = self.submit_file()
|
|
128
|
+
|
|
129
|
+
if not self._silent:
|
|
130
|
+
print('Waiting for SwissParam...')
|
|
131
|
+
|
|
132
|
+
n = 0
|
|
133
|
+
while True:
|
|
134
|
+
status = self._check_session(session_number)
|
|
135
|
+
|
|
136
|
+
if 'Calculation is finished' in status:
|
|
137
|
+
break
|
|
138
|
+
elif 'Calculation is in the queue' in status or 'Calculation currently running' in status:
|
|
139
|
+
if not self._silent:
|
|
140
|
+
print('\b' * (np.mod(n - 1, 10) + 7), end="", flush=True)
|
|
141
|
+
print('waiting' + '.' * np.mod(n, 10), end="", flush=True)
|
|
142
|
+
n += 1
|
|
143
|
+
time.sleep(wait_time)
|
|
144
|
+
else:
|
|
145
|
+
raise Exception(f'Unexpected status from SwissParam:\n{status}')
|
|
146
|
+
|
|
147
|
+
# Retrieve results as tar.gz
|
|
148
|
+
url = f'{self.BASE_URL}/retrievesession?sessionNumber={session_number}'
|
|
149
|
+
r = req.get(url)
|
|
150
|
+
r.raise_for_status()
|
|
151
|
+
|
|
152
|
+
if not self._silent:
|
|
153
|
+
print('.done')
|
|
154
|
+
|
|
155
|
+
self._tar_data = r.content
|
|
156
|
+
r.close()
|
|
157
|
+
|
|
158
|
+
return self._tar_data
|
|
159
|
+
|
|
160
|
+
def store_param_tar(self, filename='params.tar.gz'):
|
|
161
|
+
with open(filename, 'wb') as f:
|
|
162
|
+
f.write(self.get_tar_file())
|
|
163
|
+
|
|
164
|
+
def get_data_contents(self):
|
|
165
|
+
files_dict = self._cache.retrieve_calculation_data(self.get_hashable_connectivity(), 'tar_files')
|
|
166
|
+
|
|
167
|
+
if files_dict is None:
|
|
168
|
+
raw = self.get_tar_file()
|
|
169
|
+
|
|
170
|
+
# Debug: inspect the first bytes to identify format
|
|
171
|
+
if not self._silent:
|
|
172
|
+
print(f'Response first bytes: {raw[:16]}')
|
|
173
|
+
|
|
174
|
+
tar_bytes = io.BytesIO(raw)
|
|
175
|
+
|
|
176
|
+
# Try auto-detection: handles .tar, .tar.gz, .tar.bz2
|
|
177
|
+
try:
|
|
178
|
+
with tarfile.open(fileobj=tar_bytes, mode='r:*') as tar:
|
|
179
|
+
files_dict = {}
|
|
180
|
+
for member in tar.getmembers():
|
|
181
|
+
f = tar.extractfile(member)
|
|
182
|
+
if f is not None:
|
|
183
|
+
name = member.name.split('/')[-1]
|
|
184
|
+
files_dict[name] = f.read()
|
|
185
|
+
except tarfile.ReadError:
|
|
186
|
+
# Fallback: maybe it's still a zip
|
|
187
|
+
from zipfile import ZipFile
|
|
188
|
+
tar_bytes.seek(0)
|
|
189
|
+
with ZipFile(tar_bytes) as zf:
|
|
190
|
+
files_dict = {name.split('/')[-1]: zf.read(name) for name in zf.namelist()}
|
|
191
|
+
|
|
192
|
+
self._cache.store_calculation_data(self.get_hashable_connectivity(), 'tar_files', files_dict)
|
|
193
|
+
|
|
194
|
+
return files_dict
|
|
195
|
+
|
|
196
|
+
def get_itp_data(self):
|
|
197
|
+
contents = self.get_data_contents()
|
|
198
|
+
# Find .itp file regardless of internal naming
|
|
199
|
+
itp_files = [k for k in contents if k.endswith('.itp')]
|
|
200
|
+
if not itp_files:
|
|
201
|
+
raise Exception('No .itp file found in SwissParam results')
|
|
202
|
+
return contents[itp_files[0]].decode()
|
|
203
|
+
|
|
204
|
+
def get_pdb_data(self):
|
|
205
|
+
contents = self.get_data_contents()
|
|
206
|
+
pdb_files = [k for k in contents if k.endswith('.pdb')]
|
|
207
|
+
if not pdb_files:
|
|
208
|
+
raise Exception('No .pdb file found in SwissParam results')
|
|
209
|
+
return contents[pdb_files[0]].decode()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == '__main__':
|
|
213
|
+
from pyqchem.structure import Structure
|
|
214
|
+
|
|
215
|
+
structure = Structure(coordinates=[[ 0.6952, 0.0000, 0.0000],
|
|
216
|
+
[-0.6695, 0.0000, 0.0000],
|
|
217
|
+
[ 1.2321, 0.9289, 0.0000],
|
|
218
|
+
[ 1.2321,-0.9289, 0.0000],
|
|
219
|
+
[-1.2321, 0.9289, 0.0000],
|
|
220
|
+
[-1.2321,-0.9289, 0.0000]],
|
|
221
|
+
symbols=['C', 'C', 'H', 'H', 'H', 'H'],
|
|
222
|
+
charge=0,
|
|
223
|
+
multiplicity=1)
|
|
224
|
+
|
|
225
|
+
sp = SwissParams(structure)
|
gromorg/tools.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import mdtraj
|
|
3
|
+
from pyqchem import Structure
|
|
4
|
+
|
|
5
|
+
NM_TO_ANGSTROM = 1e1
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _label_to_element(label):
|
|
9
|
+
element = ''
|
|
10
|
+
for char in label:
|
|
11
|
+
if char.isnumeric():
|
|
12
|
+
break
|
|
13
|
+
element += char
|
|
14
|
+
|
|
15
|
+
return element
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mdtraj_to_pyqchem(trajectory, frame, residue, center=True):
|
|
19
|
+
"""
|
|
20
|
+
Extract molecule from MDTraj trajectory in pyqhcem Structure format
|
|
21
|
+
|
|
22
|
+
:param trajectory: MDTraj trajectory
|
|
23
|
+
:param frame: frame index
|
|
24
|
+
:param residue: residue index
|
|
25
|
+
:param center: if true center coordinates at geometric center
|
|
26
|
+
:return: PyQChem structure
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
res_indices = []
|
|
30
|
+
names = []
|
|
31
|
+
for res in trajectory.topology.residues:
|
|
32
|
+
res_indices.append([atom.index for atom in res.atoms])
|
|
33
|
+
names.append([atom.name for atom in res.atoms])
|
|
34
|
+
|
|
35
|
+
# check limits
|
|
36
|
+
if np.abs(frame) >= trajectory.n_frames:
|
|
37
|
+
raise Exception('Frame error. Trajectory length is {}'.format(trajectory.n_frames))
|
|
38
|
+
|
|
39
|
+
if np.abs(residue) >= len(res_indices):
|
|
40
|
+
raise Exception('Residue error. Number of residues is {}'.format(len(res_indices)))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
coordinates = trajectory.atom_slice(res_indices[residue])[frame].xyz[0] * NM_TO_ANGSTROM
|
|
44
|
+
symbols = [_label_to_element(symbol) for symbol in names[residue]]
|
|
45
|
+
|
|
46
|
+
if center:
|
|
47
|
+
gc_coordinates = np.average(coordinates, axis=0)
|
|
48
|
+
coordinates -= gc_coordinates
|
|
49
|
+
|
|
50
|
+
molecule = Structure(coordinates=coordinates,
|
|
51
|
+
symbols=symbols)
|
|
52
|
+
|
|
53
|
+
return molecule
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_cluster(trajectory, frame, residue, cutoff=5.0, center=True):
|
|
57
|
+
"""
|
|
58
|
+
Extract molecule cluster from a MDTraj trajectory
|
|
59
|
+
|
|
60
|
+
:param trajectory: MDTraj trajectory
|
|
61
|
+
:param frame: trajectory frame index
|
|
62
|
+
:param residue: central residue index
|
|
63
|
+
:param cutoff: cutoff distance in Angstroms
|
|
64
|
+
:param center: If true, center molecule at geometric center
|
|
65
|
+
:return: PyQChem structure
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# res_indices = list(range(trajectory.topology.n_residues))
|
|
69
|
+
|
|
70
|
+
res_indices = [(residue, i) for i in range(trajectory.topology.n_residues)]
|
|
71
|
+
distances, pairs = mdtraj.compute_contacts(trajectory[frame], res_indices, scheme='closest', ignore_nonprotein=True)
|
|
72
|
+
|
|
73
|
+
indices = np.argwhere(distances[0] < cutoff / NM_TO_ANGSTROM).T[0]
|
|
74
|
+
|
|
75
|
+
res_indices = []
|
|
76
|
+
names = []
|
|
77
|
+
for res in trajectory.topology.residues:
|
|
78
|
+
res_indices.append([atom.index for atom in res.atoms])
|
|
79
|
+
names.append([atom.name for atom in res.atoms])
|
|
80
|
+
|
|
81
|
+
coordinates = []
|
|
82
|
+
symbols = []
|
|
83
|
+
|
|
84
|
+
if len(indices) == 0:
|
|
85
|
+
indices = [0]
|
|
86
|
+
|
|
87
|
+
for i in indices:
|
|
88
|
+
coordinates += list(trajectory.atom_slice(res_indices[i])[frame].xyz[0] * NM_TO_ANGSTROM)
|
|
89
|
+
symbols += list([_label_to_element(symbol) for symbol in names[i]])
|
|
90
|
+
|
|
91
|
+
if center:
|
|
92
|
+
gc_coordinates = np.average(coordinates, axis=0)
|
|
93
|
+
coordinates -= gc_coordinates
|
|
94
|
+
|
|
95
|
+
molecule = Structure(coordinates=coordinates,
|
|
96
|
+
symbols=symbols)
|
|
97
|
+
|
|
98
|
+
return molecule
|
gromorg/utils.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import gmxapi as gmx
|
|
5
|
+
import os
|
|
6
|
+
from packaging import version
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def commandline_operation_v1(program, arguments, stdin=None, input_files=None, output_files=None):
|
|
10
|
+
|
|
11
|
+
from subprocess import Popen, PIPE
|
|
12
|
+
|
|
13
|
+
if isinstance(arguments, list):
|
|
14
|
+
arguments = ' '.join(arguments)
|
|
15
|
+
|
|
16
|
+
command = '{} {}'.format(program, arguments)
|
|
17
|
+
|
|
18
|
+
for key, value in input_files.items():
|
|
19
|
+
|
|
20
|
+
if isinstance(value, list):
|
|
21
|
+
value = ' '.join(value)
|
|
22
|
+
|
|
23
|
+
command += ' {} {}'.format(key, value)
|
|
24
|
+
|
|
25
|
+
for key, value in output_files.items():
|
|
26
|
+
if isinstance(value, list):
|
|
27
|
+
value = ' '.join(value)
|
|
28
|
+
|
|
29
|
+
command += ' {} {}'.format(key, value)
|
|
30
|
+
|
|
31
|
+
qchem_process = Popen(command, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True)
|
|
32
|
+
(output, err) = qchem_process.communicate(input=None if stdin is None else stdin.encode('utf-8'))
|
|
33
|
+
qchem_process.wait()
|
|
34
|
+
output = output.decode()
|
|
35
|
+
err = err.decode()
|
|
36
|
+
|
|
37
|
+
# mock class to return same output
|
|
38
|
+
# mock_class = type("mock_classi", (object,), {"run": lambda: None, "output":
|
|
39
|
+
# type("mock_class", (object,), {"erroroutput": type("mock_class", (object,), {"result": lambda: output + err}),
|
|
40
|
+
# "returncode": type("mock_class", (object,), {"result": lambda: 0})
|
|
41
|
+
# })})
|
|
42
|
+
# return mock_class
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def commandline_operation(program, arguments, stdin=None, input_files=None, output_files=None):
|
|
46
|
+
|
|
47
|
+
if version.parse(gmx.__version__) < version.parse('0.2.0'):
|
|
48
|
+
warnings.warn('Using mock function for commandline_operation for back compatibility. '
|
|
49
|
+
'Update to gmxapi >0.2 to get full functionally')
|
|
50
|
+
return commandline_operation_v1(program, arguments, stdin, input_files, output_files)
|
|
51
|
+
|
|
52
|
+
arguments_split = []
|
|
53
|
+
for arg in arguments:
|
|
54
|
+
arguments_split += arg.split()
|
|
55
|
+
|
|
56
|
+
grompp = gmx.commandline_operation(program, arguments_split,
|
|
57
|
+
stdin=stdin,
|
|
58
|
+
input_files=input_files,
|
|
59
|
+
output_files=output_files)
|
|
60
|
+
|
|
61
|
+
grompp.run()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if grompp.output.returncode.result() != 0:
|
|
65
|
+
try:
|
|
66
|
+
print(grompp.output.stderr.result())
|
|
67
|
+
except AttributeError:
|
|
68
|
+
pass
|
|
69
|
+
else:
|
|
70
|
+
dir_path = grompp.output.directory.result()
|
|
71
|
+
os.removedirs(dir_path)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def extract_energy(edr_file, output='property.xvg', initial=0, option=None):
|
|
75
|
+
"""
|
|
76
|
+
1 Bond 2 Angle 3 Proper-Dih. 4 Improper-Dih.
|
|
77
|
+
5 LJ-14 6 Coulomb-14 7 LJ-(SR) 8 Disper.-corr.
|
|
78
|
+
9 Coulomb-(SR) 10 Coul.-recip. 11 Potential 12 Kinetic-En.
|
|
79
|
+
13 Total-Energy 14 Conserved-En. 15 Temperature 16 Pres.-DC
|
|
80
|
+
17 Pressure 18 Vir-XX 19 Vir-XY 20 Vir-XZ
|
|
81
|
+
21 Vir-YX 22 Vir-YY 23 Vir-YZ 24 Vir-ZX
|
|
82
|
+
25 Vir-ZY 26 Vir-ZZ 27 Pres-XX 28 Pres-XY
|
|
83
|
+
29 Pres-XZ 30 Pres-YX 31 Pres-YY 32 Pres-YZ
|
|
84
|
+
33 Pres-ZX 34 Pres-ZY 35 Pres-ZZ 36 #Surf*SurfTen
|
|
85
|
+
37 T-LIG
|
|
86
|
+
|
|
87
|
+
:param edr_file: EDR filename
|
|
88
|
+
:param output: output filename
|
|
89
|
+
:param option: option number from above
|
|
90
|
+
:return:
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if option is None:
|
|
94
|
+
option = '11, 12, 13'
|
|
95
|
+
|
|
96
|
+
commandline_operation('gmx', ['energy'],
|
|
97
|
+
stdin=option,
|
|
98
|
+
input_files={'-f': edr_file},
|
|
99
|
+
output_files={'-o': edr_file + 'property.xvg'})
|
|
100
|
+
|
|
101
|
+
data = np.loadtxt(edr_file + 'property.xvg', comments=['#', '@'])[initial:].T
|
|
102
|
+
os.remove(edr_file + 'property.xvg')
|
|
103
|
+
|
|
104
|
+
data[1:, :] *= 0.010364272 # KJ/mol -> eV
|
|
105
|
+
|
|
106
|
+
if option == '11, 12, 13':
|
|
107
|
+
return {'time': list(data[0]),
|
|
108
|
+
'potential': list(data[1]),
|
|
109
|
+
'kinetic': list(data[2]),
|
|
110
|
+
'total': list(data[3])}
|
|
111
|
+
else:
|
|
112
|
+
return data
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def extract_forces(trajectory_file, tpr_file, step=500):
|
|
116
|
+
|
|
117
|
+
commandline_operation('gmx', ['traj', '-of'],
|
|
118
|
+
stdin='0',
|
|
119
|
+
input_files={'-f': trajectory_file,
|
|
120
|
+
'-s': tpr_file,
|
|
121
|
+
'-b': '{}'.format(step),
|
|
122
|
+
'-e': '{}'.format(step),
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
forces = np.loadtxt('force.xvg', comments=['#', '@'])[1:].reshape(-1, 3)
|
|
128
|
+
os.remove('force.xvg')
|
|
129
|
+
|
|
130
|
+
return forces * 0.00103642723 # KJ/(mol nm) to eV/ang
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def pdb_to_xyz(pdb_xyz):
|
|
134
|
+
"""
|
|
135
|
+
Convert a pdb file to xyz format
|
|
136
|
+
:param pdb_xyz:
|
|
137
|
+
:return:
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def _label_to_element(label):
|
|
141
|
+
element = ''
|
|
142
|
+
for char in label:
|
|
143
|
+
if char.isnumeric():
|
|
144
|
+
break
|
|
145
|
+
element += char.strip()
|
|
146
|
+
|
|
147
|
+
list_changes = {'CA': 'C', 'HA': 'H'}
|
|
148
|
+
if element in list_changes:
|
|
149
|
+
element = list_changes[element]
|
|
150
|
+
return element
|
|
151
|
+
|
|
152
|
+
def iter_coordinates():
|
|
153
|
+
for line in pdb_xyz.split('\n'):
|
|
154
|
+
if line.startswith('ATOM'):
|
|
155
|
+
yield np.array(line[30:55].split(), dtype=float)
|
|
156
|
+
|
|
157
|
+
def iter_symbols():
|
|
158
|
+
for line in pdb_xyz.split('\n'):
|
|
159
|
+
if line.startswith('ATOM'):
|
|
160
|
+
yield _label_to_element(line[13:16])
|
|
161
|
+
|
|
162
|
+
xyz_txt = '{}\n'.format(len(list(iter_symbols())))
|
|
163
|
+
for s, c in zip(iter_symbols(), iter_coordinates()):
|
|
164
|
+
xyz_txt += '\n' + s + ' {:10.5f} {:10.5f} {:10.5f}'.format(*c)
|
|
165
|
+
|
|
166
|
+
return xyz_txt
|
|
167
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gromorg
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: Simple Gromacs python wrapper
|
|
5
|
+
Home-page: https://github.com/abelcarreras/gromorg
|
|
6
|
+
Author: Abel Carreras
|
|
7
|
+
Author-email: abelcarreras83@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: openbabel
|
|
14
|
+
Requires-Dist: gmxapi
|
|
15
|
+
Requires-Dist: mdtraj
|
|
16
|
+
Requires-Dist: lxml
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
[](https://gromorg.readthedocs.io/en/latest/?badge=latest)
|
|
28
|
+
[](https://badge.fury.io/py/gromorg)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
GroMorG
|
|
32
|
+
=======
|
|
33
|
+
|
|
34
|
+
A python tool to automate the calculation of MD simulations of small organic molecules using gromacs.
|
|
35
|
+
Online documentation is available at https://gromorg.readthedocs.io/
|
|
36
|
+
|
|
37
|
+
Features
|
|
38
|
+
--------
|
|
39
|
+
- Link first principles & molecular mechanics calculations using PyQchem
|
|
40
|
+
- Get parameters from SwissParam (https://www.swissparam.ch) automatically from molecular structure
|
|
41
|
+
- Clean run without intermediate files
|
|
42
|
+
- Add solvent molecules to the system
|
|
43
|
+
- Extract structures from the trajectory (including surrounding solvent molecules)
|
|
44
|
+
|
|
45
|
+
Requirements
|
|
46
|
+
------------
|
|
47
|
+
- PyQchem (https://github.com/abelcarreras/PyQchem)
|
|
48
|
+
- Gromacs 2025+ (gmxapi) (http://www.gromacs.org)
|
|
49
|
+
- Openbabel v3.x (python API) (http://openbabel.org)
|
|
50
|
+
- MDtraj (https://www.mdtraj.org)
|
|
51
|
+
|
|
52
|
+
Basic example
|
|
53
|
+
-------------
|
|
54
|
+
```python
|
|
55
|
+
from gromorg import GromOrg
|
|
56
|
+
import matplotlib.pyplot as plt
|
|
57
|
+
from pyqchem.structure import Structure
|
|
58
|
+
|
|
59
|
+
# Define moleule as PyQchem Structure
|
|
60
|
+
structure = Structure(coordinates=[[ 0.6695, 0.0000, 0.0000],
|
|
61
|
+
[-0.6695, 0.0000, 0.0000],
|
|
62
|
+
[ 1.2321, 0.9289, 0.0000],
|
|
63
|
+
[ 1.2321,-0.9289, 0.0000],
|
|
64
|
+
[-1.2321, 0.9289, 0.0000],
|
|
65
|
+
[-1.2321,-0.9289, 0.0000]],
|
|
66
|
+
symbols=['C', 'C', 'H', 'H', 'H', 'H'])
|
|
67
|
+
|
|
68
|
+
# Define Gromacs parameters
|
|
69
|
+
gmx_params = {
|
|
70
|
+
'integrator': 'md-vv', # Verlet integrator
|
|
71
|
+
'nsteps': 5000, # 0.001 * 5000 = 50 ps
|
|
72
|
+
'dt': 0.001, # time step, in ps
|
|
73
|
+
# Temperature coupling is on
|
|
74
|
+
'tcoupl': 'nose-hoover', # Nose-Hoover thermostat
|
|
75
|
+
'tau_t': 0.3, # time constant, in ps
|
|
76
|
+
'ref_t': 300, # reference temperature, one for each group, in K
|
|
77
|
+
# Bond parameters
|
|
78
|
+
'gen_vel': 'yes', # assign velocities from Maxwell distributio
|
|
79
|
+
'gen_temp': 300, # temperature for Maxwell distribution
|
|
80
|
+
'gen_seed': -1, # generate a random seed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Define simulation
|
|
84
|
+
calc = GromOrg(structure,
|
|
85
|
+
params=gmx_params, # MDP parms
|
|
86
|
+
box=[10, 10, 10], # a, b, c in angstrom
|
|
87
|
+
angles=[90, 90, 90], # alpha, beta, gamma in degree
|
|
88
|
+
supercell=[3, 3, 3],
|
|
89
|
+
delete_scratch=True, # delete temp files when finished
|
|
90
|
+
silent=False) # print MD log info in screen
|
|
91
|
+
|
|
92
|
+
# Run simulation and get trajectory (MDTraj) and energy
|
|
93
|
+
trajectory, energy = calc.run_md(whole=True)
|
|
94
|
+
|
|
95
|
+
# plot energies
|
|
96
|
+
plt.plot(energy['potential'], label='potential')
|
|
97
|
+
plt.plot(energy['kinetic'], label='kinetic')
|
|
98
|
+
plt.plot(energy['total'], label='total')
|
|
99
|
+
plt.legend()
|
|
100
|
+
plt.show()
|
|
101
|
+
|
|
102
|
+
# Store trajectory
|
|
103
|
+
trajectory.save('trajectory.gro')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Contact info
|
|
107
|
+
------------
|
|
108
|
+
Abel Carreras
|
|
109
|
+
abelcarreras83@gmail.com
|
|
110
|
+
|
|
111
|
+
Donostia International Physics Center (DIPC)
|
|
112
|
+
Donostia-San Sebastian, Euskadi (Spain)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
gromorg/__init__.py,sha256=SndJPw0Wza9F_eKQ9xO8P3WeUSn2AP2zp7jsEg2ggOo,12796
|
|
2
|
+
gromorg/cache.py,sha256=EDTicUumhZ-SKvXhVPxE3_SLgA1wyG5jWAsZwbk_kes,3792
|
|
3
|
+
gromorg/capture.py,sha256=-TXe73KEDnu9SF22A9a7m8VyLa1UJcYoyxIGMVp02eM,656
|
|
4
|
+
gromorg/data_structure.py,sha256=DUcGUfOA8SCP12Dpd3WxOta2rsYpx1ywFdmnudSyN-A,1495
|
|
5
|
+
gromorg/setparam.py,sha256=8Gc3Lc1r2vtzqnlmCGJZD0It35FdGiBsr0H3h4eTUo0,3522
|
|
6
|
+
gromorg/swisparam.py,sha256=Q-Gv_2f-pqQ-GcLpBATk5l39sGignS4q3h4Jlo0oPoQ,8054
|
|
7
|
+
gromorg/tools.py,sha256=m6-LRTm_rfBNcm168Kxfnme6SyjvIprapa2n0GQwDok,2951
|
|
8
|
+
gromorg/utils.py,sha256=xXK_BcQXb976298mlOSMVQ2wwv55bAHkdP3dEJVjthw,5796
|
|
9
|
+
gromorg-0.3.dist-info/licenses/LICENSE,sha256=8WmMounu4uoRjJ0nhdKiAoWHs7IgG110IulVxkDxS4g,1086
|
|
10
|
+
gromorg-0.3.dist-info/METADATA,sha256=am1Ns47_WzwZLxgTtmXQC1boE-qIU_49WiSA7Nm-6ao,3949
|
|
11
|
+
gromorg-0.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
gromorg-0.3.dist-info/top_level.txt,sha256=Ojqsi-boL37ckmXZmlYHyoqctfIjKTW2VIj-n3SxN6g,8
|
|
13
|
+
gromorg-0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Abel Carreras Conill
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gromorg
|