"olla/vscode:/vscode.git/clone" did not exist on "0a850783c4868ccbd7bf72334e27c94ee2f321e6"
gromacstopfile.py 59.1 KB
Newer Older
Peter Eastman's avatar
Peter Eastman committed
1
2
3
4
5
6
7
8
"""
gromacstopfile.py: Used for loading Gromacs top files.

This is part of the OpenMM molecular simulation toolkit originating from
Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.

9
Portions copyright (c) 2012-2018 Stanford University and the Authors.
Peter Eastman's avatar
Peter Eastman committed
10
Authors: Peter Eastman
11
Contributors: Jason Swails
Peter Eastman's avatar
Peter Eastman committed
12

Justin MacCallum's avatar
Justin MacCallum committed
13
Permission is hereby granted, free of charge, to any person obtaining a
Peter Eastman's avatar
Peter Eastman committed
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
31
from __future__ import absolute_import
Peter Eastman's avatar
Peter Eastman committed
32
33
34
__author__ = "Peter Eastman"
__version__ = "1.0"

35
36
from openmm.app import Topology
from openmm.app import PDBFile
37
38
39
from . import forcefield as ff
from . import element as elem
from . import amberprmtopfile as prmtop
40
41
import openmm.unit as unit
import openmm as mm
Peter Eastman's avatar
Peter Eastman committed
42
43
import math
import os
44
import re
Robert McGibbon's avatar
Fix  
Robert McGibbon committed
45
import distutils.spawn
Sunhwan Jo's avatar
Sunhwan Jo committed
46
from collections import OrderedDict, defaultdict
47
from itertools import combinations, combinations_with_replacement
Peter Eastman's avatar
Peter Eastman committed
48
49
50
51
52
53
54

HBonds = ff.HBonds
AllBonds = ff.AllBonds
HAngles = ff.HAngles

OBC2 = prmtop.OBC2

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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

Peter Eastman's avatar
Peter Eastman committed
106
107
class GromacsTopFile(object):
    """GromacsTopFile parses a Gromacs top file and constructs a Topology and (optionally) an OpenMM System from it."""
Justin MacCallum's avatar
Justin MacCallum committed
108

Peter Eastman's avatar
Peter Eastman committed
109
110
111
112
113
114
115
116
    class _MoleculeType(object):
        """Inner class to store information about a molecule type."""
        def __init__(self):
            self.atoms = []
            self.bonds = []
            self.angles = []
            self.dihedrals = []
            self.exclusions = []
117
            self.pairs = []
118
            self.constraints = []
119
            self.cmaps = []
Sunhwan Jo's avatar
Sunhwan Jo committed
120
            self.vsites2 = []
121
            self.has_virtual_sites = False
Sunhwan Jo's avatar
Sunhwan Jo committed
122
            self.has_nbfix_terms = False
Justin MacCallum's avatar
Justin MacCallum committed
123

Peter Eastman's avatar
Peter Eastman committed
124
125
126
127
    def _processFile(self, file):
        append = ''
        for line in open(file):
            if line.strip().endswith('\\'):
128
                append = '%s %s' % (append, line[:line.rfind('\\')])
Peter Eastman's avatar
Peter Eastman committed
129
            else:
130
                self._processLine(append+' '+line, file)
Peter Eastman's avatar
Peter Eastman committed
131
                append = ''
Justin MacCallum's avatar
Justin MacCallum committed
132

Peter Eastman's avatar
Peter Eastman committed
133
134
    def _processLine(self, line, file):
        """Process one line from a file."""
135
136
        if ';' in line:
            line = line[:line.index(';')]
Peter Eastman's avatar
Peter Eastman committed
137
138
        stripped = line.strip()
        ignore = not all(self._ifStack)
139
        if stripped.startswith('*') or len(stripped) == 0:
Peter Eastman's avatar
Peter Eastman committed
140
141
            # A comment or empty line.
            return
Justin MacCallum's avatar
Justin MacCallum committed
142

Peter Eastman's avatar
Peter Eastman committed
143
144
145
146
147
        elif stripped.startswith('[') and not ignore:
            # The start of a category.
            if not stripped.endswith(']'):
                raise ValueError('Illegal line in .top file: '+line)
            self._currentCategory = stripped[1:-1].strip()
Justin MacCallum's avatar
Justin MacCallum committed
148

Peter Eastman's avatar
Peter Eastman committed
149
150
151
152
        elif stripped.startswith('#'):
            # A preprocessor command.
            fields = stripped.split()
            command = fields[0]
Robert McGibbon's avatar
Robert McGibbon committed
153
154
155
            if len(self._ifStack) != len(self._elseStack):
                raise RuntimeError('#if/#else stack out of sync')

Peter Eastman's avatar
Peter Eastman committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
            if command == '#include' and not ignore:
                # Locate the file to include
                name = stripped[len(command):].strip(' \t"<>')
                searchDirs = self._includeDirs+(os.path.dirname(file),)
                for dir in searchDirs:
                    file = os.path.join(dir, name)
                    if os.path.isfile(file):
                        # We found the file, so process it.
                        self._processFile(file)
                        break
                else:
                    raise ValueError('Could not locate #include file: '+name)
            elif command == '#define' and not ignore:
                # Add a value to our list of defines.
                if len(fields) < 2:
                    raise ValueError('Illegal line in .top file: '+line)
                name = fields[1]
173
                valueStart = stripped.find(name, len(command))+len(name)+1
Peter Eastman's avatar
Peter Eastman committed
174
                value = line[valueStart:].strip()
175
                value = value or '1' # Default define is 1
Peter Eastman's avatar
Peter Eastman committed
176
177
178
179
180
181
182
                self._defines[name] = value
            elif command == '#ifdef':
                # See whether this block should be ignored.
                if len(fields) < 2:
                    raise ValueError('Illegal line in .top file: '+line)
                name = fields[1]
                self._ifStack.append(name in self._defines)
Robert McGibbon's avatar
Robert McGibbon committed
183
                self._elseStack.append(False)
184
185
186
187
188
189
            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])
Peter Eastman's avatar
Peter Eastman committed
190
191
192
193
194
195
            elif command == '#ifndef':
                # See whether this block should be ignored.
                if len(fields) < 2:
                    raise ValueError('Illegal line in .top file: '+line)
                name = fields[1]
                self._ifStack.append(name not in self._defines)
Robert McGibbon's avatar
Robert McGibbon committed
196
                self._elseStack.append(False)
Peter Eastman's avatar
Peter Eastman committed
197
198
199
200
201
            elif command == '#endif':
                # Pop an entry off the if stack.
                if len(self._ifStack) == 0:
                    raise ValueError('Unexpected line in .top file: '+line)
                del(self._ifStack[-1])
Robert McGibbon's avatar
Robert McGibbon committed
202
                del(self._elseStack[-1])
203
204
            elif command == '#else':
                # Reverse the last entry on the if stack
Robert McGibbon's avatar
Robert McGibbon committed
205
206
207
208
209
                if len(self._ifStack) == 0:
                    raise ValueError('Unexpected line in .top file: '+line)
                if self._elseStack[-1]:
                    raise ValueError('Unexpected line in .top file: '
                                     '#else has already been used ' + line)
210
                self._ifStack[-1] = (not self._ifStack[-1])
Robert McGibbon's avatar
Robert McGibbon committed
211
                self._elseStack[-1] = True
Justin MacCallum's avatar
Justin MacCallum committed
212

Peter Eastman's avatar
Peter Eastman committed
213
        elif not ignore:
214
215
216
217
218
            # 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)
Peter Eastman's avatar
Peter Eastman committed
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
            # A line of data for the current category
            if self._currentCategory is None:
                raise ValueError('Unexpected line in .top file: '+line)
            if self._currentCategory == 'defaults':
                self._processDefaults(line)
            elif self._currentCategory == 'moleculetype':
                self._processMoleculeType(line)
            elif self._currentCategory == 'molecules':
                self._processMolecule(line)
            elif self._currentCategory == 'atoms':
                self._processAtom(line)
            elif self._currentCategory == 'bonds':
                self._processBond(line)
            elif self._currentCategory == 'angles':
                self._processAngle(line)
            elif self._currentCategory == 'dihedrals':
                self._processDihedral(line)
            elif self._currentCategory == 'exclusions':
                self._processExclusion(line)
238
239
            elif self._currentCategory == 'pairs':
                self._processPair(line)
240
241
            elif self._currentCategory == 'constraints':
                self._processConstraint(line)
242
243
            elif self._currentCategory == 'cmap':
                self._processCmap(line)
Peter Eastman's avatar
Peter Eastman committed
244
245
246
247
248
249
250
251
252
253
            elif self._currentCategory == 'atomtypes':
                self._processAtomType(line)
            elif self._currentCategory == 'bondtypes':
                self._processBondType(line)
            elif self._currentCategory == 'angletypes':
                self._processAngleType(line)
            elif self._currentCategory == 'dihedraltypes':
                self._processDihedralType(line)
            elif self._currentCategory == 'implicit_genborn_params':
                self._processImplicitType(line)
254
255
            elif self._currentCategory == 'pairtypes':
                self._processPairType(line)
256
257
            elif self._currentCategory == 'cmaptypes':
                self._processCmapType(line)
Sunhwan Jo's avatar
Sunhwan Jo committed
258
259
260
261
            elif self._currentCategory == 'nonbond_params':
                self._processNonbondType(line)
            elif self._currentCategory == 'virtual_sites2':
                self._processVirtualSites2(line)
262
263
264
265
266
            elif self._currentCategory.startswith('virtual_sites'):
                if self._currentMoleculeType is None:
                    raise ValueError('Found %s before [ moleculetype ]' %
                                     self._currentCategory)
                self._currentMoleculeType.has_virtual_sites = True
Justin MacCallum's avatar
Justin MacCallum committed
267

Peter Eastman's avatar
Peter Eastman committed
268
269
270
    def _processDefaults(self, line):
        """Process the [ defaults ] line."""
        fields = line.split()
Stephen Constable's avatar
Stephen Constable committed
271
272
273
274
275
276
        if len(fields) < 5:
            # fudgeLJ and fudgeQQ not specified, assumed 1.0 by default
            if len(fields) == 3:
                fields.append(1.0)
                fields.append(1.0)
            else:
Stephen Constable's avatar
Stephen Constable committed
277
                raise ValueError('Too few fields in [ defaults ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
278
279
        if fields[0] != '1':
            raise ValueError('Unsupported nonbonded type: '+fields[0])
280
        if not fields[1] in ('1', '2', '3'):
Peter Eastman's avatar
Peter Eastman committed
281
282
            raise ValueError('Unsupported combination rule: '+fields[1])
        if fields[2].lower() == 'no':
Jason Swails's avatar
Jason Swails committed
283
            self._genpairs = False
Peter Eastman's avatar
Peter Eastman committed
284
        self._defaults = fields
Justin MacCallum's avatar
Justin MacCallum committed
285

Peter Eastman's avatar
Peter Eastman committed
286
287
288
289
    def _processMoleculeType(self, line):
        """Process a line in the [ moleculetypes ] category."""
        fields = line.split()
        if len(fields) < 1:
Jason Swails's avatar
Jason Swails committed
290
            raise ValueError('Too few fields in [ moleculetypes ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
291
292
293
        type = GromacsTopFile._MoleculeType()
        self._moleculeTypes[fields[0]] = type
        self._currentMoleculeType = type
Justin MacCallum's avatar
Justin MacCallum committed
294

Peter Eastman's avatar
Peter Eastman committed
295
296
297
298
    def _processMolecule(self, line):
        """Process a line in the [ molecules ] category."""
        fields = line.split()
        if len(fields) < 2:
Jason Swails's avatar
Jason Swails committed
299
            raise ValueError('Too few fields in [ molecules ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
300
        self._molecules.append((fields[0], int(fields[1])))
Justin MacCallum's avatar
Justin MacCallum committed
301

Peter Eastman's avatar
Peter Eastman committed
302
303
304
305
306
307
    def _processAtom(self, line):
        """Process a line in the [ atoms ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ atoms ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 5:
Jason Swails's avatar
Jason Swails committed
308
            raise ValueError('Too few fields in [ atoms ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
309
        self._currentMoleculeType.atoms.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
310

Peter Eastman's avatar
Peter Eastman committed
311
312
313
314
315
316
    def _processBond(self, line):
        """Process a line in the [ bonds ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ bonds ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 3:
Jason Swails's avatar
Jason Swails committed
317
            raise ValueError('Too few fields in [ bonds ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
318
        if fields[2] != '1':
Jason Swails's avatar
Jason Swails committed
319
            raise ValueError('Unsupported function type in [ bonds ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
320
        self._currentMoleculeType.bonds.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
321

Peter Eastman's avatar
Peter Eastman committed
322
323
324
325
326
327
    def _processAngle(self, line):
        """Process a line in the [ angles ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ angles ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 4:
Jason Swails's avatar
Jason Swails committed
328
            raise ValueError('Too few fields in [ angles ] line: '+line)
329
        if fields[3] not in ('1', '5'):
Jason Swails's avatar
Jason Swails committed
330
            raise ValueError('Unsupported function type in [ angles ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
331
        self._currentMoleculeType.angles.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
332

Peter Eastman's avatar
Peter Eastman committed
333
334
335
336
337
338
    def _processDihedral(self, line):
        """Process a line in the [ dihedrals ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ dihedrals ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 5:
Jason Swails's avatar
Jason Swails committed
339
            raise ValueError('Too few fields in [ dihedrals ] line: '+line)
340
        if fields[4] not in ('1', '2', '3', '4', '5', '9'):
Jason Swails's avatar
Jason Swails committed
341
            raise ValueError('Unsupported function type in [ dihedrals ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
342
        self._currentMoleculeType.dihedrals.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
343

Peter Eastman's avatar
Peter Eastman committed
344
345
346
347
348
349
    def _processExclusion(self, line):
        """Process a line in the [ exclusions ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ exclusions ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 2:
Jason Swails's avatar
Jason Swails committed
350
            raise ValueError('Too few fields in [ exclusions ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
351
        self._currentMoleculeType.exclusions.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
352

353
354
355
356
357
358
    def _processPair(self, line):
        """Process a line in the [ pairs ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ pairs ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 3:
Jason Swails's avatar
Jason Swails committed
359
            raise ValueError('Too few fields in [ pairs ] line: '+line)
360
        if fields[2] != '1':
Jason Swails's avatar
Jason Swails committed
361
            raise ValueError('Unsupported function type in [ pairs ] line: '+line)
362
        self._currentMoleculeType.pairs.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
363

364
365
366
367
368
369
370
371
372
    def _processConstraint(self, line):
        """Process a line in the [ constraints ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ constraints ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 4:
            raise ValueError('Too few fields in [ constraints ] line: '+line)
        self._currentMoleculeType.constraints.append(fields)

373
374
375
376
377
378
    def _processCmap(self, line):
        """Process a line in the [ cmaps ] category."""
        if self._currentMoleculeType is None:
            raise ValueError('Found [ cmap ] section before [ moleculetype ]')
        fields = line.split()
        if len(fields) < 6:
Jason Swails's avatar
Jason Swails committed
379
            raise ValueError('Too few fields in [ cmap ] line: '+line)
380
        self._currentMoleculeType.cmaps.append(fields)
Justin MacCallum's avatar
Justin MacCallum committed
381

Peter Eastman's avatar
Peter Eastman committed
382
383
384
    def _processAtomType(self, line):
        """Process a line in the [ atomtypes ] category."""
        fields = line.split()
385
        if len(fields) < 6:
Jason Swails's avatar
Jason Swails committed
386
            raise ValueError('Too few fields in [ atomtypes ] line: '+line)
387
388
389
390
        if len(fields[3]) == 1:
            # Bonded type and atomic number are both missing.
            fields.insert(1, None)
            fields.insert(1, None)
peastman's avatar
peastman committed
391
        elif len(fields[4]) == 1 and fields[4].isalpha():
392
393
394
395
396
397
            if fields[1][0].isalpha():
                # Atomic number is missing.
                fields.insert(2, None)
            else:
                # Bonded type is missing.
                fields.insert(1, None)
Peter Eastman's avatar
Peter Eastman committed
398
        self._atomTypes[fields[0]] = fields
Justin MacCallum's avatar
Justin MacCallum committed
399

Peter Eastman's avatar
Peter Eastman committed
400
401
402
403
    def _processBondType(self, line):
        """Process a line in the [ bondtypes ] category."""
        fields = line.split()
        if len(fields) < 5:
Jason Swails's avatar
Jason Swails committed
404
            raise ValueError('Too few fields in [ bondtypes ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
405
        if fields[2] != '1':
Jason Swails's avatar
Jason Swails committed
406
            raise ValueError('Unsupported function type in [ bondtypes ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
407
        self._bondTypes[tuple(fields[:2])] = fields
Justin MacCallum's avatar
Justin MacCallum committed
408

Peter Eastman's avatar
Peter Eastman committed
409
410
411
412
    def _processAngleType(self, line):
        """Process a line in the [ angletypes ] category."""
        fields = line.split()
        if len(fields) < 6:
Jason Swails's avatar
Jason Swails committed
413
            raise ValueError('Too few fields in [ angletypes ] line: '+line)
414
        if fields[3] not in ('1', '5'):
Jason Swails's avatar
Jason Swails committed
415
            raise ValueError('Unsupported function type in [ angletypes ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
416
        self._angleTypes[tuple(fields[:3])] = fields
Justin MacCallum's avatar
Justin MacCallum committed
417

Peter Eastman's avatar
Peter Eastman committed
418
419
420
421
    def _processDihedralType(self, line):
        """Process a line in the [ dihedraltypes ] category."""
        fields = line.split()
        if len(fields) < 7:
Jason Swails's avatar
Jason Swails committed
422
            raise ValueError('Too few fields in [ dihedraltypes ] line: '+line)
423
        if fields[4] not in ('1', '2', '3', '4', '5', '9'):
Jason Swails's avatar
Jason Swails committed
424
            raise ValueError('Unsupported function type in [ dihedraltypes ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
425
426
427
428
429
430
        key = tuple(fields[:5])
        if fields[4] == '9' and key in self._dihedralTypes:
            # There are multiple dihedrals defined for these atom types.
            self._dihedralTypes[key].append(fields)
        else:
            self._dihedralTypes[key] = [fields]
Justin MacCallum's avatar
Justin MacCallum committed
431

Peter Eastman's avatar
Peter Eastman committed
432
433
434
435
    def _processImplicitType(self, line):
        """Process a line in the [ implicit_genborn_params ] category."""
        fields = line.split()
        if len(fields) < 6:
Jason Swails's avatar
Jason Swails committed
436
            raise ValueError('Too few fields in [ implicit_genborn_params ] line: '+line)
Peter Eastman's avatar
Peter Eastman committed
437
        self._implicitTypes[fields[0]] = fields
Justin MacCallum's avatar
Justin MacCallum committed
438

439
440
441
442
    def _processPairType(self, line):
        """Process a line in the [ pairtypes ] category."""
        fields = line.split()
        if len(fields) < 5:
Jason Swails's avatar
Jason Swails committed
443
            raise ValueError('Too few fields in [ pairtypes] line: '+line)
444
        if fields[2] != '1':
Jason Swails's avatar
Jason Swails committed
445
            raise ValueError('Unsupported function type in [ pairtypes ] line: '+line)
446
        self._pairTypes[tuple(fields[:2])] = fields
Justin MacCallum's avatar
Justin MacCallum committed
447

448
449
450
451
    def _processCmapType(self, line):
        """Process a line in the [ cmaptypes ] category."""
        fields = line.split()
        if len(fields) < 8 or len(fields) < 8+int(fields[6])*int(fields[7]):
Jason Swails's avatar
Jason Swails committed
452
            raise ValueError('Too few fields in [ cmaptypes ] line: '+line)
453
        if fields[5] != '1':
Jason Swails's avatar
Jason Swails committed
454
            raise ValueError('Unsupported function type in [ cmaptypes ] line: '+line)
455
        self._cmapTypes[tuple(fields[:5])] = fields
Justin MacCallum's avatar
Justin MacCallum committed
456

Sunhwan Jo's avatar
Sunhwan Jo committed
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
    def _processNonbondType(self, line):
        """Process a line in the [ nonbond_params ] category."""
        fields = line.split()
        if len(fields) < 5:
            raise ValueError('Too few fields in [ nonbond_params ] line: '+line)
        if fields[2] != '1':
            raise ValueError('Unsupported function type in [ nonbond_params ] line: '+line)
        self._nonbondTypes[tuple(sorted(fields[:2]))] = fields

    def _processVirtualSites2(self, line):
        """Process a line in the [ virtual_sites2 ] category."""
        fields = line.split()
        if len(fields) < 5:
            raise ValueError('Too few fields in [ virtual_sites2 ] line: ' + line)
        self._currentMoleculeType.vsites2.append(fields[:5])

473
    def __init__(self, file, periodicBoxVectors=None, unitCellDimensions=None, includeDir=None, defines=None):
Peter Eastman's avatar
Peter Eastman committed
474
        """Load a top file.
Justin MacCallum's avatar
Justin MacCallum committed
475

476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
        Parameters
        ----------
        file : str
            the name of the file to load
        periodicBoxVectors : tuple of Vec3=None
            the vectors defining the periodic box
        unitCellDimensions : Vec3=None
            the dimensions of the crystallographic unit cell.  For
            non-rectangular unit cells, specify periodicBoxVectors instead.
        includeDir : string=None
            A directory in which to look for other files included from the
            top file. If not specified, we will attempt to locate a gromacs
            installation on your system. When gromacs is installed in
            /usr/local, this will resolve to /usr/local/gromacs/share/gromacs/top
        defines : dict={}
            preprocessor definitions that should be predefined when parsing the file
Peter Eastman's avatar
Peter Eastman committed
492
         """
493
494
        if includeDir is None:
            includeDir = _defaultGromacsIncludeDir()
Peter Eastman's avatar
Peter Eastman committed
495
        self._includeDirs = (os.path.dirname(file), includeDir)
Robert McGibbon's avatar
Robert McGibbon committed
496
497
498
499
        # Most of the gromacs water itp files for different forcefields,
        # unless the preprocessor #define FLEXIBLE is given, don't define
        # bonds between the water hydrogen and oxygens, but only give the
        # constraint distances and exclusions.
500
501
        self._defines = OrderedDict()
        self._defines['FLEXIBLE'] = True
Jason Swails's avatar
Jason Swails committed
502
        self._genpairs = True
Robert McGibbon's avatar
Robert McGibbon committed
503
        if defines is not None:
504
505
            for define, value in defines.iteritems():
                self._defines[define] = value
Justin MacCallum's avatar
Justin MacCallum committed
506

Peter Eastman's avatar
Peter Eastman committed
507
        # Parse the file.
Justin MacCallum's avatar
Justin MacCallum committed
508

Peter Eastman's avatar
Peter Eastman committed
509
510
        self._currentCategory = None
        self._ifStack = []
Robert McGibbon's avatar
Robert McGibbon committed
511
        self._elseStack = []
Peter Eastman's avatar
Peter Eastman committed
512
513
514
515
516
517
518
519
        self._moleculeTypes = {}
        self._molecules = []
        self._currentMoleculeType = None
        self._atomTypes = {}
        self._bondTypes= {}
        self._angleTypes = {}
        self._dihedralTypes = {}
        self._implicitTypes = {}
520
        self._pairTypes = {}
521
        self._cmapTypes = {}
Sunhwan Jo's avatar
Sunhwan Jo committed
522
        self._nonbondTypes = {}
Peter Eastman's avatar
Peter Eastman committed
523
        self._processFile(file)
Justin MacCallum's avatar
Justin MacCallum committed
524

Peter Eastman's avatar
Peter Eastman committed
525
        # Create the Topology from it.
Justin MacCallum's avatar
Justin MacCallum committed
526

Peter Eastman's avatar
Peter Eastman committed
527
528
529
        top = Topology()
        ## The Topology read from the prmtop file
        self.topology = top
530
531
532
533
534
535
        if periodicBoxVectors is not None:
            if unitCellDimensions is not None:
                raise ValueError("specify either periodicBoxVectors or unitCellDimensions, but not both")
            top.setPeriodicBoxVectors(periodicBoxVectors)
        else:
            top.setUnitCellDimensions(unitCellDimensions)
Peter Eastman's avatar
Peter Eastman committed
536
537
538
539
540
        PDBFile._loadNameReplacementTables()
        for moleculeName, moleculeCount in self._molecules:
            if moleculeName not in self._moleculeTypes:
                raise ValueError("Unknown molecule type: "+moleculeName)
            moleculeType = self._moleculeTypes[moleculeName]
541
542
            if moleculeCount > 0 and moleculeType.has_virtual_sites:
                raise ValueError('Virtual sites not yet supported by Gromacs parsers')
Justin MacCallum's avatar
Justin MacCallum committed
543

Peter Eastman's avatar
Peter Eastman committed
544
            # Create the specified number of molecules of this type.
Justin MacCallum's avatar
Justin MacCallum committed
545

Peter Eastman's avatar
Peter Eastman committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
            for i in range(moleculeCount):
                atoms = []
                lastResidue = None
                c = top.addChain()
                for index, fields in enumerate(moleculeType.atoms):
                    resNumber = fields[2]
                    if resNumber != lastResidue:
                        lastResidue = resNumber
                        resName = fields[3]
                        if resName in PDBFile._residueNameReplacements:
                            resName = PDBFile._residueNameReplacements[resName]
                        r = top.addResidue(resName, c)
                        if resName in PDBFile._atomNameReplacements:
                            atomReplacements = PDBFile._atomNameReplacements[resName]
                        else:
                            atomReplacements = {}
                    atomName = fields[4]
                    if atomName in atomReplacements:
                        atomName = atomReplacements[atomName]
Justin MacCallum's avatar
Justin MacCallum committed
565

566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
                    # Try to determine the element.

                    atomicNumber = self._atomTypes[fields[1]][2]
                    if atomicNumber is None:
                        # Try to guess the element from the name.
                        upper = atomName.upper()
                        if upper.startswith('CL'):
                            element = elem.chlorine
                        elif upper.startswith('NA'):
                            element = elem.sodium
                        elif upper.startswith('MG'):
                            element = elem.magnesium
                        else:
                            try:
                                element = elem.get_by_symbol(atomName[0])
                            except KeyError:
                                element = None
                    elif atomicNumber == '0':
                        element = None
Peter Eastman's avatar
Peter Eastman committed
585
                    else:
586
                        element = elem.Element.getByAtomicNumber(int(atomicNumber))
Peter Eastman's avatar
Peter Eastman committed
587
                    atoms.append(top.addAtom(atomName, element, r))
Justin MacCallum's avatar
Justin MacCallum committed
588

Peter Eastman's avatar
Peter Eastman committed
589
                # Add bonds to the topology
Justin MacCallum's avatar
Justin MacCallum committed
590

Peter Eastman's avatar
Peter Eastman committed
591
592
593
594
                for fields in moleculeType.bonds:
                    top.addBond(atoms[int(fields[0])-1], atoms[int(fields[1])-1])

    def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
595
596
                     constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5,
                     ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None, switchDistance=None):
Robert McGibbon's avatar
Robert McGibbon committed
597
        """Construct an OpenMM System representing the topology described by this
598
        top file.
Robert McGibbon's avatar
Robert McGibbon committed
599
600
601
602
603

        Parameters
        ----------
        nonbondedMethod : object=NoCutoff
            The method to use for nonbonded interactions.  Allowed values are
Peter Eastman's avatar
Peter Eastman committed
604
            NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
Robert McGibbon's avatar
Robert McGibbon committed
605
606
607
608
609
        nonbondedCutoff : distance=1*nanometer
            The cutoff distance to use for nonbonded interactions
        constraints : object=None
            Specifies which bonds and angles should be implemented with
            constraints. Allowed values are None, HBonds, AllBonds, or HAngles.
610
611
            Regardless of this value, constraints that are explicitly specified
            in the top file will always be included.
Robert McGibbon's avatar
Robert McGibbon committed
612
613
614
615
616
617
618
619
620
621
622
623
        rigidWater : boolean=True
            If true, water molecules will be fully rigid regardless of the value
            passed for the constraints argument
        implicitSolvent : object=None
            If not None, the implicit solvent model to use.  The only allowed
            value is OBC2.
        soluteDielectric : float=1.0
            The solute dielectric constant to use in the implicit solvent model.
        solventDielectric : float=78.5
            The solvent dielectric constant to use in the implicit solvent
            model.
        ewaldErrorTolerance : float=0.0005
Stephen Constable's avatar
Stephen Constable committed
624
            The error tolerance to use if nonbondedMethod is Ewald, PME or LJPME.
Robert McGibbon's avatar
Robert McGibbon committed
625
626
627
628
629
        removeCMMotion : boolean=True
            If true, a CMMotionRemover will be added to the System
        hydrogenMass : mass=None
            The mass to use for hydrogen atoms bound to heavy atoms.  Any mass
            added to a hydrogen is subtracted from the heavy atom to keep their
630
631
            total mass the same.  If rigidWater is used to make water molecules
            rigid, then water hydrogens are not altered.
632
633
634
        switchDistance : float=None
            The distance at which the potential energy switching function is turned on for
            Lennard-Jones interactions. If this is None, no switching function will be used.
Robert McGibbon's avatar
Robert McGibbon committed
635
636
637

        Returns
        -------
638
        System
Robert McGibbon's avatar
Robert McGibbon committed
639
             the newly created System
Peter Eastman's avatar
Peter Eastman committed
640
641
        """
        # Create the System.
Justin MacCallum's avatar
Justin MacCallum committed
642

Peter Eastman's avatar
Peter Eastman committed
643
        sys = mm.System()
peastman's avatar
peastman committed
644
645
646
        boxVectors = self.topology.getPeriodicBoxVectors()
        if boxVectors is not None:
            sys.setDefaultPeriodicBoxVectors(*boxVectors)
Peter Eastman's avatar
Peter Eastman committed
647
        elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
648
            raise ValueError('Illegal nonbonded method for a non-periodic system')
649
        nb = mm.NonbondedForce()
Peter Eastman's avatar
Peter Eastman committed
650
        sys.addForce(nb)
651
652
        if self._defaults[1] in ('1', '3'):
            lj = mm.CustomNonbondedForce('A1*A2/r^12-C1*C2/r^6')
653
654
655
            lj.addPerParticleParameter('C')
            lj.addPerParticleParameter('A')
            sys.addForce(lj)
Peter Eastman's avatar
Peter Eastman committed
656
657
658
659
660
        if implicitSolvent is OBC2:
            gb = mm.GBSAOBCForce()
            gb.setSoluteDielectric(soluteDielectric)
            gb.setSolventDielectric(solventDielectric)
            sys.addForce(gb)
661
            nb.setReactionFieldDielectric(1.0)
Peter Eastman's avatar
Peter Eastman committed
662
663
664
665
666
667
        elif implicitSolvent is not None:
            raise ValueError('Illegal value for implicitSolvent')
        bonds = None
        angles = None
        periodic = None
        rb = None
668
669
670
        harmonicTorsion = None
        cmap = None
        mapIndices = {}
Peter Eastman's avatar
Peter Eastman committed
671
672
        bondIndices = []
        topologyAtoms = list(self.topology.atoms())
673
        exclusions = []
Stephen Constable's avatar
Stephen Constable committed
674
675
676
        pairs = []
        fudgeQQ = float(self._defaults[4])
        fudgeLJ = float(self._defaults[3])
Robert McGibbon's avatar
Robert McGibbon committed
677

678
        # Build a lookup table to let us process dihedrals more quickly.
Robert McGibbon's avatar
Robert McGibbon committed
679

680
681
682
683
684
685
686
687
688
689
690
691
692
        dihedralTypeTable = {}
        for key in self._dihedralTypes:
            if key[1] != 'X' and key[2] != 'X':
                if (key[1], key[2]) not in dihedralTypeTable:
                    dihedralTypeTable[(key[1], key[2])] = []
                dihedralTypeTable[(key[1], key[2])].append(key)
                if (key[2], key[1]) not in dihedralTypeTable:
                    dihedralTypeTable[(key[2], key[1])] = []
                dihedralTypeTable[(key[2], key[1])].append(key)
        wildcardDihedralTypes = []
        for key in self._dihedralTypes:
            if key[1] == 'X' or key[2] == 'X':
                wildcardDihedralTypes.append(key)
693
                for types in dihedralTypeTable.values():
694
                    types.append(key)
Justin MacCallum's avatar
Justin MacCallum committed
695

Sunhwan Jo's avatar
Sunhwan Jo committed
696
697
698
699
700
701
702
703
        # NBFIX

        atom_types = []
        for moleculeName, moleculeCount in self._molecules:
            moleculeType = self._moleculeTypes[moleculeName]
            for _ in range(moleculeCount):
                for atom in moleculeType.atoms:
                    atom_types.append(atom[1])
704
        has_nbfix_terms = any([pair in self._nonbondTypes for pair in combinations_with_replacement(sorted(set(atom_types)), 2)])
Sunhwan Jo's avatar
Sunhwan Jo committed
705
706
707
708
709
710
711
712
713

        if has_nbfix_terms:
            # Build a lookup table and angle/dihedral indices list to
            # let us handle exclusion manually.
            angleIndices = []
            torsionIndices = []
            atom_partners = defaultdict(lambda : defaultdict(set))
            atom_charges = []

Peter Eastman's avatar
Peter Eastman committed
714
        # Loop over molecules and create the specified number of each type.
Justin MacCallum's avatar
Justin MacCallum committed
715

Peter Eastman's avatar
Peter Eastman committed
716
717
718
        for moleculeName, moleculeCount in self._molecules:
            moleculeType = self._moleculeTypes[moleculeName]
            for i in range(moleculeCount):
Justin MacCallum's avatar
Justin MacCallum committed
719

Peter Eastman's avatar
Peter Eastman committed
720
                # Record the types of all atoms.
Justin MacCallum's avatar
Justin MacCallum committed
721

Peter Eastman's avatar
Peter Eastman committed
722
723
724
                baseAtomIndex = sys.getNumParticles()
                atomTypes = [atom[1] for atom in moleculeType.atoms]
                try:
725
                    bondedTypes = [self._atomTypes[t][1] for t in atomTypes]
Peter Eastman's avatar
Peter Eastman committed
726
                except KeyError as e:
727
                    raise ValueError('Unknown atom type: ' + e.message)
728
                bondedTypes = [b if b is not None else a for a, b in zip(atomTypes, bondedTypes)]
Justin MacCallum's avatar
Justin MacCallum committed
729

Peter Eastman's avatar
Peter Eastman committed
730
                # Add atoms.
Justin MacCallum's avatar
Justin MacCallum committed
731

Peter Eastman's avatar
Peter Eastman committed
732
733
734
735
                for fields in moleculeType.atoms:
                    if len(fields) >= 8:
                        mass = float(fields[7])
                    else:
736
                        mass = float(self._atomTypes[fields[1]][3])
Peter Eastman's avatar
Peter Eastman committed
737
                    sys.addParticle(mass)
Justin MacCallum's avatar
Justin MacCallum committed
738

Peter Eastman's avatar
Peter Eastman committed
739
                # Add bonds.
Justin MacCallum's avatar
Justin MacCallum committed
740

Peter Eastman's avatar
Peter Eastman committed
741
742
743
                atomBonds = [{} for x in range(len(moleculeType.atoms))]
                for fields in moleculeType.bonds:
                    atoms = [int(x)-1 for x in fields[:2]]
744
                    types = tuple(bondedTypes[i] for i in atoms)
Peter Eastman's avatar
Peter Eastman committed
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
                    if len(fields) >= 5:
                        params = fields[3:5]
                    elif types in self._bondTypes:
                        params = self._bondTypes[types][3:5]
                    elif types[::-1] in self._bondTypes:
                        params = self._bondTypes[types[::-1]][3:5]
                    else:
                        raise ValueError('No parameters specified for bond: '+fields[0]+', '+fields[1])
                    # Decide whether to use a constraint or a bond.
                    useConstraint = False
                    if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH':
                        useConstraint = True
                    if constraints in (AllBonds, HAngles):
                        useConstraint = True
                    elif constraints is HBonds:
                        elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms]
                        if elem.hydrogen in elements:
                            useConstraint = True
                    # Add the bond or constraint.
                    length = float(params[0])
                    if useConstraint:
                        sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length)
                    else:
                        if bonds is None:
                            bonds = mm.HarmonicBondForce()
                            sys.addForce(bonds)
                        bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length, float(params[1]))
                    # Record information that will be needed for constraining angles.
                    atomBonds[atoms[0]][atoms[1]] = length
                    atomBonds[atoms[1]][atoms[0]] = length
Justin MacCallum's avatar
Justin MacCallum committed
775

Peter Eastman's avatar
Peter Eastman committed
776
                # Add angles.
Justin MacCallum's avatar
Justin MacCallum committed
777

Peter Eastman's avatar
Peter Eastman committed
778
779
780
                degToRad = math.pi/180
                for fields in moleculeType.angles:
                    atoms = [int(x)-1 for x in fields[:3]]
781
                    types = tuple(bondedTypes[i] for i in atoms)
Peter Eastman's avatar
Peter Eastman committed
782
                    if len(fields) >= 6:
783
                        params = fields[4:]
Peter Eastman's avatar
Peter Eastman committed
784
                    elif types in self._angleTypes:
785
                        params = self._angleTypes[types][4:]
Peter Eastman's avatar
Peter Eastman committed
786
                    elif types[::-1] in self._angleTypes:
787
                        params = self._angleTypes[types[::-1]][4:]
Peter Eastman's avatar
Peter Eastman committed
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
                    else:
                        raise ValueError('No parameters specified for angle: '+fields[0]+', '+fields[1]+', '+fields[2])
                    # Decide whether to use a constraint or a bond.
                    useConstraint = False
                    if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH':
                        useConstraint = True
                    if constraints is HAngles:
                        elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms]
                        if elements[0] == elem.hydrogen and elements[2] == elem.hydrogen:
                            useConstraint = True
                        elif elements[1] == elem.oxygen and (elements[0] == elem.hydrogen or elements[2] == elem.hydrogen):
                            useConstraint = True
                    # Add the bond or constraint.
                    theta = float(params[0])*degToRad
                    if useConstraint:
                        # Compute the distance between atoms and add a constraint
                        if atoms[0] in atomBonds[atoms[1]] and atoms[2] in atomBonds[atoms[1]]:
                            l1 = atomBonds[atoms[1]][atoms[0]]
                            l2 = atomBonds[atoms[1]][atoms[2]]
                            length = math.sqrt(l1*l1 + l2*l2 - 2*l1*l2*math.cos(theta))
                            sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], length)
                    else:
                        if angles is None:
                            angles = mm.HarmonicAngleForce()
                            sys.addForce(angles)
                        angles.addAngle(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], theta, float(params[1]))
814
815
816
817
818
819
820
821
822
                        if fields[3] == '5':
                            # This is a Urey-Bradley term, so add the bond.
                            if bonds is None:
                                bonds = mm.HarmonicBondForce()
                                sys.addForce(bonds)
                            k = float(params[3])
                            if k != 0:
                                bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], float(params[2]), k)

Peter Eastman's avatar
Peter Eastman committed
823
                # Add torsions.
Justin MacCallum's avatar
Justin MacCallum committed
824

Peter Eastman's avatar
Peter Eastman committed
825
826
                for fields in moleculeType.dihedrals:
                    atoms = [int(x)-1 for x in fields[:4]]
827
                    types = tuple(bondedTypes[i] for i in atoms)
Peter Eastman's avatar
Peter Eastman committed
828
829
830
                    dihedralType = fields[4]
                    reversedTypes = types[::-1]+(dihedralType,)
                    types = types+(dihedralType,)
831
                    if (dihedralType in ('1', '4', '5', '9') and len(fields) > 7) or (dihedralType == '3' and len(fields) > 10) or (dihedralType == '2' and len(fields) > 6):
832
                        paramsList = [fields]
Peter Eastman's avatar
Peter Eastman committed
833
834
835
                    else:
                        # Look for a matching dihedral type.
                        paramsList = None
836
837
838
839
840
                        if (types[1], types[2]) in dihedralTypeTable:
                            dihedralTypes = dihedralTypeTable[(types[1], types[2])]
                        else:
                            dihedralTypes = wildcardDihedralTypes
                        for key in dihedralTypes:
Peter Eastman's avatar
Peter Eastman committed
841
842
843
844
845
846
                            if all(a == b or a == 'X' for a, b in zip(key, types)) or all(a == b or a == 'X' for a, b in zip(key, reversedTypes)):
                                paramsList = self._dihedralTypes[key]
                                if 'X' not in key:
                                    break
                        if paramsList is None:
                            raise ValueError('No parameters specified for dihedral: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3])
847
848
849
850
851
852
853
854
                    for params in paramsList:
                        if dihedralType in ('1', '4', '9'):
                            # Periodic torsion
                            k = float(params[6])
                            if k != 0:
                                if periodic is None:
                                    periodic = mm.PeriodicTorsionForce()
                                    sys.addForce(periodic)
855
                                periodic.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], int(float(params[7])), float(params[5])*degToRad, k)
856
857
858
                        elif dihedralType == '2':
                            # Harmonic torsion
                            k = float(params[6])
Stephen Constable's avatar
Stephen Constable committed
859
                            phi0 = float(params[5])
860
861
                            if k != 0:
                                if harmonicTorsion is None:
862
                                    harmonicTorsion = mm.CustomTorsionForce('0.5*k*(thetap-theta0)^2; thetap = step(-(theta-theta0+pi))*2*pi+theta+step(theta-theta0-pi)*(-2*pi); pi = %.15g' % math.pi)
863
864
865
                                    harmonicTorsion.addPerTorsionParameter('theta0')
                                    harmonicTorsion.addPerTorsionParameter('k')
                                    sys.addForce(harmonicTorsion)
Stephen Constable's avatar
Stephen Constable committed
866
867
                                # map phi0 into correct space
                                phi0 = phi0 - 360 if phi0 > 180 else phi0
Stephen Constable's avatar
Stephen Constable committed
868
                                harmonicTorsion.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], (phi0*degToRad, k))
869
870
871
872
873
874
875
                        else:
                            # RB Torsion
                            c = [float(x) for x in params[5:11]]
                            if any(x != 0 for x in c):
                                if rb is None:
                                    rb = mm.RBTorsionForce()
                                    sys.addForce(rb)
876
877
878
                                if dihedralType == '5':
                                    # Convert Fourier coefficients to RB coefficients.
                                    c = [c[1]+0.5*(c[0]+c[2]), 0.5*(-c[0]+3*c[2]), -c[1]+4*c[3], -2*c[2], -4*c[3], 0]
879
                                rb.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], c[0], c[1], c[2], c[3], c[4], c[5])
Justin MacCallum's avatar
Justin MacCallum committed
880

881
882
883
884
                # Add CMAP terms.

                for fields in moleculeType.cmaps:
                    atoms = [int(x)-1 for x in fields[:5]]
885
                    types = tuple(bondedTypes[i] for i in atoms)
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
                    if len(fields) >= 8 and len(fields) >= 8+int(fields[6])*int(fields[7]):
                        params = fields
                    elif types in self._cmapTypes:
                        params = self._cmapTypes[types]
                    elif types[::-1] in self._cmapTypes:
                        params = self._cmapTypes[types[::-1]]
                    else:
                        raise ValueError('No parameters specified for cmap: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3]+', '+fields[4])
                    if cmap is None:
                        cmap = mm.CMAPTorsionForce()
                        sys.addForce(cmap)
                    mapSize = int(params[6])
                    if mapSize != int(params[7]):
                        raise ValueError('Non-square CMAPs are not supported')
                    map = []
                    for i in range(mapSize):
                        for j in range(mapSize):
903
                            map.append(float(params[8+mapSize*((j+mapSize//2)%mapSize)+((i+mapSize//2)%mapSize)]))
904
905
906
907
908
909
                    map = tuple(map)
                    if map not in mapIndices:
                        mapIndices[map] = cmap.addMap(mapSize, map)
                    cmap.addTorsion(mapIndices[map], baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3],
                                 baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], baseAtomIndex+atoms[4])

Peter Eastman's avatar
Peter Eastman committed
910
                # Set nonbonded parameters for particles.
Justin MacCallum's avatar
Justin MacCallum committed
911

Peter Eastman's avatar
Peter Eastman committed
912
913
                for fields in moleculeType.atoms:
                    params = self._atomTypes[fields[1]]
Sunhwan Jo's avatar
Sunhwan Jo committed
914

Peter Eastman's avatar
Peter Eastman committed
915
916
917
                    if len(fields) > 6:
                        q = float(fields[6])
                    else:
918
                        q = float(params[4])
Stephen Constable's avatar
Stephen Constable committed
919

Sunhwan Jo's avatar
Sunhwan Jo committed
920
921
                    if has_nbfix_terms:
                        if self._defaults[1] != '2':
922
                            raise NotImplementedError('NBFIX terms with LB combination rule is not yet supported')
923
                        nb.addParticle(q, 1.0, 0.0)
Sunhwan Jo's avatar
Sunhwan Jo committed
924
925
926
927
                        atom_charges.append(q)
                    else:
                        if self._defaults[1] == '1':
                            nb.addParticle(q, 1.0, 0.0)
928
                            lj.addParticle([math.sqrt(float(params[6])), math.sqrt(float(params[7]))])
Sunhwan Jo's avatar
Sunhwan Jo committed
929
930
                        elif self._defaults[1] == '2':
                            nb.addParticle(q, float(params[6]), float(params[7]))
931
932
933
934
935
                        elif self._defaults[1] == '3':
                            nb.addParticle(q, 1.0, 0.0)
                            sigma = float(params[6])
                            epsilon = float(params[7])
                            lj.addParticle([math.sqrt(4*epsilon*sigma**6), math.sqrt(4*epsilon*sigma**12)])
Stephen Constable's avatar
Stephen Constable committed
936

Sunhwan Jo's avatar
Sunhwan Jo committed
937
                for fields in moleculeType.atoms:
Peter Eastman's avatar
Peter Eastman committed
938
939
940
941
942
943
944
945
                    if implicitSolvent is OBC2:
                        if fields[1] not in self._implicitTypes:
                            raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1])
                        gbparams = self._implicitTypes[fields[1]]
                        gb.addParticle(q, float(gbparams[4]), float(gbparams[5]))
                for fields in moleculeType.bonds:
                    atoms = [int(x)-1 for x in fields[:2]]
                    bondIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1]))
946
947
948
949
                for fields in moleculeType.constraints:
                    if fields[2] == '1':
                        atoms = [int(x)-1 for x in fields[:2]]
                        bondIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1]))
Sunhwan Jo's avatar
Sunhwan Jo committed
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
                if has_nbfix_terms:
                    for fields in moleculeType.bonds:
                        atoms = [int(x)-1 for x in fields[:2]]
                        atom_partners[baseAtomIndex+atoms[0]]['bond'].add(baseAtomIndex+atoms[1])
                        atom_partners[baseAtomIndex+atoms[1]]['bond'].add(baseAtomIndex+atoms[0])
                    for fields in moleculeType.angles:
                        atoms = [int(x)-1 for x in fields[:3]]
                        angleIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2]))
                        for pair in combinations(atoms, 2):
                            atom_partners[baseAtomIndex+pair[0]]['angle'].add(baseAtomIndex+pair[1])
                            atom_partners[baseAtomIndex+pair[1]]['angle'].add(baseAtomIndex+pair[0])
                    for fields in moleculeType.dihedrals:
                        atoms = [int(x)-1 for x in fields[:4]]
                        torsionIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3]))
                        for pair in combinations(atoms, 2):
                            atom_partners[baseAtomIndex+pair[0]]['torsion'].add(baseAtomIndex+pair[1])
                            atom_partners[baseAtomIndex+pair[1]]['torsion'].add(baseAtomIndex+pair[0])
Justin MacCallum's avatar
Justin MacCallum committed
967

968
                # Record nonbonded exceptions.
Justin MacCallum's avatar
Justin MacCallum committed
969

970
971
972
                for fields in moleculeType.pairs:
                    atoms = [int(x)-1 for x in fields[:2]]
                    types = tuple(atomTypes[i] for i in atoms)
973
974
975
976
                    atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0])
                    atom2params = nb.getParticleParameters(baseAtomIndex+atoms[1])
                    atom1params = [x.value_in_unit_system(unit.md_unit_system) for x in atom1params]
                    atom2params = [x.value_in_unit_system(unit.md_unit_system) for x in atom2params]
977
                    if len(fields) >= 5:
978
                        params = [float(x) for x in fields[3:5]]
979
                    elif types in self._pairTypes:
980
                        params = [float(x) for x in self._pairTypes[types][3:5]]
981
                    elif types[::-1] in self._pairTypes:
982
                        params = [float(x) for x in self._pairTypes[types[::-1]][3:5]]
Jason Swails's avatar
Jason Swails committed
983
984
985
                    elif not self._genpairs:
                        raise ValueError('No pair parameters defined for atom '
                                         'types %s and gen-pairs is "no"' % types)
986
987
                    elif has_nbfix_terms:
                        continue
988
                    else:
989
990
991
992
993
994
995
                        # Generate the parameters based on the atom parameters.
                        if self._defaults[1] == '2':
                            params = [0.5*(atom1params[1]+atom2params[1]), fudgeLJ*math.sqrt(atom1params[2]*atom2params[2])]
                        else:
                            atom1lj = lj.getParticleParameters(baseAtomIndex+atoms[0])
                            atom2lj = lj.getParticleParameters(baseAtomIndex+atoms[1])
                            params = [fudgeLJ*atom1lj[0]*atom2lj[0], fudgeLJ*atom1lj[1]*atom2lj[1]]
996
                    pairs.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], atom1params[0]*atom2params[0]*fudgeQQ, params[0], params[1]))
Stephen Constable's avatar
Stephen Constable committed
997
998
999
                for fields in moleculeType.exclusions:
                    atoms = [int(x)-1 for x in fields]
                    for atom in atoms[1:]:
1000
                        exclusions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom))
Robert McGibbon's avatar
Robert McGibbon committed
1001

Sunhwan Jo's avatar
Sunhwan Jo committed
1002
1003
1004
1005
1006
1007
1008
                # Record virtual sites

                for fields in moleculeType.vsites2:
                    atoms = [int(x)-1 for x in fields[:3]]
                    c1 = float(fields[4])
                    vsite = mm.TwoParticleAverageSite(baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], (1-c1), c1)
                    sys.setVirtualSite(baseAtomIndex+atoms[0], vsite)
Justin MacCallum's avatar
Justin MacCallum committed
1009

1010
1011
1012
1013
1014
1015
1016
                # Add explicitly specified constraints.

                for fields in moleculeType.constraints:
                    atoms = [int(x)-1 for x in fields[:2]]
                    length = float(fields[2])
                    sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length)

1017
        # Create nonbonded exceptions.
Sunhwan Jo's avatar
Sunhwan Jo committed
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065

        if not has_nbfix_terms:
            nb.createExceptionsFromBonds(bondIndices, fudgeQQ, fudgeLJ)
        else:
            excluded_atom_pairs = set() # save these pairs so we don't zero them out
            for tor in torsionIndices:
                # First check to see if atoms 1 and 4 are already excluded because
                # they are 1-2 or 1-3 pairs (would happen in 6-member rings or
                # fewer). Then check that they're not already added as exclusions
                if 'bond' in atom_partners[tor[3]] and tor[0] in atom_partners[tor[3]]['bond']: continue
                if 'angle' in atom_partners[tor[3]] and tor[0] in atom_partners[tor[3]]['angle']: continue
                key = min((tor[0], tor[3]),
                          (tor[3], tor[0]))
                if key in excluded_atom_pairs: continue # multiterm...
                params1 = self._atomTypes[atom_types[tor[0]]]
                params4 = self._atomTypes[atom_types[tor[3]]]
                q1 = atom_charges[tor[0]]
                rmin1 = float(params1[6])
                eps1 = float(params1[7])
                q4 = atom_charges[tor[3]]
                rmin4 = float(params4[6])
                eps4 = float(params4[7])

                charge_prod = (q1*q4)
                epsilon = (math.sqrt(abs(eps1 * eps4)))
                rmin14 = (rmin1 + rmin4) / 2
                nb.addException(tor[0], tor[3], charge_prod, rmin14, epsilon)
                excluded_atom_pairs.add(key)

            # Add excluded atoms
            for atom_idx, atom in atom_partners.items():
                # Exclude all bonds and angles
                for atom2 in atom['bond']:
                    if atom2 > atom_idx:
                        nb.addException(atom_idx, atom2, 0.0, 1.0, 0.0)
                        excluded_atom_pairs.add((atom_idx, atom2))
                for atom2 in atom['angle']:
                    if ((atom_idx, atom2) in excluded_atom_pairs):
                        continue
                    if atom2 > atom_idx:
                        nb.addException(atom_idx, atom2, 0.0, 1.0, 0.0)
                        excluded_atom_pairs.add((atom_idx, atom2))
                for atom2 in atom['dihedral']:
                    if atom2 <= atom_idx: continue
                    if ((atom_idx, atom2) in excluded_atom_pairs):
                        continue
                    nb.addException(atom_idx, atom2, 0.0, 1.0, 0.0)

1066
1067
        for exclusion in exclusions:
            nb.addException(exclusion[0], exclusion[1], 0.0, 1.0, 0.0, True)
Sunhwan Jo's avatar
Sunhwan Jo committed
1068

1069
        if self._defaults[1] in ('1', '3'):
1070
1071
            # We're using a CustomNonbondedForce for LJ interactions, so also create a CustomBondForce
            # to handle the exceptions.
Sunhwan Jo's avatar
Sunhwan Jo committed
1072

1073
            pair_bond = mm.CustomBondForce('-C/r^6+A/r^12')
Stephen Constable's avatar
Stephen Constable committed
1074
1075
1076
            pair_bond.addPerBondParameter('C')
            pair_bond.addPerBondParameter('A')
            sys.addForce(pair_bond)
1077
            lj.createExclusionsFromBonds(bondIndices, 3)
Stephen Constable's avatar
Stephen Constable committed
1078
            for pair in pairs:
1079
                nb.addException(pair[0], pair[1], pair[2], 1.0, 0.0, True)
1080
                pair_bond.addBond(pair[0], pair[1], [pair[3], pair[4]])
1081
1082
            for exclusion in exclusions:
                lj.addExclusion(exclusion[0], exclusion[1])
Stephen Constable's avatar
Stephen Constable committed
1083
        elif self._defaults[1] == '2':
1084
            for pair in pairs:
1085
                nb.addException(pair[0], pair[1], pair[2], pair[3], pair[4], True)
1086

Peter Eastman's avatar
Peter Eastman committed
1087
        # Finish configuring the NonbondedForce.
Justin MacCallum's avatar
Justin MacCallum committed
1088

Peter Eastman's avatar
Peter Eastman committed
1089
1090
1091
1092
        methodMap = {ff.NoCutoff:mm.NonbondedForce.NoCutoff,
                     ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
                     ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
                     ff.Ewald:mm.NonbondedForce.Ewald,
Peter Eastman's avatar
Peter Eastman committed
1093
1094
                     ff.PME:mm.NonbondedForce.PME,
                     ff.LJPME:mm.NonbondedForce.LJPME}
Peter Eastman's avatar
Peter Eastman committed
1095
1096
        nb.setNonbondedMethod(methodMap[nonbondedMethod])
        nb.setCutoffDistance(nonbondedCutoff)
1097
        nb.setEwaldErrorTolerance(ewaldErrorTolerance)
1098
1099
1100
        if switchDistance is not None:
            nb.setUseSwitchingFunction(True)
            nb.setSwitchingDistance(switchDistance)
1101
        if self._defaults[1] in ('1', '3'):
1102
1103
1104
1105
1106
1107
1108
1109
            methodMap = {ff.NoCutoff:mm.CustomNonbondedForce.NoCutoff,
                         ff.CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic,
                         ff.CutoffPeriodic:mm.CustomNonbondedForce.CutoffPeriodic,
                         ff.Ewald:mm.CustomNonbondedForce.CutoffPeriodic,
                         ff.PME:mm.CustomNonbondedForce.CutoffPeriodic,
                         ff.LJPME:mm.CustomNonbondedForce.CutoffPeriodic}
            lj.setNonbondedMethod(methodMap[nonbondedMethod])
            lj.setCutoffDistance(nonbondedCutoff)
1110
1111
1112
            if switchDistance is not None:
                lj.setUseSwitchingFunction(True)
                lj.setSwitchingDistance(switchDistance)
Robert McGibbon's avatar
Robert McGibbon committed
1113

Sunhwan Jo's avatar
Sunhwan Jo committed
1114
1115
        if has_nbfix_terms:
            if self._defaults[1] != '2':
1116
                raise NotImplementedError('NBFIX terms with LB combination rule is not yet supported')
Sunhwan Jo's avatar
Sunhwan Jo committed
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187

            atom_nbfix_types = set([])
            for pair in self._nonbondTypes:
                atom_nbfix_types.add(pair[0])
                atom_nbfix_types.add(pair[1])

            lj_idx_list = [0 for _ in atom_types]
            lj_radii, lj_depths = [], []
            num_lj_types = 0
            lj_type_list = []
            for i,atom_type in enumerate(atom_types):
                atom = self._atomTypes[atom_type]
                if lj_idx_list[i]: continue # already assigned
                atom_rmin = atom[6]
                atom_epsilon = atom[7]
                num_lj_types += 1
                lj_idx_list[i] = num_lj_types
                ljtype = (atom_rmin, atom_epsilon)
                lj_type_list.append(atom)
                lj_radii.append(float(atom_rmin))
                lj_depths.append(float(atom_epsilon))
                for j in range(i+1, len(atom_types)):
                    atom_type2 = atom_types[j]
                    if lj_idx_list[j] > 0: continue # already assigned
                    atom2 = self._atomTypes[atom_type2]
                    atom2_rmin = atom2[6]
                    atom2_epsilon = atom2[7]
                    if atom2 is atom:
                        lj_idx_list[j] = num_lj_types
                    elif atom_type not in atom_nbfix_types:
                        # Only non-NBFIXed atom types can be compressed
                        ljtype2 = (atom2_rmin, atom2_epsilon)
                        if ljtype == ljtype2:
                            lj_idx_list[j] = num_lj_types

            # Now everything is assigned. Create the A-coefficient and
            # B-coefficient arrays
            acoef = [0 for i in range(num_lj_types*num_lj_types)]
            bcoef = acoef[:]
            for i in range(num_lj_types):
                namei = lj_type_list[i][0]
                for j in range(num_lj_types):
                    namej = lj_type_list[j][0]
                    try:
                        params = self._nonbondTypes[tuple(sorted((namei, namej)))]
                        rij = float(params[3])
                        wdij = float(params[4])
                    except KeyError:
                        rij = (lj_radii[i] + lj_radii[j]) / 2
                        wdij = math.sqrt(lj_depths[i] * lj_depths[j])
                    acoef[i+num_lj_types*j] = 2 * math.sqrt(wdij) * rij**6
                    bcoef[i+num_lj_types*j] = 4 * wdij * rij**6
            cforce = mm.CustomNonbondedForce('(a/r6)^2-b/r6; r6=r^6;'
                                             'a=acoef(type1, type2);'
                                             'b=bcoef(type1, type2)')
            cforce.addTabulatedFunction('acoef',
                    mm.Discrete2DFunction(num_lj_types, num_lj_types, acoef))
            cforce.addTabulatedFunction('bcoef',
                    mm.Discrete2DFunction(num_lj_types, num_lj_types, bcoef))
            cforce.addPerParticleParameter('type')
            if (nonbondedMethod in (ff.PME, ff.LJPME, ff.Ewald, ff.CutoffPeriodic)):
                cforce.setNonbondedMethod(cforce.CutoffPeriodic)
                cforce.setCutoffDistance(nonbondedCutoff)
                cforce.setUseLongRangeCorrection(True)
            elif nonbondedMethod is ff.NoCutoff:
                cforce.setNonbondedMethod(cforce.NoCutoff)
            elif nonbondedMethod is ff.CutoffNonPeriodic:
                cforce.setNonbondedMethod(cforce.CutoffNonPeriodic)
                cforce.setCutoffDistance(nonbondedCutoff)
            else:
                raise ValueError('Unrecognized nonbonded method')
1188
1189
1190
            if switchDistance is not None:
                cforce.setUseSwitchingFunction(True)
                cforce.setSwitchingDistance(switchDistance)
Sunhwan Jo's avatar
Sunhwan Jo committed
1191
1192
1193
1194
1195
1196
1197
1198
            for i in lj_idx_list:
                cforce.addParticle((i - 1,)) # adjust for indexing from 0

            for i in range(nb.getNumExceptions()):
                ii, jj, q, eps, sig = nb.getExceptionParameters(i)
                cforce.addExclusion(ii, jj)
            sys.addForce(cforce)

1199
        # Adjust masses.
Robert McGibbon's avatar
Robert McGibbon committed
1200

1201
1202
1203
1204
        if hydrogenMass is not None:
            for atom1, atom2 in self.topology.bonds():
                if atom1.element == elem.hydrogen:
                    (atom1, atom2) = (atom2, atom1)
1205
1206
                if rigidWater and atom2.residue.name == 'HOH':
                    continue
1207
1208
1209
1210
                if atom2.element == elem.hydrogen and atom1.element not in (elem.hydrogen, None):
                    transferMass = hydrogenMass-sys.getParticleMass(atom2.index)
                    sys.setParticleMass(atom2.index, hydrogenMass)
                    sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass)
Peter Eastman's avatar
Peter Eastman committed
1211
1212
1213
1214
1215
1216

        # Add a CMMotionRemover.

        if removeCMMotion:
            sys.addForce(mm.CMMotionRemover())
        return sys
1217
1218

def _defaultGromacsIncludeDir():
Robert McGibbon's avatar
Robert McGibbon committed
1219
1220
    """Find the location where gromacs #include files are referenced from, by
    searching for (1) gromacs environment variables, (2) for the gromacs binary
1221
1222
    'pdb2gmx' or 'gmx' in the PATH, or (3) just using the default gromacs
    install location, /usr/local/gromacs/share/gromacs/top """
1223
1224
1225
1226
1227
1228
1229
1230
    if 'GMXDATA' in os.environ:
        return os.path.join(os.environ['GMXDATA'], 'top')
    if 'GMXBIN' in os.environ:
        return os.path.abspath(os.path.join(os.environ['GMXBIN'], '..', 'share', 'gromacs', 'top'))

    pdb2gmx_path = distutils.spawn.find_executable('pdb2gmx')
    if pdb2gmx_path is not None:
        return os.path.abspath(os.path.join(os.path.dirname(pdb2gmx_path), '..', 'share', 'gromacs', 'top'))
1231
1232
1233
1234
    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'))
1235
1236

    return '/usr/local/gromacs/share/gromacs/top'