packmol-memgen-minimal 1.1.16__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 (71) hide show
  1. packmol_memgen/__init__.py +2 -0
  2. packmol_memgen/__version__.py +34 -0
  3. packmol_memgen/data/LICENSE.Apache-2.0 +201 -0
  4. packmol_memgen/data/extra_solvents.lib +789 -0
  5. packmol_memgen/data/frcmod.lipid_ext +97 -0
  6. packmol_memgen/data/frcmod.solvents +129 -0
  7. packmol_memgen/data/insane_lipids.txt +138 -0
  8. packmol_memgen/data/insane_solvents.txt +45 -0
  9. packmol_memgen/data/leaprc.extra_solvents +42 -0
  10. packmol_memgen/data/leaprc.lipid_ext +48 -0
  11. packmol_memgen/data/lipid_ext.lib +12312 -0
  12. packmol_memgen/data/martini_v3.0.0.itp +356605 -0
  13. packmol_memgen/data/memgen.parm +4082 -0
  14. packmol_memgen/data/pdbs.tar.gz +0 -0
  15. packmol_memgen/data/solvent.parm +14 -0
  16. packmol_memgen/example/example.sh +31 -0
  17. packmol_memgen/lib/__init__.py +0 -0
  18. packmol_memgen/lib/amber.py +77 -0
  19. packmol_memgen/lib/charmmlipid2amber/__init__.py +0 -0
  20. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.csv +7164 -0
  21. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.py +225 -0
  22. packmol_memgen/lib/pdbremix/LICENSE +21 -0
  23. packmol_memgen/lib/pdbremix/__init__.py +0 -0
  24. packmol_memgen/lib/pdbremix/_version.py +1 -0
  25. packmol_memgen/lib/pdbremix/amber.py +1103 -0
  26. packmol_memgen/lib/pdbremix/asa.py +227 -0
  27. packmol_memgen/lib/pdbremix/data/aminoacid.pdb +334 -0
  28. packmol_memgen/lib/pdbremix/data/binaries.json +26 -0
  29. packmol_memgen/lib/pdbremix/data/charmm22.parameter +2250 -0
  30. packmol_memgen/lib/pdbremix/data/charmm22.topology +1635 -0
  31. packmol_memgen/lib/pdbremix/data/color_b.py +682 -0
  32. packmol_memgen/lib/pdbremix/data/hin.lib +130 -0
  33. packmol_memgen/lib/pdbremix/data/hydroxide.lib +88 -0
  34. packmol_memgen/lib/pdbremix/data/make_chi.py +92 -0
  35. packmol_memgen/lib/pdbremix/data/opls.parameter +1108 -0
  36. packmol_memgen/lib/pdbremix/data/opls.topology +1869 -0
  37. packmol_memgen/lib/pdbremix/data/phd.frcmod +82 -0
  38. packmol_memgen/lib/pdbremix/data/phd.leaprc +4 -0
  39. packmol_memgen/lib/pdbremix/data/phd.prepin +35 -0
  40. packmol_memgen/lib/pdbremix/data/template.pdb +334 -0
  41. packmol_memgen/lib/pdbremix/data/znb.frcmod +24 -0
  42. packmol_memgen/lib/pdbremix/data/znb.leaprc +7 -0
  43. packmol_memgen/lib/pdbremix/data/znb.lib +69 -0
  44. packmol_memgen/lib/pdbremix/data.py +264 -0
  45. packmol_memgen/lib/pdbremix/fetch.py +102 -0
  46. packmol_memgen/lib/pdbremix/force.py +627 -0
  47. packmol_memgen/lib/pdbremix/gromacs.py +978 -0
  48. packmol_memgen/lib/pdbremix/lib/__init__.py +0 -0
  49. packmol_memgen/lib/pdbremix/lib/docopt.py +579 -0
  50. packmol_memgen/lib/pdbremix/lib/pyqcprot.py +305 -0
  51. packmol_memgen/lib/pdbremix/namd.py +1078 -0
  52. packmol_memgen/lib/pdbremix/pdbatoms.py +543 -0
  53. packmol_memgen/lib/pdbremix/pdbtext.py +120 -0
  54. packmol_memgen/lib/pdbremix/protein.py +311 -0
  55. packmol_memgen/lib/pdbremix/pymol.py +480 -0
  56. packmol_memgen/lib/pdbremix/rmsd.py +203 -0
  57. packmol_memgen/lib/pdbremix/simulate.py +420 -0
  58. packmol_memgen/lib/pdbremix/spacehash.py +73 -0
  59. packmol_memgen/lib/pdbremix/trajectory.py +286 -0
  60. packmol_memgen/lib/pdbremix/util.py +273 -0
  61. packmol_memgen/lib/pdbremix/v3.py +16 -0
  62. packmol_memgen/lib/pdbremix/v3array.py +482 -0
  63. packmol_memgen/lib/pdbremix/v3numpy.py +350 -0
  64. packmol_memgen/lib/pdbremix/volume.py +155 -0
  65. packmol_memgen/lib/utils.py +1017 -0
  66. packmol_memgen/main.py +2827 -0
  67. packmol_memgen_minimal-1.1.16.dist-info/METADATA +664 -0
  68. packmol_memgen_minimal-1.1.16.dist-info/RECORD +71 -0
  69. packmol_memgen_minimal-1.1.16.dist-info/WHEEL +4 -0
  70. packmol_memgen_minimal-1.1.16.dist-info/entry_points.txt +2 -0
  71. packmol_memgen_minimal-1.1.16.dist-info/licenses/LICENSE +338 -0
@@ -0,0 +1,978 @@
1
+ # encoding: utf-8
2
+
3
+ __doc__ = """
4
+
5
+ Interface to the GROMACS molecular-dynamics package.
6
+
7
+ The library is split into three sections:
8
+
9
+ 1. Read and write restart files
10
+ 2. Generate restart files from PDB
11
+ 3. Run simulations from restart files
12
+ 4. Read trajectories with some post-processing
13
+
14
+ Copyright (C) 2009, 2014, Bosco K. Ho
15
+ """
16
+
17
+ import os
18
+ import shutil
19
+ import copy
20
+ import glob
21
+ import xdrlib
22
+
23
+ from . import util
24
+ from . import pdbatoms
25
+ from . import v3
26
+ from . import data
27
+ from . import pdbtext
28
+ from . import protein
29
+
30
+
31
+ # ##########################################################
32
+
33
+ # 1. Reading and writing restart files
34
+
35
+ # In PDBREMIX, restart files for GROMACS are assumed to
36
+ # have the naming scheme:
37
+
38
+ # 1. topology file: sim.top
39
+ # 2. coordinate/velocity file: sim.gro
40
+
41
+ # Parsers have been written to read .top and .gro files into
42
+ # Python structures, and to write these back into .top and .gro
43
+ # files, and to convert them into .pdb files
44
+
45
+ # The units used in GROMACS are:
46
+ # - positions: nanometers
47
+ # - velocities: nanometers/picosecond
48
+ # - force constant: kJ/mol/nm^2
49
+
50
+ # which will be converted into
51
+ # - angstroms
52
+ # - picoseconds
53
+ # - kilocalories
54
+
55
+
56
+ def read_top(top):
57
+ """
58
+ Returns a list of (mass, charge, chain_id) for the atoms
59
+ in the topology file.
60
+ """
61
+ util.check_output(top)
62
+ lines = open(top).readlines()
63
+
64
+ atoms = []
65
+ is_chain_topologies = False
66
+ chain=" "
67
+ top_dir = os.path.dirname(top)
68
+ for l in lines:
69
+ if not is_chain_topologies:
70
+ if 'chain topologies' in l:
71
+ is_chain_topologies = True
72
+ continue
73
+ if l.startswith("#include"):
74
+ itp = l.split()[1][1:-1]
75
+ itp = os.path.join(top_dir, itp)
76
+ if os.path.isfile(itp):
77
+ full_chain_name = os.path.splitext(itp)[0]
78
+ chain = full_chain_name.split('_')[-1]
79
+ these_atoms = read_top(itp, chain)
80
+ atoms.extend(these_atoms)
81
+ if l.startswith(";"):
82
+ break
83
+
84
+ is_atoms = False
85
+ qtot = None
86
+ for l in lines:
87
+ if not is_atoms:
88
+ if '[ atoms ]' in l:
89
+ is_atoms = True
90
+ continue
91
+ if l.startswith('['):
92
+ break
93
+ if l.startswith(";"):
94
+ continue
95
+ if not l.strip():
96
+ continue
97
+ words = l.split()
98
+ n = int(words[0])
99
+ res_num = int(words[2])
100
+ res_type = words[3]
101
+ q = float(words[6])
102
+ mass = float(words[7])
103
+ atoms.append((mass, q, chain))
104
+
105
+ return atoms
106
+
107
+
108
+ def AtomFromGroLine(line):
109
+ """
110
+ Returns an Atom object from a .gro atom line.
111
+ """
112
+ atom = pdbatoms.Atom()
113
+ atom.res_num = int(line[0:5])
114
+ atom.res_type = line[5:8].strip()
115
+ atom.type = line[10:15].strip(" ")
116
+ atom.element = data.guess_element(
117
+ atom.res_type, line[12:15])
118
+ atom.num = int(line[15:20])
119
+ # 10 x multiplier converts from nm to angstroms
120
+ x = 10.0*float(line[20:28])
121
+ y = 10.0*float(line[28:36])
122
+ z = 10.0*float(line[36:44])
123
+ v3.set_vector(atom.pos, x, y, z)
124
+ if len(line) > 62:
125
+ # 10 x multiplier converts from nm to angstroms
126
+ x = 10.0*float(line[44:52])
127
+ y = 10.0*float(line[52:60])
128
+ z = 10.0*float(line[60:68])
129
+ v3.set_vector(atom.vel, x, y, z)
130
+ return atom
131
+
132
+
133
+ def convert_to_pdb_atom_names(soup):
134
+ """
135
+ For the soup structure, converts residue
136
+ atom names peculiar to .gro into standard PDB names
137
+ for interacting with other systems.
138
+ """
139
+ for res in soup.residues():
140
+ if res.type == "ILE":
141
+ if res.has_atom("CD"):
142
+ res.change_atom_type("CD", "CD1")
143
+ if res.has_atom("OC2"):
144
+ res.change_atom_type("OC2", "OXT")
145
+ if res.has_atom("OC1"):
146
+ res.change_atom_type("OC1", "O")
147
+ if res.type == "SOL":
148
+ res.set_type("HOH")
149
+ res.change_atom_type("HW1", "1H")
150
+ res.change_atom_type("HW2", "2H")
151
+ res.change_atom_type("OW", "O")
152
+ if res.type in data.solvent_res_types:
153
+ for a in res.atoms():
154
+ a.is_hetatm = True
155
+ for atom in res.atoms():
156
+ if atom.type[-1].isdigit() and atom.type[0] == "H":
157
+ new_atom_type = atom.type[-1] + atom.type[:-1]
158
+ res.change_atom_type(atom.type, new_atom_type)
159
+
160
+
161
+ def convert_to_gromacs_atom_names(soup):
162
+ """
163
+ For writing into .gro files, converts PDB atom names
164
+ into GROMACS specific atom names.
165
+ """
166
+ for res in soup.residues():
167
+ if res.type == "ILE":
168
+ if res.has_atom("CD1"):
169
+ res.change_atom_type("CD1", "CD")
170
+ if res.has_atom("OXT"):
171
+ res.change_atom_type("OXT", "OC2")
172
+ if res.has_atom("O"):
173
+ res.change_atom_type("O", "OC1")
174
+ if res.type == "HOH":
175
+ res.set_type("SOL")
176
+ res.change_atom_type("1H", "HW1")
177
+ res.change_atom_type("2H", "HW2")
178
+ res.change_atom_type("O", "OW")
179
+ if res.type in data.solvent_res_types:
180
+ for a in res.atoms():
181
+ a.is_hetatm = False
182
+ for atom in res.atoms():
183
+ if atom.type[0].isdigit() and atom.type[1] == "H":
184
+ new_atom_type = atom.type[1:] + atom.type[0]
185
+ res.change_atom_type(atom.type, new_atom_type)
186
+
187
+
188
+ def soup_from_top_gro(top, gro, skip_solvent=False):
189
+ """
190
+ Returns a Soup built from GROMACS restart files.
191
+ If skip_solvent=True, will skip all solvent molecules.
192
+ """
193
+ util.check_output(top)
194
+ util.check_output(gro)
195
+
196
+ soup = pdbatoms.Soup()
197
+ soup.remaining_text = ""
198
+ soup.n_remaining_text = 0
199
+
200
+ atoms = []
201
+
202
+ # Read from .gro because .top does not contain water
203
+ # residue information, which is "inferred"
204
+ lines = open(gro, 'r').readlines()
205
+ for i_line, line in enumerate(lines[2:-1]):
206
+ atom = AtomFromGroLine(line)
207
+ if skip_solvent and atom.res_type == "SOL":
208
+ soup.remaining_text = "".join(lines[i_line+2:-1])
209
+ soup.n_remaining_text = len(lines[i_line+2:-1])
210
+ break
211
+ atoms.append(atom)
212
+ soup.box = [float(w) for w in lines[-1].split()]
213
+
214
+ for atom, (mass, q, chain_id) in zip(atoms, read_top(top)):
215
+ atom.mass = mass
216
+ atom.charge = q
217
+
218
+ curr_res_num = -1
219
+ for a in atoms:
220
+ if curr_res_num != a.res_num:
221
+ res = pdbatoms.Residue(
222
+ a.res_type, a.chain_id, a.res_num)
223
+ soup.append_residue(res.copy())
224
+ curr_res_num = a.res_num
225
+ soup.insert_atom(-1, a)
226
+
227
+ convert_to_pdb_atom_names(soup)
228
+ protein.find_chains(soup)
229
+
230
+ return soup
231
+
232
+
233
+ def write_soup_to_gro(in_soup, gro):
234
+ soup = in_soup.copy()
235
+ convert_to_gromacs_atom_names(soup)
236
+ f = open(gro, 'w')
237
+ f.write("Generated by gromacs.py\n")
238
+ atoms = soup.atoms()
239
+ n_atom = len(atoms) + soup.n_remaining_text
240
+ f.write(str(n_atom) + '\n')
241
+ for a in atoms:
242
+ # GRO doesn't care about numbering so wrap when full
243
+ res_num = a.res_num % 100000
244
+ atom_num = a.num % 100000
245
+ # 0.1 x multiplier converts from angs back to nm
246
+ s = "%5d%-5s%5s%5d%8.3f%8.3f%8.3f%8.4f%8.4f%8.4f\n" % \
247
+ (res_num, a.res_type, a.type, atom_num,
248
+ a.pos[0]*0.1, a.pos[1]*0.1, a.pos[2]*0.1,
249
+ a.vel[0]*0.1, a.vel[1]*0.1, a.vel[2]*0.1)
250
+ f.write(s)
251
+ if soup.remaining_text:
252
+ f.write(soup.remaining_text)
253
+ f.write("%10.5f%10.5f%10.5f\n" % (soup.box[0], soup.box[1], soup.box[2]))
254
+ f.close()
255
+
256
+
257
+ # The following functions wrap the above functions into a
258
+ # standard API that does not explicitly reference GROMACS
259
+
260
+ def expand_restart_files(basename):
261
+ """Returns expanded restart files based on basename"""
262
+ top = os.path.abspath(basename + '.top')
263
+ crds = os.path.abspath(basename + '.gro')
264
+ vels = '' # dummy file for cross-package interface
265
+ return top, crds, vels
266
+
267
+
268
+ def get_restart_files(basename):
269
+ """Returns restart files only if they exist"""
270
+ top, crds, vels = expand_restart_files(basename)
271
+ util.check_files(top, crds)
272
+ return top, crds, vels
273
+
274
+
275
+ def soup_from_restart_files(top, crds, vels, skip_solvent=False):
276
+ """Reads pdbatoms.Soup object from restart files."""
277
+ return soup_from_top_gro(top, crds, skip_solvent)
278
+
279
+
280
+ def write_soup_to_crds_and_vels(soup, basename):
281
+ """From soup, writes out the coordinate/velocities, used for pulsing"""
282
+ write_soup_to_gro(soup, basename + '.gro')
283
+ return basename + '.gro', ''
284
+
285
+
286
+ def convert_restart_to_pdb(basename, pdb):
287
+ """Converts restart files with basename into PDB file"""
288
+ top, crds, vels = get_restart_files(basename)
289
+ soup = soup_from_restart_files(top, crds, vels)
290
+ soup.write_pdb(pdb)
291
+
292
+
293
+ # ##########################################################
294
+
295
+ # # 2. Generate restart files from PDB
296
+
297
+ # The restart files used for PDBREMIX assumes a consistent file naming.
298
+ # For a given basename `sim`, the files are:
299
+ # 1. topology file: sim.top
300
+ # 2. coordinate/velocity file: sim.gro
301
+ # 3. restraint file: sim.pos_re.itp
302
+
303
+ # To generate a topology file from the PDB file:
304
+ # - handles multiple protein chains
305
+ # - hydrogens are removed and then regenerated by GROMACS
306
+ # - disulfide bonds are auto-detected
307
+ # - charged residue protonation states are auto-detected
308
+ # - explicit water in cubic box with 10.0 angstrom buffer
309
+ # - counterions to neutralize the system
310
+ # - GROMACS4.5: AMBER99 force-field
311
+ # - GROMACS4.0: GROMOS96 force-field
312
+
313
+ # Binaries used to generate restart files:
314
+ # 1. pdb2gmx - create topologies for atoms in PDB
315
+ # 2. editconf - define cubic box
316
+ # 3. genbox - add explicit water molecules
317
+ # 4. grompp - build .tpr file for energy terms
318
+ # 5. genion - add counterions based on .tpr file
319
+
320
+
321
+ def delete_backup_files(tag):
322
+ util.clean_fname(*util.re_glob('*', '^#' + tag))
323
+
324
+
325
+ force_field_mdp = """
326
+ ; Bond parameters
327
+ constraints = hbonds ; bonds from heavy atom to H, constrained
328
+ continuation = yes ; first dynamics run
329
+ ; constraints = all-bonds ; all bonds (even heavy atom-H bonds) constrained
330
+ ; constraint-algorithm = lincs ; holonomic constraints
331
+ ; lincs_iter = 1 ; accuracy of LINCS
332
+ ; lincs_order = 4 ; also related to accuracy
333
+
334
+ ; Neighborsearching
335
+ ns_type = grid ; search neighboring grid cels
336
+ nstlist = 5 ; 10 fs
337
+ rlist = 1.0 ; short-range neighborlist cutoff (in nm)
338
+
339
+ ; Periodic boundary conditions
340
+ pbc = xyz ; 3-dimensional periodic boundary conditions (xyz|no)
341
+
342
+ ; Electrostatics
343
+ coulombtype = PME ; Particle Mesh Ewald for long-range electrostatics
344
+ pme_order = 4 ; cubic interpolation
345
+ fourierspacing = 0.16 ; grid spacing for FFT
346
+ rcoulomb = 1.0 ; short-range electrostatic cutoff (in nm)
347
+ rvdw = 1.0 ; short-range van der Waals cutoff (in nm)
348
+
349
+ ; Add also refcoord-scaling to work with position restraints and pressure coupling
350
+ refcoord-scaling = all
351
+
352
+ ; Dispersion correction
353
+ DispCorr = EnerPres ; account for cut-off vdW scheme
354
+ """
355
+
356
+
357
+ ions_mdp = """
358
+ ; ions.mdp - used as input into grompp to generate ions.tpr
359
+ ; Parameters describing what to do, when to stop and what to save
360
+ integrator = steep ; Algorithm (steep = steepest descent minimization)
361
+ emtol = 1000.0 ; Stop minimization when the maximum force < 1000.0 kJ/mol/nm
362
+ emstep = 0.01 ; Energy step size
363
+ nsteps = 50000 ; Maximum number of (minimization) steps to perform
364
+ """
365
+
366
+
367
+ def neutralize_system_with_salt(
368
+ in_top, in_gro, basename, force_field):
369
+ """
370
+ Takes a .top file and adds counterions to neutralize the overall
371
+ charge of system, and saves to `basename.gro`.
372
+ """
373
+ # Calculate overall charege in the .top file
374
+ qtot = sum([q for mass, q, chain in read_top(in_top)])
375
+ counter_ion_charge = -int(round(qtot))
376
+ if counter_ion_charge == 0:
377
+ shutil.copy(in_gro, basename + '.gro')
378
+ return
379
+
380
+ # Create a .tpr paramater file for genion to find low-energy sites
381
+ in_mdp = basename + '.salt.grompp.mdp'
382
+ open(in_mdp, 'w').write(ions_mdp + force_field_mdp)
383
+ top = basename + '.top'
384
+ if in_top != top:
385
+ shutil.copy(in_top, top)
386
+ tpr = basename + '.salt.tpr'
387
+ out_mdp = basename + '.mdp'
388
+ data.binary(
389
+ 'grompp',
390
+ '-f %s -po %s -c %s -p %s -o %s' \
391
+ % (in_mdp, out_mdp, in_gro, top, tpr),
392
+ basename + '.salt.grompp')
393
+ util.check_files(tpr)
394
+
395
+ # Use genion to generate a gro of system with counterions
396
+ gro = basename + '.gro'
397
+ # Genion requires user input "SOL" to choose solvent for replacement
398
+ input_fname = basename + '.salt.genion.in'
399
+ open(input_fname, 'w').write('SOL')
400
+ # Different versions of Gromacs use different counterions
401
+ charge_str = ""
402
+ if 'GROMACS4.5' in force_field:
403
+ charge_str = " -pname NA -nname CL "
404
+ elif 'GROMACS4.0' in force_field:
405
+ charge_str = " -pname NA+ -nname CL- "
406
+ else:
407
+ raise ValueError("Cannot recognize force_field " + force_field)
408
+ if counter_ion_charge > 0:
409
+ charge_str += " -np %d " % counter_ion_charge
410
+ else:
411
+ charge_str += " -nn %d " % abs(counter_ion_charge)
412
+ log = basename + '.salt.genion.log'
413
+ data.binary(
414
+ 'genion',
415
+ '-g %s -s %s -o %s -p %s -neutral %s' % \
416
+ (log, tpr, gro, top, charge_str),
417
+ basename + '.salt.genion',
418
+ input_fname)
419
+ util.check_files(gro)
420
+
421
+
422
+ def pdb_to_top_and_crds(force_field, pdb, basename, solvent_buffer=10):
423
+ """
424
+ Converts a PDB file into GROMACS topology and coordinate files,
425
+ and fully converted PDB file. These constitute the restart files
426
+ of a GROMACS simulation.
427
+ """
428
+ util.check_files(pdb)
429
+ full_pdb = os.path.abspath(pdb)
430
+ save_dir = os.getcwd()
431
+
432
+ # All intermediate files placed into a subdirectory
433
+ util.goto_dir(basename + '.solvate')
434
+
435
+ # Remove all but protein heavy atoms in a single clean conformation
436
+ pdb = basename + '.clean.pdb'
437
+ pdbtext.clean_pdb(full_pdb, pdb)
438
+
439
+ # Generate protein topology in pdb2gmx_gro using pdb2gmx
440
+ pdb2gmx_gro = basename + '.pdb2gmx.gro'
441
+ top = basename + '.top'
442
+ itp = basename + '_posre.itp'
443
+ # Choose force field based on GROMACS version
444
+ if 'GROMACS4.5' in force_field:
445
+ ff = 'amber99'
446
+ elif 'GROMACS4.0' in force_field:
447
+ ff = 'G43a1'
448
+ else:
449
+ raise ValueError("Couldn't work out pdb2gmx for " + force_field)
450
+ args = '-ignh -ff %s -water spc -missing -f %s -o %s -p %s -i %s -chainsep id_or_ter -merge all' \
451
+ % (ff, pdb, pdb2gmx_gro, top, itp)
452
+ data.binary('pdb2gmx', args, basename+'.pdb2gmx')
453
+ util.check_files(pdb2gmx_gro)
454
+
455
+ # Now add a box with editconf
456
+ box_gro = basename + '.box.gro'
457
+ solvent_buffer_in_nm = solvent_buffer/10.0
458
+ data.binary(
459
+ 'editconf',
460
+ '-f %s -o %s -c -d %f -bt cubic' \
461
+ % (pdb2gmx_gro, box_gro, solvent_buffer_in_nm),
462
+ basename+'.box')
463
+ util.check_files(box_gro)
464
+
465
+ # Given box dimensions, can now populate with explict waters
466
+ solvated_gro = basename + '.solvated.gro'
467
+ data.binary(
468
+ 'genbox',
469
+ '-cp %s -cs spc216.gro -o %s -p %s' \
470
+ % (box_gro, solvated_gro, top),
471
+ '%s.solvated' % basename)
472
+ util.check_files(solvated_gro)
473
+
474
+ # Neutralize with counterions using genion to place ions
475
+ # based on energy parameters processed by grompp
476
+ gro = basename + '.gro'
477
+ neutralize_system_with_salt(top, solvated_gro, basename, force_field)
478
+ util.check_files(gro)
479
+
480
+ # Make a reference PDB file from restart files for viewing and restraints
481
+ convert_restart_to_pdb(basename, basename+'.pdb')
482
+
483
+ # Copy finished restart files back into original directory
484
+ fnames = util.re_glob(
485
+ '*', os.path.basename(basename) + r'[^\.]*\.(pdb|itp|gro|mdp|top)$')
486
+ for fname in fnames:
487
+ shutil.copy(fname, save_dir)
488
+
489
+ # Cleanup
490
+ delete_backup_files(basename)
491
+ os.chdir(save_dir)
492
+
493
+ return top, gro
494
+
495
+
496
+ # ##########################################################
497
+
498
+ # # 3. Run simulations from restart files
499
+
500
+ # Simulation approach:
501
+ # - cubic periodic box
502
+ # - optional positional restraints: 100 kcal/mol/angs**2
503
+ # - PME electrostatics on the periodic box
504
+ # - Langevin thermostat for constant temperature
505
+ # - Nose-Hoover barometer with flexible periodic box size
506
+ # - constraints on hydrogen atoms bonded to heavy atoms
507
+
508
+ # Binaries used:
509
+ # 1. grompp - process topology files for .tpr file
510
+ # 2. mdrun - run MD using .tpr on the .gro file
511
+ # mpi version - mpiexec -np 8 /...dir.../gromacs-4.0.7/bin/mdrun
512
+
513
+ # Files for trajectories:
514
+ # 1. coordinate trajectory: md.trr
515
+ # 2. restart coordinate/velocity: md.gro
516
+
517
+
518
+ minimization_parms = {
519
+ 'topology' : 'in.top',
520
+ 'input_crds' : 'in.gro',
521
+ 'output_basename' : 'min',
522
+ 'force_field': 'GROMACS',
523
+ 'restraint_pdb': '',
524
+ 'restraint_force': 100.0,
525
+ 'n_step_minimization' : 100,
526
+ }
527
+
528
+ constant_energy_parms = {
529
+ 'topology' : 'in.top',
530
+ 'input_crds' : 'in.gro',
531
+ 'output_basename' : 'md',
532
+ 'force_field': 'GROMACS',
533
+ 'solvent_state': 2,
534
+ 'surface_area': 1,
535
+ 'restraint_pdb': '',
536
+ 'restraint_force': 100.0,
537
+ 'n_step_per_snapshot' : 50,
538
+ 'n_step_dynamics' : 1000,
539
+ }
540
+
541
+ langevin_thermometer_parms = {
542
+ 'topology' : 'in.top',
543
+ 'input_crds' : 'in.gro',
544
+ 'output_basename' : 'md',
545
+ 'force_field': 'GROMACS',
546
+ 'restraint_pdb': '',
547
+ 'restraint_force': 100.0,
548
+ 'random_seed' : 2342,
549
+ 'temperature_thermometer' : 300.0,
550
+ 'temperature_initial_velocities': 0.0, # ignored if it is 0.0
551
+ 'n_step_per_snapshot' : 50,
552
+ 'n_step_dynamics' : 1000,
553
+ }
554
+
555
+ minimization_mdp = """
556
+ ; template .mdp file used as input into grompp to generate energy minimization for mdrun
557
+
558
+ ; Parameters describing what to do, when to stop and what to save
559
+ integrator = steep ; Algorithm (steep = steepest descent minimization)
560
+ emtol = 1000.0 ; Stop minimization when the maximum force < 1000.0 kJ/mol/nm
561
+ emstep = 0.01 ; Energy step size
562
+ nsteps = %(n_step_minimization)s ; Maximum number of (minimization) steps to perform
563
+ """
564
+
565
+ dynamics_mdp = """
566
+ title = Template for constant temperature/pressure
567
+
568
+ ; Run parameters
569
+ integrator = md ; leap-frog integrator
570
+ nsteps = %(n_step_dynamics)s ; time = n_step_dynamics*dt
571
+ dt = 0.001 ; time-step in fs
572
+
573
+ ; Output control
574
+ nstxout = %(n_step_per_snapshot)s ; save coordinates every 0.05 ps
575
+ nstvout = %(n_step_per_snapshot)s ; save velocities every 0.05 ps
576
+ nstenergy = %(n_step_per_snapshot)s ; save energies every 0.05 ps
577
+ nstlog = %(n_step_per_snapshot)s ; update log file every 0.05 ps
578
+
579
+ ; Pressure coupling is on
580
+ pcoupl = Parrinello-Rahman ; Pressure coupling on in NPT
581
+ pcoupltype = isotropic ; uniform scaling of box vectors
582
+ tau_p = 2.0 ; time constant, in ps
583
+ ref_p = 1.0 ; reference pressure, in bar
584
+ compressibility = 4.5e-5 ; isothermal compressibility of water, bar^-1
585
+ """
586
+
587
+ temp_mdp = """
588
+ ; Temperature coupling is on
589
+ tcoupl = V-rescale ; modified Berendsen thermostat
590
+ tc-grps = Protein Non-Protein ; two coupling groups - more accurate
591
+ tau_t = 0.1 0.1 ; time constant, in ps
592
+ ref_t = %(temperature_thermometer)s %(temperature_thermometer)s ; reference temperature, one for each group, in K
593
+ ld-seed = %(random_seed)s
594
+ """
595
+
596
+ vel_mdp = """
597
+ ; Velocity generation
598
+ gen_vel = yes ; assign velocities from Maxwell distribution
599
+ gen_temp = %(temperature_initial_velocities)s ; temperature for Maxwell distribution
600
+ gen_seed = -1 ; generate a random seed
601
+ """
602
+
603
+ restraint_mdp = """
604
+ ; Restaints turned on
605
+ define = -DPOSRES ; position restrain the protein
606
+ """
607
+
608
+ def make_mdp(parms):
609
+ """
610
+ Using GROMACS, we will always solvate, use periodic
611
+ boundaries and Particle Ewald Mesh.
612
+ """
613
+ if 'n_step_minimization' in parms:
614
+ mdp = minimization_mdp
615
+ else:
616
+ mdp = dynamics_mdp
617
+ if 'temperature_thermometer' in parms:
618
+ mdp += temp_mdp
619
+ else:
620
+ mdp += "; Temperature coupling is off\n"
621
+ mdp += "tcoupl = no\n"
622
+ if 'temperature_initial_velocities' in parms and parms['temperature_initial_velocities'] > 0.0:
623
+ mdp += vel_mdp
624
+ else:
625
+ mdp += "; Velocity generation\n"
626
+ mdp += "gen_vel = no ; Velocity generation is off \n"
627
+ if 'restraint_pdb' in parms and parms['restraint_pdb']:
628
+ mdp += restraint_mdp
629
+ mdp += force_field_mdp
630
+ return mdp % parms
631
+
632
+
633
+ def replace_include_file(chain, r1, r2):
634
+ lines = open(chain, 'r').readlines()
635
+ new_lines = []
636
+ for l in lines:
637
+ if l.startswith('#include'):
638
+ new_lines.append(l.replace(r1, r2))
639
+ else:
640
+ new_lines.append(l)
641
+ open(chain, 'w').write(''.join(new_lines))
642
+
643
+
644
+ restraint_header = """
645
+ ; In this topology include file, you will find position restraint
646
+ ; entries for all the heavy atoms in your original pdb file.
647
+ ; This means that all the protons which were added by pdb2gmx are
648
+ ; not restrained.
649
+
650
+ [ position_restraints ]
651
+ ; atom type fx fy fz
652
+ """
653
+
654
+ def make_restraint_itp(restraint_pdb, force):
655
+ txt = restraint_header
656
+ atoms = pdbatoms.Soup(restraint_pdb).atoms()
657
+ for i, atom in enumerate(atoms):
658
+ if atom.bfactor > 0.0:
659
+ txt += "%6s 1 %5.f %5.f %5.f\n" % (i+1, force, force, force)
660
+ return txt
661
+
662
+
663
+ def run(in_parms):
664
+ """
665
+ Run a GROMACS simulations using the PDBREMIX parms dictionary.
666
+ """
667
+ parms = copy.deepcopy(in_parms)
668
+ basename = parms['output_basename']
669
+
670
+ # Copies across topology and related *.itp files, with appropriate
671
+ # filename renaming in #includes
672
+ top = basename + '.top'
673
+ in_top = parms['topology']
674
+ shutil.copy(in_top, top)
675
+ in_name = os.path.basename(in_top).replace('.top', '')
676
+ in_dir = os.path.dirname(in_top)
677
+ file_tag = "%s/%s_*itp" % (in_dir, in_name)
678
+ new_files = [top]
679
+ for f in glob.glob(file_tag):
680
+ new_f = os.path.basename(f)
681
+ new_f = new_f.replace(in_name, basename)
682
+ shutil.copy(f, new_f)
683
+ new_files.append(new_f)
684
+ for f in new_files:
685
+ replace_include_file(f, in_name + "_", basename + "_")
686
+
687
+ # Copy over input coordinates/velocities
688
+ in_gro = basename + '.in.gro'
689
+ shutil.copy(parms['input_crds'], in_gro)
690
+
691
+ # Generates a postiional-restraint topology file
692
+ if parms['restraint_pdb']:
693
+ # 1kcal*mol*A**-2 = 4.184 kJ*mol*(0.1 nm)**-2
694
+ kcalmolang2_to_kJmolnm2 = 400.184
695
+ open(basename + '_posre.itp', 'w').write(
696
+ make_restraint_itp(
697
+ parms['restraint_pdb'],
698
+ parms['restraint_force'] * kcalmolang2_to_kJmolnm2))
699
+
700
+ # Generate .mdp file based on parms
701
+ in_mdp = basename + '.grompp.mdp'
702
+ open(in_mdp, 'w').write(make_mdp(parms))
703
+
704
+ # Now run .grompp to generate this .tpr file
705
+ tpr = basename + '.tpr'
706
+ # .mdp to save complete set of parameters
707
+ mdp = basename + '.mdrun.mdp'
708
+ data.binary(
709
+ 'grompp',
710
+ '-f %s -po %s -c %s -p %s -o %s' \
711
+ % (in_mdp, mdp, in_gro, top, tpr),
712
+ basename + '.grompp')
713
+ util.check_files(tpr)
714
+
715
+ # Run simulation with the .tpr file
716
+ data.binary(
717
+ 'mdrun',
718
+ '-v -deffnm %s' % (basename),
719
+ basename + '.mdrun')
720
+ top, crds, vels = get_restart_files(basename)
721
+ util.check_output(top)
722
+ util.check_output(crds)
723
+
724
+ # Cleanup
725
+ delete_backup_files(basename)
726
+
727
+
728
+ # ##########################################################
729
+
730
+ # # 4. Read trajectories with some post-processing
731
+
732
+ # The units used in these files are:
733
+
734
+ # - positions: nanometers
735
+ # - velocities: nanometers/picosecond
736
+ # - force constant: kJ/mol/nm^2
737
+
738
+
739
+ n_dim = 3
740
+
741
+ class TrrReader:
742
+ """
743
+ Class to read the coordinates of a GROMACS .trr file.
744
+
745
+ Attributes:
746
+ trr (str) - name of trajectory file
747
+ file (file) - file object to trajectory
748
+ n_atom (int) - number of atoms simulated
749
+ n_frame (int) - number of frames in trajectory
750
+ i_frame (int) - index of current frame
751
+ frame (array) - container of coordinates of current frame
752
+
753
+ Methods:
754
+ __init__ - initializes trajectory and loads 1st frame
755
+ load_frame(i) - loads the i'th frame
756
+ __getitem__ - returns the frame
757
+ save_to_crd - save current frame to a .crd file
758
+ __repr__ - string representation
759
+ """
760
+
761
+ def __init__(self, trr):
762
+ self.trr = trr
763
+ self.file = open(self.trr, 'r')
764
+ self.read_header()
765
+ self.calc_precision()
766
+ self.calc_frame_info()
767
+ self.i_frame = None
768
+ self.load_frame(0)
769
+
770
+ def read_header(self):
771
+ self.u = xdrlib.Unpacker(self.file.read(200))
772
+ self.magic = self.u.unpack_int()
773
+ self.version = self.u.unpack_string()
774
+ self.size_ir = self.u.unpack_int()
775
+ self.size_e = self.u.unpack_int()
776
+ self.size_box = self.u.unpack_int()
777
+ self.size_vir = self.u.unpack_int()
778
+ self.size_pres = self.u.unpack_int()
779
+ self.size_top = self.u.unpack_int()
780
+ self.size_sym = self.u.unpack_int()
781
+ self.size_x = self.u.unpack_int()
782
+ self.size_v = self.u.unpack_int()
783
+ self.size_f = self.u.unpack_int()
784
+ self.n_atom = self.u.unpack_int()
785
+ self.step = self.u.unpack_int()
786
+ self.nre = self.u.unpack_int()
787
+ self.t = self.u.unpack_float()
788
+ self.lamb = self.u.unpack_float()
789
+ self.pos_after_header = self.u.get_position()
790
+
791
+ def calc_precision(self):
792
+ "Returns 4 for single precision, and 8 for double precision"
793
+ if self.size_box:
794
+ self.precision = self.size_box/n_dim/n_dim
795
+ elif self.size_x:
796
+ self.precision = self.size_x/n_dim
797
+ elif self.size_v:
798
+ self.precision = self.size_v/n_dim
799
+ elif self.size_f:
800
+ self.precision = self.size_f/n_dim
801
+ else:
802
+ raise ValueError("Cannot determine precision")
803
+ if self.precision not in [4, 8]:
804
+ raise ValueError("Precision not single or double!")
805
+
806
+ def calc_frame_info(self):
807
+ """Given header info and precision, can calculate frame & n_frame"""
808
+ n_vec = 0
809
+ if self.size_box: n_vec += n_dim
810
+ if self.size_vir: n_vec += n_dim
811
+ if self.size_pres: n_vec += n_dim
812
+ if self.size_x: n_vec += self.n_atom
813
+ if self.size_v: n_vec += self.n_atom
814
+ if self.size_f: n_vec += self.n_atom
815
+ self.size_frame = n_vec*n_dim*self.precision + self.pos_after_header
816
+
817
+ # Calculates n_frame from end of file
818
+ self.file.seek(0, 2)
819
+ size_file = self.file.tell()
820
+ self.n_frame = size_file / self.size_frame
821
+
822
+ def __repr__(self):
823
+ return "< Gromacs TRR Coord file %s with %d frames of %d atoms >" % \
824
+ (self.trj, self.n_frame, self.n_atom)
825
+
826
+ def next_3_reals(self):
827
+ if self.precision == 4:
828
+ return [self.u.unpack_float() for i in range(3)]
829
+ if self.precision == 8:
830
+ return [self.u.unpack_double() for i in range(3)]
831
+
832
+ def load_frame(self, i_frame):
833
+ if i_frame < - 1*self.n_frame or i_frame >= self.n_frame:
834
+ raise IndexError
835
+ if i_frame < 0:
836
+ i_frame = self.n_frame + i_frame
837
+ if i_frame == self.i_frame:
838
+ return
839
+ self.file.seek(self.pos_after_header + i_frame*self.size_frame)
840
+ self.u = xdrlib.Unpacker(self.file.read(self.size_frame))
841
+ box, positions, velocities, forces = None, None, None, None
842
+ if self.size_box:
843
+ box = [self.next_3_reals() for i in range(n_dim)]
844
+ if self.size_vir:
845
+ dummy = [self.next_3_reals() for i in range(n_dim)]
846
+ if self.size_pres:
847
+ dummy = [self.next_3_reals() for i in range(n_dim)]
848
+ if self.size_x:
849
+ positions = [self.next_3_reals() for i in range(self.n_atom)]
850
+ if self.size_v:
851
+ velocities = [self.next_3_reals() for i in range(self.n_atom)]
852
+ if self.size_f:
853
+ forces = [self.next_3_reals() for i in range(self.n_atom)]
854
+ self.frame = box, positions, velocities, forces
855
+ self.i_frame = i_frame
856
+
857
+ def __getitem__(self, i_frame):
858
+ self.load_frame(i_frame)
859
+ return self.frame
860
+
861
+
862
+ class SoupTrajectory():
863
+ """
864
+ Class to interact with an GROMACS trajctory using soup.
865
+
866
+ Attributes:
867
+ soup (Soup) - Soup object holding current coordinates/velocities
868
+ trr (str) - coordinate/velocity trajectory file
869
+ trr_reader (TrrReader) - the reader of the frames
870
+ n_frame (int) - number of frames in trajectory
871
+ i_frame (int) - index of current frame
872
+
873
+ Methods:
874
+ __init__ - load coordinate and velocity trajectories and build soup
875
+ load_frame - loads new frame into soup
876
+ """
877
+
878
+ def __init__(self, soup, trr):
879
+ self.soup = soup
880
+ self.trr = trr
881
+ self.trr_reader = TrrReader(self.trr)
882
+ self.n_frame = self.trr_reader.n_frame
883
+ self.atoms = self.soup.atoms()
884
+ self.load_frame(0)
885
+
886
+ def __repr__(self):
887
+ return "< Gromacs Trajectory object with %d frames of %d atoms >" % \
888
+ (self.trj, self.n_frame, self.n_atom)
889
+
890
+ def load_frame(self, i_frame):
891
+ box, positions, velocities, forces = self.trr_reader[i_frame]
892
+ for i, atom in enumerate(self.atoms):
893
+ v3.set_vector(
894
+ atom.pos,
895
+ positions[i][0]*10,
896
+ positions[i][1]*10,
897
+ positions[i][2]*10)
898
+ v3.set_vector(
899
+ atom.vel,
900
+ velocities[i][0]*10,
901
+ velocities[i][1]*10,
902
+ velocities[i][2]*10)
903
+ self.i_frame = self.trr_reader.i_frame
904
+
905
+
906
+ class Trajectory(object):
907
+ """
908
+ Class to interact with an GROMACS trajctory using soup.
909
+
910
+ Attributes:
911
+ basename (str) - basename used to guess all required files
912
+ top (str) - topology file of trajectory
913
+ gro (str) - coordinate/velocity restart file
914
+ trr (str) - coordinate/velocity trajectory file
915
+ trr_reader (TrrReader) - the reader of the frames
916
+ n_frame (int) - number of frames in trajectory
917
+ i_frame (int) - index of current frame
918
+ soup (Soup) - Soup object holding current coordinates/velocities
919
+
920
+ Methods:
921
+ __init__ - load coordinate and velocity trajectories and build soup
922
+ load_frame - loads new frame into soup
923
+ """
924
+
925
+ def __init__(self, basename):
926
+ self.basename = basename
927
+ self.top = basename + '.top'
928
+ self.gro = basename + '.gro'
929
+ self.soup = soup_from_top_gro(self.top, self.gro)
930
+ self.trr = basename + '.trr'
931
+ self.soup_trj = SoupTrajectory(self.soup, self.trr)
932
+ self.n_frame = self.soup_trj.n_frame
933
+ self.i_frame = None
934
+ self.load_frame(0)
935
+
936
+ def load_frame(self, i):
937
+ self.soup_trj.load_frame(i)
938
+ self.i_frame = self.soup_trj.i_frame
939
+
940
+
941
+ def merge_trajectories(basename, traj_basenames):
942
+ """
943
+ Given a bunch of directories with consecutive trajectories, all
944
+ with the same basename, the function will splice them into a
945
+ single trajctory with basename in the current directory.
946
+ """
947
+ save_dir = os.getcwd()
948
+
949
+ trr_fname = basename + '.trr'
950
+ trr_list = [b + '.trr' for b in traj_basenames]
951
+ util.check_files(*trr_list)
952
+
953
+ f = open(trr_fname, 'w')
954
+ for trr in trr_list:
955
+ trr = TrrReader(trr)
956
+ for i_frame in range(trr.n_frame-1):
957
+ trr.file.seek(trr.size_frame*i_frame)
958
+ txt = trr.file.read(trr.size_frame)
959
+ f.write(txt)
960
+ f.close()
961
+
962
+ # Copy parameters of last pulse into current directory
963
+ traj_basename = traj_basenames[-1]
964
+ for ext in ['.top', '.itp', '.tpr', '.mdrun.mdp',
965
+ '.grompp.mdp', '.gro']:
966
+ for f in glob.glob('%s*%s' % (traj_basename, ext)):
967
+ g = f.replace(traj_basename, basename)
968
+ shutil.copy(f, g)
969
+ if g.endswith('.top'):
970
+ replace_include_file(g, traj_basename + "_", basename + "_")
971
+
972
+
973
+ os.chdir(save_dir)
974
+
975
+
976
+
977
+
978
+