ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
gmxio.py
Go to the documentation of this file.
1 """ @package forcebalance.gmxio GROMACS input/output.
2 
3 @todo Even more stuff from forcefield.py needs to go into here.
4 
5 @author Lee-Ping Wang
6 @date 12/2011
7 """
8 from __future__ import division
9 from __future__ import print_function
10 
11 from builtins import zip
12 from builtins import str
13 from builtins import range
14 import os, sys
15 import re
16 from forcebalance.nifty import *
17 from forcebalance.nifty import _exec
18 from forcebalance import BaseReader
19 from forcebalance.engine import Engine
20 from forcebalance.abinitio import AbInitio
21 from forcebalance.binding import BindingEnergy
22 from forcebalance.liquid import Liquid
23 from forcebalance.lipid import Lipid
24 from forcebalance.interaction import Interaction
25 from forcebalance.moments import Moments
26 from forcebalance.vibration import Vibration
27 from forcebalance.molecule import Molecule
28 from forcebalance.thermo import Thermo
29 from copy import deepcopy
30 from forcebalance.qchemio import QChem_Dielectric_Energy
31 import itertools
32 from collections import defaultdict, OrderedDict
33 import traceback
34 import random
35 #import IPython
36 
37 from forcebalance.output import getLogger
38 logger = getLogger(__name__)
39 
40 def edit_mdp(fin=None, fout=None, options={}, defaults={}, verbose=False):
41  """
42  Read, create or edit a Gromacs MDP file. The MDP file contains GROMACS run parameters.
43  If the input file exists, it is parsed and options are replaced where "options" overrides them.
44  If the "options" dictionary contains more options, they are added at the end.
45  If the "defaults" dictionary contains more options, they are added at the end.
46  Keys are standardized to lower-case strings where all dashes are replaced by underscores.
47  The output file contains the same comments and "dressing" as the input.
48  Also returns a dictionary with the final key/value pairs.
49 
50  Parameters
51  ----------
52  fin : str, optional
53  Input .mdp file name containing options that are more important than "defaults", but less important than "options"
54  fout : str, optional
55  Output .mdp file name.
56  options : dict, optional
57  Dictionary containing mdp options. Existing options are replaced, new options are added at the end, None values are deleted from output mdp.
58  defaults : dict, optional
59  defaults Dictionary containing "default" mdp options, added only if they don't already exist.
60  verbose : bool, optional
61  Print out additional information
62 
63  Returns
64  -------
65  OrderedDict
66  Key-value pairs combined from the input .mdp and the supplied options/defaults and equivalent to what's printed in the output mdp.
67  """
68  clashes = ["pbc"]
69  # Make sure that the keys are lowercase, and the values are all strings.
70  options = OrderedDict([(key.lower().replace('-','_'), str(val) if val is not None else None) for key, val in options.items()])
71  # List of lines in the output file.
72  out = []
73  # List of options in the output file.
74  haveopts = []
75  # List of all options in dictionary form, to be returned.
76  all_options = OrderedDict()
77  if fin is not None and os.path.isfile(fin):
78  for line in open(fin).readlines():
79  line = line.strip().expandtabs()
80  # The line structure should look something like this:
81  # key = value ; comments
82  # First split off the comments.
83  if len(line) == 0:
84  out.append('')
85  continue
86  s = line.split(';',1)
87  data = s[0]
88  comms = s[1] if len(s) > 1 else None
89  # Pure comment lines or empty lines get appended to the output.
90  if set(data).issubset([' ']):
91  out.append(line)
92  continue
93  # Now split off the key and value fields at the equals sign.
94  keyf, valf = data.split('=',1)
95  key = keyf.strip().lower().replace('-','_')
96  haveopts.append(key)
97  if key in options:
98  val = options[key]
99  val0 = valf.strip()
100  if key in clashes and val != val0:
101  logger.error("edit_mdp tried to set %s = %s but its original value was %s = %s\n" % (key, val, key, val0))
102  raise RuntimeError
103  # Passing None as the value causes the option to be deleted
104  if val is None: continue
105  if len(val) < len(valf):
106  valf = ' ' + val + ' '*(len(valf) - len(val)-1)
107  else:
108  valf = ' ' + val + ' '
109  lout = [keyf, '=', valf]
110  if comms is not None:
111  lout += [';',comms]
112  out.append(''.join(lout))
113  else:
114  out.append(line)
115  val = valf.strip()
116  all_options[key] = val
117  for key, val in options.items():
118  key = key.lower().replace('-','_')
119  if key not in haveopts:
120  haveopts.append(key)
121  out.append("%-20s = %s" % (key, val))
122  all_options[key] = val
123  # Fill in some default options.
124  for key, val in defaults.items():
125  key = key.lower().replace('-','_')
126  options[key] = val
127  if key not in haveopts:
128  out.append("%-20s = %s" % (key, val))
129  all_options[key] = val
130  if fout != None:
131  file_out = wopen(fout)
132  for line in out:
133  print(line, file=file_out)
134  file_out.close()
135  if verbose:
136  printcool_dictionary(options, title="%s -> %s with options:" % (fin, fout))
137  return all_options
138 
139 def write_ndx(fout, grps, fin=None):
140  """
141  Create or edit a Gromacs ndx file.
142  @param[in] fout Output file name, can be the same as input file name.
143  @param[in] grps Dictionary containing key : atom selections.
144  @param[in] fin Input file name.
145  """
146  ndxgrps = OrderedDict()
147  atoms = []
148  grp = None
149  if fin is not None and os.path.isfile(fin):
150  for line in open(fin):
151  s = line.split()
152  if len(s) == 0: continue
153  if line.startswith('['):
154  grp = s[1]
155  ndxgrps[grp] = []
156  elif all([isint(i) for i in s]):
157  ndxgrps[grp] += [int(i) for i in s]
158  ndxgrps.update(grps)
159  outf = wopen(fout)
160  for name, nums in ndxgrps.items():
161  print('[ %s ]' % name, file=outf)
162  for subl in list(grouper(nums, 15)):
163  print(' '.join(["%4i" % i for i in subl]) + ' ', file=outf)
164  outf.close()
165 
166 
167 nftypes = [None, 'VDW', 'VDW_BHAM']
168 
169 pftypes = [None, 'VPAIR', 'VPAIR_BHAM']
171 bftypes = [None, 'BONDS', 'G96BONDS', 'MORSE']
173 aftypes = [None, 'ANGLES', 'G96ANGLES', 'CROSS_BOND_BOND',
174  'CROSS_BOND_ANGLE', 'UREY_BRADLEY', 'QANGLES']
175 
176 dftypes = [None, 'PDIHS', 'IDIHS', 'RBDIHS', 'PIMPDIHS', 'FOURDIHS', None, None, 'TABDIHS', 'PDIHMULS']
177 
178 
184 fdict = {
185  'atomtypes' : nftypes,
186  'nonbond_params': pftypes,
187  'pairtypes' : pftypes,
188  'bonds' : bftypes,
189  'bondtypes' : bftypes,
190  'angles' : aftypes,
191  'angletypes' : aftypes,
192  'dihedrals' : dftypes,
193  'dihedraltypes' : dftypes,
194  'virtual_sites2': ['NONE','VSITE2'],
195  'virtual_sites3': ['NONE','VSITE3','VSITE3FD','VSITE3FAD','VSITE3OUT'],
196  'virtual_sites4': ['NONE','VSITE4FD','VSITE4FDN']
197  }
198 
199 
208 pdict = {'BONDS':{3:'B', 4:'K'},
209  'G96BONDS':{},
210  'MORSE':{3:'B', 4:'C', 5:'E'},
211  'ANGLES':{4:'B', 5:'K'},
212  'G96ANGLES':{},
213  'CROSS_BOND_BOND':{4:'1', 5:'2', 6:'K'},
214  'CROSS_BOND_ANGLE':{4:'1', 5:'2', 6:'3', 7:'K'},
215  'QANGLES':{4:'B', 5:'K0', 6:'K1', 7:'K2', 8:'K3', 9:'K4'},
216  'UREY_BRADLEY':{4:'T', 5:'K1', 6:'B', 7:'K2'},
217  'PDIHS1':{5:'B', 6:'K'}, 'PDIHS2':{5:'B', 6:'K'}, 'PDIHS3':{5:'B', 6:'K'},
218  'PDIHS4':{5:'B', 6:'K'}, 'PDIHS5':{5:'B', 6:'K'}, 'PDIHS6':{5:'B', 6:'K'},
219  'PIMPDIHS1':{5:'B', 6:'K'}, 'PIMPDIHS2':{5:'B', 6:'K'}, 'PIMPDIHS3':{5:'B', 6:'K'},
220  'PIMPDIHS4':{5:'B', 6:'K'}, 'PIMPDIHS5':{5:'B', 6:'K'}, 'PIMPDIHS6':{5:'B', 6:'K'},
221  'FOURDIHS1':{5:'B', 6:'K'}, 'FOURDIHS2':{5:'B', 6:'K'}, 'FOURDIHS3':{5:'B', 6:'K'},
222  'FOURDIHS4':{5:'B', 6:'K'}, 'FOURDIHS5':{5:'B', 6:'K'}, 'FOURDIHS6':{5:'B', 6:'K'},
223  'PDIHMULS1':{5:'B', 6:'K'}, 'PDIHMULS2':{5:'B', 6:'K'}, 'PDIHMULS3':{5:'B', 6:'K'},
224  'PDIHMULS4':{5:'B', 6:'K'}, 'PDIHMULS5':{5:'B', 6:'K'}, 'PDIHMULS6':{5:'B', 6:'K'},
225  'IDIHS':{5:'B', 6:'K'},
226  'VDW':{4:'S', 5:'T'},
227  'VPAIR':{3:'S', 4:'T'},
228  'COUL':{6:''},
229  'RBDIHS':{6:'K1', 7:'K2', 8:'K3', 9:'K4', 10:'K5'},
230  'VDW_BHAM':{4:'A', 5:'B', 6:'C'},
231  'VPAIR_BHAM':{3:'A', 4:'B', 5:'C'},
232  'QTPIE':{1:'C', 2:'H', 3:'A'},
233  'VSITE2':{4:'A'},
234  'VSITE3':{5:'A',6:'B'},
235  'VSITE3FD':{5:'A',6:'D'},
236  'VSITE3FAD':{5:'T',6:'D'},
237  'VSITE3OUT':{5:'A',6:'B',7:'C'},
238  'VSITE4FD':{6:'A',7:'B',8:'D'},
239  'VSITE4FDN':{6:'A',7:'B',8:'C'},
240  'DEF':{3:'FLJ',4:'FQQ'},
241  'POL':{3:'ALPHA'},
242  'DEFINE':dict([(i, '') for i in range(100)])
243  }
244 
245 def parse_atomtype_line(line):
246  """ Parses the 'atomtype' line.
247 
248  Parses lines like this:\n
249  <tt> opls_135 CT 6 12.0107 0.0000 A 3.5000e-01 2.7614e-01\n
250  C 12.0107 0.0000 A 3.7500e-01 4.3932e-01\n
251  Na 11 22.9897 0.0000 A 6.068128070229e+03 2.662662556402e+01 0.0000e+00 ; PRM 5 6\n </tt>
252  Look at all the variety!
253 
254  @param[in] line Input line.
255  @return answer Dictionary containing:\n
256  atom type\n
257  bonded atom type (if any)\n
258  atomic number (if any)\n
259  atomic mass\n
260  charge\n
261  particle type\n
262  force field parameters\n
263  number of optional fields
264  """
265  # First split the line up to the comment. We don't care about the comment at this time
266  sline = line.split(';')[0].split()
267  # The line must contain at least six fields to be considered data.
268  if len(sline) < 6:
269  return
270  # Using variable "wrd" because the line has a variable number of fields
271  # Can you think of a better way?
272  wrd = 0
273  bonus = 0
274  atomtype = sline[wrd]
275  batomtype = sline[wrd]
276  wrd += 1
277  # The bonded atom type, a pecularity of OPLS-AA
278  # Test if it begins with a letter. Seems to work. :)
279  if re.match('[A-Za-z]',sline[wrd]):
280  batomtype = sline[wrd]
281  wrd += 1
282  bonus += 1
283  # Now to test if the next line is an atomic number or a mass.
284  # Atomic numbers never have decimals...
285  atomicnum = -1
286  if isint(sline[wrd]):
287  atomicnum = int(sline[wrd])
288  wrd += 1
289  bonus += 1
290  # The mass can be overridden in the 'atoms' section.
291  mass = float(sline[wrd])
292  wrd += 1
293  # Atom types have a default charge though this is almost always overridden
294  chg = float(sline[wrd])
295  wrd += 1
296  # Particle type. Actual atom or virtual site?
297  ptp = sline[wrd]
298  wrd += 1
299  param = [float(i) for i in sline[wrd:]]
300  answer = {'atomtype':atomtype, 'batomtype':batomtype, 'atomicnum':atomicnum, 'mass':mass, 'chg':chg, 'ptp':ptp, 'param':param, 'bonus':bonus}
301  return answer
302 
303 class ITP_Reader(BaseReader):
304 
305  """Finite state machine for parsing GROMACS force field files.
306 
307  We open the force field file and read all of its lines. As we loop
308  through the force field file, we look for two types of tags: (1) section
309  markers, in GMX indicated by [ section_name ], which allows us to determine
310  the section, and (2) parameter tags, indicated by the 'PRM' or 'RPT' keywords.
311 
312  As we go through the file, we figure out the atoms involved in the interaction
313  described on each line.
314 
315  When a 'PRM' keyword is indicated, it is followed by a number which is the field
316  in the line to be modified, starting with zero. Based on the field number and the
317  section name, we can figure out the parameter type. With the parameter type
318  and the atoms in hand, we construct a 'parameter identifier' or pid which uniquely
319  identifies that parameter. We also store the physical parameter value in an array
320  called 'pvals0' and the precise location of that parameter (by filename, line number,
321  and field number) in a list called 'pfields'.
322 
323  An example: Suppose in 'my_ff.itp' I encounter the following on lines 146 and 147:
324 
325  @code
326  [ angletypes ]
327  CA CB O 1 109.47 350.00 ; PRM 4 5
328  @endcode
329 
330  From reading <tt>[ angletypes ]</tt> I know I'm in the 'angletypes' section.
331 
332  On the next line, I notice two parameters on fields 4 and 5.
333 
334  From the atom types, section type and field number I know the parameter IDs are <tt>'ANGLESBCACBO'</tt> and <tt>'ANGLESKCACBO'</tt>.
335 
336  After building <tt>map={'ANGLESBCACBO':1,'ANGLESKCACBO':2}</tt>, I store the values in
337  an array: <tt>pvals0=array([109.47,350.00])</tt>, and I put the parameter locations in
338  pfields: <tt>pfields=[['my_ff.itp',147,4,1.0],['my_ff.itp',146,5,1.0]]</tt>. The 1.0
339  is a 'multiplier' and I will explain it below.
340 
341  Note that in the creation of parameter IDs, we run into the issue that the atoms
342  involved in the interaction may be labeled in reverse order (e.g. <tt>OCACB</tt>). Thus,
343  we store both the normal and the reversed parameter ID in the map.
344 
345  Parameter repetition and multiplier:
346 
347  If <tt>'RPT'</tt> is encountered in the line, it is always in the syntax:
348  <tt>'RPT 4 ANGLESBCACAH 5 MINUS_ANGLESKCACAH /RPT'</tt>. In this case, field 4 is replaced by
349  the stored parameter value corresponding to <tt>ANGLESBCACAH</tt> and field 5 is replaced by
350  -1 times the stored value of <tt>ANGLESKCACAH</tt>. Now I just picked this as an example,
351  I don't think people actually want a negative angle force constant .. :) the <tt>MINUS</tt>
352  keyword does come in handy for assigning atomic charges and virtual site positions.
353  In order to achieve this, a multiplier of -1.0 is stored into pfields instead of 1.0.
354 
355  @todo Note that I can also create the opposite virtual site position by changing the atom
356  labeling, woo!
357 
358  """
359 
360  def __init__(self,fnm):
361  # Initialize the superclass. :)
362  super(ITP_Reader,self).__init__(fnm)
363 
364  self.sec = None
365 
366  self.nbtype = None
367 
368  self.mol = None
369 
370  self.pdict = pdict
371 
372  self.atomnames = []
373 
374  self.atomtypes = []
375 
376  self.atomtype_to_mass = {}
378  def feed(self, line):
379  """ Given a line, determine the interaction type and the atoms involved (the suffix).
380 
381  For example, we want \n
382  <tt> H O H 5 1.231258497536e+02 4.269161426840e+02 -1.033397697685e-02 1.304674117410e+04 ; PRM 4 5 6 7 </tt> \n
383  to give us itype = 'UREY_BRADLEY' and suffix = 'HOH'
384 
385  If we are in a TypeSection, it returns a list of atom types; \n
386  If we are in a TopolSection, it returns a list of atom names.
387 
388  The section is essentially a case statement that picks out the
389  appropriate interaction type and makes a list of the atoms
390  involved
391 
392  Note that we can call gmxdump for this as well, but I
393  prefer to read the force field file directly.
394 
395  ToDo: [ atoms ] section might need to be more flexible to accommodate optional fields
396 
397  """
398  s = line.split()
399  atom = []
400  self.itype = None
401  self.ln += 1
402  # No sense in doing anything for an empty line or a comment line.
403  if len(s) == 0 or re.match('^ *;',line): return None, None
404  # Now go through all the cases.
405  if re.match('^#', line):
406  self.overpfx = 'DEFINE'
407  self.oversfx = s[1]
408  elif hasattr(self, 'overpfx'):
409  delattr(self, 'overpfx')
410  delattr(self, 'oversfx')
411 
412  if re.match('^ *\[.*\]',line):
413  # Makes a word like "atoms", "bonds" etc.
414  self.sec = re.sub('[\[\] \n]','',line.strip())
415  elif self.sec == 'defaults':
416  self.itype = 'DEF'
417  self.nbtype = int(s[0])
418  elif self.sec == 'moleculetype':
419  self.mol = s[0]
420  elif self.sec == 'atomtypes':
421  atype = parse_atomtype_line(line)
422  # Basically we're shifting the word positions
423  # based on the syntax of the line in 'atomtype', but it allows the parameter typing to
424  # keep up with the flexibility of the syntax of these lines.
425  if atype['bonus'] > 0:
426  pdict['VDW'] = {4+atype['bonus']:'S',5+atype['bonus']:'T'}
427  pdict['VDW_BHAM'] = {4+atype['bonus']:'A', 5+atype['bonus']:'B', 6+atype['bonus']:'C'}
428  atom = atype['atomtype']
429  self.atomtype_to_mass[atom] = atype['mass']
430  self.itype = fdict[self.sec][self.nbtype]
431  self.AtomTypes[atype['atomtype']] = {'AtomClass' : atype['batomtype'],
432  'AtomicNumber' : atype['atomicnum'],
433  'Mass' : atype['mass'],
434  'Charge' : atype['chg'],
435  'ParticleType' : atype['ptp']}
436  elif self.sec == 'nonbond_params':
437  atom = [s[0], s[1]]
438  self.itype = pftypes[self.nbtype]
439  elif self.sec == 'pairtypes':
440  atom = [s[0], s[1]]
441  self.itype = pftypes[self.nbtype]
442  elif self.sec == 'atoms':
443  # Ah, this is the atom name, not the atom number.
444  # Maybe I should use the atom number.
445  atom = [int(s[0])]
446  self.atomnames.append(s[4])
447  self.itype = 'COUL'
448  # Build dictionaries where the key is the residue name
449  # and the value is a list of atom numbers, atom types, and atomic masses.
450  self.adict.setdefault(self.mol,[]).append(int(s[0]))
451  ffAtom = {'AtomType' : s[1], 'ResidueNumber' : int(s[2]), 'ResidueName' : s[3], 'AtomName' : s[4], 'ChargeGroupNumber' : int(s[5]), 'Charge' : float(s[6])}
452  self.Molecules.setdefault(self.mol,[]).append(ffAtom)
453  elif self.sec == 'polarization':
454  atom = [int(s[1])]
455  self.itype = 'POL'
456  elif self.sec == 'qtpie':
457  # The atom involved is labeled by the atomic number.
458  atom = [int(s[0])]
459  self.itype = 'QTPIE'
460  elif self.sec == 'bonds':
461  atom = [self.adict[self.mol][int(i)-1] for i in s[:2]]
462  self.itype = fdict[self.sec][int(s[2])]
463  elif self.sec == 'bondtypes':
464  atom = [s[0], s[1]]
465  self.itype = fdict[self.sec][int(s[2])]
466  elif self.sec == 'angles':
467  atom = [self.adict[self.mol][int(i)-1] for i in s[:3]]
468  self.itype = fdict[self.sec][int(s[3])]
469  elif self.sec == 'angletypes':
470  atom = [s[0], s[1], s[2]]
471  self.itype = fdict[self.sec][int(s[3])]
472  elif self.sec == 'dihedrals':
473  atom = [self.adict[self.mol][int(i)-1] for i in s[:4]]
474  self.itype = fdict[self.sec][int(s[4])]
475  if self.itype in ['PDIHS', 'PIMPDIHS', 'FOURDIHS', 'PDIHMULS'] and len(s) >= 7:
476  # Add the multiplicity of the dihedrals to the interaction type.
477  self.itype += s[7]
478  elif self.sec == 'dihedraltypes':
479  # LPW: This needs to be fixed, because some dihedraltypes lines only have 2 atom types.
480  # for i in range(len(s)):
481  # if isint(s[i]):
482  # nat = i
483  # atom = [s[i] for i in range(nat)]
484  # self.itype = fdict[self.sec][int(s[nat+1])]
485  # if self.itype in ['PDIHS', 'PIMPDIHS', 'FOURDIHS', 'PDIHMULS'] and len(s) > (nat+3):
486  # self.itype += s[nat+3]
487  atom = [s[0], s[1], s[2], s[3]]
488  self.itype = fdict[self.sec][int(s[4])]
489  if self.itype in ['PDIHS', 'PIMPDIHS', 'FOURDIHS', 'PDIHMULS'] and len(s) >= 7:
490  self.itype += s[7]
491  elif self.sec == 'virtual_sites2':
492  atom = [self.adict[self.mol][int(i)-1] for i in s[:1]]
493  #atom = [s[0]]
494  self.itype = fdict[self.sec][int(s[3])]
495  elif self.sec == 'virtual_sites3':
496  atom = [self.adict[self.mol][int(i)-1] for i in s[:1]]
497  #atom = [self.adict[self.mol][int(i)-1] for i in s[:3]]
498  #atom = [s[0]]
499  self.itype = fdict[self.sec][int(s[4])]
500  elif self.sec == 'virtual_sites4':
501  atom = [self.adict[self.mol][int(i)-1] for i in s[:1]]
502  #atom = [self.adict[self.mol][int(i)-1] for i in s[:4]]
503  #atom = [s[0]]
504  self.itype = fdict[self.sec][int(s[5])]
505  else:
506  return [],"Confused"
507  if type(atom) is list and (len(atom) > 1 and atom[0] > atom[-1]):
508  # Enforce a canonical ordering of the atom labels in a parameter ID
509  atom = atom[::-1]
510  if self.mol is None:
511  self.suffix = ':' + ''.join(["%s" % i for i in atom])
512  elif self.sec == 'qtpie':
513  self.suffix = ':' + '.'.join(["%s" % i for i in atom])
514  else:
515  self.suffix = ':' + '-'.join([self.mol,'.'.join(["%s" % i for i in atom])])
516  self.molatom = (self.mol, atom if type(atom) is list else [atom])
518 def rm_gmx_baks(dir):
519  # Delete the #-prepended files that GROMACS likes to make
520  for root, dirs, files in os.walk(dir):
521  for file in files:
522  if re.match('^#',file):
523  os.remove(file)
525 class GMX(Engine):
526 
527  """ Derived from Engine object for carrying out general purpose GROMACS calculations. """
528 
529  def __init__(self, name="gmx", **kwargs):
530 
531  self.valkwd = ['gmxsuffix', 'gmxpath', 'gmx_top', 'gmx_mdp', 'gmx_ndx', 'gmx_eq_barostat', 'gmx_pme_order']
532  super(GMX,self).__init__(name=name, **kwargs)
534  def setopts(self, **kwargs):
535 
536  """ Called by __init__ ; Set GROMACS-specific options. """
537 
538 
539  os.environ["GMX_MAXBACKUP"] = "-1"
540  os.environ["GMX_NO_SOLV_OPT"] = "TRUE"
541  os.environ["GMX_NO_ALLVSALL"] = "TRUE"
542 
543 
544  if 'gmxsuffix' in kwargs:
545  self.gmxsuffix = kwargs['gmxsuffix']
546  else:
547  warn_once("The 'gmxsuffix' option were not provided; using default.")
548  self.gmxsuffix = ''
549 
550 
551  if 'gmx_eq_barostat' in kwargs:
552  self.gmx_eq_barostat = kwargs['gmx_eq_barostat']
554 
555  havegmx = False
556  if 'gmxpath' in kwargs:
557  self.gmxpath = kwargs['gmxpath']
558 
559  if type(self.gmxpath) is str and len(self.gmxpath) > 0:
560 
561  if os.path.exists(os.path.join(self.gmxpath,"gmx"+self.gmxsuffix)):
562  self.gmxversion = 5
563  havegmx = True
564  elif os.path.exists(os.path.join(self.gmxpath,"mdrun"+self.gmxsuffix)):
565  self.gmxversion = 4
566  havegmx = True
567  else:
568  warn_press_key("The mdrun executable indicated by %s doesn't exist! (Check gmxpath and gmxsuffix)" \
569  % os.path.join(self.gmxpath,"mdrun"+self.gmxsuffix))
571  if not havegmx:
572  warn_once("The 'gmxpath' option was not specified; using default.")
573  if which('gmx'+self.gmxsuffix) != '':
574  self.gmxpath = which('gmx'+self.gmxsuffix)
575  self.gmxversion = 5
576  havegmx = True
577  elif which('mdrun'+self.gmxsuffix) != '':
578  self.gmxpath = which('mdrun'+self.gmxsuffix)
579  self.gmxversion = 4
580  havegmx = True
581  else:
582  warn_press_key("Please add GROMACS executables to the PATH or specify gmxpath.")
583  logger.error("Cannot find the GROMACS executables!\n")
584  raise RuntimeError
585 
586  def readsrc(self, **kwargs):
587  """ Called by __init__ ; read files from the source directory. """
588 
589 
590  self.top = onefile(kwargs.get('gmx_top'), 'top')
591  self.mdp = onefile(kwargs.get('gmx_mdp'), 'mdp')
592  if 'mol' in kwargs:
593  self.mol = kwargs['mol']
594  else:
595  self.mol = Molecule(onefile(kwargs.get('coords'), 'gro', err=True))
597  def prepare(self, pbc=False, **kwargs):
598  """ Called by __init__ ; prepare the temp directory and figure out the topology. """
600  self.gmx_defs = OrderedDict([("integrator", "md"), ("dt", "0.001"), ("nsteps", "0"),
601  ("nstxout", "0"), ("nstfout", "0"), ("nstenergy", "1"),
602  ("nstxtcout", "0"), ("constraints", "none"), ("cutoff-scheme", "group")])
603  gmx_opts = OrderedDict([])
604  warnings = []
605  self.pbc = pbc
606  self.have_constraints = False
607  if pbc:
608  minbox = min([self.mol.boxes[0].a, self.mol.boxes[0].b, self.mol.boxes[0].c])
609  if minbox <= 10:
610  warn_press_key("Periodic box is set to less than 1.0 ")
611  if minbox <= 21:
612  # Cutoff diameter should be at least one angstrom smaller than the box size
613  # Translates to 0.85 Angstrom for the SPC-216 water box
614  rlist = 0.05*(float(int(minbox - 1)))
615  else:
616  rlist = 1.0
617  # Override with user-provided nonbonded_cutoff if exist
618  # Since user provides in Angstrom, divide by a factor of 10.
619  if 'nonbonded_cutoff' in kwargs:
620  rlist = kwargs['nonbonded_cutoff'] / 10
621  # Gromacs likes rvdw to be a bit smaller than rlist
622  rvdw = rlist - 0.05
623  if rlist > 0.05*(float(int(minbox - 1))):
624  warn_press_key("nonbonded_cutoff = %.1f should be smaller than half the box size = %.1f Angstrom" % (rlist*10, minbox))
625  # Override with user-provided vdw_cutoff if exist
626  if 'vdw_cutoff' in kwargs:
627  rvdw = kwargs['vdw_cutoff'] / 10
628  if rvdw > 0.05*(float(int(minbox - 1))):
629  warn_press_key("vdw_cutoff = %.1f should be smaller than half the box size = %.1f Angstrom" % (rvdw*10, minbox))
630  rvdw_switch = rvdw-0.05
631  gmx_opts["pbc"] = "xyz"
632  self.gmx_defs["ns_type"] = "grid"
633  self.gmx_defs["nstlist"] = 20
634  self.gmx_defs["rlist"] = "%.2f" % rlist
635  self.gmx_defs["coulombtype"] = "pme"
636  self.gmx_defs["rcoulomb"] = "%.2f" % rlist
637  # self.gmx_defs["coulombtype"] = "pme-switch"
638  # self.gmx_defs["rcoulomb"] = "%.2f" % (rlist - 0.05)
639  # self.gmx_defs["rcoulomb_switch"] = "%.2f" % (rlist - 0.1)
640  self.gmx_defs["vdwtype"] = "switch"
641  self.gmx_defs["rvdw"] = "%.2f" % rvdw
642  self.gmx_defs["rvdw_switch"] = "%.2f" % rvdw_switch
643  self.gmx_defs["DispCorr"] = "EnerPres"
644  else:
645  if 'nonbonded_cutoff' in kwargs:
646  warn_press_key("Not using PBC, your provided nonbonded_cutoff will not be used")
647  if 'vdw_cutoff' in kwargs:
648  warn_press_key("Not using PBC, your provided vdw_cutoff will not be used")
649  gmx_opts["pbc"] = "no"
650  self.gmx_defs["ns_type"] = "simple"
651  self.gmx_defs["nstlist"] = 0
652  self.gmx_defs["rlist"] = "0.0"
653  self.gmx_defs["coulombtype"] = "cut-off"
654  self.gmx_defs["rcoulomb"] = "0.0"
655  self.gmx_defs["vdwtype"] = "cut-off"
656  self.gmx_defs["rvdw"] = "0.0"
657 
658 
659  if self.top is not None:
660  LinkFile(os.path.join(self.srcdir, self.top), self.top, nosrcok=True)
661  if self.mdp is not None:
662  LinkFile(os.path.join(self.srcdir, self.mdp), self.mdp, nosrcok=True)
663 
664 
665  mdp_dict = edit_mdp(fin=self.mdp)
666  if 'constraints' in mdp_dict.keys() and mdp_dict['constraints'].replace('-','_').lower() in ['h_bonds', 'all_bonds', 'h_angles', 'all_angles']:
667  self.have_constraints = True
668 
669  itptmp = False
670 
671 
672  if hasattr(self,'FF'):
673  # Create the force field in this directory if the force field object is provided.
674  # This is because the .mdp and .top file can be force field files!
675  # This bit affects how the geometry optimization is performed, but we should have
676  # a more comprehensive way to pass constraint settings through.
677  if self.FF.rigid_water:
678  self.have_constraints = True
679  if not all([os.path.exists(f) for f in self.FF.fnms]):
680  # If the parameter files don't already exist, create them for the purpose of
681  # preparing the engine, but then delete them afterward.
682  itptmp = True
683  self.FF.make(np.zeros(self.FF.np))
684  self.top = onefile(self.top, 'top')
685  self.mdp = onefile(self.mdp, 'mdp')
686  # Sanity check; the force fields should be referenced by the .top file.
687  if self.top is not None and os.path.exists(self.top):
688  if self.top not in self.FF.fnms and (not any([any([fnm in line for fnm in self.FF.fnms]) for line in open(self.top)])):
689  logger.warning('Force field file is not referenced in the .top file\nAssuming the first .itp file is to be included\n')
690  for itpfnm in self.FF.fnms:
691  if itpfnm.endswith(".itp"):
692  break
693  topol = open(self.top).readlines()
694  with wopen(self.top) as f:
695  print("#include \"%s\"" % itpfnm, file=f)
696  print(file=f)
697  for line in topol:
698  print(line, end=' ', file=f)
699  # warn_press_key("None of the force field files %s are referenced in the .top file. "
700  # "Are you referencing the files through C preprocessor directives?" % self.FF.fnms)
701 
702 
703  if hasattr(self, 'target') and hasattr(self.target,'shots'):
704  self.mol.write("%s-all.gro" % self.name, selection=range(self.target.shots))
705  else:
706  self.mol.write("%s-all.gro" % self.name)
707  self.mol[0].write('%s.gro' % self.name)
708 
709 
712  if self.top is not None and os.path.exists(self.top):
713  LinkFile(self.top, '%s.top' % self.name)
714  else:
715  logger.error("No .top file found, cannot continue.\n")
716  raise RuntimeError
717  edit_mdp(fin=self.mdp, fout="%s.mdp" % self.name, options=gmx_opts, defaults=self.gmx_defs)
718 
719 
720  o = self.warngmx("grompp -c %s.gro -p %s.top -f %s.mdp -o %s.tpr" % (self.name, self.name, self.name, self.name), warnings=warnings)
721  self.double = 0
722  for line in o:
723  if 'double precision' in line:
724  self.double = 1
725  o = self.callgmx("gmxdump -s %s.tpr -sys" % self.name, copy_stderr=True)
726  self.AtomMask = []
727  self.AtomLists = defaultdict(list)
728  ptype_dict = {'atom': 'A', 'vsite': 'D', 'shell': 'S'}
729 
730 
731  for line in o:
732  line = line.replace("=", "= ")
733  if "ptype=" in line:
734  s = line.split()
735  ptype = s[s.index("ptype=")+1].replace(',','').lower()
736  resind = int(s[s.index("resind=")+1].replace(',','').lower())
737  mass = float(s[s.index("m=")+1].replace(',','').lower())
738  charge = float(s[s.index("q=")+1].replace(',','').lower())
739  # Gather data for residue number.
740  self.AtomMask.append(ptype=='atom')
741  self.AtomLists['ResidueNumber'].append(resind)
742  self.AtomLists['ParticleType'].append(ptype_dict[ptype])
743  self.AtomLists['Charge'].append(charge)
744  self.AtomLists['Mass'].append(mass)
745  if "cgs[" in line:
746  ai = [int(i) for i in line.split("{")[1].split("}")[0].split("..")]
747  cg = int(line.split('[')[1].split(']')[0])
748  for i in range(ai[1]-ai[0]+1) : self.AtomLists['ChargeGroupNumber'].append(cg)
749  if "mols[" in line:
750  ai = [int(i) for i in line.split("{")[1].split("}")[0].split("..")]
751  mn = int(line.split('[')[1].split(']')[0])
752  for i in range(ai[1]-ai[0]+1) : self.AtomLists['MoleculeNumber'].append(mn)
753  os.unlink('mdout.mdp')
754  os.unlink('%s.tpr' % self.name)
755  if hasattr(self,'FF') and itptmp:
756  for f in self.FF.fnms:
757  os.unlink(f)
758 
759  def get_charges(self):
760  # Call gmxdump to get system information and read the charges.
761  self.warngmx("grompp -c %s.gro -p %s.top -f %s.mdp -o %s.tpr" % (self.name, self.name, self.name, self.name))
762  o = self.callgmx("gmxdump -s %s.tpr -sys" % self.name, copy_stderr=True)
763  # List of charges obtained from reading gmxdump.
764  charges = []
765  for line in o:
766  line = line.replace("=", "= ")
767  # These lines contain the charges
768  if "ptype=" in line:
769  s = line.split()
770  charge = float(s[s.index("q=")+1].replace(',','').lower())
771  charges.append(charge)
772  os.unlink('%s.tpr' % self.name)
773  return np.array(charges)
774 
775  def links(self):
776  topfile = onefile('%s.top' % self.name, 'top', err=True)
777  LinkFile(topfile, "%s.top" % self.name)
778  mdpfile = onefile('%s.mdp' % self.name, 'mdp', err=True)
779  LinkFile(mdpfile, "%s.mdp" % self.name, nosrcok=True)
780 
781  def callgmx(self, command, stdin=None, print_to_screen=False, print_command=False, **kwargs):
782 
783  """ Call GROMACS; prepend the gmxpath to the call to the GROMACS program. """
784 
785 
786  rm_gmx_baks(os.getcwd())
787 
789  self.links()
790 
791  csplit = command.split()
792  prog = os.path.join(self.gmxpath, csplit[0])
793  if self.gmxversion == 5:
794  csplit[0] = csplit[0].replace('g_','').replace('gmxdump','dump')
795  csplit = ['gmx' + self.gmxsuffix] + csplit
796  elif self.gmxversion == 4:
797  csplit[0] = prog + self.gmxsuffix
798  else:
799  raise RuntimeError('gmxversion can only be 4 or 5')
800  return _exec(' '.join(csplit), stdin=stdin, print_to_screen=print_to_screen, print_command=print_command, **kwargs)
801 
802  def warngmx(self, command, warnings=[], maxwarn=1, **kwargs):
803 
804  """ Call gromacs and allow for certain expected warnings. """
805 
806  # Common warning lines:
807  # "You are generating velocities so I am assuming"
808  # "You are not using center of mass motion removal"
809  csplit = command.split()
810  if '-maxwarn' in csplit:
811  csplit[csplit.index('-maxwarn')+1] = '%i' % maxwarn
812  else:
813  csplit += ['-maxwarn', '%i' % maxwarn]
814  command = ' '.join(csplit)
815  o = self.callgmx(command, persist=True, copy_stderr=True, print_error=False, **kwargs)
816  warnthis = []
817  fatal = 0
818  warn = 0
819  for line in o:
820  if warn:
821  warnthis.append(line)
822  warn = 0
823  if 'Fatal error' in line:
824  fatal = 1
825  if 'WARNING' in line:
826  warn = 1
827  if fatal and all([any([a in w for a in warnings]) for w in warnthis]):
828  maxwarn = len(warnthis)
829  csplit = command.split()
830  if '-maxwarn' in csplit:
831  csplit[csplit.index('-maxwarn')+1] = '%i' % maxwarn
832  else:
833  csplit += ['-maxwarn', '%i' % maxwarn]
834  command = ' '.join(csplit)
835  o = self.callgmx(command, **kwargs)
836  elif fatal:
837  for line in o:
838  logger.error(line+'\n')
839  logger.error('grompp encountered a fatal error!\n')
840  raise RuntimeError
841  return o
842 
843  def energy_termnames(self, edrfile=None):
844 
845  """ Get a list of energy term names from the .edr file by parsing a system call to g_energy. """
846 
847  if edrfile is None:
848  edrfile = "%s.edr" % self.name
849  if not os.path.exists(edrfile):
850  logger.error('Cannot determine energy term names without an .edr file\n')
851  raise RuntimeError
852 
853  o = self.callgmx("g_energy -f %s -xvg no" % (edrfile), stdin="Total-Energy\n", copy_stdout=False, copy_stderr=True)
854  parsemode = 0
855  energyterms = OrderedDict()
856  for line in o:
857  s = line.split()
858  if "Select the terms you want from the following list" in line:
859  parsemode = 1
860  if parsemode == 1:
861  if len(s) > 0 and all([isint(i) for i in s[::2]]):
862  parsemode = 2
863  if parsemode == 2:
864  if len(s) > 0:
865  try:
866  if all([isint(i) for i in s[::2]]):
867  for j in range(len(s))[::2]:
868  num = int(s[j])
869  name = s[j+1]
870  energyterms[name] = num
871  except: pass
872  return energyterms
873 
874  def optimize(self, shot, crit=1e-4, align=True, **kwargs):
875 
876  """ Optimize the geometry and align the optimized geometry to the starting geometry. """
877 
878 
879  self.mol[shot].write("%s.gro" % self.name)
880 
881  if "min_opts" in kwargs:
882  min_opts = kwargs["min_opts"]
883  else:
884  # algorithm = "steep"
885  if self.have_constraints:
886  algorithm = "steep"
887  else:
888  algorithm = "l-bfgs"
889  # Arguments for running minimization.
890  min_opts = {"integrator" : algorithm, "emtol" : crit, "nstxout" : 0, "nstfout" : 0, "nsteps" : 10000, "nstenergy" : 1}
891 
892  edit_mdp(fin="%s.mdp" % self.name, fout="%s-min.mdp" % self.name, options=min_opts)
893 
894  self.warngmx("grompp -c %s.gro -p %s.top -f %s-min.mdp -o %s-min.tpr" % (self.name, self.name, self.name, self.name))
895  self.callgmx("mdrun -deffnm %s-min -nt 1" % self.name)
896  self.callgmx("trjconv -f %s-min.trr -s %s-min.tpr -o %s-min.gro -ndec 9" % (self.name, self.name, self.name), stdin="System")
897  self.callgmx("g_energy -xvg no -f %s-min.edr -o %s-min-e.xvg" % (self.name, self.name), stdin='Potential')
898 
899  E = float(open("%s-min-e.xvg" % self.name).readlines()[-1].split()[1])
900  M = Molecule("%s.gro" % self.name, build_topology=False) + Molecule("%s-min.gro" % self.name, build_topology=False)
901  if not self.pbc:
902  M.align(center=False)
903  rmsd = M.ref_rmsd(0)[1]
904  M[1].write("%s-min.gro" % self.name)
905 
906  return E / 4.184, rmsd, M[1]
907 
908  def evaluate_(self, force=False, dipole=False, traj=None):
909 
910  """
911  Utility function for computing energy, and (optionally) forces and dipoles using GROMACS.
912 
913  Inputs:
914  force: Switch for calculating the force.
915  dipole: Switch for calculating the dipole.
916  traj: Trajectory file name. If present, will loop over these snapshots.
917  Otherwise will do a single point evaluation at the current geometry.
918 
919  Outputs:
920  Result: Dictionary containing energies, forces and/or dipoles.
921  """
922 
923  shot_opts = OrderedDict([("nsteps", 0), ("nstxout", 0), ("nstxtcout", 0), ("nstenergy", 1)])
924  shot_opts["nstfout"] = 1 if force else 0
925  edit_mdp(fin="%s.mdp" % self.name, fout="%s-1.mdp" % self.name, options=shot_opts)
926 
927 
928  self.warngmx("grompp -c %s.gro -p %s.top -f %s-1.mdp -o %s.tpr" % (self.name, self.name, self.name, self.name))
929  self.callgmx("mdrun -deffnm %s -nt 1 -rerunvsite %s" % (self.name, "-rerun %s" % traj if traj else ''))
930 
931 
932  Result = OrderedDict()
933 
934 
935  self.callgmx("g_energy -xvg no -f %s.edr -o %s-e.xvg" % (self.name, self.name), stdin='Potential')
936  Efile = open("%s-e.xvg" % self.name).readlines()
937  Result["Energy"] = np.array([float(Eline.split()[1]) for Eline in Efile])
938 
939 
940  if force:
941  self.callgmx("g_traj -xvg no -s %s.tpr -f %s.trr -of %s-f.xvg -fp" % (self.name, self.name, self.name), stdin='System')
942  Result["Force"] = np.array([[float(j) for i, j in enumerate(line.split()[1:]) if self.AtomMask[int(i/3)]] \
943  for line in open("%s-f.xvg" % self.name).readlines()])
944 
945  if dipole:
946  self.callgmx("g_dipoles -s %s.tpr -f %s -o %s-d.xvg -xvg no" % (self.name, traj if traj else '%s.gro' % self.name, self.name), stdin="System\n")
947  Result["Dipole"] = np.array([[float(i) for i in line.split()[1:4]] for line in open("%s-d.xvg" % self.name)])
948 
949  return Result
950 
951  def evaluate_snapshot(self, shot, force=False, dipole=False):
952 
953  """ Evaluate variables (energies, force and/or dipole) using GROMACS for a single snapshot. """
954 
955 
956  self.mol[shot].write("%s.gro" % self.name)
957  return self.evaluate_(force, dipole)
958 
959  def evaluate_trajectory(self, force=False, dipole=False, traj=None):
960 
961  """ Evaluate variables (energies, force and/or dipole) using GROMACS over a trajectory. """
962 
963  if traj is None:
964  if hasattr(self, 'mdtraj'):
965  traj = self.mdtraj
966  else:
967  traj = "%s-all.gro" % self.name
968  self.mol[0].write("%s.gro" % self.name)
969  return self.evaluate_(force, dipole, traj)
970 
971  def make_gro_trajectory(self, fout=None):
972  """ Return the MD trajectory as a Molecule object. """
973  if fout is None:
974  fout = "%s-mdtraj.gro" % self.name
975  if not hasattr(self, 'mdtraj'):
976  raise RuntimeError('Engine does not have an associated trajectory.')
977  self.callgmx("trjconv -f %s -o %s -ndec 9 -pbc mol -novel -noforce" % (self.mdtraj, fout), stdin='System')
978  return fout
979 
980  def energy_one(self, shot):
981 
982  """ Compute the energy using GROMACS for a snapshot. """
983 
984  return self.evaluate_snapshot(shot)["Energy"]
985 
986  def energy_force_one(self, shot):
987 
988  """ Compute the energy and force using GROMACS for a single snapshot; interfaces with AbInitio target. """
989 
990  Result = self.evaluate_snapshot(shot, force=True)
991  return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"]))
992 
993  def energy(self, traj=None):
994 
995  """ Compute the energy using GROMACS over a trajectory. """
996 
997  return self.evaluate_trajectory(traj=traj)["Energy"]
998 
999  def energy_force(self, force=True, traj=None):
1001  """ Compute the energy and force using GROMACS over a trajectory. """
1002 
1003  Result = self.evaluate_trajectory(force=force, traj=traj)
1004  return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"]))
1005 
1006  def energy_dipole(self, traj=None):
1007  Result = self.evaluate_trajectory(force=False, dipole=True, traj=traj)
1008  return np.hstack((Result["Energy"].reshape(-1,1), Result["Dipole"]))
1009 
1010  def energy_rmsd(self, shot, optimize=True):
1011 
1012  """ Calculate energy of the selected structure (optionally minimize and return the minimized energy and RMSD). In kcal/mol. """
1013 
1014  if optimize:
1015  return self.optimize(shot)
1016  else:
1017  self.mol[shot].write("%s.gro" % self.name)
1018  self.warngmx("grompp -c %s.gro -p %s.top -f %s.mdp -o %s-1.tpr" % (self.name, self.name, self.name, self.name))
1019  self.callgmx("mdrun -deffnm %s-1 -nt 1 -rerunvsite" % (self.name, self.name))
1020  self.callgmx("g_energy -xvg no -f %s-1.edr -o %s-1-e.xvg" % (self.name, self.name), stdin='Potential')
1021  E = float(open("%s-1-e.xvg" % self.name).readlines()[0].split()[1])
1022  return E, 0.0
1023 
1024  def interaction_energy(self, fraga, fragb):
1025 
1026  """ Computes the interaction energy between two fragments over a trajectory. """
1027 
1028  self.mol[0].write("%s.gro" % self.name)
1029 
1030 
1031  write_ndx('%s.ndx' % self.name, OrderedDict([('A',[i+1 for i in fraga]),('B',[i+1 for i in fragb])]))
1032 
1033 
1034  edit_mdp(fin='%s.mdp' % self.name, fout='%s-i.mdp' % self.name, options={'xtc_grps':'A B', 'energygrps':'A B'})
1035  edit_mdp(fin='%s.mdp' % self.name, fout='%s-x.mdp' % self.name, options={'xtc_grps':'A B', 'energygrps':'A B', 'energygrp-excl':'A B'})
1036 
1037 
1038  self.warngmx("grompp -c %s.gro -p %s.top -f %s-i.mdp -n %s.ndx -o %s-i.tpr" % \
1039  (self.name, self.name, self.name, self.name, self.name))
1040  self.callgmx("mdrun -deffnm %s-i -nt 1 -rerunvsite -rerun %s-all.gro" % (self.name, self.name))
1041  self.callgmx("g_energy -f %s-i.edr -o %s-i-e.xvg -xvg no" % (self.name, self.name), stdin='Potential\n')
1042  I = []
1043  for line in open('%s-i-e.xvg' % self.name):
1044  I.append(sum([float(i) for i in line.split()[1:]]))
1045  I = np.array(I)
1046 
1047 
1048  self.warngmx("grompp -c %s.gro -p %s.top -f %s-x.mdp -n %s.ndx -o %s-x.tpr" % \
1049  (self.name, self.name, self.name, self.name, self.name))
1050  self.callgmx("mdrun -deffnm %s-x -nt 1 -rerunvsite -rerun %s-all.gro" % (self.name, self.name))
1051  self.callgmx("g_energy -f %s-x.edr -o %s-x-e.xvg -xvg no" % (self.name, self.name), stdin='Potential\n')
1052  X = []
1053  for line in open('%s-x-e.xvg' % self.name):
1054  X.append(sum([float(i) for i in line.split()[1:]]))
1055  X = np.array(X)
1056 
1057  return (I - X) / 4.184 # kcal/mol
1058 
1059  def multipole_moments(self, shot=0, optimize=True, polarizability=False):
1060 
1061  """ Return the multipole moments of the 1st snapshot in Debye and Buckingham units. """
1062 
1063  if not self.double:
1064  warn_once("Single-precision GROMACS detected - recommend that you use double precision build.")
1065 
1066  if polarizability:
1067  raise NotImplementedError
1068 
1069  if optimize:
1070  self.optimize(shot)
1071  M = Molecule("%s-min.gro" % self.name)
1072  else:
1073  self.mol[shot].write("%s.gro" % self.name)
1074  M = Molecule("%s.gro" % self.name)
1075 
1076  #-----
1077  # g_dipoles uses a different reference point compared to TINKER
1078  #-----
1079  # self.callgmx("g_dipoles -s %s-d.tpr -f %s-d.gro -o %s-d.xvg -xvg no" % (self.name, self.name, self.name), stdin="System\n")
1080  # Dips = np.array([[float(i) for i in line.split()[1:4]] for line in open("%s-d.xvg" % self.name)])[0]
1081  #-----
1082 
1083  ea_debye = 4.803204255928332 # Conversion factor from e*nm to Debye
1084  q = self.get_charges()
1085  x = M.xyzs[0] - M.center_of_mass()[0]
1086 
1087  xx, xy, xz, yy, yz, zz = (x[:,i]*x[:,j] for i, j in [(0,0),(0,1),(0,2),(1,1),(1,2),(2,2)])
1088  # Multiply charges by positions to get dipole moment.
1089  dip = ea_debye * np.sum(x*q.reshape(-1,1),axis=0)
1090  dx = dip[0]
1091  dy = dip[1]
1092  dz = dip[2]
1093  qxx = 1.5 * ea_debye * np.sum(q*xx)
1094  qxy = 1.5 * ea_debye * np.sum(q*xy)
1095  qxz = 1.5 * ea_debye * np.sum(q*xz)
1096  qyy = 1.5 * ea_debye * np.sum(q*yy)
1097  qyz = 1.5 * ea_debye * np.sum(q*yz)
1098  qzz = 1.5 * ea_debye * np.sum(q*zz)
1099  tr = qxx+qyy+qzz
1100  qxx -= tr/3
1101  qyy -= tr/3
1102  qzz -= tr/3
1103 
1104  moments = [dx,dy,dz,qxx,qxy,qyy,qxz,qyz,qzz]
1105  dipole_dict = OrderedDict(zip(['x','y','z'], moments[:3]))
1106  quadrupole_dict = OrderedDict(zip(['xx','xy','yy','xz','yz','zz'], moments[3:10]))
1107  calc_moments = OrderedDict([('dipole', dipole_dict), ('quadrupole', quadrupole_dict)])
1108  # This ordering has to do with the way TINKER prints it out.
1109  return calc_moments
1110 
1111  def normal_modes(self, shot=0, optimize=True):
1112 
1113  if not self.double:
1114  warn_once("Single-precision GROMACS detected - recommend that you use double precision build.")
1115 
1116  edit_mdp(fin='%s.mdp' % self.name, fout='%s-nm.mdp' % self.name, options={'integrator':'nm'})
1117 
1118  if optimize:
1119  self.optimize(shot)
1120  self.warngmx("grompp -c %s-min.gro -p %s.top -f %s-nm.mdp -o %s-nm.tpr" % (self.name, self.name, self.name, self.name))
1121  else:
1122  warn_once("Asking for normal modes without geometry optimization?")
1123  self.mol[shot].write("%s.gro" % self.name)
1124  self.warngmx("grompp -c %s.gro -p %s.top -f %s-nm.mdp -o %s-nm.tpr" % (self.name, self.name, self.name, self.name))
1125 
1126  self.callgmx("mdrun -deffnm %s-nm -nt 1 -mtx %s-nm.mtx -v" % (self.name, self.name))
1127  self.callgmx("g_nmeig -s %s-nm.tpr -f %s-nm.mtx -of %s-nm.xvg -v %s-nm.trr -last 10000 -xvg no" % \
1128  (self.name, self.name, self.name, self.name))
1129  self.callgmx("trjconv -s %s-nm.tpr -f %s-nm.trr -o %s-nm.gro -ndec 9" % (self.name, self.name, self.name), stdin="System")
1130  NM = Molecule("%s-nm.gro" % self.name, build_topology=False)
1131 
1132  calc_eigvals = np.array([float(line.split()[1]) for line in open("%s-nm.xvg" % self.name).readlines()])
1133  calc_eigvecs = NM.xyzs[1:]
1134  # Copied from tinkerio.py code
1135  calc_eigvals = np.array(calc_eigvals)
1136  calc_eigvecs = np.array(calc_eigvecs)
1137  # Sort by frequency absolute value and discard the six that are closest to zero
1138  calc_eigvecs = calc_eigvecs[np.argsort(np.abs(calc_eigvals))][6:]
1139  calc_eigvals = calc_eigvals[np.argsort(np.abs(calc_eigvals))][6:]
1140  # Sort again by frequency
1141  calc_eigvecs = calc_eigvecs[np.argsort(calc_eigvals)]
1142  calc_eigvals = calc_eigvals[np.argsort(calc_eigvals)]
1143  for i in range(len(calc_eigvecs)):
1144  calc_eigvecs[i] /= np.linalg.norm(calc_eigvecs[i])
1145  return calc_eigvals, calc_eigvecs
1146 
1147  def generate_positions(self):
1148 
1149  self.warngmx("grompp -c %s.gro -p %s.top -f %s.mdp -o %s.tpr" % (self.name, self.name, self.name, self.name))
1150  self.callgmx("mdrun -deffnm %s -nt 1 -rerunvsite -rerun %s-all.gro" % (self.name, self.name))
1151  self.callgmx("trjconv -f %s.trr -o %s-out.gro -ndec 9 -novel -noforce" % (self.name, self.name), stdin='System')
1152  NewMol = Molecule("%s-out.gro" % self.name)
1153  return NewMol.xyzs
1154 
1155  def n_snaps(self, nsteps, step_interval, timestep):
1156  return int((nsteps * 1.0 / step_interval) * timestep)
1157 
1158  def scd_persnap(self, ndx, timestep, final_frame):
1159  Scd = []
1160  for snap in range(0, final_frame + 1):
1161  self.callgmx("g_order -s %s-md.tpr -f %s-md.trr -n %s-%s.ndx -od %s-%s.xvg -b %i -e %i -xvg no" % (self.name, self.name, self.name, ndx, self.name, ndx, snap, snap))
1162  Scd_snap = []
1163  for line in open("%s-%s.xvg" % (self.name, ndx)):
1164  s = [float(i) for i in line.split()]
1165  Scd_snap.append(s[-1])
1166  Scd.append(Scd_snap)
1167  return Scd
1168 
1169  def calc_scd(self, n_snap, timestep):
1170  # Find deuterium order parameter via g_order.
1171  # Create index files for each lipid tail.
1172  sn1_ndx = ['a C15', 'a C17', 'a C18', 'a C19', 'a C20', 'a C21', 'a C22', 'a C23', 'a C24', 'a C25', 'a C26', 'a C27', 'a C28', 'a C29', 'a C30', 'a C31', 'del 0-5', 'q', '']
1173  sn2_ndx = ['a C34', 'a C36', 'a C37', 'a C38', 'a C39', 'a C40', 'a C41', 'a C42', 'a C43', 'a C44', 'a C45', 'a C46', 'a C47', 'a C48', 'a C49', 'a C50', 'del 0-5', 'q', '']
1174  self.callgmx("make_ndx -f %s-md.tpr -o %s-sn1.ndx" % (self.name, self.name), stdin="\n".join(sn1_ndx))
1175  self.callgmx("make_ndx -f %s-md.tpr -o %s-sn2.ndx" % (self.name, self.name), stdin="\n".join(sn2_ndx))
1176 
1177  # Loop over g_order for each frame.
1178  # Adjust nsteps to account for nstxout = 1000.
1179  sn1 = self.scd_persnap('sn1', timestep, n_snap)
1180  sn2 = self.scd_persnap('sn2', timestep, n_snap)
1181  for i in range(0, n_snap + 1):
1182  sn1[i].extend(sn2[i])
1183  Scds = np.abs(np.array(sn1))
1184  return Scds
1185 
1186  def n_nonwater(self, structure_file):
1187  mol = Molecule(structure_file)
1188  n_mol = len(mol.molecules)
1189  n_sol = len([i for i in mol.Data['resname'] if i == 'SOL']) * 1.0 / 3
1190  return n_mol - n_sol
1191 
1192  def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=0, minimize=True, threads=None, verbose=False, bilayer=False, **kwargs):
1193 
1194  """
1195  Method for running a molecular dynamics simulation.
1196 
1197  Required arguments:
1198  nsteps = (int) Number of total time steps
1199  timestep = (float) Time step in FEMTOSECONDS
1200  temperature = (float) Temperature control (Kelvin)
1201  pressure = (float) Pressure control (atmospheres)
1202  nequil = (int) Number of additional time steps at the beginning for equilibration
1203  nsave = (int) Step interval for saving data
1204  minimize = (bool) Perform an energy minimization prior to dynamics
1205  threads = (int) Number of MPI-threads
1206 
1207  Returns simulation data:
1208  Rhos = (array) Density in kilogram m^-3
1209  Potentials = (array) Potential energies
1210  Kinetics = (array) Kinetic energies
1211  Volumes = (array) Box volumes
1212  Dips = (3xN array) Dipole moments
1213  EComps = (dict) Energy components
1214  Als = (array) Average area per lipid in nm^2
1215  Scds = (Nx28 array) Deuterium order parameter
1216  """
1217 
1218  if verbose: logger.info("Molecular dynamics simulation with GROMACS engine.\n")
1219 
1220  # Set the number of threads.
1221  if threads is None:
1222  if "OMP_NUM_THREADS" in os.environ:
1223  threads = int(os.environ["OMP_NUM_THREADS"])
1224  else:
1225  threads = 1
1226 
1227  # Molecular dynamics options.
1228  md_opts = OrderedDict([("integrator", "md"), ("nsteps", nsteps), ("dt", timestep / 1000)])
1229  # Default options if not user specified.
1230  md_defs = OrderedDict()
1231 
1232  # Read the provided mdp_opts file.
1233  # This contains information on the number of coupling groups
1234  # which affects the proper formatting of ref_t and ref_p
1235  mdp_opts = edit_mdp(fin='%s.mdp' % self.name)
1236  if temperature is not None:
1237  ref_t_str = ' '.join(["%f" % temperature]*len(mdp_opts.get('tc_grps', 'System').split()))
1238  if pressure is not None:
1239  ref_p_str = ' '.join(["%f" % pressure]*len(mdp_opts.get('tc_grps', 'System').split()))
1240 
1241  warnings = []
1242  if temperature is not None:
1243  md_opts["ref_t"] = ref_t_str
1244  md_opts["gen_vel"] = "no"
1245  md_defs["tc_grps"] = "System"
1246  md_defs["tcoupl"] = "v-rescale"
1247  md_defs["tau_t"] = 1.0
1248  if self.pbc:
1249  md_opts["comm_mode"] = "linear"
1250  if pressure is not None:
1251  md_opts["ref_p"] = ref_p_str
1252  md_defs["pcoupl"] = "parrinello-rahman"
1253  md_defs["tau_p"] = 1.5
1254  else:
1255  md_opts["comm_mode"] = "None"
1256  md_opts["nstcomm"] = 0
1257 
1258  # In gromacs version 5, default cutoff scheme becomes verlet.
1259  # Need to set to group for backwards compatibility
1260  md_defs["cutoff-scheme"] = 'group'
1261  md_opts["nstenergy"] = nsave
1262  md_opts["nstcalcenergy"] = nsave
1263  md_opts["nstxout"] = nsave
1264  md_opts["nstvout"] = nsave
1265  md_opts["nstfout"] = 0
1266  md_opts["nstxtcout"] = 0
1267 
1268  # Minimize the energy.
1269  if minimize:
1270  min_opts = OrderedDict([("integrator", "steep"), ("emtol", 10.0), ("nsteps", 10000)])
1271  if verbose: logger.info("Minimizing energy... ")
1272  self.optimize(0, min_opts=min_opts)
1273  if verbose: logger.info("Done\n")
1274  gro1="%s-min.gro" % self.name
1275  else:
1276  gro1="%s.gro" % self.name
1277  self.mol[0].write(gro1)
1278 
1279  # Run equilibration.
1280  if nequil > 0:
1281  if verbose: logger.info("Equilibrating...\n")
1282  eq_opts = deepcopy(md_opts)
1283  eq_opts.update({"nsteps" : nequil, "nstenergy" : 0, "nstxout" : 0,
1284  "gen-vel": "yes", "gen-temp" : temperature, "gen-seed" : random.randrange(100000,999999)})
1285  eq_defs = deepcopy(md_defs)
1286  if "pcoupl" in eq_defs and hasattr(self, 'gmx_eq_barostat'):
1287  eq_opts["pcoupl"] = self.gmx_eq_barostat
1288  edit_mdp(fin='%s.mdp' % self.name, fout="%s-eq.mdp" % self.name, options=eq_opts, defaults=eq_defs)
1289  self.warngmx("grompp -c %s -p %s.top -f %s-eq.mdp -o %s-eq.tpr" % (gro1, self.name, self.name, self.name), warnings=warnings, print_command=verbose)
1290  self.callgmx("mdrun -v -deffnm %s-eq -nt %i -stepout %i" % (self.name, threads, nsave), print_command=verbose, print_to_screen=verbose)
1291  gro2="%s-eq.gro" % self.name
1292  else:
1293  gro2=gro1
1294 
1295  # Run production.
1296  if verbose: logger.info("Production run...\n")
1297  edit_mdp(fin="%s.mdp" % self.name, fout="%s-md.mdp" % self.name, options=md_opts, defaults=md_defs)
1298  self.warngmx("grompp -c %s -p %s.top -f %s-md.mdp -o %s-md.tpr" % (gro2, self.name, self.name, self.name), warnings=warnings, print_command=verbose)
1299  self.callgmx("mdrun -v -deffnm %s-md -nt %i -stepout %i" % (self.name, threads, nsave), print_command=verbose, print_to_screen=verbose)
1300 
1301  self.mdtraj = '%s-md.trr' % self.name
1302 
1303  if verbose: logger.info("Production run finished, calculating properties...\n")
1304  # Figure out dipoles - note we use g_dipoles and not the multipole_moments function.
1305  self.callgmx("g_dipoles -s %s-md.tpr -f %s-md.trr -o %s-md-dip.xvg -xvg no" % (self.name, self.name, self.name), stdin="System\n")
1306 
1307  # Figure out which energy terms need to be printed.
1308  energyterms = self.energy_termnames(edrfile="%s-md.edr" % self.name)
1309  ekeep = [k for k,v in energyterms.items() if v <= energyterms['Total-Energy']]
1310  ekeep += ['Temperature', 'Volume', 'Density']
1311 
1312  # Calculate deuterium order parameter for bilayer optimization.
1313  if bilayer:
1314  # Figure out how many lipids in simulation.
1315  n_lip = self.n_nonwater('%s.gro' % self.name)
1316  n_snap = self.n_snaps(nsteps, 1000, timestep)
1317  Scds = self.calc_scd(n_snap, timestep)
1318  al_vars = ['Box-Y', 'Box-X']
1319  self.callgmx("g_energy -f %s-md.edr -o %s-md-energy-xy.xvg -xvg no" % (self.name, self.name), stdin="\n".join(al_vars))
1320  Xs = []
1321  Ys = []
1322  for line in open("%s-md-energy-xy.xvg" % self.name):
1323  s = [float(i) for i in line.split()]
1324  Xs.append(s[-1])
1325  Ys.append(s[-2])
1326  Xs = np.array(Xs)
1327  Ys = np.array(Ys)
1328  Als = 2 * (Xs * Ys) / n_lip
1329  else:
1330  Scds = 0
1331  Als = 0
1332 
1333  # Perform energy component analysis and return properties.
1334  self.callgmx("g_energy -f %s-md.edr -o %s-md-energy.xvg -xvg no" % (self.name, self.name), stdin="\n".join(ekeep))
1335  ecomp = OrderedDict()
1336  Rhos = []
1337  Volumes = []
1338  Kinetics = []
1339  Potentials = []
1340  for line in open("%s-md-energy.xvg" % self.name):
1341  s = [float(i) for i in line.split()]
1342  for i in range(len(ekeep) - 2):
1343  val = s[i+1]
1344  if ekeep[i] in ecomp:
1345  ecomp[ekeep[i]].append(val)
1346  else:
1347  ecomp[ekeep[i]] = [val]
1348  Rhos.append(s[-1])
1349  Volumes.append(s[-2])
1350  Rhos = np.array(Rhos)
1351  Volumes = np.array(Volumes)
1352  Potentials = np.array(ecomp['Potential'])
1353  Kinetics = np.array(ecomp['Kinetic-En.'])
1354  Dips = np.array([[float(i) for i in line.split()[1:4]] for line in open("%s-md-dip.xvg" % self.name)])
1355  Ecomps = OrderedDict([(key, np.array(val)) for key, val in ecomp.items()])
1356  # Initialized property dictionary.
1357  prop_return = OrderedDict()
1358  prop_return.update({'Rhos': Rhos, 'Potentials': Potentials, 'Kinetics': Kinetics, 'Volumes': Volumes, 'Dips': Dips, 'Ecomps': Ecomps, 'Als': Als, 'Scds': Scds})
1359  if verbose: logger.info("Finished!\n")
1360  return prop_return
1361 
1362  def md(self, nsteps=0, nequil=0, verbose=False, deffnm=None, **kwargs):
1363 
1364  """
1365  Method for running a molecular dynamics simulation. A little different than molecular_dynamics (for thermo.py)
1366 
1367  Required arguments:
1368 
1369  nsteps = (int) Number of total time steps
1370 
1371  nequil = (int) Number of additional time steps at the beginning
1372  for equilibration
1373 
1374  verbose = (bool) Be loud and noisy
1375 
1376  deffnm = (string) default names for simulation output files
1377 
1378  The simulation data is written to the working directory.
1379 
1380  """
1381 
1382  if verbose:
1383  logger.info("Molecular dynamics simulation with GROMACS engine.\n")
1384 
1385  # Molecular dynamics options.
1386  md_opts = OrderedDict()
1387  # Default options
1388  md_defs = OrderedDict(**kwargs)
1389 
1390  if nsteps > 0:
1391  md_opts["nsteps"] = nsteps
1392 
1393  warnings = []
1394 
1395  if "gmx_ndx" in kwargs:
1396  ndx_flag = "-n " + kwargs["gmx_ndx"]
1397  else:
1398  ndx_flag = ""
1399 
1400  gro1 = "%s.gro" % self.name
1401 
1402  # Run equilibration.
1403  if nequil > 0:
1404  if verbose:
1405  logger.info("Equilibrating...\n")
1406  eq_opts = deepcopy(md_opts)
1407  eq_opts.update({"nsteps" : nequil, "nstenergy" : 0, "nstxout" : 0})
1408  eq_defs = deepcopy(md_defs)
1409  edit_mdp(fin='%s.mdp' % self.name,
1410  fout="%s-eq.mdp" % self.name,
1411  options=eq_opts,
1412  defaults=eq_defs)
1413 
1414  self.warngmx(("grompp " +
1415  "-c %s " % gro1 +
1416  "-f %s-eq.mdp " % self.name +
1417  "-p %s.top " % self.name +
1418  "%s " % ndx_flag +
1419  "-o %s-eq.tpr" % self.name),
1420  warnings=warnings,
1421  print_command=verbose)
1422  self.callgmx(("mdrun -v " +
1423  "-deffnm %s-eq" % self.name),
1424  print_command=verbose,
1425  print_to_screen=verbose)
1426 
1427  gro2 = "%s-eq.gro" % self.name
1428  else:
1429  gro2 = gro1
1430 
1431  self.mdtraj = '%s-md.trr' % self.name
1432  self.mdene = '%s-md.edr' % self.name
1433 
1434  # Run production.
1435  if verbose:
1436  logger.info("Production run...\n")
1437  edit_mdp(fin="%s.mdp" % self.name,
1438  fout="%s-md.mdp" % self.name,
1439  options=md_opts,
1440  defaults=md_defs)
1441  self.warngmx(("grompp " +
1442  "-c %s " % gro2 +
1443  "-f %s-md.mdp " % self.name +
1444  "-p %s.top " % self.name +
1445  "%s " % ndx_flag +
1446  "-o %s-md.tpr" % self.name),
1447  warnings=warnings,
1448  print_command=verbose)
1449  self.callgmx(("mdrun -v " +
1450  "-deffnm %s-md " % self.name),
1451  print_command=verbose,
1452  print_to_screen=verbose)
1453 
1454  self.mdtraj = '%s-md.trr' % self.name
1455 
1456  if verbose:
1457  logger.info("Production run finished...\n")
1458 
1459 class Liquid_GMX(Liquid):
1460  def __init__(self,options,tgt_opts,forcefield):
1461  # Path to GROMACS executables.
1462  self.set_option(options,'gmxpath')
1463  # Suffix for GROMACS executables.
1464  self.set_option(options,'gmxsuffix')
1465  # Number of threads for mdrun.
1466  self.set_option(tgt_opts,'md_threads')
1467  # Name of the liquid coordinate file.
1468  self.set_option(tgt_opts,'liquid_coords',default='liquid.gro',forceprint=True)
1469  # Name of the gas coordinate file.
1470  self.set_option(tgt_opts,'gas_coords',default='gas.gro',forceprint=True)
1471  # Whether to use a different barostat for equilibration (default berendsen)
1472  self.set_option(tgt_opts,'gmx_eq_barostat',forceprint=True)
1473  # Class for creating engine object.
1474  self.engine_ = GMX
1475  # Name of the engine to pass to npt.py.
1476  self.engname = "gromacs"
1477  # Command prefix.
1478  self.nptpfx = "bash rungmx.sh"
1479  if tgt_opts['remote_backup']:
1480  self.nptpfx += " -b"
1481  # Extra files to be linked into the temp-directory.
1482  self.nptfiles = ['%s.mdp' % os.path.splitext(f)[0] for f in [self.liquid_coords, self.gas_coords]]
1483  self.nptfiles += ['%s.top' % os.path.splitext(f)[0] for f in [self.liquid_coords, self.gas_coords]]
1484  # Set some options for the polarization correction calculation.
1485  self.gas_engine_args = {'gmx_top' : '%s.top' % os.path.splitext(self.gas_coords)[0],
1486  'gmx_mdp' : '%s.mdp' % os.path.splitext(self.gas_coords)[0]}
1487  # Scripts to be copied from the ForceBalance installation directory.
1488  self.scripts = ['rungmx.sh']
1489  # Initialize the base class.
1490  super(Liquid_GMX,self).__init__(options,tgt_opts,forcefield)
1491  # Error checking.
1492  for i in self.nptfiles:
1493  if not os.path.exists(os.path.join(self.root, self.tgtdir, i)):
1494  logger.error('Please provide %s; it is needed to proceed.\n' % i)
1495  raise RuntimeError
1496  # Send back last frame of production trajectory.
1497  self.extra_output = ['liquid-md.gro']
1498  # Send back the trajectory file.
1499  if self.save_traj > 0:
1500  self.extra_output += ['liquid-md.trr']
1501  # Dictionary of last frames.
1502  self.LfDict = OrderedDict()
1503  self.LfDict_New = OrderedDict()
1504  # These functions need to be called after self.nptfiles is populated
1505  self.post_init(options)
1506 
1507  def npt_simulation(self, temperature, pressure, simnum):
1508  """ Submit a NPT simulation to the Work Queue. """
1509  if self.goodstep and (temperature, pressure) in self.LfDict_New:
1510  self.LfDict[(temperature, pressure)] = self.LfDict_New[(temperature, pressure)]
1511  if (temperature, pressure) in self.LfDict:
1512  lfsrc = self.LfDict[(temperature, pressure)]
1513  lfdest = os.path.join(os.getcwd(), 'liquid.gro')
1514  logger.info("Copying previous iteration final geometry .gro file: %s to %s\n" % (lfsrc, lfdest))
1515  shutil.copy2(lfsrc,lfdest)
1516  self.nptfiles.append(lfdest)
1517  self.LfDict_New[(temperature, pressure)] = os.path.join(os.getcwd(),'liquid-md.gro')
1518  super(Liquid_GMX, self).npt_simulation(temperature, pressure, simnum)
1519  self.last_traj = [i for i in self.last_traj if '.gro' not in i]
1520 
1521 class Lipid_GMX(Lipid):
1522  def __init__(self,options,tgt_opts,forcefield):
1523  # Path to GROMACS executables.
1524  self.set_option(options,'gmxpath')
1525  # Suffix for GROMACS executables.
1526  self.set_option(options,'gmxsuffix')
1527  # Number of threads for mdrun.
1528  self.set_option(tgt_opts,'md_threads')
1529  # Name of the lipid coordinate file.
1530  self.set_option(tgt_opts,'lipid_coords',default='lipid.gro',forceprint=True)
1531  # Whether to use a different barostat for equilibration (default berendsen)
1532  self.set_option(tgt_opts,'gmx_eq_barostat',forceprint=True)
1533  # Class for creating engine object.
1534  self.engine_ = GMX
1535  # Name of the engine to pass to npt.py.
1536  self.engname = "gromacs"
1537  # Command prefix.
1538  self.nptpfx = "bash rungmx.sh"
1539  if tgt_opts['remote_backup']:
1540  self.nptpfx += " -b"
1541  # Extra files to be linked into the temp-directory.
1542  self.nptfiles = ['%s.mdp' % os.path.splitext(f)[0] for f in [self.lipid_coords]]
1543  self.nptfiles += ['%s.top' % os.path.splitext(f)[0] for f in [self.lipid_coords]]
1544  # Scripts to be copied from the ForceBalance installation directory.
1545  self.scripts = ['rungmx.sh']
1546  # Initialize the base class.
1547  super(Lipid_GMX,self).__init__(options,tgt_opts,forcefield)
1548  # Error checking.
1549  for i in self.nptfiles:
1550  if not os.path.exists(os.path.join(self.root, self.tgtdir, i)):
1551  logger.error('Please provide %s; it is needed to proceed.\n' % i)
1552  raise RuntimeError
1553  # Send back last frame of production trajectory.
1554  self.extra_output = ['lipid-md.gro']
1555  # Send back the trajectory file.
1556  if self.save_traj > 0:
1557  self.extra_output += ['lipid-md.trr']
1558  # Dictionary of last frames.
1559  self.LfDict = OrderedDict()
1560  self.LfDict_New = OrderedDict()
1561 
1562  def npt_simulation(self, temperature, pressure, simnum):
1563  """ Submit a NPT simulation to the Work Queue. """
1564  if "n_ic" in self.RefData:
1565  # Get PT state information.
1566  phase_reorder = list(zip(*self.PhasePoints))
1567  t_index = [i for i, x in enumerate(phase_reorder[0]) if x == temperature]
1568  p_index = [i for i, x in enumerate(phase_reorder[1]) if x == pressure]
1569  p_u = phase_reorder[-1][list(set(t_index) & set(p_index))[0]]
1570  PT_vals = (temperature, pressure, p_u)
1571  # Build dictionary of current iteration final frames, indexed by phase points.
1572  if not PT_vals in self.lipid_mols_new:
1573  self.lipid_mols_new[PT_vals] = [os.path.join(os.getcwd(),'lipid-md.gro')]
1574  else:
1575  self.lipid_mols_new[PT_vals].append(os.path.join(os.getcwd(),'lipid-md.gro'))
1576  # Run NPT simulation.
1577  super(Lipid_GMX, self).npt_simulation(temperature, pressure, simnum)
1578  # When lipid_mols_new is full, move values to lipid_mols for the next iteration.
1579  if len(self.lipid_mols_new[PT_vals]) == int(self.RefData['n_ic'][PT_vals]):
1580  self.lipid_mols[PT_vals] = self.lipid_mols_new[PT_vals]
1581  self.lipid_mols_new.pop(PT_vals)
1582  else:
1583  if self.goodstep and (temperature, pressure) in self.LfDict_New:
1584  self.LfDict[(temperature, pressure)] = self.LfDict_New[(temperature, pressure)]
1585  if (temperature, pressure) in self.LfDict:
1586  lfsrc = self.LfDict[(temperature, pressure)]
1587  lfdest = os.path.join(os.getcwd(), 'lipid.gro')
1588  logger.info("Copying previous iteration final geometry .gro file: %s to %s\n" % (lfsrc, lfdest))
1589  shutil.copy2(lfsrc,lfdest)
1590  self.nptfiles.append(lfdest)
1591  self.LfDict_New[(temperature, pressure)] = os.path.join(os.getcwd(),'lipid-md.gro')
1592  super(Lipid_GMX, self).npt_simulation(temperature, pressure, simnum)
1593  self.last_traj = [i for i in self.last_traj if '.gro' not in i]
1594 
1595 class AbInitio_GMX(AbInitio):
1596  """ Subclass of AbInitio for force and energy matching using GROMACS. """
1597  def __init__(self,options,tgt_opts,forcefield):
1598 
1599  self.set_option(tgt_opts,'coords',default="all.gro")
1600  self.set_option(tgt_opts,'gmx_top',default="topol.top")
1601  self.set_option(tgt_opts,'gmx_mdp',default="shot.mdp")
1602  self.engine_ = GMX
1603 
1604  super(AbInitio_GMX,self).__init__(options,tgt_opts,forcefield)
1605 
1606 class BindingEnergy_GMX(BindingEnergy):
1607  """ Binding energy matching using Gromacs. """
1608  def __init__(self,options,tgt_opts,forcefield):
1609  self.engine_ = GMX
1610 
1611  super(BindingEnergy_GMX,self).__init__(options,tgt_opts,forcefield)
1612 
1613 class Interaction_GMX(Interaction):
1614  """ Interaction energy matching using GROMACS. """
1615  def __init__(self,options,tgt_opts,forcefield):
1616 
1617  self.set_option(tgt_opts,'coords',default="all.gro")
1618  self.set_option(tgt_opts,'gmx_top',default="topol.top")
1619  self.set_option(tgt_opts,'gmx_mdp',default="shot.mdp")
1620  self.engine_ = GMX
1621 
1622  super(Interaction_GMX,self).__init__(options,tgt_opts,forcefield)
1623 
1624 class Moments_GMX(Moments):
1625  """ Multipole moment matching using GROMACS. """
1626  def __init__(self,options,tgt_opts,forcefield):
1628  self.set_option(tgt_opts,'coords',default="conf.gro")
1629  self.set_option(tgt_opts,'gmx_top',default="topol.top")
1630  self.set_option(tgt_opts,'gmx_mdp',default="shot.mdp")
1631  self.engine_ = GMX
1633  super(Moments_GMX,self).__init__(options,tgt_opts,forcefield)
1634 
1635 class Vibration_GMX(Vibration):
1636  """ Vibrational frequency matching using GROMACS. """
1637  def __init__(self,options,tgt_opts,forcefield):
1639  self.set_option(tgt_opts,'coords',default="conf.gro")
1640  self.engine_ = GMX
1641 
1642  super(Vibration_GMX,self).__init__(options,tgt_opts,forcefield)
1643 
1644 class Thermo_GMX(Thermo):
1645  """ Thermodynamical property matching using GROMACS. """
1646  def __init__(self,options,tgt_opts,forcefield):
1647  # Path to GROMACS executables.
1648  self.set_option(options,'gmxpath')
1649  # Suffix for GROMACS executables.
1650  self.set_option(options,'gmxsuffix')
1651  self.engine_ = GMX
1652  # Name of the engine to pass to scripts.
1653  self.engname = "gromacs"
1654  # Command prefix.
1655  self.mdpfx = "bash gmxprefix.bash"
1656  # Scripts to be copied from the ForceBalance installation directory.
1657  self.scripts = ['gmxprefix.bash', 'md_chain.py']
1659  super(Thermo_GMX,self).__init__(options,tgt_opts,forcefield)
def edit_mdp(fin=None, fout=None, options={}, defaults={}, verbose=False)
Read, create or edit a Gromacs MDP file.
Definition: gmxio.py:69
def md(self, nsteps=0, nequil=0, verbose=False, deffnm=None, kwargs)
Method for running a molecular dynamics simulation.
Definition: gmxio.py:1406
Q-Chem input file parser.
atomtype_to_mass
A dictionary of atomic masses.
Definition: gmxio.py:381
def npt_simulation(self, temperature, pressure, simnum)
Submit a NPT simulation to the Work Queue.
Definition: gmxio.py:1536
def energy_force_one(self, shot)
Compute the energy and force using GROMACS for a single snapshot; interfaces with AbInitio target...
Definition: gmxio.py:1007
atomtypes
Listing of all atom types in the file, (probably unnecessary)
Definition: gmxio.py:379
Subclass of AbInitio for force and energy matching using GROMACS.
Definition: gmxio.py:1626
def energy_rmsd(self, shot, optimize=True)
Calculate energy of the selected structure (optionally minimize and return the minimized energy and R...
Definition: gmxio.py:1034
Vibrational mode fitting module.
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1627
def evaluate_(self, force=False, dipole=False, traj=None)
Utility function for computing energy, and (optionally) forces and dipoles using GROMACS.
Definition: gmxio.py:935
def feed(self, line)
Given a line, determine the interaction type and the atoms involved (the suffix). ...
Definition: gmxio.py:403
Nifty functions, intended to be imported by any module within ForceBalance.
Ab-initio fitting module (energies, forces, resp).
def parse_atomtype_line(line)
Parses the &#39;atomtype&#39; line.
Definition: gmxio.py:268
Multipole moment matching using GROMACS.
Definition: gmxio.py:1658
sec
The current section that we&#39;re in.
Definition: gmxio.py:369
def __init__(self, name="gmx", kwargs)
Definition: gmxio.py:536
def optimize(self, shot, crit=1e-4, align=True, kwargs)
Optimize the geometry and align the optimized geometry to the starting geometry.
Definition: gmxio.py:889
Thermodynamical property matching using GROMACS.
Definition: gmxio.py:1680
def LinkFile(src, dest, nosrcok=False)
Definition: nifty.py:1313
def evaluate_trajectory(self, force=False, dipole=False, traj=None)
Evaluate variables (energies, force and/or dipole) using GROMACS over a trajectory.
Definition: gmxio.py:977
def setopts(self, kwargs)
Called by init ; Set GROMACS-specific options.
Definition: gmxio.py:543
def energy_force(self, force=True, traj=None)
Compute the energy and force using GROMACS over a trajectory.
Definition: gmxio.py:1022
def energy(self, traj=None)
Compute the energy using GROMACS over a trajectory.
Definition: gmxio.py:1015
def npt_simulation(self, temperature, pressure, simnum)
Submit a NPT simulation to the Work Queue.
Definition: gmxio.py:1592
def callgmx(self, command, stdin=None, print_to_screen=False, print_command=False, kwargs)
Call GROMACS; prepend the gmxpath to the call to the GROMACS program.
Definition: gmxio.py:793
def isint(word)
ONLY matches integers! If you have a decimal point? None shall pass!
Definition: molecule.py:401
def scd_persnap(self, ndx, timestep, final_frame)
Definition: gmxio.py:1183
def warngmx(self, command, warnings=[], maxwarn=1, kwargs)
Call gromacs and allow for certain expected warnings.
Definition: gmxio.py:815
def which(fnm)
Definition: nifty.py:1362
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1639
engine_
Default file names for coordinates, top and mdp files.
Definition: gmxio.py:1652
gmxsuffix
Disable some optimizations.
Definition: gmxio.py:553
def multipole_moments(self, shot=0, optimize=True, polarizability=False)
Return the multipole moments of the 1st snapshot in Debye and Buckingham units.
Definition: gmxio.py:1085
Binding energy fitting module.
def n_snaps(self, nsteps, step_interval, timestep)
Definition: gmxio.py:1180
def write_ndx(fout, grps, fin=None)
Create or edit a Gromacs ndx file.
Definition: gmxio.py:148
def links(self)
Definition: gmxio.py:785
Interaction energy matching using GROMACS.
Definition: gmxio.py:1646
gmxversion
Figure out the GROMACS version - it will determine how programs are called.
Definition: gmxio.py:570
mol
The current molecule (set by the moleculetype keyword)
Definition: gmxio.py:373
pdict
The parameter dictionary (defined in this file)
Definition: gmxio.py:375
engine_
Default file names for coordinates, top and mdp files.
Definition: gmxio.py:1632
nbtype
Nonbonded type.
Definition: gmxio.py:371
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1647
have_constraints
Link files into the temp directory.
Definition: gmxio.py:616
Binding energy matching using Gromacs.
Definition: gmxio.py:1638
def make_gro_trajectory(self, fout=None)
Return the MD trajectory as a Molecule object.
Definition: gmxio.py:990
def generate_positions(self)
Definition: gmxio.py:1172
def energy_one(self, shot)
Compute the energy using GROMACS for a snapshot.
Definition: gmxio.py:1000
Derived from Engine object for carrying out general purpose GROMACS calculations. ...
Definition: gmxio.py:533
def warn_press_key(warning, timeout=10)
Definition: nifty.py:1599
def printcool_dictionary(Dict, title="Dictionary Keys : Values", bold=False, color=2, keywidth=25, topwidth=50, center=True, leftpad=0)
See documentation for printcool; this is a nice way to print out keys/values in a dictionary...
Definition: nifty.py:366
def readsrc(self, kwargs)
Called by init ; read files from the source directory.
Definition: gmxio.py:596
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1487
Finite state machine for parsing GROMACS force field files.
Definition: gmxio.py:362
def prepare(self, pbc=False, kwargs)
Called by init ; prepare the temp directory and figure out the topology.
Definition: gmxio.py:608
def rm_gmx_baks(dir)
Definition: gmxio.py:524
Interaction energy fitting module.
def __init__(self, fnm)
Definition: gmxio.py:365
def n_nonwater(self, structure_file)
Definition: gmxio.py:1211
def onefile(fnm=None, ext=None, err=False)
Definition: nifty.py:1119
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1550
double
Write out the trajectory coordinates to a .gro file.
Definition: gmxio.py:731
def evaluate_snapshot(self, shot, force=False, dipole=False)
Evaluate variables (energies, force and/or dipole) using GROMACS for a single snapshot.
Definition: gmxio.py:968
def interaction_energy(self, fraga, fragb)
Computes the interaction energy between two fragments over a trajectory.
Definition: gmxio.py:1049
def warn_once(warning, warnhash=None)
Prints a warning but will only do so once in a given run.
Definition: nifty.py:1611
Matching of lipid bulk properties.
def __init__(self, options, tgt_opts, forcefield)
Definition: gmxio.py:1659
top
Attempt to determine file names of .gro, .top, and .mdp files.
Definition: gmxio.py:599
gmxpath
The directory containing GROMACS executables (e.g.
Definition: gmxio.py:565
Multipole moment fitting module.
def get_charges(self)
Definition: gmxio.py:769
def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=0, minimize=True, threads=None, verbose=False, bilayer=False, kwargs)
Method for running a molecular dynamics simulation.
Definition: gmxio.py:1241
def wopen(dest, binary=False)
If trying to write to a symbolic link, remove it first.
Definition: nifty.py:1304
def energy_termnames(self, edrfile=None)
Get a list of energy term names from the .edr file by parsing a system call to g_energy.
Definition: gmxio.py:857
gmx_eq_barostat
Barostat keyword for equilibration.
Definition: gmxio.py:560
Matching of liquid bulk properties.
Vibrational frequency matching using GROMACS.
Definition: gmxio.py:1670
def grouper(n, iterable)
Groups a big long iterable into groups of ten or what have you.
Definition: molecule.py:661
def energy_dipole(self, traj=None)
Definition: gmxio.py:1028
def calc_scd(self, n_snap, timestep)
Definition: gmxio.py:1194
atomnames
Listing of all atom names in the file, (probably unnecessary)
Definition: gmxio.py:377
def normal_modes(self, shot=0, optimize=True)
Definition: gmxio.py:1136