Commit a1483ec5 authored by Jason Swails's avatar Jason Swails
Browse files

Fix Gromacs #define replacement and add support for Gromacs 5

Gromacs uses the preprocessor to replace previously #define'd tokens in strings.
This is used, for instance, to override certain parameters without having to
introduce new file types.  This is how Gromacs implemented ff99SB-ILDN (i.e., by
simply replacing the chi torsion for isoleucine, leucine, aspartate, and
asparagine with a define token).  So OpenMM *was* missing some in-line
parameters that were defined.

The immediate consequence I see is that whenever someone tried to run
ff99SB-ILDN with OpenMM, what they were *really* getting was ff99SB. Not sure
where else, if anywhere, this kind of thing was happening.
parent 78e8a786
...@@ -8,7 +8,7 @@ Medical Research, grant U54 GM072970. See https://simtk.org. ...@@ -8,7 +8,7 @@ Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2012-2015 Stanford University and the Authors. Portions copyright (c) 2012-2015 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors: Jason Swails
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
...@@ -40,7 +40,9 @@ import simtk.unit as unit ...@@ -40,7 +40,9 @@ import simtk.unit as unit
import simtk.openmm as mm import simtk.openmm as mm
import math import math
import os import os
import re
import distutils.spawn import distutils.spawn
from collections import OrderedDict
HBonds = ff.HBonds HBonds = ff.HBonds
AllBonds = ff.AllBonds AllBonds = ff.AllBonds
...@@ -48,6 +50,57 @@ HAngles = ff.HAngles ...@@ -48,6 +50,57 @@ HAngles = ff.HAngles
OBC2 = prmtop.OBC2 OBC2 = prmtop.OBC2
novarcharre = re.compile(r'\W')
def _find_all_instances_in_string(string, substr):
""" Find indices of all instances of substr in string """
indices = []
idx = string.find(substr, 0)
while idx > -1:
indices.append(idx)
idx = string.find(substr, idx+1)
return indices
def _replace_defines(line, defines):
""" Replaces defined tokens in a given line """
if not defines: return line
for define in reversed(defines):
value = defines[define]
indices = _find_all_instances_in_string(line, define)
if not indices: continue
# Check to see if it's inside of quotes
inside = ''
idx = 0
n_to_skip = 0
new_line = []
for i, char in enumerate(line):
if n_to_skip:
n_to_skip -= 1
continue
if char in ('\'"'):
if not inside:
inside = char
else:
if inside == char:
inside = ''
if idx < len(indices) and i == indices[idx]:
if inside:
new_line.append(char)
idx += 1
continue
if i == 0 or novarcharre.match(line[i-1]):
endidx = indices[idx] + len(define)
if endidx >= len(line) or novarcharre.match(line[endidx]):
new_line.extend(list(value))
n_to_skip = len(define) - 1
idx += 1
continue
idx += 1
new_line.append(char)
line = ''.join(new_line)
return line
class GromacsTopFile(object): class GromacsTopFile(object):
"""GromacsTopFile parses a Gromacs top file and constructs a Topology and (optionally) an OpenMM System from it.""" """GromacsTopFile parses a Gromacs top file and constructs a Topology and (optionally) an OpenMM System from it."""
...@@ -113,6 +166,7 @@ class GromacsTopFile(object): ...@@ -113,6 +166,7 @@ class GromacsTopFile(object):
name = fields[1] name = fields[1]
valueStart = stripped.find(name, len(command))+len(name)+1 valueStart = stripped.find(name, len(command))+len(name)+1
value = line[valueStart:].strip() value = line[valueStart:].strip()
value = value or '1' # Default define is 1
self._defines[name] = value self._defines[name] = value
elif command == '#ifdef': elif command == '#ifdef':
# See whether this block should be ignored. # See whether this block should be ignored.
...@@ -121,6 +175,12 @@ class GromacsTopFile(object): ...@@ -121,6 +175,12 @@ class GromacsTopFile(object):
name = fields[1] name = fields[1]
self._ifStack.append(name in self._defines) self._ifStack.append(name in self._defines)
self._elseStack.append(False) self._elseStack.append(False)
elif command == '#undef':
# Un-define a variable
if len(fields) < 2:
raise ValueError('Illegal line in .top file: '+line)
if fields[1] in self._defines:
self._defines.pop(fields[1])
elif command == '#ifndef': elif command == '#ifndef':
# See whether this block should be ignored. # See whether this block should be ignored.
if len(fields) < 2: if len(fields) < 2:
...@@ -145,6 +205,11 @@ class GromacsTopFile(object): ...@@ -145,6 +205,11 @@ class GromacsTopFile(object):
self._elseStack[-1] = True self._elseStack[-1] = True
elif not ignore: elif not ignore:
# Gromacs occasionally uses #define's to introduce specific
# parameters for individual terms (for instance, this is how
# ff99SB-ILDN is implemented). So make sure we do the appropriate
# pre-processor replacements necessary
line = _replace_defines(line, self._defines)
# A line of data for the current category # A line of data for the current category
if self._currentCategory is None: if self._currentCategory is None:
raise ValueError('Unexpected line in .top file: '+line) raise ValueError('Unexpected line in .top file: '+line)
...@@ -379,9 +444,11 @@ class GromacsTopFile(object): ...@@ -379,9 +444,11 @@ class GromacsTopFile(object):
# unless the preprocessor #define FLEXIBLE is given, don't define # unless the preprocessor #define FLEXIBLE is given, don't define
# bonds between the water hydrogen and oxygens, but only give the # bonds between the water hydrogen and oxygens, but only give the
# constraint distances and exclusions. # constraint distances and exclusions.
self._defines = {'FLEXIBLE': True} self._defines = OrderedDict()
self._defines['FLEXIBLE'] = True
if defines is not None: if defines is not None:
self._defines.update(defines) for define, value in defines.iteritems():
self._defines[define] = value
# Parse the file. # Parse the file.
...@@ -796,8 +863,8 @@ class GromacsTopFile(object): ...@@ -796,8 +863,8 @@ class GromacsTopFile(object):
def _defaultGromacsIncludeDir(): def _defaultGromacsIncludeDir():
"""Find the location where gromacs #include files are referenced from, by """Find the location where gromacs #include files are referenced from, by
searching for (1) gromacs environment variables, (2) for the gromacs binary searching for (1) gromacs environment variables, (2) for the gromacs binary
'pdb2gmx' in the PATH, or (3) just using the default gromacs install 'pdb2gmx' or 'gmx' in the PATH, or (3) just using the default gromacs
location, /usr/local/gromacs/share/gromacs/top """ install location, /usr/local/gromacs/share/gromacs/top """
if 'GMXDATA' in os.environ: if 'GMXDATA' in os.environ:
return os.path.join(os.environ['GMXDATA'], 'top') return os.path.join(os.environ['GMXDATA'], 'top')
if 'GMXBIN' in os.environ: if 'GMXBIN' in os.environ:
...@@ -806,5 +873,9 @@ def _defaultGromacsIncludeDir(): ...@@ -806,5 +873,9 @@ def _defaultGromacsIncludeDir():
pdb2gmx_path = distutils.spawn.find_executable('pdb2gmx') pdb2gmx_path = distutils.spawn.find_executable('pdb2gmx')
if pdb2gmx_path is not None: if pdb2gmx_path is not None:
return os.path.abspath(os.path.join(os.path.dirname(pdb2gmx_path), '..', 'share', 'gromacs', 'top')) return os.path.abspath(os.path.join(os.path.dirname(pdb2gmx_path), '..', 'share', 'gromacs', 'top'))
else:
gmx_path = distutils.spawn.find_executable('gmx')
if gmx_path is not None:
return os.path.abspath(os.path.join(os.path.dirname(gmx_path), '..', 'share', 'gromacs', 'top'))
return '/usr/local/gromacs/share/gromacs/top' return '/usr/local/gromacs/share/gromacs/top'
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment