| 1 | """
|
|---|
| 2 | forcefield.py: Constructs OpenMM System objects based on a Topology and an XML force field description
|
|---|
| 3 |
|
|---|
| 4 | This is part of the OpenMM molecular simulation toolkit originating from
|
|---|
| 5 | Simbios, the NIH National Center for Physics-Based Simulation of
|
|---|
| 6 | Biological Structures at Stanford, funded under the NIH Roadmap for
|
|---|
| 7 | Medical Research, grant U54 GM072970. See https://simtk.org.
|
|---|
| 8 |
|
|---|
| 9 | Portions copyright (c) 2012-2016 Stanford University and the Authors.
|
|---|
| 10 | Authors: Peter Eastman, Mark Friedrichs
|
|---|
| 11 | Contributors:
|
|---|
| 12 |
|
|---|
| 13 | Permission is hereby granted, free of charge, to any person obtaining a
|
|---|
| 14 | copy of this software and associated documentation files (the "Software"),
|
|---|
| 15 | to deal in the Software without restriction, including without limitation
|
|---|
| 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|---|
| 17 | and/or sell copies of the Software, and to permit persons to whom the
|
|---|
| 18 | Software is furnished to do so, subject to the following conditions:
|
|---|
| 19 |
|
|---|
| 20 | The above copyright notice and this permission notice shall be included in
|
|---|
| 21 | all copies or substantial portions of the Software.
|
|---|
| 22 |
|
|---|
| 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|---|
| 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|---|
| 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|---|
| 26 | THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|---|
| 27 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|---|
| 28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|---|
| 29 | USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|---|
| 30 | """
|
|---|
| 31 | from __future__ import absolute_import, print_function
|
|---|
| 32 |
|
|---|
| 33 | __author__ = "Peter Eastman"
|
|---|
| 34 | __version__ = "1.0"
|
|---|
| 35 |
|
|---|
| 36 | import os
|
|---|
| 37 | import itertools
|
|---|
| 38 | import xml.etree.ElementTree as etree
|
|---|
| 39 | import math
|
|---|
| 40 | from math import sqrt, cos
|
|---|
| 41 | from copy import deepcopy
|
|---|
| 42 | from heapq import heappush, heappop
|
|---|
| 43 | from collections import defaultdict
|
|---|
| 44 | import simtk.openmm as mm
|
|---|
| 45 | import simtk.unit as unit
|
|---|
| 46 | from . import element as elem
|
|---|
| 47 | from simtk.openmm.app import Topology
|
|---|
| 48 | from simtk.openmm.app.internal.singleton import Singleton
|
|---|
| 49 |
|
|---|
| 50 | def _convertParameterToNumber(param):
|
|---|
| 51 | if unit.is_quantity(param):
|
|---|
| 52 | if param.unit.is_compatible(unit.bar):
|
|---|
| 53 | return param / unit.bar
|
|---|
| 54 | return param.value_in_unit_system(unit.md_unit_system)
|
|---|
| 55 | return float(param)
|
|---|
| 56 |
|
|---|
| 57 | def _parseFunctions(element):
|
|---|
| 58 | """Parse the attributes on an XML tag to find any tabulated functions it defines."""
|
|---|
| 59 | functions = []
|
|---|
| 60 | for function in element.findall('Function'):
|
|---|
| 61 | values = [float(x) for x in function.text.split()]
|
|---|
| 62 | if 'type' in function.attrib:
|
|---|
| 63 | functionType = function.attrib['type']
|
|---|
| 64 | else:
|
|---|
| 65 | functionType = 'Continuous1D'
|
|---|
| 66 | params = {}
|
|---|
| 67 | for key in function.attrib:
|
|---|
| 68 | if key.endswith('size'):
|
|---|
| 69 | params[key] = int(function.attrib[key])
|
|---|
| 70 | elif key.endswith('min') or key.endswith('max'):
|
|---|
| 71 | params[key] = float(function.attrib[key])
|
|---|
| 72 | functions.append((function.attrib['name'], functionType, values, params))
|
|---|
| 73 | return functions
|
|---|
| 74 |
|
|---|
| 75 | def _createFunctions(force, functions):
|
|---|
| 76 | """Add TabulatedFunctions to a Force based on the information that was recorded by _parseFunctions()."""
|
|---|
| 77 | for (name, type, values, params) in functions:
|
|---|
| 78 | if type == 'Continuous1D':
|
|---|
| 79 | force.addTabulatedFunction(name, mm.Continuous1DFunction(values, params['min'], params['max']))
|
|---|
| 80 | elif type == 'Continuous2D':
|
|---|
| 81 | force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax']))
|
|---|
| 82 | elif type == 'Continuous3D':
|
|---|
| 83 | force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], params['zsize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax'], params['zmin'], params['zmax']))
|
|---|
| 84 | elif type == 'Discrete1D':
|
|---|
| 85 | force.addTabulatedFunction(name, mm.Discrete1DFunction(values))
|
|---|
| 86 | elif type == 'Discrete2D':
|
|---|
| 87 | force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], values))
|
|---|
| 88 | elif type == 'Discrete3D':
|
|---|
| 89 | force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
|
|---|
| 90 |
|
|---|
| 91 | # Enumerated values for nonbonded method
|
|---|
| 92 |
|
|---|
| 93 | class NoCutoff(Singleton):
|
|---|
| 94 | def __repr__(self):
|
|---|
| 95 | return 'NoCutoff'
|
|---|
| 96 | NoCutoff = NoCutoff()
|
|---|
| 97 |
|
|---|
| 98 | class CutoffNonPeriodic(Singleton):
|
|---|
| 99 | def __repr__(self):
|
|---|
| 100 | return 'CutoffNonPeriodic'
|
|---|
| 101 | CutoffNonPeriodic = CutoffNonPeriodic()
|
|---|
| 102 |
|
|---|
| 103 | class CutoffPeriodic(Singleton):
|
|---|
| 104 | def __repr__(self):
|
|---|
| 105 | return 'CutoffPeriodic'
|
|---|
| 106 | CutoffPeriodic = CutoffPeriodic()
|
|---|
| 107 |
|
|---|
| 108 | class Ewald(Singleton):
|
|---|
| 109 | def __repr__(self):
|
|---|
| 110 | return 'Ewald'
|
|---|
| 111 | Ewald = Ewald()
|
|---|
| 112 |
|
|---|
| 113 | class PME(Singleton):
|
|---|
| 114 | def __repr__(self):
|
|---|
| 115 | return 'PME'
|
|---|
| 116 | PME = PME()
|
|---|
| 117 |
|
|---|
| 118 | # Enumerated values for constraint type
|
|---|
| 119 |
|
|---|
| 120 | class HBonds(Singleton):
|
|---|
| 121 | def __repr__(self):
|
|---|
| 122 | return 'HBonds'
|
|---|
| 123 | HBonds = HBonds()
|
|---|
| 124 |
|
|---|
| 125 | class AllBonds(Singleton):
|
|---|
| 126 | def __repr__(self):
|
|---|
| 127 | return 'AllBonds'
|
|---|
| 128 | AllBonds = AllBonds()
|
|---|
| 129 |
|
|---|
| 130 | class HAngles(Singleton):
|
|---|
| 131 | def __repr__(self):
|
|---|
| 132 | return 'HAngles'
|
|---|
| 133 | HAngles = HAngles()
|
|---|
| 134 |
|
|---|
| 135 | # A map of functions to parse elements of the XML file.
|
|---|
| 136 |
|
|---|
| 137 | parsers = {}
|
|---|
| 138 |
|
|---|
| 139 | class ForceField(object):
|
|---|
| 140 | """A ForceField constructs OpenMM System objects based on a Topology."""
|
|---|
| 141 |
|
|---|
| 142 | def __init__(self, *files):
|
|---|
| 143 | """Load one or more XML files and create a ForceField object based on them.
|
|---|
| 144 |
|
|---|
| 145 | Parameters
|
|---|
| 146 | ----------
|
|---|
| 147 | files : list
|
|---|
| 148 | A list of XML files defining the force field. Each entry may
|
|---|
| 149 | be an absolute file path, a path relative to the current working
|
|---|
| 150 | directory, a path relative to this module's data subdirectory
|
|---|
| 151 | (for built in force fields), or an open file-like object with a
|
|---|
| 152 | read() method from which the forcefield XML data can be loaded.
|
|---|
| 153 | """
|
|---|
| 154 | self._atomTypes = {}
|
|---|
| 155 | self._templates = {}
|
|---|
| 156 | self._patches = {}
|
|---|
| 157 | self._templatePatches = {}
|
|---|
| 158 | self._templateSignatures = {None:[]}
|
|---|
| 159 | self._atomClasses = {'':set()}
|
|---|
| 160 | self._forces = []
|
|---|
| 161 | self._scripts = []
|
|---|
| 162 | self._templateGenerators = []
|
|---|
| 163 | self.loadFile(files)
|
|---|
| 164 |
|
|---|
| 165 | def loadFile(self, files):
|
|---|
| 166 | """Load an XML file and add the definitions from it to this ForceField.
|
|---|
| 167 |
|
|---|
| 168 | Parameters
|
|---|
| 169 | ----------
|
|---|
| 170 | files : string or file or tuple
|
|---|
| 171 | An XML file or tuple of XML files containing force field definitions.
|
|---|
| 172 | Each entry may be either an absolute file path, a path relative to the current working
|
|---|
| 173 | directory, a path relative to this module's data subdirectory (for
|
|---|
| 174 | built in force fields), or an open file-like object with a read()
|
|---|
| 175 | method from which the forcefield XML data can be loaded.
|
|---|
| 176 | """
|
|---|
| 177 |
|
|---|
| 178 | if not isinstance(files, tuple):
|
|---|
| 179 | files = (files,)
|
|---|
| 180 |
|
|---|
| 181 | trees = []
|
|---|
| 182 |
|
|---|
| 183 | for file in files:
|
|---|
| 184 | try:
|
|---|
| 185 | # this handles either filenames or open file-like objects
|
|---|
| 186 | tree = etree.parse(file)
|
|---|
| 187 | except IOError:
|
|---|
| 188 | tree = etree.parse(os.path.join(os.path.dirname(__file__), 'data', file))
|
|---|
| 189 | except Exception as e:
|
|---|
| 190 | # Fail with an error message about which file could not be read.
|
|---|
| 191 | # TODO: Also handle case where fallback to 'data' directory encounters problems,
|
|---|
| 192 | # but this is much less worrisome because we control those files.
|
|---|
| 193 | msg = str(e) + '\n'
|
|---|
| 194 | if hasattr(file, 'name'):
|
|---|
| 195 | filename = file.name
|
|---|
| 196 | else:
|
|---|
| 197 | filename = str(file)
|
|---|
| 198 | msg += "ForceField.loadFile() encountered an error reading file '%s'\n" % filename
|
|---|
| 199 | raise Exception(msg)
|
|---|
| 200 |
|
|---|
| 201 | trees.append(tree)
|
|---|
| 202 |
|
|---|
| 203 |
|
|---|
| 204 | # Load the atom types.
|
|---|
| 205 |
|
|---|
| 206 | for tree in trees:
|
|---|
| 207 | if tree.getroot().find('AtomTypes') is not None:
|
|---|
| 208 | for type in tree.getroot().find('AtomTypes').findall('Type'):
|
|---|
| 209 | self.registerAtomType(type.attrib)
|
|---|
| 210 |
|
|---|
| 211 | # Load the residue templates.
|
|---|
| 212 |
|
|---|
| 213 | for tree in trees:
|
|---|
| 214 | if tree.getroot().find('Residues') is not None:
|
|---|
| 215 | for residue in tree.getroot().find('Residues').findall('Residue'):
|
|---|
| 216 | resName = residue.attrib['name']
|
|---|
| 217 | template = ForceField._TemplateData(resName)
|
|---|
| 218 | if 'override' in residue.attrib:
|
|---|
| 219 | template.overrideLevel = int(residue.attrib['override'])
|
|---|
| 220 | atomIndices = {}
|
|---|
| 221 | for atom in residue.findall('Atom'):
|
|---|
| 222 | params = {}
|
|---|
| 223 | for key in atom.attrib:
|
|---|
| 224 | if key not in ('name', 'type'):
|
|---|
| 225 | params[key] = _convertParameterToNumber(atom.attrib[key])
|
|---|
| 226 | atomName = atom.attrib['name']
|
|---|
| 227 | if atomName in atomIndices:
|
|---|
| 228 | raise ValueError('Residue '+resName+' contains multiple atoms named '+atomName)
|
|---|
| 229 | atomIndices[atomName] = len(template.atoms)
|
|---|
| 230 | typeName = atom.attrib['type']
|
|---|
| 231 | template.atoms.append(ForceField._TemplateAtomData(atomName, typeName, self._atomTypes[typeName].element, params))
|
|---|
| 232 | for site in residue.findall('VirtualSite'):
|
|---|
| 233 | template.virtualSites.append(ForceField._VirtualSiteData(site, atomIndices))
|
|---|
| 234 | for bond in residue.findall('Bond'):
|
|---|
| 235 | if 'atomName1' in bond.attrib:
|
|---|
| 236 | template.addBondByName(bond.attrib['atomName1'], bond.attrib['atomName2'])
|
|---|
| 237 | else:
|
|---|
| 238 | template.addBond(int(bond.attrib['from']), int(bond.attrib['to']))
|
|---|
| 239 | for bond in residue.findall('ExternalBond'):
|
|---|
| 240 | if 'atomName' in bond.attrib:
|
|---|
| 241 | template.addExternalBondByName(bond.attrib['atomName'])
|
|---|
| 242 | else:
|
|---|
| 243 | template.addExternalBond(int(bond.attrib['from']))
|
|---|
| 244 | for patch in residue.findall('AllowPatch'):
|
|---|
| 245 | patchName = patch.attrib['name']
|
|---|
| 246 | if ':' in patchName:
|
|---|
| 247 | colonIndex = patchName.find(':')
|
|---|
| 248 | self.registerTemplatePatch(resName, patchName[:colonIndex], int(patchName[colonIndex+1:])-1)
|
|---|
| 249 | else:
|
|---|
| 250 | self.registerTemplatePatch(resName, patchName, 0)
|
|---|
| 251 | self.registerResidueTemplate(template)
|
|---|
| 252 |
|
|---|
| 253 | # Load the patch defintions.
|
|---|
| 254 |
|
|---|
| 255 | for tree in trees:
|
|---|
| 256 | if tree.getroot().find('Patches') is not None:
|
|---|
| 257 | for patch in tree.getroot().find('Patches').findall('Patch'):
|
|---|
| 258 | patchName = patch.attrib['name']
|
|---|
| 259 | if 'residues' in patch.attrib:
|
|---|
| 260 | numResidues = int(patch.attrib['residues'])
|
|---|
| 261 | else:
|
|---|
| 262 | numResidues = 1
|
|---|
| 263 | patchData = ForceField._PatchData(patchName, numResidues)
|
|---|
| 264 | allAtomNames = set()
|
|---|
| 265 | for atom in patch.findall('AddAtom'):
|
|---|
| 266 | params = {}
|
|---|
| 267 | for key in atom.attrib:
|
|---|
| 268 | if key not in ('name', 'type'):
|
|---|
| 269 | params[key] = _convertParameterToNumber(atom.attrib[key])
|
|---|
| 270 | atomName = atom.attrib['name']
|
|---|
| 271 | if atomName in allAtomNames:
|
|---|
| 272 | raise ValueError('Patch '+patchName+' contains multiple atoms named '+atomName)
|
|---|
| 273 | allAtomNames.add(atomName)
|
|---|
| 274 | atomDescription = ForceField._PatchAtomData(atomName)
|
|---|
| 275 | typeName = atom.attrib['type']
|
|---|
| 276 | patchData.addedAtoms[atomDescription.residue].append(ForceField._TemplateAtomData(atomDescription.name, typeName, self._atomTypes[typeName].element, params))
|
|---|
| 277 | for atom in patch.findall('ChangeAtom'):
|
|---|
| 278 | params = {}
|
|---|
| 279 | for key in atom.attrib:
|
|---|
| 280 | if key not in ('name', 'type'):
|
|---|
| 281 | params[key] = _convertParameterToNumber(atom.attrib[key])
|
|---|
| 282 | atomName = atom.attrib['name']
|
|---|
| 283 | if atomName in allAtomNames:
|
|---|
| 284 | raise ValueError('Patch '+patchName+' contains multiple atoms named '+atomName)
|
|---|
| 285 | allAtomNames.add(atomName)
|
|---|
| 286 | atomDescription = ForceField._PatchAtomData(atomName)
|
|---|
| 287 | typeName = atom.attrib['type']
|
|---|
| 288 | patchData.changedAtoms[atomDescription.residue].append(ForceField._TemplateAtomData(atomDescription.name, typeName, self._atomTypes[typeName].element, params))
|
|---|
| 289 | for atom in patch.findall('RemoveAtom'):
|
|---|
| 290 | atomName = atom.attrib['name']
|
|---|
| 291 | if atomName in allAtomNames:
|
|---|
| 292 | raise ValueError('Patch '+patchName+' contains multiple atoms named '+atomName)
|
|---|
| 293 | allAtomNames.add(atomName)
|
|---|
| 294 | atomDescription = ForceField._PatchAtomData(atomName)
|
|---|
| 295 | patchData.deletedAtoms.append(atomDescription)
|
|---|
| 296 | for bond in patch.findall('AddBond'):
|
|---|
| 297 | atom1 = ForceField._PatchAtomData(bond.attrib['atomName1'])
|
|---|
| 298 | atom2 = ForceField._PatchAtomData(bond.attrib['atomName2'])
|
|---|
| 299 | patchData.addedBonds.append((atom1, atom2))
|
|---|
| 300 | for bond in patch.findall('RemoveBond'):
|
|---|
| 301 | atom1 = ForceField._PatchAtomData(bond.attrib['atomName1'])
|
|---|
| 302 | atom2 = ForceField._PatchAtomData(bond.attrib['atomName2'])
|
|---|
| 303 | patchData.deletedBonds.append((atom1, atom2))
|
|---|
| 304 | for bond in patch.findall('AddExternalBond'):
|
|---|
| 305 | atom = ForceField._PatchAtomData(bond.attrib['atomName'])
|
|---|
| 306 | patchData.addedExternalBonds.append(atom)
|
|---|
| 307 | for bond in patch.findall('RemoveExternalBond'):
|
|---|
| 308 | atom = ForceField._PatchAtomData(bond.attrib['atomName'])
|
|---|
| 309 | patchData.deletedExternalBonds.append(atom)
|
|---|
| 310 | for residue in patch.findall('ApplyToResidue'):
|
|---|
| 311 | name = residue.attrib['name']
|
|---|
| 312 | if ':' in name:
|
|---|
| 313 | colonIndex = name.find(':')
|
|---|
| 314 | self.registerTemplatePatch(name[colonIndex+1:], patchName, int(name[:colonIndex])-1)
|
|---|
| 315 | else:
|
|---|
| 316 | self.registerTemplatePatch(name, patchName, 0)
|
|---|
| 317 | self.registerPatch(patchData)
|
|---|
| 318 |
|
|---|
| 319 | # Load force definitions
|
|---|
| 320 |
|
|---|
| 321 | for tree in trees:
|
|---|
| 322 | for child in tree.getroot():
|
|---|
| 323 | if child.tag in parsers:
|
|---|
| 324 | parsers[child.tag](child, self)
|
|---|
| 325 |
|
|---|
| 326 | # Load scripts
|
|---|
| 327 |
|
|---|
| 328 | for tree in trees:
|
|---|
| 329 | for node in tree.getroot().findall('Script'):
|
|---|
| 330 | self.registerScript(node.text)
|
|---|
| 331 |
|
|---|
| 332 | def getGenerators(self):
|
|---|
| 333 | """Get the list of all registered generators."""
|
|---|
| 334 | return self._forces
|
|---|
| 335 |
|
|---|
| 336 | def registerGenerator(self, generator):
|
|---|
| 337 | """Register a new generator."""
|
|---|
| 338 | self._forces.append(generator)
|
|---|
| 339 |
|
|---|
| 340 | def registerAtomType(self, parameters):
|
|---|
| 341 | """Register a new atom type."""
|
|---|
| 342 | name = parameters['name']
|
|---|
| 343 | if name in self._atomTypes:
|
|---|
| 344 | raise ValueError('Found multiple definitions for atom type: '+name)
|
|---|
| 345 | atomClass = parameters['class']
|
|---|
| 346 | mass = _convertParameterToNumber(parameters['mass'])
|
|---|
| 347 | element = None
|
|---|
| 348 | if 'element' in parameters:
|
|---|
| 349 | element = parameters['element']
|
|---|
| 350 | if not isinstance(element, elem.Element):
|
|---|
| 351 | element = elem.get_by_symbol(element)
|
|---|
| 352 | self._atomTypes[name] = ForceField._AtomType(name, atomClass, mass, element)
|
|---|
| 353 | if atomClass in self._atomClasses:
|
|---|
| 354 | typeSet = self._atomClasses[atomClass]
|
|---|
| 355 | else:
|
|---|
| 356 | typeSet = set()
|
|---|
| 357 | self._atomClasses[atomClass] = typeSet
|
|---|
| 358 | typeSet.add(name)
|
|---|
| 359 | self._atomClasses[''].add(name)
|
|---|
| 360 |
|
|---|
| 361 | def registerResidueTemplate(self, template):
|
|---|
| 362 | """Register a new residue template."""
|
|---|
| 363 | if template.name in self._templates:
|
|---|
| 364 | # There is already a template with this name, so check the override levels.
|
|---|
| 365 |
|
|---|
| 366 | existingTemplate = self._templates[template.name]
|
|---|
| 367 | if template.overrideLevel < existingTemplate.overrideLevel:
|
|---|
| 368 | # The existing one takes precedence, so just return.
|
|---|
| 369 | return
|
|---|
| 370 | if template.overrideLevel > existingTemplate.overrideLevel:
|
|---|
| 371 | # We need to delete the existing template.
|
|---|
| 372 | del self._templates[template.name]
|
|---|
| 373 | existingSignature = _createResidueSignature([atom.element for atom in existingTemplate.atoms])
|
|---|
| 374 | self._templateSignatures[existingSignature].remove(existingTemplate)
|
|---|
| 375 | else:
|
|---|
| 376 | raise ValueError('Residue template %s with the same override level %d already exists.' % (template.name, template.overrideLevel))
|
|---|
| 377 |
|
|---|
| 378 | # Register the template.
|
|---|
| 379 |
|
|---|
| 380 | self._templates[template.name] = template
|
|---|
| 381 | signature = _createResidueSignature([atom.element for atom in template.atoms])
|
|---|
| 382 | if signature in self._templateSignatures:
|
|---|
| 383 | self._templateSignatures[signature].append(template)
|
|---|
| 384 | else:
|
|---|
| 385 | self._templateSignatures[signature] = [template]
|
|---|
| 386 |
|
|---|
| 387 | def registerPatch(self, patch):
|
|---|
| 388 | """Register a new patch that can be applied to templates."""
|
|---|
| 389 | self._patches[patch.name] = patch
|
|---|
| 390 |
|
|---|
| 391 | def registerTemplatePatch(self, residue, patch, patchResidueIndex):
|
|---|
| 392 | """Register that a particular patch can be used with a particular residue."""
|
|---|
| 393 | if residue not in self._templatePatches:
|
|---|
| 394 | self._templatePatches[residue] = []
|
|---|
| 395 | self._templatePatches[residue].append((patch, patchResidueIndex))
|
|---|
| 396 |
|
|---|
| 397 | def registerScript(self, script):
|
|---|
| 398 | """Register a new script to be executed after building the System."""
|
|---|
| 399 | self._scripts.append(script)
|
|---|
| 400 |
|
|---|
| 401 | def registerTemplateGenerator(self, generator):
|
|---|
| 402 | """Register a residue template generator that can be used to parameterize residues that do not match existing forcefield templates.
|
|---|
| 403 |
|
|---|
| 404 | This functionality can be used to add handlers to parameterize small molecules or unnatural/modified residues.
|
|---|
| 405 |
|
|---|
| 406 | .. CAUTION:: This method is experimental, and its API is subject to change.
|
|---|
| 407 |
|
|---|
| 408 | Parameters
|
|---|
| 409 | ----------
|
|---|
| 410 | generator : function
|
|---|
| 411 | A function that will be called when a residue is encountered that does not match an existing forcefield template.
|
|---|
| 412 |
|
|---|
| 413 | When a residue without a template is encountered, the `generator` function is called with:
|
|---|
| 414 |
|
|---|
| 415 | ::
|
|---|
| 416 | success = generator(forcefield, residue)
|
|---|
| 417 | ```
|
|---|
| 418 |
|
|---|
| 419 | where `forcefield` is the calling `ForceField` object and `residue` is a simtk.openmm.app.topology.Residue object.
|
|---|
| 420 |
|
|---|
| 421 | `generator` must conform to the following API:
|
|---|
| 422 | ::
|
|---|
| 423 | Parameters
|
|---|
| 424 | ----------
|
|---|
| 425 | forcefield : simtk.openmm.app.ForceField
|
|---|
| 426 | The ForceField object to which residue templates and/or parameters are to be added.
|
|---|
| 427 | residue : simtk.openmm.app.Topology.Residue
|
|---|
| 428 | The residue topology for which a template is to be generated.
|
|---|
| 429 |
|
|---|
| 430 | Returns
|
|---|
| 431 | -------
|
|---|
| 432 | success : bool
|
|---|
| 433 | If the generator is able to successfully parameterize the residue, `True` is returned.
|
|---|
| 434 | If the generator cannot parameterize the residue, it should return `False` and not modify `forcefield`.
|
|---|
| 435 |
|
|---|
| 436 | The generator should either register a residue template directly with `forcefield.registerResidueTemplate(template)`
|
|---|
| 437 | or it should call `forcefield.loadFile(file)` to load residue definitions from an ffxml file.
|
|---|
| 438 |
|
|---|
| 439 | It can also use the `ForceField` programmatic API to add additional atom types (via `forcefield.registerAtomType(parameters)`)
|
|---|
| 440 | or additional parameters.
|
|---|
| 441 |
|
|---|
| 442 | """
|
|---|
| 443 | self._templateGenerators.append(generator)
|
|---|
| 444 |
|
|---|
| 445 | def _findAtomTypes(self, attrib, num):
|
|---|
| 446 | """Parse the attributes on an XML tag to find the set of atom types for each atom it involves.
|
|---|
| 447 |
|
|---|
| 448 | Parameters
|
|---|
| 449 | ----------
|
|---|
| 450 | attrib : dict of attributes
|
|---|
| 451 | The dictionary of attributes for an XML parameter tag.
|
|---|
| 452 | num : int
|
|---|
| 453 | The number of atom specifiers (e.g. 'class1' through 'class4') to extract.
|
|---|
| 454 |
|
|---|
| 455 | Returns
|
|---|
| 456 | -------
|
|---|
| 457 | types : list
|
|---|
| 458 | A list of atom types that match.
|
|---|
| 459 |
|
|---|
| 460 | """
|
|---|
| 461 | types = []
|
|---|
| 462 | for i in range(num):
|
|---|
| 463 | if num == 1:
|
|---|
| 464 | suffix = ''
|
|---|
| 465 | else:
|
|---|
| 466 | suffix = str(i+1)
|
|---|
| 467 | classAttrib = 'class'+suffix
|
|---|
| 468 | typeAttrib = 'type'+suffix
|
|---|
| 469 | if classAttrib in attrib:
|
|---|
| 470 | if typeAttrib in attrib:
|
|---|
| 471 | raise ValueError('Specified both a type and a class for the same atom: '+str(attrib))
|
|---|
| 472 | if attrib[classAttrib] not in self._atomClasses:
|
|---|
| 473 | types.append(None) # Unknown atom class
|
|---|
| 474 | else:
|
|---|
| 475 | types.append(self._atomClasses[attrib[classAttrib]])
|
|---|
| 476 | elif typeAttrib in attrib:
|
|---|
| 477 | if attrib[typeAttrib] == '':
|
|---|
| 478 | types.append(self._atomClasses[''])
|
|---|
| 479 | elif attrib[typeAttrib] not in self._atomTypes:
|
|---|
| 480 | types.append(None) # Unknown atom type
|
|---|
| 481 | else:
|
|---|
| 482 | types.append([attrib[typeAttrib]])
|
|---|
| 483 | else:
|
|---|
| 484 | types.append(None) # Unknown atom type
|
|---|
| 485 | return types
|
|---|
| 486 |
|
|---|
| 487 | def _parseTorsion(self, attrib):
|
|---|
| 488 | """Parse the node defining a torsion."""
|
|---|
| 489 | types = self._findAtomTypes(attrib, 4)
|
|---|
| 490 | if None in types:
|
|---|
| 491 | return None
|
|---|
| 492 | torsion = PeriodicTorsion(types)
|
|---|
| 493 | index = 1
|
|---|
| 494 | while 'phase%d'%index in attrib:
|
|---|
| 495 | torsion.periodicity.append(int(attrib['periodicity%d'%index]))
|
|---|
| 496 | torsion.phase.append(_convertParameterToNumber(attrib['phase%d'%index]))
|
|---|
| 497 | torsion.k.append(_convertParameterToNumber(attrib['k%d'%index]))
|
|---|
| 498 | index += 1
|
|---|
| 499 | return torsion
|
|---|
| 500 |
|
|---|
| 501 | class _SystemData(object):
|
|---|
| 502 | """Inner class used to encapsulate data about the system being created."""
|
|---|
| 503 | def __init__(self):
|
|---|
| 504 | self.atomType = {}
|
|---|
| 505 | self.atomParameters = {}
|
|---|
| 506 | self.atoms = []
|
|---|
| 507 | self.excludeAtomWith = []
|
|---|
| 508 | self.virtualSites = {}
|
|---|
| 509 | self.bonds = []
|
|---|
| 510 | self.angles = []
|
|---|
| 511 | self.propers = []
|
|---|
| 512 | self.impropers = []
|
|---|
| 513 | self.atomBonds = []
|
|---|
| 514 | self.isAngleConstrained = []
|
|---|
| 515 | self.constraints = {}
|
|---|
| 516 |
|
|---|
| 517 | def addConstraint(self, system, atom1, atom2, distance):
|
|---|
| 518 | """Add a constraint to the system, avoiding duplicate constraints."""
|
|---|
| 519 | key = (min(atom1, atom2), max(atom1, atom2))
|
|---|
| 520 | if key in self.constraints:
|
|---|
| 521 | if self.constraints[key] != distance:
|
|---|
| 522 | raise ValueError('Two constraints were specified between atoms %d and %d with different distances' % (atom1, atom2))
|
|---|
| 523 | else:
|
|---|
| 524 | self.constraints[key] = distance
|
|---|
| 525 | system.addConstraint(atom1, atom2, distance)
|
|---|
| 526 |
|
|---|
| 527 | def recordMatchedAtomParameters(self, residue, template, matches):
|
|---|
| 528 | """Record parameters for atoms based on having matched a residue to a template."""
|
|---|
| 529 | matchAtoms = dict(zip(matches, residue.atoms()))
|
|---|
| 530 | for atom, match in zip(residue.atoms(), matches):
|
|---|
| 531 | self.atomType[atom] = template.atoms[match].type
|
|---|
| 532 | self.atomParameters[atom] = template.atoms[match].parameters
|
|---|
| 533 | for site in template.virtualSites:
|
|---|
| 534 | if match == site.index:
|
|---|
| 535 | self.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index)
|
|---|
| 536 |
|
|---|
| 537 | class _TemplateData(object):
|
|---|
| 538 | """Inner class used to encapsulate data about a residue template definition."""
|
|---|
| 539 | def __init__(self, name):
|
|---|
| 540 | self.name = name
|
|---|
| 541 | self.atoms = []
|
|---|
| 542 | self.virtualSites = []
|
|---|
| 543 | self.bonds = []
|
|---|
| 544 | self.externalBonds = []
|
|---|
| 545 | self.overrideLevel = 0
|
|---|
| 546 |
|
|---|
| 547 | def getAtomIndexByName(self, atom_name):
|
|---|
| 548 | """Look up an atom index by atom name, providing a helpful error message if not found."""
|
|---|
| 549 | for (index, atom) in enumerate(self.atoms):
|
|---|
| 550 | if atom.name == atom_name:
|
|---|
| 551 | return index
|
|---|
| 552 |
|
|---|
| 553 | # Provide a helpful error message if atom name not found.
|
|---|
| 554 | msg = "Atom name '%s' not found in residue template '%s'.\n" % (atom_name, self.name)
|
|---|
| 555 | msg += "Possible atom names are: %s" % str(atomIndices.keys())
|
|---|
| 556 | raise ValueError(msg)
|
|---|
| 557 |
|
|---|
| 558 | def addBond(self, atom1, atom2):
|
|---|
| 559 | """Add a bond between two atoms in a template given their indices in the template."""
|
|---|
| 560 | self.bonds.append((atom1, atom2))
|
|---|
| 561 | self.atoms[atom1].bondedTo.append(atom2)
|
|---|
| 562 | self.atoms[atom2].bondedTo.append(atom1)
|
|---|
| 563 |
|
|---|
| 564 | def addBondByName(self, atom1_name, atom2_name):
|
|---|
| 565 | """Add a bond between two atoms in a template given their atom names."""
|
|---|
| 566 | atom1 = self.getAtomIndexByName(atom1_name)
|
|---|
| 567 | atom2 = self.getAtomIndexByName(atom2_name)
|
|---|
| 568 | self.addBond(atom1, atom2)
|
|---|
| 569 |
|
|---|
| 570 | def addExternalBond(self, atom_index):
|
|---|
| 571 | """Designate that an atom in a residue template has an external bond, using atom index within template."""
|
|---|
| 572 | self.externalBonds.append(atom_index)
|
|---|
| 573 | self.atoms[atom_index].externalBonds += 1
|
|---|
| 574 |
|
|---|
| 575 | def addExternalBondByName(self, atom_name):
|
|---|
| 576 | """Designate that an atom in a residue template has an external bond, using atom name within template."""
|
|---|
| 577 | atom = self.getAtomIndexByName(atom_name)
|
|---|
| 578 | self.addExternalBond(atom)
|
|---|
| 579 |
|
|---|
| 580 | class _TemplateAtomData(object):
|
|---|
| 581 | """Inner class used to encapsulate data about an atom in a residue template definition."""
|
|---|
| 582 | def __init__(self, name, type, element, parameters={}):
|
|---|
| 583 | self.name = name
|
|---|
| 584 | self.type = type
|
|---|
| 585 | self.element = element
|
|---|
| 586 | self.parameters = parameters
|
|---|
| 587 | self.bondedTo = []
|
|---|
| 588 | self.externalBonds = 0
|
|---|
| 589 |
|
|---|
| 590 | class _BondData(object):
|
|---|
| 591 | """Inner class used to encapsulate data about a bond."""
|
|---|
| 592 | def __init__(self, atom1, atom2):
|
|---|
| 593 | self.atom1 = atom1
|
|---|
| 594 | self.atom2 = atom2
|
|---|
| 595 | self.isConstrained = False
|
|---|
| 596 | self.length = 0.0
|
|---|
| 597 |
|
|---|
| 598 | class _VirtualSiteData(object):
|
|---|
| 599 | """Inner class used to encapsulate data about a virtual site."""
|
|---|
| 600 | def __init__(self, node, atomIndices):
|
|---|
| 601 | attrib = node.attrib
|
|---|
| 602 | self.type = attrib['type']
|
|---|
| 603 | if self.type == 'average2':
|
|---|
| 604 | numAtoms = 2
|
|---|
| 605 | self.weights = [float(attrib['weight1']), float(attrib['weight2'])]
|
|---|
| 606 | elif self.type == 'average3':
|
|---|
| 607 | numAtoms = 3
|
|---|
| 608 | self.weights = [float(attrib['weight1']), float(attrib['weight2']), float(attrib['weight3'])]
|
|---|
| 609 | elif self.type == 'outOfPlane':
|
|---|
| 610 | numAtoms = 3
|
|---|
| 611 | self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])]
|
|---|
| 612 | elif self.type == 'localCoords':
|
|---|
| 613 | numAtoms = 3
|
|---|
| 614 | self.originWeights = [float(attrib['wo1']), float(attrib['wo2']), float(attrib['wo3'])]
|
|---|
| 615 | self.xWeights = [float(attrib['wx1']), float(attrib['wx2']), float(attrib['wx3'])]
|
|---|
| 616 | self.yWeights = [float(attrib['wy1']), float(attrib['wy2']), float(attrib['wy3'])]
|
|---|
| 617 | self.localPos = [float(attrib['p1']), float(attrib['p2']), float(attrib['p3'])]
|
|---|
| 618 | else:
|
|---|
| 619 | raise ValueError('Unknown virtual site type: %s' % self.type)
|
|---|
| 620 | if 'siteName' in attrib:
|
|---|
| 621 | self.index = atomIndices[attrib['siteName']]
|
|---|
| 622 | self.atoms = [atomIndices[attrib['atomName%d'%(i+1)]] for i in range(numAtoms)]
|
|---|
| 623 | else:
|
|---|
| 624 | self.index = int(attrib['index'])
|
|---|
| 625 | self.atoms = [int(attrib['atom%d'%(i+1)]) for i in range(numAtoms)]
|
|---|
| 626 | if 'excludeWith' in attrib:
|
|---|
| 627 | self.excludeWith = int(attrib['excludeWith'])
|
|---|
| 628 | else:
|
|---|
| 629 | self.excludeWith = self.atoms[0]
|
|---|
| 630 |
|
|---|
| 631 | class _PatchData(object):
|
|---|
| 632 | """Inner class used to encapsulate data about a patch definition."""
|
|---|
| 633 | def __init__(self, name, numResidues):
|
|---|
| 634 | self.name = name
|
|---|
| 635 | self.numResidues = numResidues
|
|---|
| 636 | self.addedAtoms = [[] for i in range(numResidues)]
|
|---|
| 637 | self.changedAtoms = [[] for i in range(numResidues)]
|
|---|
| 638 | self.deletedAtoms = []
|
|---|
| 639 | self.addedBonds = []
|
|---|
| 640 | self.deletedBonds = []
|
|---|
| 641 | self.addedExternalBonds = []
|
|---|
| 642 | self.deletedExternalBonds = []
|
|---|
| 643 |
|
|---|
| 644 | def createPatchedTemplates(self, templates):
|
|---|
| 645 | """Apply this patch to a set of templates, creating new modified ones."""
|
|---|
| 646 | if len(templates) != self.numResidues:
|
|---|
| 647 | raise ValueError("Patch '%s' expected %d templates, received %d", (self.name, self.numResidues, len(templates)))
|
|---|
| 648 |
|
|---|
| 649 | # Construct a new version of each template.
|
|---|
| 650 |
|
|---|
| 651 | newTemplates = []
|
|---|
| 652 | for index, template in enumerate(templates):
|
|---|
| 653 | newTemplate = ForceField._TemplateData("%s-%s" % (template.name, self.name))
|
|---|
| 654 | newTemplates.append(newTemplate)
|
|---|
| 655 |
|
|---|
| 656 | # Build the list of atoms in it.
|
|---|
| 657 |
|
|---|
| 658 | for atom in template.atoms:
|
|---|
| 659 | if not any(deleted.name == atom.name and deleted.residue == index for deleted in self.deletedAtoms):
|
|---|
| 660 | newTemplate.atoms.append(ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters))
|
|---|
| 661 | for atom in self.addedAtoms[index]:
|
|---|
| 662 | if any(a.name == atom.name for a in newTemplate.atoms):
|
|---|
| 663 | raise ValueError("Patch '%s' adds an atom with the same name as an existing atom: %s" % (self.name, atom.name))
|
|---|
| 664 | newTemplate.atoms.append(ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters))
|
|---|
| 665 | oldAtomIndex = dict([(atom.name, i) for i, atom in enumerate(template.atoms)])
|
|---|
| 666 | newAtomIndex = dict([(atom.name, i) for i, atom in enumerate(newTemplate.atoms)])
|
|---|
| 667 | for atom in self.changedAtoms[index]:
|
|---|
| 668 | if atom.name not in newAtomIndex:
|
|---|
| 669 | raise ValueError("Patch '%s' modifies nonexistent atom '%s' in template '%s'" % (self.name, atom.name, template.name))
|
|---|
| 670 | newTemplate.atoms[newAtomIndex[atom.name]] = ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters)
|
|---|
| 671 |
|
|---|
| 672 | # Copy over the virtual sites, translating the atom indices.
|
|---|
| 673 |
|
|---|
| 674 | indexMap = dict([(oldAtomIndex[name], newAtomIndex[name]) for name in newAtomIndex if name in oldAtomIndex])
|
|---|
| 675 | for site in template.virtualSites:
|
|---|
| 676 | if site.index in indexMap and all(i in indexMap for i in site.atoms):
|
|---|
| 677 | newSite = deepcopy(site)
|
|---|
| 678 | newSite.index = indexMap[site.index]
|
|---|
| 679 | newSite.atoms = [indexMap[i] for i in site.atoms]
|
|---|
| 680 | newTemplate.virtualSites.append(newSite)
|
|---|
| 681 |
|
|---|
| 682 | # Build the lists of bonds and external bonds.
|
|---|
| 683 |
|
|---|
| 684 | atomMap = dict([(template.atoms[i], indexMap[i]) for i in indexMap])
|
|---|
| 685 | deletedBonds = [(atom1.name, atom2.name) for atom1, atom2 in self.deletedBonds if atom1.residue == index and atom2.residue == index]
|
|---|
| 686 | for atom1, atom2 in template.bonds:
|
|---|
| 687 | a1 = template.atoms[atom1]
|
|---|
| 688 | a2 = template.atoms[atom2]
|
|---|
| 689 | if a1 in atomMap and a2 in atomMap and (a1.name, a2.name) not in deletedBonds and (a2.name, a1.name) not in deletedBonds:
|
|---|
| 690 | newTemplate.addBond(atomMap[a1], atomMap[a2])
|
|---|
| 691 | deletedExternalBonds = [atom.name for atom in self.deletedExternalBonds if atom.residue == index]
|
|---|
| 692 | for atom in template.externalBonds:
|
|---|
| 693 | if template.atoms[atom].name not in deletedExternalBonds:
|
|---|
| 694 | newTemplate.addExternalBond(indexMap[atom])
|
|---|
| 695 | for atom1, atom2 in self.addedBonds:
|
|---|
| 696 | if atom1.residue == index and atom2.residue == index:
|
|---|
| 697 | newTemplate.addBondByName(atom1.name, atom2.name)
|
|---|
| 698 | elif atom1.residue == index:
|
|---|
| 699 | newTemplate.addExternalBondByName(atom1.name)
|
|---|
| 700 | elif atom2.residue == index:
|
|---|
| 701 | newTemplate.addExternalBondByName(atom2.name)
|
|---|
| 702 | for atom in self.addedExternalBonds:
|
|---|
| 703 | newTemplate.addExternalBondByName(atom.name)
|
|---|
| 704 | return newTemplates
|
|---|
| 705 |
|
|---|
| 706 | class _PatchAtomData(object):
|
|---|
| 707 | """Inner class used to encapsulate data about an atom in a patch definition."""
|
|---|
| 708 | def __init__(self, description):
|
|---|
| 709 | if ':' in description:
|
|---|
| 710 | colonIndex = description.find(':')
|
|---|
| 711 | self.residue = int(description[:colonIndex])-1
|
|---|
| 712 | self.name = description[colonIndex+1:]
|
|---|
| 713 | else:
|
|---|
| 714 | self.residue = 0
|
|---|
| 715 | self.name = description
|
|---|
| 716 |
|
|---|
| 717 | class _AtomType(object):
|
|---|
| 718 | """Inner class used to record atom types and associated properties."""
|
|---|
| 719 | def __init__(self, name, atomClass, mass, element):
|
|---|
| 720 | self.name = name
|
|---|
| 721 | self.atomClass = atomClass
|
|---|
| 722 | self.mass = mass
|
|---|
| 723 | self.element = element
|
|---|
| 724 |
|
|---|
| 725 | class _AtomTypeParameters(object):
|
|---|
| 726 | """Inner class used to record parameter values for atom types."""
|
|---|
| 727 | def __init__(self, forcefield, forceName, atomTag, paramNames):
|
|---|
| 728 | self.ff = forcefield
|
|---|
| 729 | self.forceName = forceName
|
|---|
| 730 | self.atomTag = atomTag
|
|---|
| 731 | self.paramNames = paramNames
|
|---|
| 732 | self.paramsForType = {}
|
|---|
| 733 | self.extraParamsForType = {}
|
|---|
| 734 |
|
|---|
| 735 | def registerAtom(self, parameters, expectedParams=None):
|
|---|
| 736 | if expectedParams is None:
|
|---|
| 737 | expectedParams = self.paramNames
|
|---|
| 738 | types = self.ff._findAtomTypes(parameters, 1)
|
|---|
| 739 | if None not in types:
|
|---|
| 740 | values = {}
|
|---|
| 741 | extraValues = {}
|
|---|
| 742 | for key in parameters:
|
|---|
| 743 | if key in expectedParams:
|
|---|
| 744 | values[key] = _convertParameterToNumber(parameters[key])
|
|---|
| 745 | else:
|
|---|
| 746 | extraValues[key] = parameters[key]
|
|---|
| 747 | if len(values) < len(expectedParams):
|
|---|
| 748 | for key in expectedParams:
|
|---|
| 749 | if key not in values:
|
|---|
| 750 | raise ValueError('%s: No value specified for "%s"' % (self.forceName, key))
|
|---|
| 751 | for t in types[0]:
|
|---|
| 752 | self.paramsForType[t] = values
|
|---|
| 753 | self.extraParamsForType[t] = extraValues
|
|---|
| 754 |
|
|---|
| 755 | def parseDefinitions(self, element):
|
|---|
| 756 | """"Load the definitions from an XML element."""
|
|---|
| 757 | expectedParams = list(self.paramNames)
|
|---|
| 758 | excludedParams = [node.attrib['name'] for node in element.findall('UseAttributeFromResidue')]
|
|---|
| 759 | for param in excludedParams:
|
|---|
| 760 | if param not in expectedParams:
|
|---|
| 761 | raise ValueError('%s: <UseAttributeFromResidue> specified an invalid attribute: %s' % (self.forceName, param))
|
|---|
| 762 | expectedParams.remove(param)
|
|---|
| 763 | for atom in element.findall(self.atomTag):
|
|---|
| 764 | for param in excludedParams:
|
|---|
| 765 | if param in atom.attrib:
|
|---|
| 766 | raise ValueError('%s: The attribute "%s" appeared in both <%s> and <UseAttributeFromResidue> tags' % (self.forceName, param, self.atomTag))
|
|---|
| 767 | self.registerAtom(atom.attrib, expectedParams)
|
|---|
| 768 |
|
|---|
| 769 | def getAtomParameters(self, atom, data):
|
|---|
| 770 | """Get the parameter values for a particular atom."""
|
|---|
| 771 | t = data.atomType[atom]
|
|---|
| 772 | p = data.atomParameters[atom]
|
|---|
| 773 | if t in self.paramsForType:
|
|---|
| 774 | values = self.paramsForType[t]
|
|---|
| 775 | result = [None]*len(self.paramNames)
|
|---|
| 776 | for i, name in enumerate(self.paramNames):
|
|---|
| 777 | if name in values:
|
|---|
| 778 | result[i] = values[name]
|
|---|
| 779 | elif name in p:
|
|---|
| 780 | result[i] = p[name]
|
|---|
| 781 | else:
|
|---|
| 782 | raise ValueError('%s: No value specified for "%s"' % (self.forceName, name))
|
|---|
| 783 | return result
|
|---|
| 784 | else:
|
|---|
| 785 | raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
|
|---|
| 786 |
|
|---|
| 787 | def getExtraParameters(self, atom, data):
|
|---|
| 788 | """Get extra parameter values for an atom that appeared in the <Atom> tag but were not included in paramNames."""
|
|---|
| 789 | t = data.atomType[atom]
|
|---|
| 790 | if t in self.paramsForType:
|
|---|
| 791 | return self.extraParamsForType[t]
|
|---|
| 792 | else:
|
|---|
| 793 | raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
|
|---|
| 794 |
|
|---|
| 795 |
|
|---|
| 796 | def _getResidueTemplateMatches(self, res, bondedToAtom, templateSignatures=None, ignoreExternalBonds=False):
|
|---|
| 797 | """Return the residue template matches, or None if none are found.
|
|---|
| 798 |
|
|---|
| 799 | Parameters
|
|---|
| 800 | ----------
|
|---|
| 801 | res : Topology.Residue
|
|---|
| 802 | The residue for which template matches are to be retrieved.
|
|---|
| 803 | bondedToAtom : list of set of int
|
|---|
| 804 | bondedToAtom[i] is the set of atoms bonded to atom index i
|
|---|
| 805 |
|
|---|
| 806 | Returns
|
|---|
| 807 | -------
|
|---|
| 808 | template : _ForceFieldTemplate
|
|---|
| 809 | The matching forcefield residue template, or None if no matches are found.
|
|---|
| 810 | matches : list
|
|---|
| 811 | a list specifying which atom of the template each atom of the residue
|
|---|
| 812 | corresponds to, or None if it does not match the template
|
|---|
| 813 |
|
|---|
| 814 | """
|
|---|
| 815 | template = None
|
|---|
| 816 | matches = None
|
|---|
| 817 | if templateSignatures is None:
|
|---|
| 818 | templateSignatures = self._templateSignatures
|
|---|
| 819 | signature = _createResidueSignature([atom.element for atom in res.atoms()])
|
|---|
| 820 | if signature in templateSignatures:
|
|---|
| 821 | allMatches = []
|
|---|
| 822 | '''
|
|---|
| 823 | Search through once including external bonds. We only want to
|
|---|
| 824 | consider the ignoreExternalBonds switch if no complete template
|
|---|
| 825 | is found, otherwise we run into all sorts of problems.
|
|---|
| 826 | '''
|
|---|
| 827 | for t in templateSignatures[signature]:
|
|---|
| 828 | match = _matchResidue(res, t, bondedToAtom)
|
|---|
| 829 | if match is not None:
|
|---|
| 830 | allMatches.append((t, match))
|
|---|
| 831 | if len(allMatches) == 0 and ignoreExternalBonds:
|
|---|
| 832 | for t in templateSignatures[signature]:
|
|---|
| 833 | match = _matchResidue(res, t, bondedToAtom, ignoreMissingExternalBonds)
|
|---|
| 834 | if match is not None:
|
|---|
| 835 | allMatches.append((t, match))
|
|---|
| 836 | if len(allMatches) == 1:
|
|---|
| 837 | template = allMatches[0][0]
|
|---|
| 838 | matches = allMatches[0][1]
|
|---|
| 839 | elif len(allMatches) > 1:
|
|---|
| 840 | raise Exception('Multiple matching templates found for residue %d (%s): %s.' % (res.index+1, res.name, ', '.join(match[0].name for match in allMatches)))
|
|---|
| 841 | return [template, matches]
|
|---|
| 842 |
|
|---|
| 843 | def _buildBondedToAtomList(self, topology):
|
|---|
| 844 | """Build a list of which atom indices are bonded to each atom.
|
|---|
| 845 |
|
|---|
| 846 | Parameters
|
|---|
| 847 | ----------
|
|---|
| 848 | topology : Topology
|
|---|
| 849 | The Topology whose bonds are to be indexed.
|
|---|
| 850 |
|
|---|
| 851 | Returns
|
|---|
| 852 | -------
|
|---|
| 853 | bondedToAtom : list of set of int
|
|---|
| 854 | bondedToAtom[index] is the set of atom indices bonded to atom `index`
|
|---|
| 855 |
|
|---|
| 856 | """
|
|---|
| 857 | bondedToAtom = []
|
|---|
| 858 | for atom in topology.atoms():
|
|---|
| 859 | bondedToAtom.append(set())
|
|---|
| 860 | for (atom1, atom2) in topology.bonds():
|
|---|
| 861 | bondedToAtom[atom1.index].add(atom2.index)
|
|---|
| 862 | bondedToAtom[atom2.index].add(atom1.index)
|
|---|
| 863 | return bondedToAtom
|
|---|
| 864 |
|
|---|
| 865 | def getUnmatchedResidues(self, topology):
|
|---|
| 866 | """Return a list of Residue objects from specified topology for which no forcefield templates are available.
|
|---|
| 867 |
|
|---|
| 868 | .. CAUTION:: This method is experimental, and its API is subject to change.
|
|---|
| 869 |
|
|---|
| 870 | Parameters
|
|---|
| 871 | ----------
|
|---|
| 872 | topology : Topology
|
|---|
| 873 | The Topology whose residues are to be checked against the forcefield residue templates.
|
|---|
| 874 |
|
|---|
| 875 | Returns
|
|---|
| 876 | -------
|
|---|
| 877 | unmatched_residues : list of Residue
|
|---|
| 878 | List of Residue objects from `topology` for which no forcefield residue templates are available.
|
|---|
| 879 | Note that multiple instances of the same residue appearing at different points in the topology may be returned.
|
|---|
| 880 |
|
|---|
| 881 | This method may be of use in generating missing residue templates or diagnosing parameterization failures.
|
|---|
| 882 | """
|
|---|
| 883 | # Find the template matching each residue, compiling a list of residues for which no templates are available.
|
|---|
| 884 | bondedToAtom = self._buildBondedToAtomList(topology)
|
|---|
| 885 | unmatched_residues = list() # list of unmatched residues
|
|---|
| 886 | for res in topology.residues():
|
|---|
| 887 | # Attempt to match one of the existing templates.
|
|---|
| 888 | [template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
|
|---|
| 889 | if matches is None:
|
|---|
| 890 | # No existing templates match.
|
|---|
| 891 | unmatched_residues.append(res)
|
|---|
| 892 |
|
|---|
| 893 | return unmatched_residues
|
|---|
| 894 |
|
|---|
| 895 | def getMatchingTemplates(self, topology, ignoreExternalBonds=False):
|
|---|
| 896 | """Return a list of forcefield residue templates matching residues in the specified topology.
|
|---|
| 897 |
|
|---|
| 898 | .. CAUTION:: This method is experimental, and its API is subject to change.
|
|---|
| 899 |
|
|---|
| 900 | Parameters
|
|---|
| 901 | ----------
|
|---|
| 902 | topology : Topology
|
|---|
| 903 | The Topology whose residues are to be checked against the forcefield residue templates.
|
|---|
| 904 | ignoreExternalBonds : bool=False
|
|---|
| 905 | If true, ignore external bonds when matching residues to templates.
|
|---|
| 906 | Returns
|
|---|
| 907 | -------
|
|---|
| 908 | templates : list of _TemplateData
|
|---|
| 909 | List of forcefield residue templates corresponding to residues in the topology.
|
|---|
| 910 | templates[index] is template corresponding to residue `index` in topology.residues()
|
|---|
| 911 |
|
|---|
| 912 | This method may be of use in debugging issues related to parameter assignment.
|
|---|
| 913 | """
|
|---|
| 914 | # Find the template matching each residue, compiling a list of residues for which no templates are available.
|
|---|
| 915 | bondedToAtom = self._buildBondedToAtomList(topology)
|
|---|
| 916 | templates = list() # list of templates matching the corresponding residues
|
|---|
| 917 | for res in topology.residues():
|
|---|
| 918 | # Attempt to match one of the existing templates.
|
|---|
| 919 | [template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
|
|---|
| 920 | # Raise an exception if we have found no templates that match.
|
|---|
| 921 | if matches is None:
|
|---|
| 922 | raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res)))
|
|---|
| 923 | else:
|
|---|
| 924 | templates.append(template)
|
|---|
| 925 |
|
|---|
| 926 | return templates
|
|---|
| 927 |
|
|---|
| 928 | def generateTemplatesForUnmatchedResidues(self, topology):
|
|---|
| 929 | """Generate forcefield residue templates for residues in specified topology for which no forcefield templates are available.
|
|---|
| 930 |
|
|---|
| 931 | .. CAUTION:: This method is experimental, and its API is subject to change.
|
|---|
| 932 |
|
|---|
| 933 | Parameters
|
|---|
| 934 | ----------
|
|---|
| 935 | topology : Topology
|
|---|
| 936 | The Topology whose residues are to be checked against the forcefield residue templates.
|
|---|
| 937 |
|
|---|
| 938 | Returns
|
|---|
| 939 | -------
|
|---|
| 940 | templates : list of _TemplateData
|
|---|
| 941 | List of forcefield residue templates corresponding to residues in `topology` for which no forcefield templates are currently available.
|
|---|
| 942 | Atom types will be set to `None`, but template name, atom names, elements, and connectivity will be taken from corresponding Residue objects.
|
|---|
| 943 | residues : list of Residue
|
|---|
| 944 | List of Residue objects that were used to generate the templates.
|
|---|
| 945 | `residues[index]` is the Residue that was used to generate the template `templates[index]`
|
|---|
| 946 |
|
|---|
| 947 | """
|
|---|
| 948 | # Get a non-unique list of unmatched residues.
|
|---|
| 949 | unmatched_residues = self.getUnmatchedResidues(topology)
|
|---|
| 950 | # Generate a unique list of unmatched residues by comparing fingerprints.
|
|---|
| 951 | bondedToAtom = self._buildBondedToAtomList(topology)
|
|---|
| 952 | unique_unmatched_residues = list() # list of unique unmatched Residue objects from topology
|
|---|
| 953 | templates = list() # corresponding _TemplateData templates
|
|---|
| 954 | signatures = set()
|
|---|
| 955 | for residue in unmatched_residues:
|
|---|
| 956 | signature = _createResidueSignature([ atom.element for atom in residue.atoms() ])
|
|---|
| 957 | template = _createResidueTemplate(residue)
|
|---|
| 958 | is_unique = True
|
|---|
| 959 | if signature in signatures:
|
|---|
| 960 | # Signature is the same as an existing residue; check connectivity.
|
|---|
| 961 | for check_residue in unique_unmatched_residues:
|
|---|
| 962 | matches = _matchResidue(check_residue, template, bondedToAtom, False)
|
|---|
| 963 | if matches is not None:
|
|---|
| 964 | is_unique = False
|
|---|
| 965 | if is_unique:
|
|---|
| 966 | # Residue is unique.
|
|---|
| 967 | unique_unmatched_residues.append(residue)
|
|---|
| 968 | signatures.add(signature)
|
|---|
| 969 | templates.append(template)
|
|---|
| 970 |
|
|---|
| 971 | return [templates, unique_unmatched_residues]
|
|---|
| 972 |
|
|---|
| 973 | def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
|
|---|
| 974 | constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, residueTemplates=dict(),
|
|---|
| 975 | ignoreExternalBonds=False, **args):
|
|---|
| 976 | """Construct an OpenMM System representing a Topology with this force field.
|
|---|
| 977 |
|
|---|
| 978 | Parameters
|
|---|
| 979 | ----------
|
|---|
| 980 | topology : Topology
|
|---|
| 981 | The Topology for which to create a System
|
|---|
| 982 | nonbondedMethod : object=NoCutoff
|
|---|
| 983 | The method to use for nonbonded interactions. Allowed values are
|
|---|
| 984 | NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
|
|---|
| 985 | nonbondedCutoff : distance=1*nanometer
|
|---|
| 986 | The cutoff distance to use for nonbonded interactions
|
|---|
| 987 | constraints : object=None
|
|---|
| 988 | Specifies which bonds and angles should be implemented with constraints.
|
|---|
| 989 | Allowed values are None, HBonds, AllBonds, or HAngles.
|
|---|
| 990 | rigidWater : boolean=True
|
|---|
| 991 | If true, water molecules will be fully rigid regardless of the value
|
|---|
| 992 | passed for the constraints argument
|
|---|
| 993 | removeCMMotion : boolean=True
|
|---|
| 994 | If true, a CMMotionRemover will be added to the System
|
|---|
| 995 | hydrogenMass : mass=None
|
|---|
| 996 | The mass to use for hydrogen atoms bound to heavy atoms. Any mass
|
|---|
| 997 | added to a hydrogen is subtracted from the heavy atom to keep
|
|---|
| 998 | their total mass the same.
|
|---|
| 999 | residueTemplates : dict=dict()
|
|---|
| 1000 | Key: Topology Residue object
|
|---|
| 1001 | Value: string, name of _TemplateData residue template object to use for (Key) residue.
|
|---|
| 1002 | This allows user to specify which template to apply to particular Residues
|
|---|
| 1003 | in the event that multiple matching templates are available (e.g Fe2+ and Fe3+
|
|---|
| 1004 | templates in the ForceField for a monoatomic iron ion in the topology).
|
|---|
| 1005 | ignoreExternalBonds : boolean=False
|
|---|
| 1006 | If true, ignore external bonds when matching residues to templates. This is
|
|---|
| 1007 | useful when the Topology represents one piece of a larger molecule, so chains are
|
|---|
| 1008 | not terminated properly. This option can create ambiguities where multiple
|
|---|
| 1009 | templates match the same residue. If that happens, use the residueTemplates
|
|---|
| 1010 | argument to specify which one to use.
|
|---|
| 1011 | args
|
|---|
| 1012 | Arbitrary additional keyword arguments may also be specified.
|
|---|
| 1013 | This allows extra parameters to be specified that are specific to
|
|---|
| 1014 | particular force fields.
|
|---|
| 1015 |
|
|---|
| 1016 | Returns
|
|---|
| 1017 | -------
|
|---|
| 1018 | system
|
|---|
| 1019 | the newly created System
|
|---|
| 1020 | """
|
|---|
| 1021 | data = ForceField._SystemData()
|
|---|
| 1022 | data.atoms = list(topology.atoms())
|
|---|
| 1023 | for atom in data.atoms:
|
|---|
| 1024 | data.excludeAtomWith.append([])
|
|---|
| 1025 |
|
|---|
| 1026 | # Make a list of all bonds
|
|---|
| 1027 |
|
|---|
| 1028 | for bond in topology.bonds():
|
|---|
| 1029 | data.bonds.append(ForceField._BondData(bond[0].index, bond[1].index))
|
|---|
| 1030 |
|
|---|
| 1031 | # Record which atoms are bonded to each other atom
|
|---|
| 1032 |
|
|---|
| 1033 | bondedToAtom = []
|
|---|
| 1034 | for i in range(len(data.atoms)):
|
|---|
| 1035 | bondedToAtom.append(set())
|
|---|
| 1036 | data.atomBonds.append([])
|
|---|
| 1037 | for i in range(len(data.bonds)):
|
|---|
| 1038 | bond = data.bonds[i]
|
|---|
| 1039 | bondedToAtom[bond.atom1].add(bond.atom2)
|
|---|
| 1040 | bondedToAtom[bond.atom2].add(bond.atom1)
|
|---|
| 1041 | data.atomBonds[bond.atom1].append(i)
|
|---|
| 1042 | data.atomBonds[bond.atom2].append(i)
|
|---|
| 1043 |
|
|---|
| 1044 | # Find the template matching each residue and assign atom types.
|
|---|
| 1045 |
|
|---|
| 1046 | unmatchedResidues = []
|
|---|
| 1047 | for chain in topology.chains():
|
|---|
| 1048 | for res in chain.residues():
|
|---|
| 1049 | if res in residueTemplates:
|
|---|
| 1050 | tname = residueTemplates[res]
|
|---|
| 1051 | template = self._templates[tname]
|
|---|
| 1052 | matches = _matchResidue(res, template, bondedToAtom, ignoreExternalBonds)
|
|---|
| 1053 | if matches is None:
|
|---|
| 1054 | raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name))
|
|---|
| 1055 | else:
|
|---|
| 1056 | # Attempt to match one of the existing templates.
|
|---|
| 1057 | [template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
|
|---|
| 1058 | if matches is None:
|
|---|
| 1059 | unmatchedResidues.append(res)
|
|---|
| 1060 | else:
|
|---|
| 1061 | data.recordMatchedAtomParameters(res, template, matches)
|
|---|
| 1062 |
|
|---|
| 1063 | # Try to apply patches to find matches for any unmatched residues.
|
|---|
| 1064 |
|
|---|
| 1065 | if len(unmatchedResidues) > 0:
|
|---|
| 1066 | unmatchedResidues = _applyPatchesToMatchResidues(self, data, unmatchedResidues, bondedToAtom, ignoreExternalBonds)
|
|---|
| 1067 |
|
|---|
| 1068 | # If we still haven't found a match for a residue, attempt to use residue template generators to create
|
|---|
| 1069 | # new templates (and potentially atom types/parameters).
|
|---|
| 1070 |
|
|---|
| 1071 | for res in unmatchedResidues:
|
|---|
| 1072 | # A template might have been generated on an earlier iteration of this loop.
|
|---|
| 1073 | [template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
|
|---|
| 1074 | if matches is None:
|
|---|
| 1075 | # Try all generators.
|
|---|
| 1076 | for generator in self._templateGenerators:
|
|---|
| 1077 | if generator(self, res):
|
|---|
| 1078 | # This generator has registered a new residue template that should match.
|
|---|
| 1079 | [template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
|
|---|
| 1080 | if matches is None:
|
|---|
| 1081 | # Something went wrong because the generated template does not match the residue signature.
|
|---|
| 1082 | raise Exception('The residue handler %s indicated it had correctly parameterized residue %s, but the generated template did not match the residue signature.' % (generator.__class__.__name__, str(res)))
|
|---|
| 1083 | else:
|
|---|
| 1084 | # We successfully generated a residue template. Break out of the for loop.
|
|---|
| 1085 | break
|
|---|
| 1086 | if matches is None:
|
|---|
| 1087 | raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res)))
|
|---|
| 1088 | else:
|
|---|
| 1089 | data.recordMatchedAtomParameters(res, template, matches)
|
|---|
| 1090 |
|
|---|
| 1091 | # Create the System and add atoms
|
|---|
| 1092 |
|
|---|
| 1093 | sys = mm.System()
|
|---|
| 1094 | for atom in topology.atoms():
|
|---|
| 1095 | # Look up the atom type name, returning a helpful error message if it cannot be found.
|
|---|
| 1096 | if atom not in data.atomType:
|
|---|
| 1097 | raise Exception("Could not identify atom type for atom '%s'." % str(atom))
|
|---|
| 1098 | typename = data.atomType[atom]
|
|---|
| 1099 |
|
|---|
| 1100 | # Look up the type name in the list of registered atom types, returning a helpful error message if it cannot be found.
|
|---|
| 1101 | if typename not in self._atomTypes:
|
|---|
| 1102 | msg = "Could not find typename '%s' for atom '%s' in list of known atom types.\n" % (typename, str(atom))
|
|---|
| 1103 | msg += "Known atom types are: %s" % str(self._atomTypes.keys())
|
|---|
| 1104 | raise Exception(msg)
|
|---|
| 1105 |
|
|---|
| 1106 | # Add the particle to the OpenMM system.
|
|---|
| 1107 | mass = self._atomTypes[typename].mass
|
|---|
| 1108 | sys.addParticle(mass)
|
|---|
| 1109 |
|
|---|
| 1110 | # Adjust hydrogen masses if requested.
|
|---|
| 1111 |
|
|---|
| 1112 | if hydrogenMass is not None:
|
|---|
| 1113 | if not unit.is_quantity(hydrogenMass):
|
|---|
| 1114 | hydrogenMass *= unit.dalton
|
|---|
| 1115 | for atom1, atom2 in topology.bonds():
|
|---|
| 1116 | if atom1.element == elem.hydrogen:
|
|---|
| 1117 | (atom1, atom2) = (atom2, atom1)
|
|---|
| 1118 | if atom2.element == elem.hydrogen and atom1.element not in (elem.hydrogen, None):
|
|---|
| 1119 | transferMass = hydrogenMass-sys.getParticleMass(atom2.index)
|
|---|
| 1120 | sys.setParticleMass(atom2.index, hydrogenMass)
|
|---|
| 1121 | sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass)
|
|---|
| 1122 |
|
|---|
| 1123 | # Set periodic boundary conditions.
|
|---|
| 1124 |
|
|---|
| 1125 | boxVectors = topology.getPeriodicBoxVectors()
|
|---|
| 1126 | if boxVectors is not None:
|
|---|
| 1127 | sys.setDefaultPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2])
|
|---|
| 1128 | elif nonbondedMethod not in [NoCutoff, CutoffNonPeriodic]:
|
|---|
| 1129 | raise ValueError('Requested periodic boundary conditions for a Topology that does not specify periodic box dimensions')
|
|---|
| 1130 |
|
|---|
| 1131 | # Make a list of all unique angles
|
|---|
| 1132 |
|
|---|
| 1133 | uniqueAngles = set()
|
|---|
| 1134 | for bond in data.bonds:
|
|---|
| 1135 | for atom in bondedToAtom[bond.atom1]:
|
|---|
| 1136 | if atom != bond.atom2:
|
|---|
| 1137 | if atom < bond.atom2:
|
|---|
| 1138 | uniqueAngles.add((atom, bond.atom1, bond.atom2))
|
|---|
| 1139 | else:
|
|---|
| 1140 | uniqueAngles.add((bond.atom2, bond.atom1, atom))
|
|---|
| 1141 | for atom in bondedToAtom[bond.atom2]:
|
|---|
| 1142 | if atom != bond.atom1:
|
|---|
| 1143 | if atom > bond.atom1:
|
|---|
| 1144 | uniqueAngles.add((bond.atom1, bond.atom2, atom))
|
|---|
| 1145 | else:
|
|---|
| 1146 | uniqueAngles.add((atom, bond.atom2, bond.atom1))
|
|---|
| 1147 | data.angles = sorted(list(uniqueAngles))
|
|---|
| 1148 |
|
|---|
| 1149 | # Make a list of all unique proper torsions
|
|---|
| 1150 |
|
|---|
| 1151 | uniquePropers = set()
|
|---|
| 1152 | for angle in data.angles:
|
|---|
| 1153 | for atom in bondedToAtom[angle[0]]:
|
|---|
| 1154 | if atom not in angle:
|
|---|
| 1155 | if atom < angle[2]:
|
|---|
| 1156 | uniquePropers.add((atom, angle[0], angle[1], angle[2]))
|
|---|
| 1157 | else:
|
|---|
| 1158 | uniquePropers.add((angle[2], angle[1], angle[0], atom))
|
|---|
| 1159 | for atom in bondedToAtom[angle[2]]:
|
|---|
| 1160 | if atom not in angle:
|
|---|
| 1161 | if atom > angle[0]:
|
|---|
| 1162 | uniquePropers.add((angle[0], angle[1], angle[2], atom))
|
|---|
| 1163 | else:
|
|---|
| 1164 | uniquePropers.add((atom, angle[2], angle[1], angle[0]))
|
|---|
| 1165 | data.propers = sorted(list(uniquePropers))
|
|---|
| 1166 |
|
|---|
| 1167 | # Make a list of all unique improper torsions
|
|---|
| 1168 |
|
|---|
| 1169 | for atom in range(len(bondedToAtom)):
|
|---|
| 1170 | bondedTo = bondedToAtom[atom]
|
|---|
| 1171 | if len(bondedTo) > 2:
|
|---|
| 1172 | for subset in itertools.combinations(bondedTo, 3):
|
|---|
| 1173 | data.impropers.append((atom, subset[0], subset[1], subset[2]))
|
|---|
| 1174 |
|
|---|
| 1175 | # Identify bonds that should be implemented with constraints
|
|---|
| 1176 |
|
|---|
| 1177 | if constraints == AllBonds or constraints == HAngles:
|
|---|
| 1178 | for bond in data.bonds:
|
|---|
| 1179 | bond.isConstrained = True
|
|---|
| 1180 | elif constraints == HBonds:
|
|---|
| 1181 | for bond in data.bonds:
|
|---|
| 1182 | atom1 = data.atoms[bond.atom1]
|
|---|
| 1183 | atom2 = data.atoms[bond.atom2]
|
|---|
| 1184 | bond.isConstrained = atom1.name.startswith('H') or atom2.name.startswith('H')
|
|---|
| 1185 | if rigidWater:
|
|---|
| 1186 | for bond in data.bonds:
|
|---|
| 1187 | atom1 = data.atoms[bond.atom1]
|
|---|
| 1188 | atom2 = data.atoms[bond.atom2]
|
|---|
| 1189 | if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH':
|
|---|
| 1190 | bond.isConstrained = True
|
|---|
| 1191 |
|
|---|
| 1192 | # Identify angles that should be implemented with constraints
|
|---|
| 1193 |
|
|---|
| 1194 | if constraints == HAngles:
|
|---|
| 1195 | for angle in data.angles:
|
|---|
| 1196 | atom1 = data.atoms[angle[0]]
|
|---|
| 1197 | atom2 = data.atoms[angle[1]]
|
|---|
| 1198 | atom3 = data.atoms[angle[2]]
|
|---|
| 1199 | numH = 0
|
|---|
| 1200 | if atom1.name.startswith('H'):
|
|---|
| 1201 | numH += 1
|
|---|
| 1202 | if atom3.name.startswith('H'):
|
|---|
| 1203 | numH += 1
|
|---|
| 1204 | data.isAngleConstrained.append(numH == 2 or (numH == 1 and atom2.name.startswith('O')))
|
|---|
| 1205 | else:
|
|---|
| 1206 | data.isAngleConstrained = len(data.angles)*[False]
|
|---|
| 1207 | if rigidWater:
|
|---|
| 1208 | for i in range(len(data.angles)):
|
|---|
| 1209 | angle = data.angles[i]
|
|---|
| 1210 | atom1 = data.atoms[angle[0]]
|
|---|
| 1211 | atom2 = data.atoms[angle[1]]
|
|---|
| 1212 | atom3 = data.atoms[angle[2]]
|
|---|
| 1213 | if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH' and atom3.residue.name == 'HOH':
|
|---|
| 1214 | data.isAngleConstrained[i] = True
|
|---|
| 1215 |
|
|---|
| 1216 | # Add virtual sites
|
|---|
| 1217 |
|
|---|
| 1218 | for atom in data.virtualSites:
|
|---|
| 1219 | (site, atoms, excludeWith) = data.virtualSites[atom]
|
|---|
| 1220 | index = atom.index
|
|---|
| 1221 | data.excludeAtomWith[excludeWith].append(index)
|
|---|
| 1222 | if site.type == 'average2':
|
|---|
| 1223 | sys.setVirtualSite(index, mm.TwoParticleAverageSite(atoms[0], atoms[1], site.weights[0], site.weights[1]))
|
|---|
| 1224 | elif site.type == 'average3':
|
|---|
| 1225 | sys.setVirtualSite(index, mm.ThreeParticleAverageSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2]))
|
|---|
| 1226 | elif site.type == 'outOfPlane':
|
|---|
| 1227 | sys.setVirtualSite(index, mm.OutOfPlaneSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2]))
|
|---|
| 1228 | elif site.type == 'localCoords':
|
|---|
| 1229 | sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms[0], atoms[1], atoms[2],
|
|---|
| 1230 | mm.Vec3(site.originWeights[0], site.originWeights[1], site.originWeights[2]),
|
|---|
| 1231 | mm.Vec3(site.xWeights[0], site.xWeights[1], site.xWeights[2]),
|
|---|
| 1232 | mm.Vec3(site.yWeights[0], site.yWeights[1], site.yWeights[2]),
|
|---|
| 1233 | mm.Vec3(site.localPos[0], site.localPos[1], site.localPos[2])))
|
|---|
| 1234 |
|
|---|
| 1235 | # Add forces to the System
|
|---|
| 1236 |
|
|---|
| 1237 | for force in self._forces:
|
|---|
| 1238 | force.createForce(sys, data, nonbondedMethod, nonbondedCutoff, args)
|
|---|
| 1239 | if removeCMMotion:
|
|---|
| 1240 | sys.addForce(mm.CMMotionRemover())
|
|---|
| 1241 |
|
|---|
| 1242 | # Let force generators do postprocessing
|
|---|
| 1243 |
|
|---|
| 1244 | for force in self._forces:
|
|---|
| 1245 | if 'postprocessSystem' in dir(force):
|
|---|
| 1246 | force.postprocessSystem(sys, data, args)
|
|---|
| 1247 |
|
|---|
| 1248 | # Execute scripts found in the XML files.
|
|---|
| 1249 |
|
|---|
| 1250 | for script in self._scripts:
|
|---|
| 1251 | exec(script, locals())
|
|---|
| 1252 | return sys
|
|---|
| 1253 |
|
|---|
| 1254 |
|
|---|
| 1255 | def _findBondsForExclusions(data, sys):
|
|---|
| 1256 | """Create a list of bonds to use when identifying exclusions."""
|
|---|
| 1257 | bondIndices = []
|
|---|
| 1258 | for bond in data.bonds:
|
|---|
| 1259 | bondIndices.append((bond.atom1, bond.atom2))
|
|---|
| 1260 |
|
|---|
| 1261 | # If a virtual site does *not* share exclusions with another atom, add a bond between it and its first parent atom.
|
|---|
| 1262 |
|
|---|
| 1263 | for i in range(sys.getNumParticles()):
|
|---|
| 1264 | if sys.isVirtualSite(i):
|
|---|
| 1265 | (site, atoms, excludeWith) = data.virtualSites[data.atoms[i]]
|
|---|
| 1266 | if excludeWith is None:
|
|---|
| 1267 | bondIndices.append((i, site.getParticle(0)))
|
|---|
| 1268 |
|
|---|
| 1269 | # Certain particles, such as lone pairs and Drude particles, share exclusions with a parent atom.
|
|---|
| 1270 | # If the parent atom does not interact with an atom, the child particle does not either.
|
|---|
| 1271 |
|
|---|
| 1272 | for atom1, atom2 in bondIndices:
|
|---|
| 1273 | for child1 in data.excludeAtomWith[atom1]:
|
|---|
| 1274 | bondIndices.append((child1, atom2))
|
|---|
| 1275 | for child2 in data.excludeAtomWith[atom2]:
|
|---|
| 1276 | bondIndices.append((child1, child2))
|
|---|
| 1277 | for child2 in data.excludeAtomWith[atom2]:
|
|---|
| 1278 | bondIndices.append((atom1, child2))
|
|---|
| 1279 | for atom in data.atoms:
|
|---|
| 1280 | for child in data.excludeAtomWith[atom.index]:
|
|---|
| 1281 | bondIndices.append((child, atom.index))
|
|---|
| 1282 | return bondIndices
|
|---|
| 1283 |
|
|---|
| 1284 | def _countResidueAtoms(elements):
|
|---|
| 1285 | """Count the number of atoms of each element in a residue."""
|
|---|
| 1286 | counts = {}
|
|---|
| 1287 | for element in elements:
|
|---|
| 1288 | if element in counts:
|
|---|
| 1289 | counts[element] += 1
|
|---|
| 1290 | else:
|
|---|
| 1291 | counts[element] = 1
|
|---|
| 1292 | return counts
|
|---|
| 1293 |
|
|---|
| 1294 |
|
|---|
| 1295 | def _createResidueSignature(elements):
|
|---|
| 1296 | """Create a signature for a residue based on the elements of the atoms it contains."""
|
|---|
| 1297 | counts = _countResidueAtoms(elements)
|
|---|
| 1298 | sig = []
|
|---|
| 1299 | for c in counts:
|
|---|
| 1300 | if c is not None:
|
|---|
| 1301 | sig.append((c, counts[c]))
|
|---|
| 1302 | sig.sort(key=lambda x: -x[0].mass)
|
|---|
| 1303 |
|
|---|
| 1304 | # Convert it to a string.
|
|---|
| 1305 |
|
|---|
| 1306 | s = ''
|
|---|
| 1307 | for element, count in sig:
|
|---|
| 1308 | s += element.symbol+str(count)
|
|---|
| 1309 | return s
|
|---|
| 1310 |
|
|---|
| 1311 | def _matchResidue(res, template, bondedToAtom, ignoreExternalBonds=False):
|
|---|
| 1312 | """Determine whether a residue matches a template and return a list of corresponding atoms.
|
|---|
| 1313 |
|
|---|
| 1314 | Parameters
|
|---|
| 1315 | ----------
|
|---|
| 1316 | res : Residue
|
|---|
| 1317 | The residue to check
|
|---|
| 1318 | template : _TemplateData
|
|---|
| 1319 | The template to compare it to
|
|---|
| 1320 | bondedToAtom : list
|
|---|
| 1321 | Enumerates which other atoms each atom is bonded to
|
|---|
| 1322 | ignoreExternalBonds : bool
|
|---|
| 1323 | If true, ignore external bonds when matching templates
|
|---|
| 1324 |
|
|---|
| 1325 | Returns
|
|---|
| 1326 | -------
|
|---|
| 1327 | list
|
|---|
| 1328 | a list specifying which atom of the template each atom of the residue
|
|---|
| 1329 | corresponds to, or None if it does not match the template
|
|---|
| 1330 | """
|
|---|
| 1331 | atoms = list(res.atoms())
|
|---|
| 1332 | numAtoms = len(atoms)
|
|---|
| 1333 | if numAtoms != len(template.atoms):
|
|---|
| 1334 | return None
|
|---|
| 1335 |
|
|---|
| 1336 | # Translate from global to local atom indices, and record the bonds for each atom.
|
|---|
| 1337 |
|
|---|
| 1338 | renumberAtoms = {}
|
|---|
| 1339 | for i in range(numAtoms):
|
|---|
| 1340 | renumberAtoms[atoms[i].index] = i
|
|---|
| 1341 | bondedTo = []
|
|---|
| 1342 | externalBonds = []
|
|---|
| 1343 | for atom in atoms:
|
|---|
| 1344 | bonds = [renumberAtoms[x] for x in bondedToAtom[atom.index] if x in renumberAtoms]
|
|---|
| 1345 | bondedTo.append(bonds)
|
|---|
| 1346 | externalBonds.append(0 if ignoreExternalBonds else len([x for x in bondedToAtom[atom.index] if x not in renumberAtoms]))
|
|---|
| 1347 |
|
|---|
| 1348 | # For each unique combination of element and number of bonds, make sure the residue and
|
|---|
| 1349 | # template have the same number of atoms.
|
|---|
| 1350 |
|
|---|
| 1351 | residueTypeCount = {}
|
|---|
| 1352 | for i, atom in enumerate(atoms):
|
|---|
| 1353 | key = (atom.element, len(bondedTo[i]), externalBonds[i])
|
|---|
| 1354 | if key not in residueTypeCount:
|
|---|
| 1355 | residueTypeCount[key] = 1
|
|---|
| 1356 | residueTypeCount[key] += 1
|
|---|
| 1357 | templateTypeCount = {}
|
|---|
| 1358 | for i, atom in enumerate(template.atoms):
|
|---|
| 1359 | key = (atom.element, len(atom.bondedTo), 0 if ignoreExternalBonds else atom.externalBonds)
|
|---|
| 1360 | if key not in templateTypeCount:
|
|---|
| 1361 | templateTypeCount[key] = 1
|
|---|
| 1362 | templateTypeCount[key] += 1
|
|---|
| 1363 | if residueTypeCount != templateTypeCount:
|
|---|
| 1364 | return None
|
|---|
| 1365 |
|
|---|
| 1366 | # Identify template atoms that could potentially be matches for each atom.
|
|---|
| 1367 |
|
|---|
| 1368 | candidates = [[] for i in range(numAtoms)]
|
|---|
| 1369 | for i in range(numAtoms):
|
|---|
| 1370 | for j, atom in enumerate(template.atoms):
|
|---|
| 1371 | if (atom.element is not None and atom.element != atoms[i].element) or (atom.element is None and atom.name != atoms[i].name):
|
|---|
| 1372 | continue
|
|---|
| 1373 | if len(atom.bondedTo) != len(bondedTo[i]):
|
|---|
| 1374 | continue
|
|---|
| 1375 | if not ignoreExternalBonds and atom.externalBonds != externalBonds[i]:
|
|---|
| 1376 | continue
|
|---|
| 1377 | candidates[i].append(j)
|
|---|
| 1378 |
|
|---|
| 1379 | # Find an optimal ordering for matching atoms. This means 1) start with the one that has the fewest options,
|
|---|
| 1380 | # and 2) follow with ones that are bonded to an already matched atom.
|
|---|
| 1381 |
|
|---|
| 1382 | searchOrder = []
|
|---|
| 1383 | atomsToOrder = set(range(numAtoms))
|
|---|
| 1384 | efficientAtomSet = set()
|
|---|
| 1385 | efficientAtomHeap = []
|
|---|
| 1386 | while len(atomsToOrder) > 0:
|
|---|
| 1387 | if len(efficientAtomSet) == 0:
|
|---|
| 1388 | fewestNeighbors = numAtoms+1
|
|---|
| 1389 | for i in atomsToOrder:
|
|---|
| 1390 | if len(candidates[i]) < fewestNeighbors:
|
|---|
| 1391 | nextAtom = i
|
|---|
| 1392 | fewestNeighbors = len(candidates[i])
|
|---|
| 1393 | else:
|
|---|
| 1394 | nextAtom = heappop(efficientAtomHeap)[1]
|
|---|
| 1395 | efficientAtomSet.remove(nextAtom)
|
|---|
| 1396 | searchOrder.append(nextAtom)
|
|---|
| 1397 | atomsToOrder.remove(nextAtom)
|
|---|
| 1398 | for i in bondedTo[nextAtom]:
|
|---|
| 1399 | if i in atomsToOrder:
|
|---|
| 1400 | if i not in efficientAtomSet:
|
|---|
| 1401 | efficientAtomSet.add(i)
|
|---|
| 1402 | heappush(efficientAtomHeap, (len(candidates[i]), i))
|
|---|
| 1403 | inverseSearchOrder = [0]*numAtoms
|
|---|
| 1404 | for i in range(numAtoms):
|
|---|
| 1405 | inverseSearchOrder[searchOrder[i]] = i
|
|---|
| 1406 | bondedTo = [[inverseSearchOrder[bondedTo[i][j]] for j in range(len(bondedTo[i]))] for i in searchOrder]
|
|---|
| 1407 | candidates = [candidates[i] for i in searchOrder]
|
|---|
| 1408 |
|
|---|
| 1409 | # Recursively match atoms.
|
|---|
| 1410 |
|
|---|
| 1411 | matches = numAtoms*[0]
|
|---|
| 1412 | hasMatch = numAtoms*[False]
|
|---|
| 1413 | if _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, 0):
|
|---|
| 1414 | return [matches[inverseSearchOrder[i]] for i in range(numAtoms)]
|
|---|
| 1415 | return None
|
|---|
| 1416 |
|
|---|
| 1417 |
|
|---|
| 1418 | def _getAtomMatchCandidates(template, bondedTo, matches, candidates, position):
|
|---|
| 1419 | """Get a list of template atoms that are potential matches for the next atom."""
|
|---|
| 1420 | for bonded in bondedTo[position]:
|
|---|
| 1421 | if bonded < position:
|
|---|
| 1422 | # This atom is bonded to another one for which we already have a match, so only consider
|
|---|
| 1423 | # template atoms that *that* one is bonded to.
|
|---|
| 1424 | return template.atoms[matches[bonded]].bondedTo
|
|---|
| 1425 | return candidates[position]
|
|---|
| 1426 |
|
|---|
| 1427 |
|
|---|
| 1428 | def _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position):
|
|---|
| 1429 | """This is called recursively from inside _matchResidue() to identify matching atoms."""
|
|---|
| 1430 | if position == len(matches):
|
|---|
| 1431 | return True
|
|---|
| 1432 | for i in _getAtomMatchCandidates(template, bondedTo, matches, candidates, position):
|
|---|
| 1433 | atom = template.atoms[i]
|
|---|
| 1434 | if not hasMatch[i] and i in candidates[position]:
|
|---|
| 1435 | # See if the bonds for this identification are consistent
|
|---|
| 1436 |
|
|---|
| 1437 | allBondsMatch = all((bonded > position or matches[bonded] in atom.bondedTo for bonded in bondedTo[position]))
|
|---|
| 1438 | if allBondsMatch:
|
|---|
| 1439 | # This is a possible match, so try matching the rest of the residue.
|
|---|
| 1440 |
|
|---|
| 1441 | matches[position] = i
|
|---|
| 1442 | hasMatch[i] = True
|
|---|
| 1443 | if _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position+1):
|
|---|
| 1444 | return True
|
|---|
| 1445 | hasMatch[i] = False
|
|---|
| 1446 | return False
|
|---|
| 1447 |
|
|---|
| 1448 |
|
|---|
| 1449 | def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignoreExternalBonds):
|
|---|
| 1450 | """Try to apply patches to find matches for residues."""
|
|---|
| 1451 | # Start by creating all templates than can be created by applying a combination of one-residue patches
|
|---|
| 1452 | # to a single template. The number of these is usually not too large, and they often cover a large fraction
|
|---|
| 1453 | # of residues.
|
|---|
| 1454 |
|
|---|
| 1455 | patchedTemplateSignatures = {}
|
|---|
| 1456 | patchedTemplates = {}
|
|---|
| 1457 | for name, template in forcefield._templates.items():
|
|---|
| 1458 | if name in forcefield._templatePatches:
|
|---|
| 1459 | patches = [forcefield._patches[patchName] for patchName, patchResidueIndex in forcefield._templatePatches[name] if forcefield._patches[patchName].numResidues == 1]
|
|---|
| 1460 | if len(patches) > 0:
|
|---|
| 1461 | newTemplates = []
|
|---|
| 1462 | patchedTemplates[name] = newTemplates
|
|---|
| 1463 | _generatePatchedSingleResidueTemplates(template, patches, 0, newTemplates)
|
|---|
| 1464 | for patchedTemplate in newTemplates:
|
|---|
| 1465 | signature = _createResidueSignature([atom.element for atom in patchedTemplate.atoms])
|
|---|
| 1466 | if signature in patchedTemplateSignatures:
|
|---|
| 1467 | patchedTemplateSignatures[signature].append(patchedTemplate)
|
|---|
| 1468 | else:
|
|---|
| 1469 | patchedTemplateSignatures[signature] = [patchedTemplate]
|
|---|
| 1470 |
|
|---|
| 1471 | # Now see if any of those templates matches any of the residues.
|
|---|
| 1472 |
|
|---|
| 1473 | unmatchedResidues = []
|
|---|
| 1474 | for res in residues:
|
|---|
| 1475 | [template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures, ignoreExternalBonds)
|
|---|
| 1476 | if matches is None:
|
|---|
| 1477 | unmatchedResidues.append(res)
|
|---|
| 1478 | else:
|
|---|
| 1479 | data.recordMatchedAtomParameters(res, template, matches)
|
|---|
| 1480 | if len(unmatchedResidues) == 0:
|
|---|
| 1481 | return []
|
|---|
| 1482 |
|
|---|
| 1483 | # We need to consider multi-residue patches. This can easily lead to a combinatorial explosion, so we make a simplifying
|
|---|
| 1484 | # assumption: that no residue is affected by more than one multi-residue patch (in addition to any number of single-residue
|
|---|
| 1485 | # patches). Record all multi-residue patches, and the templates they can be applied to.
|
|---|
| 1486 |
|
|---|
| 1487 | patches = {}
|
|---|
| 1488 | maxPatchSize = 0
|
|---|
| 1489 | for patch in forcefield._patches.values():
|
|---|
| 1490 | if patch.numResidues > 1:
|
|---|
| 1491 | patches[patch.name] = [[] for i in range(patch.numResidues)]
|
|---|
| 1492 | maxPatchSize = max(maxPatchSize, patch.numResidues)
|
|---|
| 1493 | if maxPatchSize == 0:
|
|---|
| 1494 | return unmatchedResidues # There aren't any multi-residue patches
|
|---|
| 1495 | for templateName in forcefield._templatePatches:
|
|---|
| 1496 | for patchName, patchResidueIndex in forcefield._templatePatches[templateName]:
|
|---|
| 1497 | if patchName in patches:
|
|---|
| 1498 | # The patch should accept this template, *and* all patched versions of it generated above.
|
|---|
| 1499 | patches[patchName][patchResidueIndex].append(forcefield._templates[templateName])
|
|---|
| 1500 | if templateName in patchedTemplates:
|
|---|
| 1501 | patches[patchName][patchResidueIndex] += patchedTemplates[templateName]
|
|---|
| 1502 |
|
|---|
| 1503 | # Record which unmatched residues are bonded to each other.
|
|---|
| 1504 |
|
|---|
| 1505 | bonds = set()
|
|---|
| 1506 | topology = residues[0].chain.topology
|
|---|
| 1507 | for atom1, atom2 in topology.bonds():
|
|---|
| 1508 | if atom1.residue != atom2.residue:
|
|---|
| 1509 | res1 = atom1.residue
|
|---|
| 1510 | res2 = atom2.residue
|
|---|
| 1511 | if res1 in unmatchedResidues and res2 in unmatchedResidues:
|
|---|
| 1512 | bond = tuple(sorted((res1, res2), key=lambda x: x.index))
|
|---|
| 1513 | if bond not in bonds:
|
|---|
| 1514 | bonds.add(bond)
|
|---|
| 1515 |
|
|---|
| 1516 | # Identify clusters of unmatched residues that are all bonded to each other. These are the ones we'll
|
|---|
| 1517 | # try to apply multi-residue patches to.
|
|---|
| 1518 |
|
|---|
| 1519 | clusterSize = 2
|
|---|
| 1520 | clusters = bonds
|
|---|
| 1521 | while clusterSize <= maxPatchSize:
|
|---|
| 1522 | # Try to apply patches to clusters of this size.
|
|---|
| 1523 |
|
|---|
| 1524 | for patchName in patches:
|
|---|
| 1525 | patch = forcefield._patches[patchName]
|
|---|
| 1526 | if patch.numResidues == clusterSize:
|
|---|
| 1527 | matchedClusters = _matchToMultiResiduePatchedTemplates(data, clusters, patch, patches[patchName], bondedToAtom, ignoreExternalBonds)
|
|---|
| 1528 | for cluster in matchedClusters:
|
|---|
| 1529 | for residue in cluster:
|
|---|
| 1530 | unmatchedResidues.remove(residue)
|
|---|
| 1531 | bonds = set(bond for bond in bonds if bond[0] in unmatchedResidues and bond[1] in unmatchedResidues)
|
|---|
| 1532 |
|
|---|
| 1533 | # Now extend the clusters to find ones of the next size up.
|
|---|
| 1534 |
|
|---|
| 1535 | largerClusters = set()
|
|---|
| 1536 | for cluster in clusters:
|
|---|
| 1537 | for bond in bonds:
|
|---|
| 1538 | if bond[0] in cluster and bond[1] not in cluster:
|
|---|
| 1539 | newCluster = tuple(sorted(cluster+(bond[1],), key=lambda x: x.index))
|
|---|
| 1540 | largerClusters.add(newCluster)
|
|---|
| 1541 | elif bond[1] in cluster and bond[0] not in cluster:
|
|---|
| 1542 | newCluster = tuple(sorted(cluster+(bond[0],), key=lambda x: x.index))
|
|---|
| 1543 | largerClusters.add(newCluster)
|
|---|
| 1544 | if len(largerClusters) == 0:
|
|---|
| 1545 | # There are no clusters of this size or larger
|
|---|
| 1546 | break
|
|---|
| 1547 | clusters = largerClusters
|
|---|
| 1548 | clusterSize += 1
|
|---|
| 1549 |
|
|---|
| 1550 | return unmatchedResidues
|
|---|
| 1551 |
|
|---|
| 1552 |
|
|---|
| 1553 | def _generatePatchedSingleResidueTemplates(template, patches, index, newTemplates):
|
|---|
| 1554 | """Apply all possible combinations of a set of single-residue patches to a template."""
|
|---|
| 1555 | try:
|
|---|
| 1556 | patchedTemplate = patches[index].createPatchedTemplates([template])[0]
|
|---|
| 1557 | newTemplates.append(patchedTemplate)
|
|---|
| 1558 | except:
|
|---|
| 1559 | # This probably means the patch is inconsistent with another one that has already been applied,
|
|---|
| 1560 | # so just ignore it.
|
|---|
| 1561 | patchedTemplate = None
|
|---|
| 1562 |
|
|---|
| 1563 | # Call this function recursively to generate combinations of patches.
|
|---|
| 1564 |
|
|---|
| 1565 | if index+1 < len(patches):
|
|---|
| 1566 | _generatePatchedSingleResidueTemplates(template, patches, index+1, newTemplates)
|
|---|
| 1567 | if patchedTemplate is not None:
|
|---|
| 1568 | _generatePatchedSingleResidueTemplates(patchedTemplate, patches, index+1, newTemplates)
|
|---|
| 1569 |
|
|---|
| 1570 |
|
|---|
| 1571 | def _matchToMultiResiduePatchedTemplates(data, clusters, patch, residueTemplates, bondedToAtom, ignoreExternalBonds):
|
|---|
| 1572 | """Apply a multi-residue patch to templates, then try to match them against clusters of residues."""
|
|---|
| 1573 | matchedClusters = []
|
|---|
| 1574 | selectedTemplates = [None]*patch.numResidues
|
|---|
| 1575 | _applyMultiResiduePatch(data, clusters, patch, residueTemplates, selectedTemplates, 0, matchedClusters, bondedToAtom, ignoreExternalBonds)
|
|---|
| 1576 | return matchedClusters
|
|---|
| 1577 |
|
|---|
| 1578 |
|
|---|
| 1579 | def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index, matchedClusters, bondedToAtom, ignoreExternalBonds):
|
|---|
| 1580 | """This is called recursively to apply a multi-residue patch to all possible combinations of templates."""
|
|---|
| 1581 |
|
|---|
| 1582 | if index < patch.numResidues:
|
|---|
| 1583 | for template in candidateTemplates[index]:
|
|---|
| 1584 | selectedTemplates[index] = template
|
|---|
| 1585 | _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index+1, matchedClusters, bondedToAtom, ignoreExternalBonds)
|
|---|
| 1586 | else:
|
|---|
| 1587 | # We're at the deepest level of the recursion. We've selected a template for each residue, so apply the patch,
|
|---|
| 1588 | # then try to match it against clusters.
|
|---|
| 1589 |
|
|---|
| 1590 | try:
|
|---|
| 1591 | patchedTemplates = patch.createPatchedTemplates(selectedTemplates)
|
|---|
| 1592 | except:
|
|---|
| 1593 | # This probably means the patch is inconsistent with another one that has already been applied,
|
|---|
| 1594 | # so just ignore it.
|
|---|
| 1595 | raise
|
|---|
| 1596 | return
|
|---|
| 1597 | newlyMatchedClusters = []
|
|---|
| 1598 | for cluster in clusters:
|
|---|
| 1599 | for residues in itertools.permutations(cluster):
|
|---|
| 1600 | residueMatches = []
|
|---|
| 1601 | for residue, template in zip(residues, patchedTemplates):
|
|---|
| 1602 | matches = _matchResidue(residue, template, bondedToAtom, ignoreExternalBonds)
|
|---|
| 1603 | if matches is None:
|
|---|
| 1604 | residueMatches = None
|
|---|
| 1605 | break
|
|---|
| 1606 | else:
|
|---|
| 1607 | residueMatches.append(matches)
|
|---|
| 1608 | if residueMatches is not None:
|
|---|
| 1609 | # We successfully matched the template to the residues. Record the parameters.
|
|---|
| 1610 |
|
|---|
| 1611 | for i in range(patch.numResidues):
|
|---|
| 1612 | data.recordMatchedAtomParameters(residues[i], patchedTemplates[i], residueMatches[i])
|
|---|
| 1613 | newlyMatchedClusters.append(cluster)
|
|---|
| 1614 | break
|
|---|
| 1615 |
|
|---|
| 1616 | # Record which clusters were successfully matched.
|
|---|
| 1617 |
|
|---|
| 1618 | matchedClusters += newlyMatchedClusters
|
|---|
| 1619 | for cluster in newlyMatchedClusters:
|
|---|
| 1620 | clusters.remove(cluster)
|
|---|
| 1621 |
|
|---|
| 1622 |
|
|---|
| 1623 | def _findMatchErrors(forcefield, res):
|
|---|
| 1624 | """Try to guess why a residue failed to match any template and return an error message."""
|
|---|
| 1625 | residueCounts = _countResidueAtoms([atom.element for atom in res.atoms()])
|
|---|
| 1626 | numResidueAtoms = sum(residueCounts.values())
|
|---|
| 1627 | numResidueHeavyAtoms = sum(residueCounts[element] for element in residueCounts if element not in (None, elem.hydrogen))
|
|---|
| 1628 |
|
|---|
| 1629 | # Loop over templates and see how closely each one might match.
|
|---|
| 1630 |
|
|---|
| 1631 | bestMatchName = None
|
|---|
| 1632 | numBestMatchAtoms = 3*numResidueAtoms
|
|---|
| 1633 | numBestMatchHeavyAtoms = 2*numResidueHeavyAtoms
|
|---|
| 1634 | for templateName in forcefield._templates:
|
|---|
| 1635 | template = forcefield._templates[templateName]
|
|---|
| 1636 | templateCounts = _countResidueAtoms([atom.element for atom in template.atoms])
|
|---|
| 1637 |
|
|---|
| 1638 | # Does the residue have any atoms that clearly aren't in the template?
|
|---|
| 1639 |
|
|---|
| 1640 | if any(element not in templateCounts or templateCounts[element] < residueCounts[element] for element in residueCounts):
|
|---|
| 1641 | continue
|
|---|
| 1642 |
|
|---|
| 1643 | # If there are too many missing atoms, discard this template.
|
|---|
| 1644 |
|
|---|
| 1645 | numTemplateAtoms = sum(templateCounts.values())
|
|---|
| 1646 | numTemplateHeavyAtoms = sum(templateCounts[element] for element in templateCounts if element not in (None, elem.hydrogen))
|
|---|
| 1647 | if numTemplateAtoms > numBestMatchAtoms:
|
|---|
| 1648 | continue
|
|---|
| 1649 | if numTemplateHeavyAtoms > numBestMatchHeavyAtoms:
|
|---|
| 1650 | continue
|
|---|
| 1651 |
|
|---|
| 1652 | # If this template has the same number of missing atoms as our previous best one, look at the name
|
|---|
| 1653 | # to decide which one to use.
|
|---|
| 1654 |
|
|---|
| 1655 | if numTemplateAtoms == numBestMatchAtoms:
|
|---|
| 1656 | if bestMatchName == res.name or res.name not in templateName:
|
|---|
| 1657 | continue
|
|---|
| 1658 |
|
|---|
| 1659 | # Accept this as our new best match.
|
|---|
| 1660 |
|
|---|
| 1661 | bestMatchName = templateName
|
|---|
| 1662 | numBestMatchAtoms = numTemplateAtoms
|
|---|
| 1663 | numBestMatchHeavyAtoms = numTemplateHeavyAtoms
|
|---|
| 1664 | numBestMatchExtraParticles = len([atom for atom in template.atoms if atom.element is None])
|
|---|
| 1665 |
|
|---|
| 1666 | # Return an appropriate error message.
|
|---|
| 1667 |
|
|---|
| 1668 | if numBestMatchAtoms == numResidueAtoms:
|
|---|
| 1669 | chainResidues = list(res.chain.residues())
|
|---|
| 1670 | if len(chainResidues) > 1 and (res == chainResidues[0] or res == chainResidues[-1]):
|
|---|
| 1671 | return 'The set of atoms matches %s, but the bonds are different. Perhaps the chain is missing a terminal group?' % bestMatchName
|
|---|
| 1672 | return 'The set of atoms matches %s, but the bonds are different.' % bestMatchName
|
|---|
| 1673 | if bestMatchName is not None:
|
|---|
| 1674 | if numBestMatchHeavyAtoms == numResidueHeavyAtoms:
|
|---|
| 1675 | numResidueExtraParticles = len([atom for atom in res.atoms() if atom.element is None])
|
|---|
| 1676 | if numResidueExtraParticles == 0 and numBestMatchExtraParticles == 0:
|
|---|
| 1677 | return 'The set of atoms is similar to %s, but it is missing %d hydrogen atoms.' % (bestMatchName, numBestMatchAtoms-numResidueAtoms)
|
|---|
| 1678 | if numBestMatchExtraParticles-numResidueExtraParticles == numBestMatchAtoms-numResidueAtoms:
|
|---|
| 1679 | return 'The set of atoms is similar to %s, but it is missing %d extra particles. You can add them with Modeller.addExtraParticles().' % (bestMatchName, numBestMatchAtoms-numResidueAtoms)
|
|---|
| 1680 | return 'The set of atoms is similar to %s, but it is missing %d atoms.' % (bestMatchName, numBestMatchAtoms-numResidueAtoms)
|
|---|
| 1681 | return 'This might mean your input topology is missing some atoms or bonds, or possibly that you are using the wrong force field.'
|
|---|
| 1682 |
|
|---|
| 1683 | def _createResidueTemplate(residue):
|
|---|
| 1684 | """Create a _TemplateData template from a Residue object.
|
|---|
| 1685 |
|
|---|
| 1686 | Parameters
|
|---|
| 1687 | ----------
|
|---|
| 1688 | residue : Residue
|
|---|
| 1689 | The Residue from which the template is to be constructed.
|
|---|
| 1690 |
|
|---|
| 1691 | Returns
|
|---|
| 1692 | -------
|
|---|
| 1693 | template : _TemplateData
|
|---|
| 1694 | The residue template, with atom types set to None.
|
|---|
| 1695 |
|
|---|
| 1696 | This method may be useful in creating new residue templates for residues without templates defined by the ForceField.
|
|---|
| 1697 |
|
|---|
| 1698 | """
|
|---|
| 1699 | template = ForceField._TemplateData(residue.name)
|
|---|
| 1700 | for atom in residue.atoms():
|
|---|
| 1701 | template.atoms.append(ForceField._TemplateAtomData(atom.name, None, atom.element))
|
|---|
| 1702 | for (atom1,atom2) in residue.internal_bonds():
|
|---|
| 1703 | template.addBondByName(atom1.name, atom2.name)
|
|---|
| 1704 | residue_atoms = [ atom for atom in residue.atoms() ]
|
|---|
| 1705 | for (atom1,atom2) in residue.external_bonds():
|
|---|
| 1706 | if atom1 in residue_atoms:
|
|---|
| 1707 | template.addExternalBondByName(atom1.name)
|
|---|
| 1708 | elif atom2 in residue_atoms:
|
|---|
| 1709 | template.addExternalBondByName(atom2.name)
|
|---|
| 1710 | return template
|
|---|
| 1711 |
|
|---|
| 1712 | # The following classes are generators that know how to create Force subclasses and add them to a System that is being
|
|---|
| 1713 | # created. Each generator class must define two methods: 1) a static method that takes an etree Element and a ForceField,
|
|---|
| 1714 | # and returns the corresponding generator object; 2) a createForce() method that constructs the Force object and adds it
|
|---|
| 1715 | # to the System. The static method should be added to the parsers map.
|
|---|
| 1716 |
|
|---|
| 1717 | ## @private
|
|---|
| 1718 | class HarmonicBondGenerator(object):
|
|---|
| 1719 | """A HarmonicBondGenerator constructs a HarmonicBondForce."""
|
|---|
| 1720 |
|
|---|
| 1721 | def __init__(self, forcefield):
|
|---|
| 1722 | self.ff = forcefield
|
|---|
| 1723 | self.bondsForAtomType = defaultdict(set)
|
|---|
| 1724 | self.types1 = []
|
|---|
| 1725 | self.types2 = []
|
|---|
| 1726 | self.length = []
|
|---|
| 1727 | self.k = []
|
|---|
| 1728 |
|
|---|
| 1729 | def registerBond(self, parameters):
|
|---|
| 1730 | types = self.ff._findAtomTypes(parameters, 2)
|
|---|
| 1731 | if None not in types:
|
|---|
| 1732 | index = len(self.types1)
|
|---|
| 1733 | self.types1.append(types[0])
|
|---|
| 1734 | self.types2.append(types[1])
|
|---|
| 1735 | for t in types[0]:
|
|---|
| 1736 | self.bondsForAtomType[t].add(index)
|
|---|
| 1737 | for t in types[1]:
|
|---|
| 1738 | self.bondsForAtomType[t].add(index)
|
|---|
| 1739 | self.length.append(_convertParameterToNumber(parameters['length']))
|
|---|
| 1740 | self.k.append(_convertParameterToNumber(parameters['k']))
|
|---|
| 1741 |
|
|---|
| 1742 | @staticmethod
|
|---|
| 1743 | def parseElement(element, ff):
|
|---|
| 1744 | existing = [f for f in ff._forces if isinstance(f, HarmonicBondGenerator)]
|
|---|
| 1745 | if len(existing) == 0:
|
|---|
| 1746 | generator = HarmonicBondGenerator(ff)
|
|---|
| 1747 | ff.registerGenerator(generator)
|
|---|
| 1748 | else:
|
|---|
| 1749 | generator = existing[0]
|
|---|
| 1750 | for bond in element.findall('Bond'):
|
|---|
| 1751 | generator.registerBond(bond.attrib)
|
|---|
| 1752 |
|
|---|
| 1753 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 1754 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 1755 | existing = [f for f in existing if type(f) == mm.HarmonicBondForce]
|
|---|
| 1756 | if len(existing) == 0:
|
|---|
| 1757 | force = mm.HarmonicBondForce()
|
|---|
| 1758 | sys.addForce(force)
|
|---|
| 1759 | else:
|
|---|
| 1760 | force = existing[0]
|
|---|
| 1761 | for bond in data.bonds:
|
|---|
| 1762 | type1 = data.atomType[data.atoms[bond.atom1]]
|
|---|
| 1763 | type2 = data.atomType[data.atoms[bond.atom2]]
|
|---|
| 1764 | for i in self.bondsForAtomType[type1]:
|
|---|
| 1765 | types1 = self.types1[i]
|
|---|
| 1766 | types2 = self.types2[i]
|
|---|
| 1767 | if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
|
|---|
| 1768 | bond.length = self.length[i]
|
|---|
| 1769 | if bond.isConstrained:
|
|---|
| 1770 | data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
|
|---|
| 1771 | elif self.k[i] != 0:
|
|---|
| 1772 | force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
|
|---|
| 1773 | break
|
|---|
| 1774 |
|
|---|
| 1775 | parsers["HarmonicBondForce"] = HarmonicBondGenerator.parseElement
|
|---|
| 1776 |
|
|---|
| 1777 |
|
|---|
| 1778 | ## @private
|
|---|
| 1779 | class HarmonicAngleGenerator(object):
|
|---|
| 1780 | """A HarmonicAngleGenerator constructs a HarmonicAngleForce."""
|
|---|
| 1781 |
|
|---|
| 1782 | def __init__(self, forcefield):
|
|---|
| 1783 | self.ff = forcefield
|
|---|
| 1784 | self.anglesForAtom2Type = defaultdict(list)
|
|---|
| 1785 | self.types1 = []
|
|---|
| 1786 | self.types2 = []
|
|---|
| 1787 | self.types3 = []
|
|---|
| 1788 | self.angle = []
|
|---|
| 1789 | self.k = []
|
|---|
| 1790 |
|
|---|
| 1791 | def registerAngle(self, parameters):
|
|---|
| 1792 | types = self.ff._findAtomTypes(parameters, 3)
|
|---|
| 1793 | if None not in types:
|
|---|
| 1794 | index = len(self.types1)
|
|---|
| 1795 | self.types1.append(types[0])
|
|---|
| 1796 | self.types2.append(types[1])
|
|---|
| 1797 | self.types3.append(types[2])
|
|---|
| 1798 | for t in types[1]:
|
|---|
| 1799 | self.anglesForAtom2Type[t].append(index)
|
|---|
| 1800 | self.angle.append(_convertParameterToNumber(parameters['angle']))
|
|---|
| 1801 | self.k.append(_convertParameterToNumber(parameters['k']))
|
|---|
| 1802 |
|
|---|
| 1803 | @staticmethod
|
|---|
| 1804 | def parseElement(element, ff):
|
|---|
| 1805 | existing = [f for f in ff._forces if isinstance(f, HarmonicAngleGenerator)]
|
|---|
| 1806 | if len(existing) == 0:
|
|---|
| 1807 | generator = HarmonicAngleGenerator(ff)
|
|---|
| 1808 | ff.registerGenerator(generator)
|
|---|
| 1809 | else:
|
|---|
| 1810 | generator = existing[0]
|
|---|
| 1811 | for angle in element.findall('Angle'):
|
|---|
| 1812 | generator.registerAngle(angle.attrib)
|
|---|
| 1813 |
|
|---|
| 1814 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 1815 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 1816 | existing = [f for f in existing if type(f) == mm.HarmonicAngleForce]
|
|---|
| 1817 | if len(existing) == 0:
|
|---|
| 1818 | force = mm.HarmonicAngleForce()
|
|---|
| 1819 | sys.addForce(force)
|
|---|
| 1820 | else:
|
|---|
| 1821 | force = existing[0]
|
|---|
| 1822 | for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
|
|---|
| 1823 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 1824 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 1825 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 1826 | for i in self.anglesForAtom2Type[type2]:
|
|---|
| 1827 | types1 = self.types1[i]
|
|---|
| 1828 | types2 = self.types2[i]
|
|---|
| 1829 | types3 = self.types3[i]
|
|---|
| 1830 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 1831 | if isConstrained:
|
|---|
| 1832 | # Find the two bonds that make this angle.
|
|---|
| 1833 |
|
|---|
| 1834 | bond1 = None
|
|---|
| 1835 | bond2 = None
|
|---|
| 1836 | for bond in data.atomBonds[angle[1]]:
|
|---|
| 1837 | atom1 = data.bonds[bond].atom1
|
|---|
| 1838 | atom2 = data.bonds[bond].atom2
|
|---|
| 1839 | if atom1 == angle[0] or atom2 == angle[0]:
|
|---|
| 1840 | bond1 = bond
|
|---|
| 1841 | elif atom1 == angle[2] or atom2 == angle[2]:
|
|---|
| 1842 | bond2 = bond
|
|---|
| 1843 |
|
|---|
| 1844 | # Compute the distance between atoms and add a constraint
|
|---|
| 1845 |
|
|---|
| 1846 | if bond1 is not None and bond2 is not None:
|
|---|
| 1847 | l1 = data.bonds[bond1].length
|
|---|
| 1848 | l2 = data.bonds[bond2].length
|
|---|
| 1849 | if l1 is not None and l2 is not None:
|
|---|
| 1850 | length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i]))
|
|---|
| 1851 | data.addConstraint(sys, angle[0], angle[2], length)
|
|---|
| 1852 | elif self.k[i] != 0:
|
|---|
| 1853 | force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i])
|
|---|
| 1854 | break
|
|---|
| 1855 |
|
|---|
| 1856 | parsers["HarmonicAngleForce"] = HarmonicAngleGenerator.parseElement
|
|---|
| 1857 |
|
|---|
| 1858 |
|
|---|
| 1859 | ## @private
|
|---|
| 1860 | class PeriodicTorsion(object):
|
|---|
| 1861 | """A PeriodicTorsion records the information for a periodic torsion definition."""
|
|---|
| 1862 |
|
|---|
| 1863 | def __init__(self, types):
|
|---|
| 1864 | self.types1 = types[0]
|
|---|
| 1865 | self.types2 = types[1]
|
|---|
| 1866 | self.types3 = types[2]
|
|---|
| 1867 | self.types4 = types[3]
|
|---|
| 1868 | self.periodicity = []
|
|---|
| 1869 | self.phase = []
|
|---|
| 1870 | self.k = []
|
|---|
| 1871 |
|
|---|
| 1872 | ## @private
|
|---|
| 1873 | class PeriodicTorsionGenerator(object):
|
|---|
| 1874 | """A PeriodicTorsionGenerator constructs a PeriodicTorsionForce."""
|
|---|
| 1875 |
|
|---|
| 1876 | def __init__(self, forcefield):
|
|---|
| 1877 | self.ff = forcefield
|
|---|
| 1878 | self.proper = []
|
|---|
| 1879 | self.improper = []
|
|---|
| 1880 |
|
|---|
| 1881 | def registerProperTorsion(self, parameters):
|
|---|
| 1882 | torsion = self.ff._parseTorsion(parameters)
|
|---|
| 1883 | if torsion is not None:
|
|---|
| 1884 | self.proper.append(torsion)
|
|---|
| 1885 |
|
|---|
| 1886 | def registerImproperTorsion(self, parameters):
|
|---|
| 1887 | torsion = self.ff._parseTorsion(parameters)
|
|---|
| 1888 | if torsion is not None:
|
|---|
| 1889 | self.improper.append(torsion)
|
|---|
| 1890 |
|
|---|
| 1891 | @staticmethod
|
|---|
| 1892 | def parseElement(element, ff):
|
|---|
| 1893 | existing = [f for f in ff._forces if isinstance(f, PeriodicTorsionGenerator)]
|
|---|
| 1894 | if len(existing) == 0:
|
|---|
| 1895 | generator = PeriodicTorsionGenerator(ff)
|
|---|
| 1896 | ff.registerGenerator(generator)
|
|---|
| 1897 | else:
|
|---|
| 1898 | generator = existing[0]
|
|---|
| 1899 | for torsion in element.findall('Proper'):
|
|---|
| 1900 | generator.registerProperTorsion(torsion.attrib)
|
|---|
| 1901 | for torsion in element.findall('Improper'):
|
|---|
| 1902 | generator.registerImproperTorsion(torsion.attrib)
|
|---|
| 1903 |
|
|---|
| 1904 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 1905 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 1906 | existing = [f for f in existing if type(f) == mm.PeriodicTorsionForce]
|
|---|
| 1907 | if len(existing) == 0:
|
|---|
| 1908 | force = mm.PeriodicTorsionForce()
|
|---|
| 1909 | sys.addForce(force)
|
|---|
| 1910 | else:
|
|---|
| 1911 | force = existing[0]
|
|---|
| 1912 | wildcard = self.ff._atomClasses['']
|
|---|
| 1913 | for torsion in data.propers:
|
|---|
| 1914 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 1915 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 1916 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 1917 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 1918 | match = None
|
|---|
| 1919 | for tordef in self.proper:
|
|---|
| 1920 | types1 = tordef.types1
|
|---|
| 1921 | types2 = tordef.types2
|
|---|
| 1922 | types3 = tordef.types3
|
|---|
| 1923 | types4 = tordef.types4
|
|---|
| 1924 | if (type2 in types2 and type3 in types3 and type4 in types4 and type1 in types1) or (type2 in types3 and type3 in types2 and type4 in types1 and type1 in types4):
|
|---|
| 1925 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 1926 | if match is None or not hasWildcard: # Prefer specific definitions over ones with wildcards
|
|---|
| 1927 | match = tordef
|
|---|
| 1928 | if not hasWildcard:
|
|---|
| 1929 | break
|
|---|
| 1930 | if match is not None:
|
|---|
| 1931 | for i in range(len(match.phase)):
|
|---|
| 1932 | if match.k[i] != 0:
|
|---|
| 1933 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.periodicity[i], match.phase[i], match.k[i])
|
|---|
| 1934 | for torsion in data.impropers:
|
|---|
| 1935 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 1936 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 1937 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 1938 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 1939 | match = None
|
|---|
| 1940 | for tordef in self.improper:
|
|---|
| 1941 | types1 = tordef.types1
|
|---|
| 1942 | types2 = tordef.types2
|
|---|
| 1943 | types3 = tordef.types3
|
|---|
| 1944 | types4 = tordef.types4
|
|---|
| 1945 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 1946 | if match is not None and hasWildcard:
|
|---|
| 1947 | # Prefer specific definitions over ones with wildcards
|
|---|
| 1948 | continue
|
|---|
| 1949 | if type1 in types1:
|
|---|
| 1950 | for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
|
|---|
| 1951 | if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
|
|---|
| 1952 | # Workaround to be more consistent with AMBER. It uses wildcards to define most of its
|
|---|
| 1953 | # impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
|
|---|
| 1954 | # to pick the order.
|
|---|
| 1955 | a1 = torsion[t2[1]]
|
|---|
| 1956 | a2 = torsion[t3[1]]
|
|---|
| 1957 | e1 = data.atoms[a1].element
|
|---|
| 1958 | e2 = data.atoms[a2].element
|
|---|
| 1959 | if e1 == e2 and a1 > a2:
|
|---|
| 1960 | (a1, a2) = (a2, a1)
|
|---|
| 1961 | elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
|
|---|
| 1962 | (a1, a2) = (a2, a1)
|
|---|
| 1963 | match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
|
|---|
| 1964 | break
|
|---|
| 1965 | if match is not None:
|
|---|
| 1966 | (a1, a2, a3, a4, tordef) = match
|
|---|
| 1967 | for i in range(len(tordef.phase)):
|
|---|
| 1968 | if tordef.k[i] != 0:
|
|---|
| 1969 | force.addTorsion(a1, a2, a3, a4, tordef.periodicity[i], tordef.phase[i], tordef.k[i])
|
|---|
| 1970 |
|
|---|
| 1971 | parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement
|
|---|
| 1972 |
|
|---|
| 1973 |
|
|---|
| 1974 | ## @private
|
|---|
| 1975 | class RBTorsion(object):
|
|---|
| 1976 | """An RBTorsion records the information for a Ryckaert-Bellemans torsion definition."""
|
|---|
| 1977 |
|
|---|
| 1978 | def __init__(self, types, c):
|
|---|
| 1979 | self.types1 = types[0]
|
|---|
| 1980 | self.types2 = types[1]
|
|---|
| 1981 | self.types3 = types[2]
|
|---|
| 1982 | self.types4 = types[3]
|
|---|
| 1983 | self.c = c
|
|---|
| 1984 |
|
|---|
| 1985 | ## @private
|
|---|
| 1986 | class RBTorsionGenerator(object):
|
|---|
| 1987 | """An RBTorsionGenerator constructs an RBTorsionForce."""
|
|---|
| 1988 |
|
|---|
| 1989 | def __init__(self, forcefield):
|
|---|
| 1990 | self.ff = forcefield
|
|---|
| 1991 | self.proper = []
|
|---|
| 1992 | self.improper = []
|
|---|
| 1993 |
|
|---|
| 1994 | @staticmethod
|
|---|
| 1995 | def parseElement(element, ff):
|
|---|
| 1996 | existing = [f for f in ff._forces if isinstance(f, RBTorsionGenerator)]
|
|---|
| 1997 | if len(existing) == 0:
|
|---|
| 1998 | generator = RBTorsionGenerator(ff)
|
|---|
| 1999 | ff.registerGenerator(generator)
|
|---|
| 2000 | else:
|
|---|
| 2001 | generator = existing[0]
|
|---|
| 2002 | for torsion in element.findall('Proper'):
|
|---|
| 2003 | types = ff._findAtomTypes(torsion.attrib, 4)
|
|---|
| 2004 | if None not in types:
|
|---|
| 2005 | generator.proper.append(RBTorsion(types, [float(torsion.attrib['c'+str(i)]) for i in range(6)]))
|
|---|
| 2006 | for torsion in element.findall('Improper'):
|
|---|
| 2007 | types = ff._findAtomTypes(torsion.attrib, 4)
|
|---|
| 2008 | if None not in types:
|
|---|
| 2009 | generator.improper.append(RBTorsion(types, [float(torsion.attrib['c'+str(i)]) for i in range(6)]))
|
|---|
| 2010 |
|
|---|
| 2011 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2012 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 2013 | existing = [f for f in existing if type(f) == mm.RBTorsionForce]
|
|---|
| 2014 | if len(existing) == 0:
|
|---|
| 2015 | force = mm.RBTorsionForce()
|
|---|
| 2016 | sys.addForce(force)
|
|---|
| 2017 | else:
|
|---|
| 2018 | force = existing[0]
|
|---|
| 2019 | wildcard = self.ff._atomClasses['']
|
|---|
| 2020 | for torsion in data.propers:
|
|---|
| 2021 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 2022 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 2023 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 2024 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 2025 | match = None
|
|---|
| 2026 | for tordef in self.proper:
|
|---|
| 2027 | types1 = tordef.types1
|
|---|
| 2028 | types2 = tordef.types2
|
|---|
| 2029 | types3 = tordef.types3
|
|---|
| 2030 | types4 = tordef.types4
|
|---|
| 2031 | if (type2 in types2 and type3 in types3 and type4 in types4 and type1 in types1) or (type2 in types3 and type3 in types2 and type4 in types1 and type1 in types4):
|
|---|
| 2032 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 2033 | if match is None or not hasWildcard: # Prefer specific definitions over ones with wildcards
|
|---|
| 2034 | match = tordef
|
|---|
| 2035 | if not hasWildcard:
|
|---|
| 2036 | break
|
|---|
| 2037 | if match is not None:
|
|---|
| 2038 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.c[0], match.c[1], match.c[2], match.c[3], match.c[4], match.c[5])
|
|---|
| 2039 | for torsion in data.impropers:
|
|---|
| 2040 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 2041 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 2042 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 2043 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 2044 | match = None
|
|---|
| 2045 | for tordef in self.improper:
|
|---|
| 2046 | types1 = tordef.types1
|
|---|
| 2047 | types2 = tordef.types2
|
|---|
| 2048 | types3 = tordef.types3
|
|---|
| 2049 | types4 = tordef.types4
|
|---|
| 2050 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 2051 | if match is not None and hasWildcard:
|
|---|
| 2052 | # Prefer specific definitions over ones with wildcards
|
|---|
| 2053 | continue
|
|---|
| 2054 | if type1 in types1:
|
|---|
| 2055 | for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
|
|---|
| 2056 | if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
|
|---|
| 2057 | if hasWildcard:
|
|---|
| 2058 | # Workaround to be more consistent with AMBER. It uses wildcards to define most of its
|
|---|
| 2059 | # impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
|
|---|
| 2060 | # to pick the order.
|
|---|
| 2061 | a1 = torsion[t2[1]]
|
|---|
| 2062 | a2 = torsion[t3[1]]
|
|---|
| 2063 | e1 = data.atoms[a1].element
|
|---|
| 2064 | e2 = data.atoms[a2].element
|
|---|
| 2065 | if e1 == e2 and a1 > a2:
|
|---|
| 2066 | (a1, a2) = (a2, a1)
|
|---|
| 2067 | elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
|
|---|
| 2068 | (a1, a2) = (a2, a1)
|
|---|
| 2069 | match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
|
|---|
| 2070 | else:
|
|---|
| 2071 | # There are no wildcards, so the order is unambiguous.
|
|---|
| 2072 | match = (torsion[0], torsion[t2[1]], torsion[t3[1]], torsion[t4[1]], tordef)
|
|---|
| 2073 | break
|
|---|
| 2074 | if match is not None:
|
|---|
| 2075 | (a1, a2, a3, a4, tordef) = match
|
|---|
| 2076 | force.addTorsion(a1, a2, a3, a4, tordef.c[0], tordef.c[1], tordef.c[2], tordef.c[3], tordef.c[4], tordef.c[5])
|
|---|
| 2077 |
|
|---|
| 2078 | parsers["RBTorsionForce"] = RBTorsionGenerator.parseElement
|
|---|
| 2079 |
|
|---|
| 2080 |
|
|---|
| 2081 | ## @private
|
|---|
| 2082 | class CMAPTorsion(object):
|
|---|
| 2083 | """A CMAPTorsion records the information for a CMAP torsion definition."""
|
|---|
| 2084 |
|
|---|
| 2085 | def __init__(self, types, map):
|
|---|
| 2086 | self.types1 = types[0]
|
|---|
| 2087 | self.types2 = types[1]
|
|---|
| 2088 | self.types3 = types[2]
|
|---|
| 2089 | self.types4 = types[3]
|
|---|
| 2090 | self.types5 = types[4]
|
|---|
| 2091 | self.map = map
|
|---|
| 2092 |
|
|---|
| 2093 | ## @private
|
|---|
| 2094 | class CMAPTorsionGenerator(object):
|
|---|
| 2095 | """A CMAPTorsionGenerator constructs a CMAPTorsionForce."""
|
|---|
| 2096 |
|
|---|
| 2097 | def __init__(self, forcefield):
|
|---|
| 2098 | self.ff = forcefield
|
|---|
| 2099 | self.torsions = []
|
|---|
| 2100 | self.maps = []
|
|---|
| 2101 |
|
|---|
| 2102 | @staticmethod
|
|---|
| 2103 | def parseElement(element, ff):
|
|---|
| 2104 | existing = [f for f in ff._forces if isinstance(f, CMAPTorsionGenerator)]
|
|---|
| 2105 | if len(existing) == 0:
|
|---|
| 2106 | generator = CMAPTorsionGenerator(ff)
|
|---|
| 2107 | ff.registerGenerator(generator)
|
|---|
| 2108 | else:
|
|---|
| 2109 | generator = existing[0]
|
|---|
| 2110 | for map in element.findall('Map'):
|
|---|
| 2111 | values = [float(x) for x in map.text.split()]
|
|---|
| 2112 | size = sqrt(len(values))
|
|---|
| 2113 | if size*size != len(values):
|
|---|
| 2114 | raise ValueError('CMAP must have the same number of elements along each dimension')
|
|---|
| 2115 | generator.maps.append(values)
|
|---|
| 2116 | for torsion in element.findall('Torsion'):
|
|---|
| 2117 | types = ff._findAtomTypes(torsion.attrib, 5)
|
|---|
| 2118 | if None not in types:
|
|---|
| 2119 | generator.torsions.append(CMAPTorsion(types, int(torsion.attrib['map'])))
|
|---|
| 2120 |
|
|---|
| 2121 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2122 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 2123 | existing = [f for f in existing if type(f) == mm.CMAPTorsionForce]
|
|---|
| 2124 | if len(existing) == 0:
|
|---|
| 2125 | force = mm.CMAPTorsionForce()
|
|---|
| 2126 | sys.addForce(force)
|
|---|
| 2127 | else:
|
|---|
| 2128 | force = existing[0]
|
|---|
| 2129 | for map in self.maps:
|
|---|
| 2130 | force.addMap(int(sqrt(len(map))), map)
|
|---|
| 2131 |
|
|---|
| 2132 | # Find all chains of length 5
|
|---|
| 2133 |
|
|---|
| 2134 | uniqueTorsions = set()
|
|---|
| 2135 | for torsion in data.propers:
|
|---|
| 2136 | for bond in (data.bonds[x] for x in data.atomBonds[torsion[0]]):
|
|---|
| 2137 | if bond.atom1 == torsion[0]:
|
|---|
| 2138 | atom = bond.atom2
|
|---|
| 2139 | else:
|
|---|
| 2140 | atom = bond.atom1
|
|---|
| 2141 | if atom != torsion[1]:
|
|---|
| 2142 | uniqueTorsions.add((atom, torsion[0], torsion[1], torsion[2], torsion[3]))
|
|---|
| 2143 | for bond in (data.bonds[x] for x in data.atomBonds[torsion[3]]):
|
|---|
| 2144 | if bond.atom1 == torsion[3]:
|
|---|
| 2145 | atom = bond.atom2
|
|---|
| 2146 | else:
|
|---|
| 2147 | atom = bond.atom1
|
|---|
| 2148 | if atom != torsion[2]:
|
|---|
| 2149 | uniqueTorsions.add((torsion[0], torsion[1], torsion[2], torsion[3], atom))
|
|---|
| 2150 | torsions = sorted(list(uniqueTorsions))
|
|---|
| 2151 | wildcard = self.ff._atomClasses['']
|
|---|
| 2152 | for torsion in torsions:
|
|---|
| 2153 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 2154 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 2155 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 2156 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 2157 | type5 = data.atomType[data.atoms[torsion[4]]]
|
|---|
| 2158 | match = None
|
|---|
| 2159 | for tordef in self.torsions:
|
|---|
| 2160 | types1 = tordef.types1
|
|---|
| 2161 | types2 = tordef.types2
|
|---|
| 2162 | types3 = tordef.types3
|
|---|
| 2163 | types4 = tordef.types4
|
|---|
| 2164 | types5 = tordef.types5
|
|---|
| 2165 | if (type1 in types1 and type2 in types2 and type3 in types3 and type4 in types4 and type5 in types5) or (type1 in types5 and type2 in types4 and type3 in types3 and type4 in types2 and type5 in types1):
|
|---|
| 2166 | hasWildcard = (wildcard in (types1, types2, types3, types4, types5))
|
|---|
| 2167 | if match is None or not hasWildcard: # Prefer specific definitions over ones with wildcards
|
|---|
| 2168 | match = tordef
|
|---|
| 2169 | if not hasWildcard:
|
|---|
| 2170 | break
|
|---|
| 2171 | if match is not None:
|
|---|
| 2172 | force.addTorsion(match.map, torsion[0], torsion[1], torsion[2], torsion[3], torsion[1], torsion[2], torsion[3], torsion[4])
|
|---|
| 2173 |
|
|---|
| 2174 | parsers["CMAPTorsionForce"] = CMAPTorsionGenerator.parseElement
|
|---|
| 2175 |
|
|---|
| 2176 |
|
|---|
| 2177 | ## @private
|
|---|
| 2178 | class NonbondedGenerator(object):
|
|---|
| 2179 | """A NonbondedGenerator constructs a NonbondedForce."""
|
|---|
| 2180 |
|
|---|
| 2181 | SCALETOL = 1e-5
|
|---|
| 2182 |
|
|---|
| 2183 | def __init__(self, forcefield, coulomb14scale, lj14scale):
|
|---|
| 2184 | self.ff = forcefield
|
|---|
| 2185 | self.coulomb14scale = coulomb14scale
|
|---|
| 2186 | self.lj14scale = lj14scale
|
|---|
| 2187 | self.params = ForceField._AtomTypeParameters(forcefield, 'NonbondedForce', 'Atom', ('charge', 'sigma', 'epsilon'))
|
|---|
| 2188 |
|
|---|
| 2189 | def registerAtom(self, parameters):
|
|---|
| 2190 | self.params.registerAtom(parameters)
|
|---|
| 2191 |
|
|---|
| 2192 | @staticmethod
|
|---|
| 2193 | def parseElement(element, ff):
|
|---|
| 2194 | existing = [f for f in ff._forces if isinstance(f, NonbondedGenerator)]
|
|---|
| 2195 | if len(existing) == 0:
|
|---|
| 2196 | generator = NonbondedGenerator(ff, float(element.attrib['coulomb14scale']), float(element.attrib['lj14scale']))
|
|---|
| 2197 | ff.registerGenerator(generator)
|
|---|
| 2198 | else:
|
|---|
| 2199 | # Multiple <NonbondedForce> tags were found, probably in different files. Simply add more types to the existing one.
|
|---|
| 2200 | generator = existing[0]
|
|---|
| 2201 | if abs(generator.coulomb14scale - float(element.attrib['coulomb14scale'])) > NonbondedGenerator.SCALETOL or \
|
|---|
| 2202 | abs(generator.lj14scale - float(element.attrib['lj14scale'])) > NonbondedGenerator.SCALETOL:
|
|---|
| 2203 | raise ValueError('Found multiple NonbondedForce tags with different 1-4 scales')
|
|---|
| 2204 | generator.params.parseDefinitions(element)
|
|---|
| 2205 |
|
|---|
| 2206 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2207 | methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff,
|
|---|
| 2208 | CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
|
|---|
| 2209 | CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
|
|---|
| 2210 | Ewald:mm.NonbondedForce.Ewald,
|
|---|
| 2211 | PME:mm.NonbondedForce.PME}
|
|---|
| 2212 | if nonbondedMethod not in methodMap:
|
|---|
| 2213 | raise ValueError('Illegal nonbonded method for NonbondedForce')
|
|---|
| 2214 | force = mm.NonbondedForce()
|
|---|
| 2215 | for atom in data.atoms:
|
|---|
| 2216 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 2217 | force.addParticle(values[0], values[1], values[2])
|
|---|
| 2218 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2219 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2220 | if 'ewaldErrorTolerance' in args:
|
|---|
| 2221 | force.setEwaldErrorTolerance(args['ewaldErrorTolerance'])
|
|---|
| 2222 | if 'useDispersionCorrection' in args:
|
|---|
| 2223 | force.setUseDispersionCorrection(bool(args['useDispersionCorrection']))
|
|---|
| 2224 | sys.addForce(force)
|
|---|
| 2225 |
|
|---|
| 2226 | def postprocessSystem(self, sys, data, args):
|
|---|
| 2227 | # Create the exceptions.
|
|---|
| 2228 |
|
|---|
| 2229 | bondIndices = _findBondsForExclusions(data, sys)
|
|---|
| 2230 | nonbonded = [f for f in sys.getForces() if isinstance(f, mm.NonbondedForce)][0]
|
|---|
| 2231 | nonbonded.createExceptionsFromBonds(bondIndices, self.coulomb14scale, self.lj14scale)
|
|---|
| 2232 |
|
|---|
| 2233 | parsers["NonbondedForce"] = NonbondedGenerator.parseElement
|
|---|
| 2234 |
|
|---|
| 2235 |
|
|---|
| 2236 | ## @private
|
|---|
| 2237 | class LennardJonesGenerator(object):
|
|---|
| 2238 | """A NBFix generator to construct the L-J force with NBFIX implemented as a lookup table"""
|
|---|
| 2239 |
|
|---|
| 2240 | def __init__(self, forcefield, lj14scale):
|
|---|
| 2241 | self.ff = forcefield
|
|---|
| 2242 | self.nbfixTypes = {}
|
|---|
| 2243 | self.lj14scale = lj14scale
|
|---|
| 2244 | self.ljTypes = ForceField._AtomTypeParameters(forcefield, 'LennardJonesForce', 'Atom', ('sigma', 'epsilon'))
|
|---|
| 2245 |
|
|---|
| 2246 | def registerNBFIX(self, parameters):
|
|---|
| 2247 | types = self.ff._findAtomTypes(parameters, 2)
|
|---|
| 2248 | if None not in types:
|
|---|
| 2249 | type1 = types[0][0]
|
|---|
| 2250 | type2 = types[1][0]
|
|---|
| 2251 | epsilon = _convertParameterToNumber(parameters['epsilon'])
|
|---|
| 2252 | sigma = _convertParameterToNumber(parameters['sigma'])
|
|---|
| 2253 | self.nbfixTypes[(type1, type2)] = [sigma, epsilon]
|
|---|
| 2254 | self.nbfixTypes[(type2, type1)] = [sigma, epsilon]
|
|---|
| 2255 |
|
|---|
| 2256 | def registerLennardJones(self, parameters):
|
|---|
| 2257 | self.ljTypes.registerAtom(parameters)
|
|---|
| 2258 |
|
|---|
| 2259 | @staticmethod
|
|---|
| 2260 | def parseElement(element, ff):
|
|---|
| 2261 | existing = [f for f in ff._forces if isinstance(f, LennardJonesGenerator)]
|
|---|
| 2262 | if len(existing) == 0:
|
|---|
| 2263 | generator = LennardJonesGenerator(ff, float(element.attrib['lj14scale']))
|
|---|
| 2264 | ff.registerGenerator(generator)
|
|---|
| 2265 | else:
|
|---|
| 2266 | # Multiple <LennardJonesForce> tags were found, probably in different files
|
|---|
| 2267 | generator = existing[0]
|
|---|
| 2268 | if abs(generator.lj14scale - float(element.attrib['lj14scale'])) > NonbondedGenerator.SCALETOL:
|
|---|
| 2269 | raise ValueError('Found multiple LennardJonesForce tags with different 1-4 scales')
|
|---|
| 2270 | for LJ in element.findall('Atom'):
|
|---|
| 2271 | generator.registerLennardJones(LJ.attrib)
|
|---|
| 2272 | for Nbfix in element.findall('NBFixPair'):
|
|---|
| 2273 | generator.registerNBFIX(Nbfix.attrib)
|
|---|
| 2274 |
|
|---|
| 2275 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2276 | # First derive the lookup tables
|
|---|
| 2277 |
|
|---|
| 2278 | nbfixTypeSet = set().union(*self.nbfixTypes)
|
|---|
| 2279 | ljIndexList = [None]*len(data.atoms)
|
|---|
| 2280 | numLjTypes = 0
|
|---|
| 2281 | ljTypeList = []
|
|---|
| 2282 | typeMap = {}
|
|---|
| 2283 | for i, atom in enumerate(data.atoms):
|
|---|
| 2284 | atype = data.atomType[atom]
|
|---|
| 2285 | values = tuple(self.ljTypes.getAtomParameters(atom, data))
|
|---|
| 2286 | if values in typeMap and atype not in nbfixTypeSet:
|
|---|
| 2287 | # Only non-NBFIX types can be compressed
|
|---|
| 2288 | ljIndexList[i] = typeMap[values]
|
|---|
| 2289 | else:
|
|---|
| 2290 | typeMap[values] = numLjTypes
|
|---|
| 2291 | ljIndexList[i] = numLjTypes
|
|---|
| 2292 | numLjTypes += 1
|
|---|
| 2293 | ljTypeList.append(atype)
|
|---|
| 2294 | reverseMap = [0]*len(typeMap)
|
|---|
| 2295 | for typeValue in typeMap:
|
|---|
| 2296 | reverseMap[typeMap[typeValue]] = typeValue
|
|---|
| 2297 |
|
|---|
| 2298 | # Now everything is assigned. Create the A- and B-coefficient arrays
|
|---|
| 2299 |
|
|---|
| 2300 | acoef = [0]*(numLjTypes*numLjTypes)
|
|---|
| 2301 | bcoef = acoef[:]
|
|---|
| 2302 | for m in range(numLjTypes):
|
|---|
| 2303 | for n in range(numLjTypes):
|
|---|
| 2304 | pair = (ljTypeList[m], ljTypeList[n])
|
|---|
| 2305 | if pair in self.nbfixTypes:
|
|---|
| 2306 | epsilon = self.nbfixTypes[pair][1]
|
|---|
| 2307 | sigma = self.nbfixTypes[pair][0]
|
|---|
| 2308 | sigma6 = sigma**6
|
|---|
| 2309 | acoef[m+numLjTypes*n] = 4*epsilon*sigma6*sigma6
|
|---|
| 2310 | bcoef[m+numLjTypes*n] = 4*epsilon*sigma6
|
|---|
| 2311 | continue
|
|---|
| 2312 | else:
|
|---|
| 2313 | sigma = 0.5*(reverseMap[m][0]+reverseMap[n][0])
|
|---|
| 2314 | sigma6 = sigma**6
|
|---|
| 2315 | epsilon = math.sqrt(reverseMap[m][-1]*reverseMap[n][-1])
|
|---|
| 2316 | acoef[m+numLjTypes*n] = 4*epsilon*sigma6*sigma6
|
|---|
| 2317 | bcoef[m+numLjTypes*n] = 4*epsilon*sigma6
|
|---|
| 2318 |
|
|---|
| 2319 | self.force = mm.CustomNonbondedForce('acoef(type1, type2)/r^12 - bcoef(type1, type2)/r^6;')
|
|---|
| 2320 | self.force.addTabulatedFunction('acoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, acoef))
|
|---|
| 2321 | self.force.addTabulatedFunction('bcoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, bcoef))
|
|---|
| 2322 | self.force.addPerParticleParameter('type')
|
|---|
| 2323 | if nonbondedMethod in [CutoffPeriodic, Ewald, PME]:
|
|---|
| 2324 | self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
|
|---|
| 2325 | elif nonbondedMethod is NoCutoff:
|
|---|
| 2326 | self.force.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff)
|
|---|
| 2327 | elif nonbondedMethod is CutoffNonPeriodic:
|
|---|
| 2328 | self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffNonPeriodic)
|
|---|
| 2329 | else:
|
|---|
| 2330 | raise AssertionError('Unrecognized nonbonded method [%s]' % nonbondedMethod)
|
|---|
| 2331 |
|
|---|
| 2332 | # Add the particles
|
|---|
| 2333 |
|
|---|
| 2334 | for i in ljIndexList:
|
|---|
| 2335 | self.force.addParticle((i,))
|
|---|
| 2336 | self.force.setUseLongRangeCorrection(True)
|
|---|
| 2337 | self.force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2338 | sys.addForce(self.force)
|
|---|
| 2339 |
|
|---|
| 2340 | def postprocessSystem(self, sys, data, args):
|
|---|
| 2341 | # Create the exceptions.
|
|---|
| 2342 |
|
|---|
| 2343 | bondIndices = _findBondsForExclusions(data, sys)
|
|---|
| 2344 | if self.lj14scale == 1:
|
|---|
| 2345 | # Just exclude the 1-2 and 1-3 interactions.
|
|---|
| 2346 |
|
|---|
| 2347 | self.force.createExclusionsFromBonds(bondIndices, 2)
|
|---|
| 2348 | else:
|
|---|
| 2349 | forceCopy = deepcopy(self.force)
|
|---|
| 2350 | forceCopy.createExclusionsFromBonds(bondIndices, 2)
|
|---|
| 2351 | self.force.createExclusionsFromBonds(bondIndices, 3)
|
|---|
| 2352 | if self.force.getNumExclusions() > forceCopy.getNumExclusions() and self.lj14scale != 0:
|
|---|
| 2353 | # We need to create a CustomBondForce and use it to implement the scaled 1-4 interactions.
|
|---|
| 2354 |
|
|---|
| 2355 | bonded = mm.CustomBondForce('%g*epsilon*((sigma/r)^12-(sigma/r)^6)' % (4*self.lj14scale))
|
|---|
| 2356 | bonded.addPerBondParameter('sigma')
|
|---|
| 2357 | bonded.addPerBondParameter('epsilon')
|
|---|
| 2358 | sys.addForce(bonded)
|
|---|
| 2359 | skip = set(tuple(forceCopy.getExclusionParticles(i)) for i in range(forceCopy.getNumExclusions()))
|
|---|
| 2360 | for i in range(self.force.getNumExclusions()):
|
|---|
| 2361 | p1,p2 = self.force.getExclusionParticles(i)
|
|---|
| 2362 | a1 = data.atoms[p1]
|
|---|
| 2363 | a2 = data.atoms[p2]
|
|---|
| 2364 | if (p1,p2) not in skip and (p2,p1) not in skip:
|
|---|
| 2365 | type1 = data.atomType[a1]
|
|---|
| 2366 | type2 = data.atomType[a2]
|
|---|
| 2367 | if (type1, type2) in self.nbfixTypes:
|
|---|
| 2368 | sigma, epsilon = self.nbfixTypes[(type1, type2)]
|
|---|
| 2369 | else:
|
|---|
| 2370 | values1 = self.ljTypes.getAtomParameters(a1, data)
|
|---|
| 2371 | values2 = self.ljTypes.getAtomParameters(a2, data)
|
|---|
| 2372 | sigma = 0.5*(values1[0]+values2[0])
|
|---|
| 2373 | epsilon = sqrt(values1[1]*values2[1])
|
|---|
| 2374 | bonded.addBond(p1, p2, (sigma, epsilon))
|
|---|
| 2375 |
|
|---|
| 2376 | parsers["LennardJonesForce"] = LennardJonesGenerator.parseElement
|
|---|
| 2377 |
|
|---|
| 2378 |
|
|---|
| 2379 | ## @private
|
|---|
| 2380 | class GBSAOBCGenerator(object):
|
|---|
| 2381 | """A GBSAOBCGenerator constructs a GBSAOBCForce."""
|
|---|
| 2382 |
|
|---|
| 2383 | def __init__(self, forcefield):
|
|---|
| 2384 | self.ff = forcefield
|
|---|
| 2385 | self.params = ForceField._AtomTypeParameters(forcefield, 'GBSAOBCForce', 'Atom', ('charge', 'radius', 'scale'))
|
|---|
| 2386 |
|
|---|
| 2387 | def registerAtom(self, parameters):
|
|---|
| 2388 | self.params.registerAtom(parameters)
|
|---|
| 2389 |
|
|---|
| 2390 | @staticmethod
|
|---|
| 2391 | def parseElement(element, ff):
|
|---|
| 2392 | existing = [f for f in ff._forces if isinstance(f, GBSAOBCGenerator)]
|
|---|
| 2393 | if len(existing) == 0:
|
|---|
| 2394 | generator = GBSAOBCGenerator(ff)
|
|---|
| 2395 | ff.registerGenerator(generator)
|
|---|
| 2396 | else:
|
|---|
| 2397 | # Multiple <GBSAOBCForce> tags were found, probably in different files. Simply add more types to the existing one.
|
|---|
| 2398 | generator = existing[0]
|
|---|
| 2399 | generator.params.parseDefinitions(element)
|
|---|
| 2400 |
|
|---|
| 2401 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2402 | methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff,
|
|---|
| 2403 | CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
|
|---|
| 2404 | CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic}
|
|---|
| 2405 | if nonbondedMethod not in methodMap:
|
|---|
| 2406 | raise ValueError('Illegal nonbonded method for GBSAOBCForce')
|
|---|
| 2407 | force = mm.GBSAOBCForce()
|
|---|
| 2408 | for atom in data.atoms:
|
|---|
| 2409 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 2410 | force.addParticle(values[0], values[1], values[2])
|
|---|
| 2411 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2412 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2413 | if 'soluteDielectric' in args:
|
|---|
| 2414 | force.setSoluteDielectric(float(args['soluteDielectric']))
|
|---|
| 2415 | if 'solventDielectric' in args:
|
|---|
| 2416 | force.setSolventDielectric(float(args['solventDielectric']))
|
|---|
| 2417 | sys.addForce(force)
|
|---|
| 2418 |
|
|---|
| 2419 | def postprocessSystem(self, sys, data, args):
|
|---|
| 2420 | # Disable the reaction field approximation, since it produces bad results when combined with GB.
|
|---|
| 2421 |
|
|---|
| 2422 | for force in sys.getForces():
|
|---|
| 2423 | if isinstance(force, mm.NonbondedForce):
|
|---|
| 2424 | force.setReactionFieldDielectric(1.0)
|
|---|
| 2425 |
|
|---|
| 2426 | parsers["GBSAOBCForce"] = GBSAOBCGenerator.parseElement
|
|---|
| 2427 |
|
|---|
| 2428 |
|
|---|
| 2429 | ## @private
|
|---|
| 2430 | class CustomBondGenerator(object):
|
|---|
| 2431 | """A CustomBondGenerator constructs a CustomBondForce."""
|
|---|
| 2432 |
|
|---|
| 2433 | def __init__(self, forcefield):
|
|---|
| 2434 | self.ff = forcefield
|
|---|
| 2435 | self.types1 = []
|
|---|
| 2436 | self.types2 = []
|
|---|
| 2437 | self.globalParams = {}
|
|---|
| 2438 | self.perBondParams = []
|
|---|
| 2439 | self.paramValues = []
|
|---|
| 2440 |
|
|---|
| 2441 | @staticmethod
|
|---|
| 2442 | def parseElement(element, ff):
|
|---|
| 2443 | generator = CustomBondGenerator(ff)
|
|---|
| 2444 | ff.registerGenerator(generator)
|
|---|
| 2445 | generator.energy = element.attrib['energy']
|
|---|
| 2446 | for param in element.findall('GlobalParameter'):
|
|---|
| 2447 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2448 | for param in element.findall('PerBondParameter'):
|
|---|
| 2449 | generator.perBondParams.append(param.attrib['name'])
|
|---|
| 2450 | for bond in element.findall('Bond'):
|
|---|
| 2451 | types = ff._findAtomTypes(bond.attrib, 2)
|
|---|
| 2452 | if None not in types:
|
|---|
| 2453 | generator.types1.append(types[0])
|
|---|
| 2454 | generator.types2.append(types[1])
|
|---|
| 2455 | generator.paramValues.append([float(bond.attrib[param]) for param in generator.perBondParams])
|
|---|
| 2456 |
|
|---|
| 2457 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2458 | force = mm.CustomBondForce(self.energy)
|
|---|
| 2459 | sys.addForce(force)
|
|---|
| 2460 | for param in self.globalParams:
|
|---|
| 2461 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2462 | for param in self.perBondParams:
|
|---|
| 2463 | force.addPerBondParameter(param)
|
|---|
| 2464 | for bond in data.bonds:
|
|---|
| 2465 | type1 = data.atomType[data.atoms[bond.atom1]]
|
|---|
| 2466 | type2 = data.atomType[data.atoms[bond.atom2]]
|
|---|
| 2467 | for i in range(len(self.types1)):
|
|---|
| 2468 | types1 = self.types1[i]
|
|---|
| 2469 | types2 = self.types2[i]
|
|---|
| 2470 | if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
|
|---|
| 2471 | force.addBond(bond.atom1, bond.atom2, self.paramValues[i])
|
|---|
| 2472 | break
|
|---|
| 2473 |
|
|---|
| 2474 | parsers["CustomBondForce"] = CustomBondGenerator.parseElement
|
|---|
| 2475 |
|
|---|
| 2476 |
|
|---|
| 2477 | ## @private
|
|---|
| 2478 | class CustomAngleGenerator(object):
|
|---|
| 2479 | """A CustomAngleGenerator constructs a CustomAngleForce."""
|
|---|
| 2480 |
|
|---|
| 2481 | def __init__(self, forcefield):
|
|---|
| 2482 | self.ff = forcefield
|
|---|
| 2483 | self.types1 = []
|
|---|
| 2484 | self.types2 = []
|
|---|
| 2485 | self.types3 = []
|
|---|
| 2486 | self.globalParams = {}
|
|---|
| 2487 | self.perAngleParams = []
|
|---|
| 2488 | self.paramValues = []
|
|---|
| 2489 |
|
|---|
| 2490 | @staticmethod
|
|---|
| 2491 | def parseElement(element, ff):
|
|---|
| 2492 | generator = CustomAngleGenerator(ff)
|
|---|
| 2493 | ff.registerGenerator(generator)
|
|---|
| 2494 | generator.energy = element.attrib['energy']
|
|---|
| 2495 | for param in element.findall('GlobalParameter'):
|
|---|
| 2496 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2497 | for param in element.findall('PerAngleParameter'):
|
|---|
| 2498 | generator.perAngleParams.append(param.attrib['name'])
|
|---|
| 2499 | for angle in element.findall('Angle'):
|
|---|
| 2500 | types = ff._findAtomTypes(angle.attrib, 3)
|
|---|
| 2501 | if None not in types:
|
|---|
| 2502 | generator.types1.append(types[0])
|
|---|
| 2503 | generator.types2.append(types[1])
|
|---|
| 2504 | generator.types3.append(types[2])
|
|---|
| 2505 | generator.paramValues.append([float(angle.attrib[param]) for param in generator.perAngleParams])
|
|---|
| 2506 |
|
|---|
| 2507 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2508 | force = mm.CustomAngleForce(self.energy)
|
|---|
| 2509 | sys.addForce(force)
|
|---|
| 2510 | for param in self.globalParams:
|
|---|
| 2511 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2512 | for param in self.perAngleParams:
|
|---|
| 2513 | force.addPerAngleParameter(param)
|
|---|
| 2514 | for angle in data.angles:
|
|---|
| 2515 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 2516 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 2517 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 2518 | for i in range(len(self.types1)):
|
|---|
| 2519 | types1 = self.types1[i]
|
|---|
| 2520 | types2 = self.types2[i]
|
|---|
| 2521 | types3 = self.types3[i]
|
|---|
| 2522 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 2523 | force.addAngle(angle[0], angle[1], angle[2], self.paramValues[i])
|
|---|
| 2524 | break
|
|---|
| 2525 |
|
|---|
| 2526 | parsers["CustomAngleForce"] = CustomAngleGenerator.parseElement
|
|---|
| 2527 |
|
|---|
| 2528 |
|
|---|
| 2529 | ## @private
|
|---|
| 2530 | class CustomTorsion(object):
|
|---|
| 2531 | """A CustomTorsion records the information for a custom torsion definition."""
|
|---|
| 2532 |
|
|---|
| 2533 | def __init__(self, types, paramValues):
|
|---|
| 2534 | self.types1 = types[0]
|
|---|
| 2535 | self.types2 = types[1]
|
|---|
| 2536 | self.types3 = types[2]
|
|---|
| 2537 | self.types4 = types[3]
|
|---|
| 2538 | self.paramValues = paramValues
|
|---|
| 2539 |
|
|---|
| 2540 | ## @private
|
|---|
| 2541 | class CustomTorsionGenerator(object):
|
|---|
| 2542 | """A CustomTorsionGenerator constructs a CustomTorsionForce."""
|
|---|
| 2543 |
|
|---|
| 2544 | def __init__(self, forcefield):
|
|---|
| 2545 | self.ff = forcefield
|
|---|
| 2546 | self.proper = []
|
|---|
| 2547 | self.improper = []
|
|---|
| 2548 | self.globalParams = {}
|
|---|
| 2549 | self.perTorsionParams = []
|
|---|
| 2550 |
|
|---|
| 2551 | @staticmethod
|
|---|
| 2552 | def parseElement(element, ff):
|
|---|
| 2553 | generator = CustomTorsionGenerator(ff)
|
|---|
| 2554 | ff.registerGenerator(generator)
|
|---|
| 2555 | generator.energy = element.attrib['energy']
|
|---|
| 2556 | for param in element.findall('GlobalParameter'):
|
|---|
| 2557 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2558 | for param in element.findall('PerTorsionParameter'):
|
|---|
| 2559 | generator.perTorsionParams.append(param.attrib['name'])
|
|---|
| 2560 | for torsion in element.findall('Proper'):
|
|---|
| 2561 | types = ff._findAtomTypes(torsion.attrib, 4)
|
|---|
| 2562 | if None not in types:
|
|---|
| 2563 | generator.proper.append(CustomTorsion(types, [float(torsion.attrib[param]) for param in generator.perTorsionParams]))
|
|---|
| 2564 | for torsion in element.findall('Improper'):
|
|---|
| 2565 | types = ff._findAtomTypes(torsion.attrib, 4)
|
|---|
| 2566 | if None not in types:
|
|---|
| 2567 | generator.improper.append(CustomTorsion(types, [float(torsion.attrib[param]) for param in generator.perTorsionParams]))
|
|---|
| 2568 |
|
|---|
| 2569 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2570 | force = mm.CustomTorsionForce(self.energy)
|
|---|
| 2571 | sys.addForce(force)
|
|---|
| 2572 | for param in self.globalParams:
|
|---|
| 2573 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2574 | for param in self.perTorsionParams:
|
|---|
| 2575 | force.addPerTorsionParameter(param)
|
|---|
| 2576 | wildcard = self.ff._atomClasses['']
|
|---|
| 2577 | for torsion in data.propers:
|
|---|
| 2578 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 2579 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 2580 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 2581 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 2582 | match = None
|
|---|
| 2583 | for tordef in self.proper:
|
|---|
| 2584 | types1 = tordef.types1
|
|---|
| 2585 | types2 = tordef.types2
|
|---|
| 2586 | types3 = tordef.types3
|
|---|
| 2587 | types4 = tordef.types4
|
|---|
| 2588 | if (type2 in types2 and type3 in types3 and type4 in types4 and type1 in types1) or (type2 in types3 and type3 in types2 and type4 in types1 and type1 in types4):
|
|---|
| 2589 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 2590 | if match is None or not hasWildcard: # Prefer specific definitions over ones with wildcards
|
|---|
| 2591 | match = tordef
|
|---|
| 2592 | if not hasWildcard:
|
|---|
| 2593 | break
|
|---|
| 2594 | if match is not None:
|
|---|
| 2595 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.paramValues)
|
|---|
| 2596 | for torsion in data.impropers:
|
|---|
| 2597 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 2598 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 2599 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 2600 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 2601 | match = None
|
|---|
| 2602 | for tordef in self.improper:
|
|---|
| 2603 | types1 = tordef.types1
|
|---|
| 2604 | types2 = tordef.types2
|
|---|
| 2605 | types3 = tordef.types3
|
|---|
| 2606 | types4 = tordef.types4
|
|---|
| 2607 | hasWildcard = (wildcard in (types1, types2, types3, types4))
|
|---|
| 2608 | if match is not None and hasWildcard:
|
|---|
| 2609 | # Prefer specific definitions over ones with wildcards
|
|---|
| 2610 | continue
|
|---|
| 2611 | if type1 in types1:
|
|---|
| 2612 | for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
|
|---|
| 2613 | if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
|
|---|
| 2614 | if hasWildcard:
|
|---|
| 2615 | # Workaround to be more consistent with AMBER. It uses wildcards to define most of its
|
|---|
| 2616 | # impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
|
|---|
| 2617 | # to pick the order.
|
|---|
| 2618 | a1 = torsion[t2[1]]
|
|---|
| 2619 | a2 = torsion[t3[1]]
|
|---|
| 2620 | e1 = data.atoms[a1].element
|
|---|
| 2621 | e2 = data.atoms[a2].element
|
|---|
| 2622 | if e1 == e2 and a1 > a2:
|
|---|
| 2623 | (a1, a2) = (a2, a1)
|
|---|
| 2624 | elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
|
|---|
| 2625 | (a1, a2) = (a2, a1)
|
|---|
| 2626 | match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
|
|---|
| 2627 | else:
|
|---|
| 2628 | # There are no wildcards, so the order is unambiguous.
|
|---|
| 2629 | match = (torsion[0], torsion[t2[1]], torsion[t3[1]], torsion[t4[1]], tordef)
|
|---|
| 2630 | break
|
|---|
| 2631 | if match is not None:
|
|---|
| 2632 | (a1, a2, a3, a4, tordef) = match
|
|---|
| 2633 | force.addTorsion(a1, a2, a3, a4, tordef.paramValues)
|
|---|
| 2634 |
|
|---|
| 2635 | parsers["CustomTorsionForce"] = CustomTorsionGenerator.parseElement
|
|---|
| 2636 |
|
|---|
| 2637 |
|
|---|
| 2638 | ## @private
|
|---|
| 2639 | class CustomNonbondedGenerator(object):
|
|---|
| 2640 | """A CustomNonbondedGenerator constructs a CustomNonbondedForce."""
|
|---|
| 2641 |
|
|---|
| 2642 | def __init__(self, forcefield, energy, bondCutoff):
|
|---|
| 2643 | self.ff = forcefield
|
|---|
| 2644 | self.energy = energy
|
|---|
| 2645 | self.bondCutoff = bondCutoff
|
|---|
| 2646 | self.globalParams = {}
|
|---|
| 2647 | self.perParticleParams = []
|
|---|
| 2648 | self.functions = []
|
|---|
| 2649 |
|
|---|
| 2650 | @staticmethod
|
|---|
| 2651 | def parseElement(element, ff):
|
|---|
| 2652 | generator = CustomNonbondedGenerator(ff, element.attrib['energy'], int(element.attrib['bondCutoff']))
|
|---|
| 2653 | ff.registerGenerator(generator)
|
|---|
| 2654 | for param in element.findall('GlobalParameter'):
|
|---|
| 2655 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2656 | for param in element.findall('PerParticleParameter'):
|
|---|
| 2657 | generator.perParticleParams.append(param.attrib['name'])
|
|---|
| 2658 | generator.params = ForceField._AtomTypeParameters(ff, 'CustomNonbondedForce', 'Atom', generator.perParticleParams)
|
|---|
| 2659 | generator.params.parseDefinitions(element)
|
|---|
| 2660 | generator.functions += _parseFunctions(element)
|
|---|
| 2661 |
|
|---|
| 2662 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2663 | methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff,
|
|---|
| 2664 | CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic,
|
|---|
| 2665 | CutoffPeriodic:mm.CustomNonbondedForce.CutoffPeriodic}
|
|---|
| 2666 | if nonbondedMethod not in methodMap:
|
|---|
| 2667 | raise ValueError('Illegal nonbonded method for CustomNonbondedForce')
|
|---|
| 2668 | force = mm.CustomNonbondedForce(self.energy)
|
|---|
| 2669 | for param in self.globalParams:
|
|---|
| 2670 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2671 | for param in self.perParticleParams:
|
|---|
| 2672 | force.addPerParticleParameter(param)
|
|---|
| 2673 | _createFunctions(force, self.functions)
|
|---|
| 2674 | for atom in data.atoms:
|
|---|
| 2675 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 2676 | force.addParticle(values)
|
|---|
| 2677 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2678 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2679 | sys.addForce(force)
|
|---|
| 2680 |
|
|---|
| 2681 | def postprocessSystem(self, sys, data, args):
|
|---|
| 2682 | # Create the exclusions.
|
|---|
| 2683 |
|
|---|
| 2684 | bondIndices = _findBondsForExclusions(data, sys)
|
|---|
| 2685 | nonbonded = [f for f in sys.getForces() if isinstance(f, mm.CustomNonbondedForce)][0]
|
|---|
| 2686 | nonbonded.createExclusionsFromBonds(bondIndices, self.bondCutoff)
|
|---|
| 2687 |
|
|---|
| 2688 | parsers["CustomNonbondedForce"] = CustomNonbondedGenerator.parseElement
|
|---|
| 2689 |
|
|---|
| 2690 |
|
|---|
| 2691 | ## @private
|
|---|
| 2692 | class CustomGBGenerator(object):
|
|---|
| 2693 | """A CustomGBGenerator constructs a CustomGBForce."""
|
|---|
| 2694 |
|
|---|
| 2695 | def __init__(self, forcefield):
|
|---|
| 2696 | self.ff = forcefield
|
|---|
| 2697 | self.globalParams = {}
|
|---|
| 2698 | self.perParticleParams = []
|
|---|
| 2699 | self.computedValues = []
|
|---|
| 2700 | self.energyTerms = []
|
|---|
| 2701 | self.functions = []
|
|---|
| 2702 |
|
|---|
| 2703 | @staticmethod
|
|---|
| 2704 | def parseElement(element, ff):
|
|---|
| 2705 | generator = CustomGBGenerator(ff)
|
|---|
| 2706 | ff.registerGenerator(generator)
|
|---|
| 2707 | for param in element.findall('GlobalParameter'):
|
|---|
| 2708 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2709 | for param in element.findall('PerParticleParameter'):
|
|---|
| 2710 | generator.perParticleParams.append(param.attrib['name'])
|
|---|
| 2711 | generator.params = ForceField._AtomTypeParameters(ff, 'CustomGBForce', 'Atom', generator.perParticleParams)
|
|---|
| 2712 | generator.params.parseDefinitions(element)
|
|---|
| 2713 | computationMap = {"SingleParticle" : mm.CustomGBForce.SingleParticle,
|
|---|
| 2714 | "ParticlePair" : mm.CustomGBForce.ParticlePair,
|
|---|
| 2715 | "ParticlePairNoExclusions" : mm.CustomGBForce.ParticlePairNoExclusions}
|
|---|
| 2716 | for value in element.findall('ComputedValue'):
|
|---|
| 2717 | generator.computedValues.append((value.attrib['name'], value.text, computationMap[value.attrib['type']]))
|
|---|
| 2718 | for term in element.findall('EnergyTerm'):
|
|---|
| 2719 | generator.energyTerms.append((term.text, computationMap[term.attrib['type']]))
|
|---|
| 2720 | generator.functions += _parseFunctions(element)
|
|---|
| 2721 |
|
|---|
| 2722 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2723 | methodMap = {NoCutoff:mm.CustomGBForce.NoCutoff,
|
|---|
| 2724 | CutoffNonPeriodic:mm.CustomGBForce.CutoffNonPeriodic,
|
|---|
| 2725 | CutoffPeriodic:mm.CustomGBForce.CutoffPeriodic}
|
|---|
| 2726 | if nonbondedMethod not in methodMap:
|
|---|
| 2727 | raise ValueError('Illegal nonbonded method for CustomGBForce')
|
|---|
| 2728 | force = mm.CustomGBForce()
|
|---|
| 2729 | for param in self.globalParams:
|
|---|
| 2730 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2731 | for param in self.perParticleParams:
|
|---|
| 2732 | force.addPerParticleParameter(param)
|
|---|
| 2733 | for value in self.computedValues:
|
|---|
| 2734 | force.addComputedValue(value[0], value[1], value[2])
|
|---|
| 2735 | for term in self.energyTerms:
|
|---|
| 2736 | force.addEnergyTerm(term[0], term[1])
|
|---|
| 2737 | _createFunctions(force, self.functions)
|
|---|
| 2738 | for atom in data.atoms:
|
|---|
| 2739 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 2740 | force.addParticle(values)
|
|---|
| 2741 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2742 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2743 | sys.addForce(force)
|
|---|
| 2744 |
|
|---|
| 2745 | parsers["CustomGBForce"] = CustomGBGenerator.parseElement
|
|---|
| 2746 |
|
|---|
| 2747 |
|
|---|
| 2748 | ## @private
|
|---|
| 2749 | class CustomHbondGenerator(object):
|
|---|
| 2750 | """A CustomHbondGenerator constructs a CustomHbondForce."""
|
|---|
| 2751 |
|
|---|
| 2752 | def __init__(self, forcefield):
|
|---|
| 2753 | self.ff = forcefield
|
|---|
| 2754 | self.donorTypes1 = []
|
|---|
| 2755 | self.donorTypes2 = []
|
|---|
| 2756 | self.donorTypes3 = []
|
|---|
| 2757 | self.acceptorTypes1 = []
|
|---|
| 2758 | self.acceptorTypes2 = []
|
|---|
| 2759 | self.acceptorTypes3 = []
|
|---|
| 2760 | self.globalParams = {}
|
|---|
| 2761 | self.perDonorParams = []
|
|---|
| 2762 | self.perAcceptorParams = []
|
|---|
| 2763 | self.donorParamValues = []
|
|---|
| 2764 | self.acceptorParamValues = []
|
|---|
| 2765 | self.functions = []
|
|---|
| 2766 |
|
|---|
| 2767 | @staticmethod
|
|---|
| 2768 | def parseElement(element, ff):
|
|---|
| 2769 | generator = CustomHbondGenerator(ff)
|
|---|
| 2770 | ff.registerGenerator(generator)
|
|---|
| 2771 | generator.energy = element.attrib['energy']
|
|---|
| 2772 | generator.bondCutoff = int(element.attrib['bondCutoff'])
|
|---|
| 2773 | generator.particlesPerDonor = int(element.attrib['particlesPerDonor'])
|
|---|
| 2774 | generator.particlesPerAcceptor = int(element.attrib['particlesPerAcceptor'])
|
|---|
| 2775 | if generator.particlesPerDonor < 1 or generator.particlesPerDonor > 3:
|
|---|
| 2776 | raise ValueError('Illegal value for particlesPerDonor for CustomHbondForce')
|
|---|
| 2777 | if generator.particlesPerAcceptor < 1 or generator.particlesPerAcceptor > 3:
|
|---|
| 2778 | raise ValueError('Illegal value for particlesPerAcceptor for CustomHbondForce')
|
|---|
| 2779 | for param in element.findall('GlobalParameter'):
|
|---|
| 2780 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2781 | for param in element.findall('PerDonorParameter'):
|
|---|
| 2782 | generator.perDonorParams.append(param.attrib['name'])
|
|---|
| 2783 | for param in element.findall('PerAcceptorParameter'):
|
|---|
| 2784 | generator.perAcceptorParams.append(param.attrib['name'])
|
|---|
| 2785 | for donor in element.findall('Donor'):
|
|---|
| 2786 | types = ff._findAtomTypes(donor.attrib, 3)[:generator.particlesPerDonor]
|
|---|
| 2787 | if None not in types:
|
|---|
| 2788 | generator.donorTypes1.append(types[0])
|
|---|
| 2789 | if len(types) > 1:
|
|---|
| 2790 | generator.donorTypes2.append(types[1])
|
|---|
| 2791 | if len(types) > 2:
|
|---|
| 2792 | generator.donorTypes3.append(types[2])
|
|---|
| 2793 | generator.donorParamValues.append([float(donor.attrib[param]) for param in generator.perDonorParams])
|
|---|
| 2794 | for acceptor in element.findall('Acceptor'):
|
|---|
| 2795 | types = ff._findAtomTypes(acceptor.attrib, 3)[:generator.particlesPerAcceptor]
|
|---|
| 2796 | if None not in types:
|
|---|
| 2797 | generator.acceptorTypes1.append(types[0])
|
|---|
| 2798 | if len(types) > 1:
|
|---|
| 2799 | generator.acceptorTypes2.append(types[1])
|
|---|
| 2800 | if len(types) > 2:
|
|---|
| 2801 | generator.acceptorTypes3.append(types[2])
|
|---|
| 2802 | generator.acceptorParamValues.append([float(acceptor.attrib[param]) for param in generator.perAcceptorParams])
|
|---|
| 2803 | generator.functions += _parseFunctions(element)
|
|---|
| 2804 |
|
|---|
| 2805 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2806 | methodMap = {NoCutoff:mm.CustomHbondForce.NoCutoff,
|
|---|
| 2807 | CutoffNonPeriodic:mm.CustomHbondForce.CutoffNonPeriodic,
|
|---|
| 2808 | CutoffPeriodic:mm.CustomHbondForce.CutoffPeriodic}
|
|---|
| 2809 | if nonbondedMethod not in methodMap:
|
|---|
| 2810 | raise ValueError('Illegal nonbonded method for CustomNonbondedForce')
|
|---|
| 2811 | force = mm.CustomHbondForce(self.energy)
|
|---|
| 2812 | sys.addForce(force)
|
|---|
| 2813 | for param in self.globalParams:
|
|---|
| 2814 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2815 | for param in self.perDonorParams:
|
|---|
| 2816 | force.addPerDonorParameter(param)
|
|---|
| 2817 | for param in self.perAcceptorParams:
|
|---|
| 2818 | force.addPerAcceptorParameter(param)
|
|---|
| 2819 | _createFunctions(force, self.functions)
|
|---|
| 2820 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2821 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2822 |
|
|---|
| 2823 | # Add donors.
|
|---|
| 2824 |
|
|---|
| 2825 | if self.particlesPerDonor == 1:
|
|---|
| 2826 | for atom in data.atoms:
|
|---|
| 2827 | type1 = data.atomType[atom]
|
|---|
| 2828 | for i in range(len(self.donorTypes1)):
|
|---|
| 2829 | types1 = self.donorTypes1[i]
|
|---|
| 2830 | if type1 in self.donorTypes1[i]:
|
|---|
| 2831 | force.addDonor(atom.index, -1, -1, self.donorParamValues[i])
|
|---|
| 2832 | elif self.particlesPerDonor == 2:
|
|---|
| 2833 | for bond in data.bonds:
|
|---|
| 2834 | type1 = data.atomType[data.atoms[bond.atom1]]
|
|---|
| 2835 | type2 = data.atomType[data.atoms[bond.atom2]]
|
|---|
| 2836 | for i in range(len(self.donorTypes1)):
|
|---|
| 2837 | types1 = self.donorTypes1[i]
|
|---|
| 2838 | types2 = self.donorTypes2[i]
|
|---|
| 2839 | if type1 in types1 and type2 in types2:
|
|---|
| 2840 | force.addDonor(bond.atom1, bond.atom2, -1, self.donorParamValues[i])
|
|---|
| 2841 | elif type1 in types2 and type2 in types1:
|
|---|
| 2842 | force.addDonor(bond.atom2, bond.atom1, -1, self.donorParamValues[i])
|
|---|
| 2843 | else:
|
|---|
| 2844 | for angle in data.angles:
|
|---|
| 2845 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 2846 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 2847 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 2848 | for i in range(len(self.donorTypes1)):
|
|---|
| 2849 | types1 = self.donorTypes1[i]
|
|---|
| 2850 | types2 = self.donorTypes2[i]
|
|---|
| 2851 | types3 = self.donorTypes3[i]
|
|---|
| 2852 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 2853 | force.addDonor(angle[0], angle[1], angle[2], self.donorParamValues[i])
|
|---|
| 2854 |
|
|---|
| 2855 | # Add acceptors.
|
|---|
| 2856 |
|
|---|
| 2857 | if self.particlesPerAcceptor == 1:
|
|---|
| 2858 | for atom in data.atoms:
|
|---|
| 2859 | type1 = data.atomType[atom]
|
|---|
| 2860 | for i in range(len(self.acceptorTypes1)):
|
|---|
| 2861 | types1 = self.acceptorTypes1[i]
|
|---|
| 2862 | if type1 in self.acceptorTypes1[i]:
|
|---|
| 2863 | force.addAcceptor(atom.index, -1, -1, self.acceptorParamValues[i])
|
|---|
| 2864 | elif self.particlesPerAcceptor == 2:
|
|---|
| 2865 | for bond in data.bonds:
|
|---|
| 2866 | type1 = data.atomType[data.atoms[bond.atom1]]
|
|---|
| 2867 | type2 = data.atomType[data.atoms[bond.atom2]]
|
|---|
| 2868 | for i in range(len(self.acceptorTypes1)):
|
|---|
| 2869 | types1 = self.acceptorTypes1[i]
|
|---|
| 2870 | types2 = self.acceptorTypes2[i]
|
|---|
| 2871 | if type1 in types1 and type2 in types2:
|
|---|
| 2872 | force.addAcceptor(bond.atom1, bond.atom2, -1, self.acceptorParamValues[i])
|
|---|
| 2873 | elif type1 in types2 and type2 in types1:
|
|---|
| 2874 | force.addAcceptor(bond.atom2, bond.atom1, -1, self.acceptorParamValues[i])
|
|---|
| 2875 | else:
|
|---|
| 2876 | for angle in data.angles:
|
|---|
| 2877 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 2878 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 2879 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 2880 | for i in range(len(self.acceptorTypes1)):
|
|---|
| 2881 | types1 = self.acceptorTypes1[i]
|
|---|
| 2882 | types2 = self.acceptorTypes2[i]
|
|---|
| 2883 | types3 = self.acceptorTypes3[i]
|
|---|
| 2884 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 2885 | force.addAcceptor(angle[0], angle[1], angle[2], self.acceptorParamValues[i])
|
|---|
| 2886 |
|
|---|
| 2887 | # Add exclusions.
|
|---|
| 2888 |
|
|---|
| 2889 | for donor in range(force.getNumDonors()):
|
|---|
| 2890 | (d1, d2, d3, params) = force.getDonorParameters(donor)
|
|---|
| 2891 | outerAtoms = set((d1, d2, d3))
|
|---|
| 2892 | if -1 in outerAtoms:
|
|---|
| 2893 | outerAtoms.remove(-1)
|
|---|
| 2894 | excludedAtoms = set(outerAtoms)
|
|---|
| 2895 | for i in range(self.bondCutoff):
|
|---|
| 2896 | newOuterAtoms = set()
|
|---|
| 2897 | for atom in outerAtoms:
|
|---|
| 2898 | for bond in data.atomBonds[atom]:
|
|---|
| 2899 | b = data.bonds[bond]
|
|---|
| 2900 | bondedAtom = (b.atom2 if b.atom1 == atom else b.atom1)
|
|---|
| 2901 | if bondedAtom not in excludedAtoms:
|
|---|
| 2902 | newOuterAtoms.add(bondedAtom)
|
|---|
| 2903 | excludedAtoms.add(bondedAtom)
|
|---|
| 2904 | outerAtoms = newOuterAtoms
|
|---|
| 2905 | for acceptor in range(force.getNumAcceptors()):
|
|---|
| 2906 | (a1, a2, a3, params) = force.getAcceptorParameters(acceptor)
|
|---|
| 2907 | if a1 in excludedAtoms or a2 in excludedAtoms or a3 in excludedAtoms:
|
|---|
| 2908 | force.addExclusion(donor, acceptor)
|
|---|
| 2909 |
|
|---|
| 2910 | parsers["CustomHbondForce"] = CustomHbondGenerator.parseElement
|
|---|
| 2911 |
|
|---|
| 2912 |
|
|---|
| 2913 | ## @private
|
|---|
| 2914 | class CustomManyParticleGenerator(object):
|
|---|
| 2915 | """A CustomManyParticleGenerator constructs a CustomManyParticleForce."""
|
|---|
| 2916 |
|
|---|
| 2917 | def __init__(self, forcefield, particlesPerSet, energy, permutationMode, bondCutoff):
|
|---|
| 2918 | self.ff = forcefield
|
|---|
| 2919 | self.particlesPerSet = particlesPerSet
|
|---|
| 2920 | self.energy = energy
|
|---|
| 2921 | self.permutationMode = permutationMode
|
|---|
| 2922 | self.bondCutoff = bondCutoff
|
|---|
| 2923 | self.globalParams = {}
|
|---|
| 2924 | self.perParticleParams = []
|
|---|
| 2925 | self.functions = []
|
|---|
| 2926 | self.typeFilters = []
|
|---|
| 2927 |
|
|---|
| 2928 | @staticmethod
|
|---|
| 2929 | def parseElement(element, ff):
|
|---|
| 2930 | permutationMap = {"SinglePermutation" : mm.CustomManyParticleForce.SinglePermutation,
|
|---|
| 2931 | "UniqueCentralParticle" : mm.CustomManyParticleForce.UniqueCentralParticle}
|
|---|
| 2932 | generator = CustomManyParticleGenerator(ff, int(element.attrib['particlesPerSet']), element.attrib['energy'], permutationMap[element.attrib['permutationMode']], int(element.attrib['bondCutoff']))
|
|---|
| 2933 | ff.registerGenerator(generator)
|
|---|
| 2934 | for param in element.findall('GlobalParameter'):
|
|---|
| 2935 | generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
|
|---|
| 2936 | for param in element.findall('PerParticleParameter'):
|
|---|
| 2937 | generator.perParticleParams.append(param.attrib['name'])
|
|---|
| 2938 | for param in element.findall('TypeFilter'):
|
|---|
| 2939 | generator.typeFilters.append((int(param.attrib['index']), [int(x) for x in param.attrib['types'].split(',')]))
|
|---|
| 2940 | generator.params = ForceField._AtomTypeParameters(ff, 'CustomManyParticleForce', 'Atom', generator.perParticleParams)
|
|---|
| 2941 | generator.params.parseDefinitions(element)
|
|---|
| 2942 |
|
|---|
| 2943 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 2944 | methodMap = {NoCutoff:mm.CustomManyParticleForce.NoCutoff,
|
|---|
| 2945 | CutoffNonPeriodic:mm.CustomManyParticleForce.CutoffNonPeriodic,
|
|---|
| 2946 | CutoffPeriodic:mm.CustomManyParticleForce.CutoffPeriodic}
|
|---|
| 2947 | if nonbondedMethod not in methodMap:
|
|---|
| 2948 | raise ValueError('Illegal nonbonded method for CustomManyParticleForce')
|
|---|
| 2949 | force = mm.CustomManyParticleForce(self.particlesPerSet, self.energy)
|
|---|
| 2950 | force.setPermutationMode(self.permutationMode)
|
|---|
| 2951 | for param in self.globalParams:
|
|---|
| 2952 | force.addGlobalParameter(param, self.globalParams[param])
|
|---|
| 2953 | for param in self.perParticleParams:
|
|---|
| 2954 | force.addPerParticleParameter(param)
|
|---|
| 2955 | for index, types in self.typeFilters:
|
|---|
| 2956 | force.setTypeFilter(index, types)
|
|---|
| 2957 | for (name, type, values, params) in self.functions:
|
|---|
| 2958 | if type == 'Continuous1D':
|
|---|
| 2959 | force.addTabulatedFunction(name, mm.Continuous1DFunction(values, params['min'], params['max']))
|
|---|
| 2960 | elif type == 'Continuous2D':
|
|---|
| 2961 | force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax']))
|
|---|
| 2962 | elif type == 'Continuous3D':
|
|---|
| 2963 | force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], params['zsize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax'], params['zmin'], params['zmax']))
|
|---|
| 2964 | elif type == 'Discrete1D':
|
|---|
| 2965 | force.addTabulatedFunction(name, mm.Discrete1DFunction(values))
|
|---|
| 2966 | elif type == 'Discrete2D':
|
|---|
| 2967 | force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], values))
|
|---|
| 2968 | elif type == 'Discrete3D':
|
|---|
| 2969 | force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
|
|---|
| 2970 | for atom in data.atoms:
|
|---|
| 2971 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 2972 | type = int(self.params.getExtraParameters(atom, data)['filterType'])
|
|---|
| 2973 | force.addParticle(values, type)
|
|---|
| 2974 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 2975 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 2976 | sys.addForce(force)
|
|---|
| 2977 |
|
|---|
| 2978 | def postprocessSystem(self, sys, data, args):
|
|---|
| 2979 | # Create exclusions based on bonds.
|
|---|
| 2980 |
|
|---|
| 2981 | bondIndices = []
|
|---|
| 2982 | for bond in data.bonds:
|
|---|
| 2983 | bondIndices.append((bond.atom1, bond.atom2))
|
|---|
| 2984 |
|
|---|
| 2985 | # If a virtual site does *not* share exclusions with another atom, add a bond between it and its first parent atom.
|
|---|
| 2986 |
|
|---|
| 2987 | for i in range(sys.getNumParticles()):
|
|---|
| 2988 | if sys.isVirtualSite(i):
|
|---|
| 2989 | (site, atoms, excludeWith) = data.virtualSites[data.atoms[i]]
|
|---|
| 2990 | if excludeWith is None:
|
|---|
| 2991 | bondIndices.append((i, site.getParticle(0)))
|
|---|
| 2992 |
|
|---|
| 2993 | # Certain particles, such as lone pairs and Drude particles, share exclusions with a parent atom.
|
|---|
| 2994 | # If the parent atom does not interact with an atom, the child particle does not either.
|
|---|
| 2995 |
|
|---|
| 2996 | for atom1, atom2 in bondIndices:
|
|---|
| 2997 | for child1 in data.excludeAtomWith[atom1]:
|
|---|
| 2998 | bondIndices.append((child1, atom2))
|
|---|
| 2999 | for child2 in data.excludeAtomWith[atom2]:
|
|---|
| 3000 | bondIndices.append((child1, child2))
|
|---|
| 3001 | for child2 in data.excludeAtomWith[atom2]:
|
|---|
| 3002 | bondIndices.append((atom1, child2))
|
|---|
| 3003 |
|
|---|
| 3004 | # Create the exclusions.
|
|---|
| 3005 |
|
|---|
| 3006 | nonbonded = [f for f in sys.getForces() if isinstance(f, mm.CustomManyParticleForce)][0]
|
|---|
| 3007 | nonbonded.createExclusionsFromBonds(bondIndices, self.bondCutoff)
|
|---|
| 3008 |
|
|---|
| 3009 | parsers["CustomManyParticleForce"] = CustomManyParticleGenerator.parseElement
|
|---|
| 3010 |
|
|---|
| 3011 | def getAtomPrint(data, atomIndex):
|
|---|
| 3012 |
|
|---|
| 3013 | if (atomIndex < len(data.atoms)):
|
|---|
| 3014 | atom = data.atoms[atomIndex]
|
|---|
| 3015 | returnString = "%4s %4s %5d" % (atom.name, atom.residue.name, atom.residue.index)
|
|---|
| 3016 | else:
|
|---|
| 3017 | returnString = "NA"
|
|---|
| 3018 |
|
|---|
| 3019 | return returnString
|
|---|
| 3020 |
|
|---|
| 3021 | #=============================================================================================
|
|---|
| 3022 |
|
|---|
| 3023 | def countConstraint(data):
|
|---|
| 3024 |
|
|---|
| 3025 | bondCount = 0
|
|---|
| 3026 | angleCount = 0
|
|---|
| 3027 | for bond in data.bonds:
|
|---|
| 3028 | if bond.isConstrained:
|
|---|
| 3029 | bondCount += 1
|
|---|
| 3030 |
|
|---|
| 3031 | angleCount = 0
|
|---|
| 3032 | for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
|
|---|
| 3033 | if (isConstrained):
|
|---|
| 3034 | angleCount += 1
|
|---|
| 3035 |
|
|---|
| 3036 | print("Constraints bond=%d angle=%d total=%d" % (bondCount, angleCount, (bondCount+angleCount)))
|
|---|
| 3037 |
|
|---|
| 3038 | ## @private
|
|---|
| 3039 | class AmoebaBondGenerator(object):
|
|---|
| 3040 |
|
|---|
| 3041 | #=============================================================================================
|
|---|
| 3042 |
|
|---|
| 3043 | """An AmoebaBondGenerator constructs a AmoebaBondForce."""
|
|---|
| 3044 |
|
|---|
| 3045 | #=============================================================================================
|
|---|
| 3046 |
|
|---|
| 3047 | def __init__(self, cubic, quartic):
|
|---|
| 3048 |
|
|---|
| 3049 | self.cubic = cubic
|
|---|
| 3050 | self.quartic = quartic
|
|---|
| 3051 | self.types1 = []
|
|---|
| 3052 | self.types2 = []
|
|---|
| 3053 | self.length = []
|
|---|
| 3054 | self.k = []
|
|---|
| 3055 |
|
|---|
| 3056 | #=============================================================================================
|
|---|
| 3057 |
|
|---|
| 3058 | @staticmethod
|
|---|
| 3059 | def parseElement(element, forceField):
|
|---|
| 3060 |
|
|---|
| 3061 | # <AmoebaBondForce bond-cubic="-25.5" bond-quartic="379.3125">
|
|---|
| 3062 | # <Bond class1="1" class2="2" length="0.1437" k="156900.0"/>
|
|---|
| 3063 |
|
|---|
| 3064 | generator = AmoebaBondGenerator(float(element.attrib['bond-cubic']), float(element.attrib['bond-quartic']))
|
|---|
| 3065 | forceField._forces.append(generator)
|
|---|
| 3066 | for bond in element.findall('Bond'):
|
|---|
| 3067 | types = forceField._findAtomTypes(bond.attrib, 2)
|
|---|
| 3068 | if None not in types:
|
|---|
| 3069 | generator.types1.append(types[0])
|
|---|
| 3070 | generator.types2.append(types[1])
|
|---|
| 3071 | generator.length.append(float(bond.attrib['length']))
|
|---|
| 3072 | generator.k.append(float(bond.attrib['k']))
|
|---|
| 3073 | else:
|
|---|
| 3074 | outputString = "AmoebaBondGenerator: error getting types: %s %s" % (
|
|---|
| 3075 | bond.attrib['class1'],
|
|---|
| 3076 | bond.attrib['class2'])
|
|---|
| 3077 | raise ValueError(outputString)
|
|---|
| 3078 |
|
|---|
| 3079 | #=============================================================================================
|
|---|
| 3080 |
|
|---|
| 3081 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 3082 |
|
|---|
| 3083 | #countConstraint(data)
|
|---|
| 3084 |
|
|---|
| 3085 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3086 | existing = [f for f in existing if type(f) == mm.AmoebaBondForce]
|
|---|
| 3087 | if len(existing) == 0:
|
|---|
| 3088 | force = mm.AmoebaBondForce()
|
|---|
| 3089 | sys.addForce(force)
|
|---|
| 3090 | else:
|
|---|
| 3091 | force = existing[0]
|
|---|
| 3092 |
|
|---|
| 3093 | force.setAmoebaGlobalBondCubic(self.cubic)
|
|---|
| 3094 | force.setAmoebaGlobalBondQuartic(self.quartic)
|
|---|
| 3095 |
|
|---|
| 3096 | for bond in data.bonds:
|
|---|
| 3097 | type1 = data.atomType[data.atoms[bond.atom1]]
|
|---|
| 3098 | type2 = data.atomType[data.atoms[bond.atom2]]
|
|---|
| 3099 | for i in range(len(self.types1)):
|
|---|
| 3100 | types1 = self.types1[i]
|
|---|
| 3101 | types2 = self.types2[i]
|
|---|
| 3102 | if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
|
|---|
| 3103 | bond.length = self.length[i]
|
|---|
| 3104 | if bond.isConstrained:
|
|---|
| 3105 | data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
|
|---|
| 3106 | elif self.k[i] != 0:
|
|---|
| 3107 | force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
|
|---|
| 3108 | break
|
|---|
| 3109 |
|
|---|
| 3110 | parsers["AmoebaBondForce"] = AmoebaBondGenerator.parseElement
|
|---|
| 3111 |
|
|---|
| 3112 | #=============================================================================================
|
|---|
| 3113 | # Add angle constraint
|
|---|
| 3114 | #=============================================================================================
|
|---|
| 3115 |
|
|---|
| 3116 | def addAngleConstraint(angle, idealAngle, data, sys):
|
|---|
| 3117 |
|
|---|
| 3118 | # Find the two bonds that make this angle.
|
|---|
| 3119 |
|
|---|
| 3120 | bond1 = None
|
|---|
| 3121 | bond2 = None
|
|---|
| 3122 | for bond in data.atomBonds[angle[1]]:
|
|---|
| 3123 | atom1 = data.bonds[bond].atom1
|
|---|
| 3124 | atom2 = data.bonds[bond].atom2
|
|---|
| 3125 | if atom1 == angle[0] or atom2 == angle[0]:
|
|---|
| 3126 | bond1 = bond
|
|---|
| 3127 | elif atom1 == angle[2] or atom2 == angle[2]:
|
|---|
| 3128 | bond2 = bond
|
|---|
| 3129 |
|
|---|
| 3130 | # Compute the distance between atoms and add a constraint
|
|---|
| 3131 |
|
|---|
| 3132 | if bond1 is not None and bond2 is not None:
|
|---|
| 3133 | l1 = data.bonds[bond1].length
|
|---|
| 3134 | l2 = data.bonds[bond2].length
|
|---|
| 3135 | if l1 is not None and l2 is not None:
|
|---|
| 3136 | length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(idealAngle))
|
|---|
| 3137 | data.addConstraint(sys, angle[0], angle[2], length)
|
|---|
| 3138 | return
|
|---|
| 3139 |
|
|---|
| 3140 | #=============================================================================================
|
|---|
| 3141 | ## @private
|
|---|
| 3142 | class AmoebaAngleGenerator(object):
|
|---|
| 3143 |
|
|---|
| 3144 | #=============================================================================================
|
|---|
| 3145 | """An AmoebaAngleGenerator constructs a AmoebaAngleForce."""
|
|---|
| 3146 | #=============================================================================================
|
|---|
| 3147 |
|
|---|
| 3148 | def __init__(self, forceField, cubic, quartic, pentic, sextic):
|
|---|
| 3149 |
|
|---|
| 3150 | self.forceField = forceField
|
|---|
| 3151 | self.cubic = cubic
|
|---|
| 3152 | self.quartic = quartic
|
|---|
| 3153 | self.pentic = pentic
|
|---|
| 3154 | self.sextic = sextic
|
|---|
| 3155 |
|
|---|
| 3156 | self.types1 = []
|
|---|
| 3157 | self.types2 = []
|
|---|
| 3158 | self.types3 = []
|
|---|
| 3159 |
|
|---|
| 3160 | self.angle = []
|
|---|
| 3161 | self.k = []
|
|---|
| 3162 |
|
|---|
| 3163 | #=============================================================================================
|
|---|
| 3164 |
|
|---|
| 3165 | @staticmethod
|
|---|
| 3166 | def parseElement(element, forceField):
|
|---|
| 3167 |
|
|---|
| 3168 | # <AmoebaAngleForce angle-cubic="-0.014" angle-quartic="5.6e-05" angle-pentic="-7e-07" angle-sextic="2.2e-08">
|
|---|
| 3169 | # <Angle class1="2" class2="1" class3="3" k="0.0637259642196" angle1="122.00" />
|
|---|
| 3170 |
|
|---|
| 3171 | generator = AmoebaAngleGenerator(forceField, float(element.attrib['angle-cubic']), float(element.attrib['angle-quartic']), float(element.attrib['angle-pentic']), float(element.attrib['angle-sextic']))
|
|---|
| 3172 | forceField._forces.append(generator)
|
|---|
| 3173 | for angle in element.findall('Angle'):
|
|---|
| 3174 | types = forceField._findAtomTypes(angle.attrib, 3)
|
|---|
| 3175 | if None not in types:
|
|---|
| 3176 |
|
|---|
| 3177 | generator.types1.append(types[0])
|
|---|
| 3178 | generator.types2.append(types[1])
|
|---|
| 3179 | generator.types3.append(types[2])
|
|---|
| 3180 |
|
|---|
| 3181 | angleList = []
|
|---|
| 3182 | angleList.append(float(angle.attrib['angle1']))
|
|---|
| 3183 |
|
|---|
| 3184 | try:
|
|---|
| 3185 | angleList.append(float(angle.attrib['angle2']))
|
|---|
| 3186 | try:
|
|---|
| 3187 | angleList.append(float(angle.attrib['angle3']))
|
|---|
| 3188 | except:
|
|---|
| 3189 | pass
|
|---|
| 3190 | except:
|
|---|
| 3191 | pass
|
|---|
| 3192 | generator.angle.append(angleList)
|
|---|
| 3193 | generator.k.append(float(angle.attrib['k']))
|
|---|
| 3194 | else:
|
|---|
| 3195 | outputString = "AmoebaAngleGenerator: error getting types: %s %s %s" % (
|
|---|
| 3196 | angle.attrib['class1'],
|
|---|
| 3197 | angle.attrib['class2'],
|
|---|
| 3198 | angle.attrib['class3'])
|
|---|
| 3199 | raise ValueError(outputString)
|
|---|
| 3200 |
|
|---|
| 3201 | #=============================================================================================
|
|---|
| 3202 | # createForce is bypassed here since the AmoebaOutOfPlaneBendForce generator must first execute
|
|---|
| 3203 | # and partition angles into in-plane and non-in-plane angles
|
|---|
| 3204 | #=============================================================================================
|
|---|
| 3205 |
|
|---|
| 3206 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 3207 | pass
|
|---|
| 3208 |
|
|---|
| 3209 | #=============================================================================================
|
|---|
| 3210 | # createForcePostOpBendAngle is called by AmoebaOutOfPlaneBendForce with the list of
|
|---|
| 3211 | # non-in-plane angles
|
|---|
| 3212 | #=============================================================================================
|
|---|
| 3213 |
|
|---|
| 3214 | def createForcePostOpBendAngle(self, sys, data, nonbondedMethod, nonbondedCutoff, angleList, args):
|
|---|
| 3215 |
|
|---|
| 3216 | # get force
|
|---|
| 3217 |
|
|---|
| 3218 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3219 | existing = [f for f in existing if type(f) == mm.AmoebaAngleForce]
|
|---|
| 3220 |
|
|---|
| 3221 | if len(existing) == 0:
|
|---|
| 3222 | force = mm.AmoebaAngleForce()
|
|---|
| 3223 | sys.addForce(force)
|
|---|
| 3224 | else:
|
|---|
| 3225 | force = existing[0]
|
|---|
| 3226 |
|
|---|
| 3227 | # set scalars
|
|---|
| 3228 |
|
|---|
| 3229 | force.setAmoebaGlobalAngleCubic(self.cubic)
|
|---|
| 3230 | force.setAmoebaGlobalAngleQuartic(self.quartic)
|
|---|
| 3231 | force.setAmoebaGlobalAnglePentic(self.pentic)
|
|---|
| 3232 | force.setAmoebaGlobalAngleSextic(self.sextic)
|
|---|
| 3233 |
|
|---|
| 3234 | for angleDict in angleList:
|
|---|
| 3235 | angle = angleDict['angle']
|
|---|
| 3236 | isConstrained = angleDict['isConstrained']
|
|---|
| 3237 |
|
|---|
| 3238 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 3239 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 3240 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 3241 | for i in range(len(self.types1)):
|
|---|
| 3242 | types1 = self.types1[i]
|
|---|
| 3243 | types2 = self.types2[i]
|
|---|
| 3244 | types3 = self.types3[i]
|
|---|
| 3245 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 3246 | if isConstrained and self.k[i] != 0.0:
|
|---|
| 3247 | angleDict['idealAngle'] = self.angle[i][0]
|
|---|
| 3248 | addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys)
|
|---|
| 3249 | elif self.k[i] != 0:
|
|---|
| 3250 | lenAngle = len(self.angle[i])
|
|---|
| 3251 | if (lenAngle > 1):
|
|---|
| 3252 | # get k-index by counting number of non-angle hydrogens on the central atom
|
|---|
| 3253 | # based on kangle.f
|
|---|
| 3254 | numberOfHydrogens = 0
|
|---|
| 3255 | for bond in data.atomBonds[angle[1]]:
|
|---|
| 3256 | atom1 = data.bonds[bond].atom1
|
|---|
| 3257 | atom2 = data.bonds[bond].atom2
|
|---|
| 3258 | if (atom1 == angle[1] and atom2 != angle[0] and atom2 != angle[2] and (sys.getParticleMass(atom2)/unit.dalton) < 1.90):
|
|---|
| 3259 | numberOfHydrogens += 1
|
|---|
| 3260 | if (atom2 == angle[1] and atom1 != angle[0] and atom1 != angle[2] and (sys.getParticleMass(atom1)/unit.dalton) < 1.90):
|
|---|
| 3261 | numberOfHydrogens += 1
|
|---|
| 3262 | if (numberOfHydrogens < lenAngle):
|
|---|
| 3263 | angleValue = self.angle[i][numberOfHydrogens]
|
|---|
| 3264 | else:
|
|---|
| 3265 | outputString = "AmoebaAngleGenerator angle index=%d is out of range: [0, %5d] " % (numberOfHydrogens, lenAngle)
|
|---|
| 3266 | raise ValueError(outputString)
|
|---|
| 3267 | else:
|
|---|
| 3268 | angleValue = self.angle[i][0]
|
|---|
| 3269 |
|
|---|
| 3270 | angleDict['idealAngle'] = angleValue
|
|---|
| 3271 | force.addAngle(angle[0], angle[1], angle[2], angleValue, self.k[i])
|
|---|
| 3272 | break
|
|---|
| 3273 |
|
|---|
| 3274 | #=============================================================================================
|
|---|
| 3275 | # createForcePostOpBendInPlaneAngle is called by AmoebaOutOfPlaneBendForce with the list of
|
|---|
| 3276 | # in-plane angles
|
|---|
| 3277 | #=============================================================================================
|
|---|
| 3278 |
|
|---|
| 3279 | def createForcePostOpBendInPlaneAngle(self, sys, data, nonbondedMethod, nonbondedCutoff, angleList, args):
|
|---|
| 3280 |
|
|---|
| 3281 | # get force
|
|---|
| 3282 |
|
|---|
| 3283 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3284 | existing = [f for f in existing if type(f) == mm.AmoebaInPlaneAngleForce]
|
|---|
| 3285 |
|
|---|
| 3286 | if len(existing) == 0:
|
|---|
| 3287 | force = mm.AmoebaInPlaneAngleForce()
|
|---|
| 3288 | sys.addForce(force)
|
|---|
| 3289 | else:
|
|---|
| 3290 | force = existing[0]
|
|---|
| 3291 |
|
|---|
| 3292 | # scalars
|
|---|
| 3293 |
|
|---|
| 3294 | force.setAmoebaGlobalInPlaneAngleCubic(self.cubic)
|
|---|
| 3295 | force.setAmoebaGlobalInPlaneAngleQuartic(self.quartic)
|
|---|
| 3296 | force.setAmoebaGlobalInPlaneAnglePentic(self.pentic)
|
|---|
| 3297 | force.setAmoebaGlobalInPlaneAngleSextic(self.sextic)
|
|---|
| 3298 |
|
|---|
| 3299 | for angleDict in angleList:
|
|---|
| 3300 |
|
|---|
| 3301 | angle = angleDict['angle']
|
|---|
| 3302 | isConstrained = angleDict['isConstrained']
|
|---|
| 3303 |
|
|---|
| 3304 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 3305 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 3306 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 3307 |
|
|---|
| 3308 | for i in range(len(self.types1)):
|
|---|
| 3309 |
|
|---|
| 3310 | types1 = self.types1[i]
|
|---|
| 3311 | types2 = self.types2[i]
|
|---|
| 3312 | types3 = self.types3[i]
|
|---|
| 3313 |
|
|---|
| 3314 | if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
|
|---|
| 3315 | angleDict['idealAngle'] = self.angle[i][0]
|
|---|
| 3316 | if (isConstrained and self.k[i] != 0.0):
|
|---|
| 3317 | addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys)
|
|---|
| 3318 | else:
|
|---|
| 3319 | force.addAngle(angle[0], angle[1], angle[2], angle[3], self.angle[i][0], self.k[i])
|
|---|
| 3320 | break
|
|---|
| 3321 |
|
|---|
| 3322 | parsers["AmoebaAngleForce"] = AmoebaAngleGenerator.parseElement
|
|---|
| 3323 |
|
|---|
| 3324 | #=============================================================================================
|
|---|
| 3325 | # Generator for the AmoebaOutOfPlaneBend covalent force; also calls methods in the
|
|---|
| 3326 | # AmoebaAngleGenerator to generate the AmoebaAngleForce and
|
|---|
| 3327 | # AmoebaInPlaneAngleForce
|
|---|
| 3328 | #=============================================================================================
|
|---|
| 3329 |
|
|---|
| 3330 | ## @private
|
|---|
| 3331 | class AmoebaOutOfPlaneBendGenerator(object):
|
|---|
| 3332 |
|
|---|
| 3333 | #=============================================================================================
|
|---|
| 3334 |
|
|---|
| 3335 | """An AmoebaOutOfPlaneBendGenerator constructs a AmoebaOutOfPlaneBendForce."""
|
|---|
| 3336 |
|
|---|
| 3337 | #=============================================================================================
|
|---|
| 3338 |
|
|---|
| 3339 | def __init__(self, forceField, type, cubic, quartic, pentic, sextic):
|
|---|
| 3340 |
|
|---|
| 3341 | self.forceField = forceField
|
|---|
| 3342 | self.type = type
|
|---|
| 3343 | self.cubic = cubic
|
|---|
| 3344 | self.quartic = quartic
|
|---|
| 3345 | self.pentic = pentic
|
|---|
| 3346 | self.sextic = sextic
|
|---|
| 3347 |
|
|---|
| 3348 | self.types1 = []
|
|---|
| 3349 | self.types2 = []
|
|---|
| 3350 | self.types3 = []
|
|---|
| 3351 | self.types4 = []
|
|---|
| 3352 |
|
|---|
| 3353 | self.ks = []
|
|---|
| 3354 |
|
|---|
| 3355 | #=============================================================================================
|
|---|
| 3356 | # Local version of findAtomTypes needed since class indices are 0 (i.e., not recognized)
|
|---|
| 3357 | # for types3 and 4
|
|---|
| 3358 | #=============================================================================================
|
|---|
| 3359 |
|
|---|
| 3360 | def findAtomTypes(self, forceField, node, num):
|
|---|
| 3361 | """Parse the attributes on an XML tag to find the set of atom types for each atom it involves."""
|
|---|
| 3362 | types = []
|
|---|
| 3363 | attrib = node.attrib
|
|---|
| 3364 | for i in range(num):
|
|---|
| 3365 | if num == 1:
|
|---|
| 3366 | suffix = ''
|
|---|
| 3367 | else:
|
|---|
| 3368 | suffix = str(i+1)
|
|---|
| 3369 | classAttrib = 'class'+suffix
|
|---|
| 3370 | if classAttrib in attrib:
|
|---|
| 3371 | if attrib[classAttrib] in forceField._atomClasses:
|
|---|
| 3372 | types.append(forceField._atomClasses[attrib[classAttrib]])
|
|---|
| 3373 | else:
|
|---|
| 3374 | types.append(set())
|
|---|
| 3375 | return types
|
|---|
| 3376 |
|
|---|
| 3377 | #=============================================================================================
|
|---|
| 3378 |
|
|---|
| 3379 | @staticmethod
|
|---|
| 3380 | def parseElement(element, forceField):
|
|---|
| 3381 |
|
|---|
| 3382 | # <AmoebaOutOfPlaneBendForce type="ALLINGER" opbend-cubic="-0.014" opbend-quartic="5.6e-05" opbend-pentic="-7e-07" opbend-sextic="2.2e-08">
|
|---|
| 3383 | # <Angle class1="2" class2="1" class3="0" class4="0" k="0.0531474541591"/>
|
|---|
| 3384 | # <Angle class1="3" class2="1" class3="0" class4="0" k="0.0898536095496"/>
|
|---|
| 3385 |
|
|---|
| 3386 | # get global scalar parameters
|
|---|
| 3387 |
|
|---|
| 3388 | generator = AmoebaOutOfPlaneBendGenerator(forceField, element.attrib['type'],
|
|---|
| 3389 | float(element.attrib['opbend-cubic']),
|
|---|
| 3390 | float(element.attrib['opbend-quartic']),
|
|---|
| 3391 | float(element.attrib['opbend-pentic']),
|
|---|
| 3392 | float(element.attrib['opbend-sextic']))
|
|---|
| 3393 |
|
|---|
| 3394 | forceField._forces.append(generator)
|
|---|
| 3395 |
|
|---|
| 3396 | for angle in element.findall('Angle'):
|
|---|
| 3397 | types = generator.findAtomTypes(forceField, angle, 4)
|
|---|
| 3398 | if types is not None:
|
|---|
| 3399 |
|
|---|
| 3400 | generator.types1.append(types[0])
|
|---|
| 3401 | generator.types2.append(types[1])
|
|---|
| 3402 | generator.types3.append(types[2])
|
|---|
| 3403 | generator.types4.append(types[3])
|
|---|
| 3404 |
|
|---|
| 3405 | generator.ks.append(float(angle.attrib['k']))
|
|---|
| 3406 |
|
|---|
| 3407 | else:
|
|---|
| 3408 | outputString = "AmoebaOutOfPlaneBendGenerator error getting types: %s %s %s %s." % (
|
|---|
| 3409 | angle.attrib['class1'], angle.attrib['class2'], angle.attrib['class3'], angle.attrib['class4'])
|
|---|
| 3410 | raise ValueError(outputString)
|
|---|
| 3411 |
|
|---|
| 3412 | #=============================================================================================
|
|---|
| 3413 | # Get middle atom in a angle
|
|---|
| 3414 | # return index of middle atom or -1 if no middle is found
|
|---|
| 3415 | # This method appears not to be needed since the angle[1] entry appears to always
|
|---|
| 3416 | # be the middle atom. However, was unsure if this is guaranteed
|
|---|
| 3417 | #=============================================================================================
|
|---|
| 3418 |
|
|---|
| 3419 | def getMiddleAtom(self, angle, data):
|
|---|
| 3420 |
|
|---|
| 3421 | # find atom shared by both bonds making up the angle
|
|---|
| 3422 |
|
|---|
| 3423 | middleAtom = -1
|
|---|
| 3424 | for atomIndex in angle:
|
|---|
| 3425 | isMiddle = 0
|
|---|
| 3426 | for bond in data.atomBonds[atomIndex]:
|
|---|
| 3427 | atom1 = data.bonds[bond].atom1
|
|---|
| 3428 | atom2 = data.bonds[bond].atom2
|
|---|
| 3429 | if (atom1 != atomIndex):
|
|---|
| 3430 | partner = atom1
|
|---|
| 3431 | else:
|
|---|
| 3432 | partner = atom2
|
|---|
| 3433 | if (partner == angle[0] or partner == angle[1] or partner == angle[2]):
|
|---|
| 3434 | isMiddle += 1
|
|---|
| 3435 |
|
|---|
| 3436 | if (isMiddle == 2):
|
|---|
| 3437 | return atomIndex
|
|---|
| 3438 | return -1
|
|---|
| 3439 |
|
|---|
| 3440 | #=============================================================================================
|
|---|
| 3441 |
|
|---|
| 3442 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 3443 |
|
|---|
| 3444 | # get force
|
|---|
| 3445 |
|
|---|
| 3446 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3447 | existing = [f for f in existing if type(f) == mm.AmoebaOutOfPlaneBendForce]
|
|---|
| 3448 | if len(existing) == 0:
|
|---|
| 3449 | force = mm.AmoebaOutOfPlaneBendForce()
|
|---|
| 3450 | sys.addForce(force)
|
|---|
| 3451 | else:
|
|---|
| 3452 | force = existing[0]
|
|---|
| 3453 |
|
|---|
| 3454 | # set scalars
|
|---|
| 3455 |
|
|---|
| 3456 | force.setAmoebaGlobalOutOfPlaneBendCubic( self.cubic)
|
|---|
| 3457 | force.setAmoebaGlobalOutOfPlaneBendQuartic(self.quartic)
|
|---|
| 3458 | force.setAmoebaGlobalOutOfPlaneBendPentic( self.pentic)
|
|---|
| 3459 | force.setAmoebaGlobalOutOfPlaneBendSextic( self.sextic)
|
|---|
| 3460 |
|
|---|
| 3461 | # this hash is used to insure the out-of-plane-bend bonds
|
|---|
| 3462 | # are only added once
|
|---|
| 3463 |
|
|---|
| 3464 | skipAtoms = dict()
|
|---|
| 3465 |
|
|---|
| 3466 | # these lists are used in the partitioning of the angles into
|
|---|
| 3467 | # angle and inPlane angles
|
|---|
| 3468 |
|
|---|
| 3469 | inPlaneAngles = []
|
|---|
| 3470 | nonInPlaneAngles = []
|
|---|
| 3471 | nonInPlaneAnglesConstrained = []
|
|---|
| 3472 | idealAngles = []*len(data.angles)
|
|---|
| 3473 |
|
|---|
| 3474 | for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
|
|---|
| 3475 |
|
|---|
| 3476 | middleAtom = self.getMiddleAtom(angle, data)
|
|---|
| 3477 | if (middleAtom > -1):
|
|---|
| 3478 | middleType = data.atomType[data.atoms[middleAtom]]
|
|---|
| 3479 | middleCovalency = len(data.atomBonds[middleAtom])
|
|---|
| 3480 | else:
|
|---|
| 3481 | middleType = -1
|
|---|
| 3482 | middleCovalency = -1
|
|---|
| 3483 |
|
|---|
| 3484 | # if middle atom has covalency of 3 and
|
|---|
| 3485 | # the types of the middle atom and the partner atom (atom bonded to
|
|---|
| 3486 | # middle atom, but not in angle) match types1 and types2, then
|
|---|
| 3487 | # three out-of-plane bend angles are generated. Three in-plane angle
|
|---|
| 3488 | # are also generated. If the conditions are not satisfied, the angle is marked as 'generic' angle (not a in-plane angle)
|
|---|
| 3489 |
|
|---|
| 3490 | if (middleAtom > -1 and middleCovalency == 3 and middleAtom not in skipAtoms):
|
|---|
| 3491 |
|
|---|
| 3492 | partners = []
|
|---|
| 3493 | partnerSet = set()
|
|---|
| 3494 | partnerTypes = []
|
|---|
| 3495 | partnerK = []
|
|---|
| 3496 |
|
|---|
| 3497 | for bond in data.atomBonds[middleAtom]:
|
|---|
| 3498 | atom1 = data.bonds[bond].atom1
|
|---|
| 3499 | atom2 = data.bonds[bond].atom2
|
|---|
| 3500 | if (atom1 != middleAtom):
|
|---|
| 3501 | partner = atom1
|
|---|
| 3502 | else:
|
|---|
| 3503 | partner = atom2
|
|---|
| 3504 |
|
|---|
| 3505 | partnerType = data.atomType[data.atoms[partner]]
|
|---|
| 3506 | for i in range(len(self.types1)):
|
|---|
| 3507 | types1 = self.types1[i]
|
|---|
| 3508 | types2 = self.types2[i]
|
|---|
| 3509 | if (middleType in types2 and partnerType in types1):
|
|---|
| 3510 | partners.append(partner)
|
|---|
| 3511 | partnerSet.add(partner)
|
|---|
| 3512 | partnerTypes.append(partnerType)
|
|---|
| 3513 | partnerK.append(self.ks[i])
|
|---|
| 3514 |
|
|---|
| 3515 | if (len(partners) == 3):
|
|---|
| 3516 |
|
|---|
| 3517 | force.addOutOfPlaneBend(partners[0], middleAtom, partners[1], partners[2], partnerK[2])
|
|---|
| 3518 | force.addOutOfPlaneBend(partners[0], middleAtom, partners[2], partners[1], partnerK[1])
|
|---|
| 3519 | force.addOutOfPlaneBend(partners[1], middleAtom, partners[2], partners[0], partnerK[0])
|
|---|
| 3520 |
|
|---|
| 3521 | # skipAtoms is used to insure angles are only included once
|
|---|
| 3522 |
|
|---|
| 3523 | skipAtoms[middleAtom] = set()
|
|---|
| 3524 | skipAtoms[middleAtom].add(partners[0])
|
|---|
| 3525 | skipAtoms[middleAtom].add(partners[1])
|
|---|
| 3526 | skipAtoms[middleAtom].add(partners[2])
|
|---|
| 3527 |
|
|---|
| 3528 | # in-plane angle
|
|---|
| 3529 |
|
|---|
| 3530 | angleDict = {}
|
|---|
| 3531 | angleList = []
|
|---|
| 3532 | angleList.append(angle[0])
|
|---|
| 3533 | angleList.append(angle[1])
|
|---|
| 3534 | angleList.append(angle[2])
|
|---|
| 3535 | angleDict['angle'] = angleList
|
|---|
| 3536 |
|
|---|
| 3537 | angleDict['isConstrained'] = 0
|
|---|
| 3538 |
|
|---|
| 3539 | angleSet = set()
|
|---|
| 3540 | angleSet.add(angle[0])
|
|---|
| 3541 | angleSet.add(angle[1])
|
|---|
| 3542 | angleSet.add(angle[2])
|
|---|
| 3543 |
|
|---|
| 3544 | for atomIndex in partnerSet:
|
|---|
| 3545 | if (atomIndex not in angleSet):
|
|---|
| 3546 | angleList.append(atomIndex)
|
|---|
| 3547 |
|
|---|
| 3548 | inPlaneAngles.append(angleDict)
|
|---|
| 3549 |
|
|---|
| 3550 | else:
|
|---|
| 3551 | angleDict = {}
|
|---|
| 3552 | angleDict['angle'] = angle
|
|---|
| 3553 | angleDict['isConstrained'] = isConstrained
|
|---|
| 3554 | nonInPlaneAngles.append(angleDict)
|
|---|
| 3555 | else:
|
|---|
| 3556 | if (middleAtom > -1 and middleCovalency == 3 and middleAtom in skipAtoms):
|
|---|
| 3557 |
|
|---|
| 3558 | partnerSet = skipAtoms[middleAtom]
|
|---|
| 3559 |
|
|---|
| 3560 | angleDict = {}
|
|---|
| 3561 |
|
|---|
| 3562 | angleList = []
|
|---|
| 3563 | angleList.append(angle[0])
|
|---|
| 3564 | angleList.append(angle[1])
|
|---|
| 3565 | angleList.append(angle[2])
|
|---|
| 3566 | angleDict['angle'] = angleList
|
|---|
| 3567 |
|
|---|
| 3568 | angleDict['isConstrained'] = isConstrained
|
|---|
| 3569 |
|
|---|
| 3570 | angleSet = set()
|
|---|
| 3571 | angleSet.add(angle[0])
|
|---|
| 3572 | angleSet.add(angle[1])
|
|---|
| 3573 | angleSet.add(angle[2])
|
|---|
| 3574 |
|
|---|
| 3575 | for atomIndex in partnerSet:
|
|---|
| 3576 | if (atomIndex not in angleSet):
|
|---|
| 3577 | angleList.append(atomIndex)
|
|---|
| 3578 |
|
|---|
| 3579 | inPlaneAngles.append(angleDict)
|
|---|
| 3580 |
|
|---|
| 3581 | else:
|
|---|
| 3582 | angleDict = {}
|
|---|
| 3583 | angleDict['angle'] = angle
|
|---|
| 3584 | angleDict['isConstrained'] = isConstrained
|
|---|
| 3585 | nonInPlaneAngles.append(angleDict)
|
|---|
| 3586 |
|
|---|
| 3587 | # get AmoebaAngleGenerator and add AmoebaAngle and AmoebaInPlaneAngle forces
|
|---|
| 3588 |
|
|---|
| 3589 | for force in self.forceField._forces:
|
|---|
| 3590 | if (force.__class__.__name__ == 'AmoebaAngleGenerator'):
|
|---|
| 3591 | force.createForcePostOpBendAngle(sys, data, nonbondedMethod, nonbondedCutoff, nonInPlaneAngles, args)
|
|---|
| 3592 | force.createForcePostOpBendInPlaneAngle(sys, data, nonbondedMethod, nonbondedCutoff, inPlaneAngles, args)
|
|---|
| 3593 |
|
|---|
| 3594 | for force in self.forceField._forces:
|
|---|
| 3595 | if (force.__class__.__name__ == 'AmoebaStretchBendGenerator'):
|
|---|
| 3596 | for angleDict in inPlaneAngles:
|
|---|
| 3597 | nonInPlaneAngles.append(angleDict)
|
|---|
| 3598 | force.createForcePostAmoebaBondForce(sys, data, nonbondedMethod, nonbondedCutoff, nonInPlaneAngles, args)
|
|---|
| 3599 |
|
|---|
| 3600 | parsers["AmoebaOutOfPlaneBendForce"] = AmoebaOutOfPlaneBendGenerator.parseElement
|
|---|
| 3601 |
|
|---|
| 3602 | #=============================================================================================
|
|---|
| 3603 |
|
|---|
| 3604 | ## @private
|
|---|
| 3605 | class AmoebaTorsionGenerator(object):
|
|---|
| 3606 |
|
|---|
| 3607 | #=============================================================================================
|
|---|
| 3608 | """An AmoebaTorsionGenerator constructs a AmoebaTorsionForce."""
|
|---|
| 3609 | #=============================================================================================
|
|---|
| 3610 |
|
|---|
| 3611 | def __init__(self, torsionUnit):
|
|---|
| 3612 |
|
|---|
| 3613 | self.torsionUnit = torsionUnit
|
|---|
| 3614 |
|
|---|
| 3615 | self.types1 = []
|
|---|
| 3616 | self.types2 = []
|
|---|
| 3617 | self.types3 = []
|
|---|
| 3618 | self.types4 = []
|
|---|
| 3619 |
|
|---|
| 3620 | self.t1 = []
|
|---|
| 3621 | self.t2 = []
|
|---|
| 3622 | self.t3 = []
|
|---|
| 3623 |
|
|---|
| 3624 | #=============================================================================================
|
|---|
| 3625 |
|
|---|
| 3626 | @staticmethod
|
|---|
| 3627 | def parseElement(element, forceField):
|
|---|
| 3628 |
|
|---|
| 3629 | # <AmoebaTorsionForce torsionUnit="0.5">
|
|---|
| 3630 | # <Torsion class1="3" class2="1" class3="2" class4="3" amp1="0.0" angle1="0.0" amp2="0.0" angle2="3.14159265359" amp3="0.0" angle3="0.0" />
|
|---|
| 3631 | # <Torsion class1="3" class2="1" class3="2" class4="6" amp1="0.0" angle1="0.0" amp2="0.0" angle2="3.14159265359" amp3="-0.263592" angle3="0.0" />
|
|---|
| 3632 |
|
|---|
| 3633 | generator = AmoebaTorsionGenerator(float(element.attrib['torsionUnit']))
|
|---|
| 3634 | forceField._forces.append(generator)
|
|---|
| 3635 |
|
|---|
| 3636 | # collect particle classes and t1,t2,t3,
|
|---|
| 3637 | # where ti=[amplitude_i,angle_i]
|
|---|
| 3638 |
|
|---|
| 3639 | for torsion in element.findall('Torsion'):
|
|---|
| 3640 | types = forceField._findAtomTypes(torsion.attrib, 4)
|
|---|
| 3641 | if None not in types:
|
|---|
| 3642 |
|
|---|
| 3643 | generator.types1.append(types[0])
|
|---|
| 3644 | generator.types2.append(types[1])
|
|---|
| 3645 | generator.types3.append(types[2])
|
|---|
| 3646 | generator.types4.append(types[3])
|
|---|
| 3647 |
|
|---|
| 3648 | for ii in range(1,4):
|
|---|
| 3649 | tInfo = []
|
|---|
| 3650 | suffix = str(ii)
|
|---|
| 3651 | ampName = 'amp' + suffix
|
|---|
| 3652 | tInfo.append(float(torsion.attrib[ampName]))
|
|---|
| 3653 |
|
|---|
| 3654 | angName = 'angle' + suffix
|
|---|
| 3655 | tInfo.append(float(torsion.attrib[angName]))
|
|---|
| 3656 |
|
|---|
| 3657 | if (ii == 1):
|
|---|
| 3658 | generator.t1.append(tInfo)
|
|---|
| 3659 | elif (ii == 2):
|
|---|
| 3660 | generator.t2.append(tInfo)
|
|---|
| 3661 | elif (ii == 3):
|
|---|
| 3662 | generator.t3.append(tInfo)
|
|---|
| 3663 |
|
|---|
| 3664 | else:
|
|---|
| 3665 | outputString = "AmoebaTorsionGenerator: error getting types: %s %s %s %s" % (
|
|---|
| 3666 | torsion.attrib['class1'],
|
|---|
| 3667 | torsion.attrib['class2'],
|
|---|
| 3668 | torsion.attrib['class3'],
|
|---|
| 3669 | torsion.attrib['class4'])
|
|---|
| 3670 | raise ValueError(outputString)
|
|---|
| 3671 |
|
|---|
| 3672 | #=============================================================================================
|
|---|
| 3673 |
|
|---|
| 3674 | def createForce(self, sys, data, nontorsionedMethod, nontorsionedCutoff, args):
|
|---|
| 3675 |
|
|---|
| 3676 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3677 | existing = [f for f in existing if type(f) == mm.PeriodicTorsionForce]
|
|---|
| 3678 | if len(existing) == 0:
|
|---|
| 3679 | force = mm.PeriodicTorsionForce()
|
|---|
| 3680 | sys.addForce(force)
|
|---|
| 3681 | else:
|
|---|
| 3682 | force = existing[0]
|
|---|
| 3683 |
|
|---|
| 3684 | for torsion in data.propers:
|
|---|
| 3685 |
|
|---|
| 3686 | type1 = data.atomType[data.atoms[torsion[0]]]
|
|---|
| 3687 | type2 = data.atomType[data.atoms[torsion[1]]]
|
|---|
| 3688 | type3 = data.atomType[data.atoms[torsion[2]]]
|
|---|
| 3689 | type4 = data.atomType[data.atoms[torsion[3]]]
|
|---|
| 3690 |
|
|---|
| 3691 | for i in range(len(self.types1)):
|
|---|
| 3692 |
|
|---|
| 3693 | types1 = self.types1[i]
|
|---|
| 3694 | types2 = self.types2[i]
|
|---|
| 3695 | types3 = self.types3[i]
|
|---|
| 3696 | types4 = self.types4[i]
|
|---|
| 3697 |
|
|---|
| 3698 | # match types in forward or reverse direction
|
|---|
| 3699 |
|
|---|
| 3700 | if (type1 in types1 and type2 in types2 and type3 in types3 and type4 in types4) or (type4 in types1 and type3 in types2 and type2 in types3 and type1 in types4):
|
|---|
| 3701 | if self.t1[i][0] != 0:
|
|---|
| 3702 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], 1, self.t1[i][1], self.t1[i][0])
|
|---|
| 3703 | if self.t2[i][0] != 0:
|
|---|
| 3704 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], 2, self.t2[i][1], self.t2[i][0])
|
|---|
| 3705 | if self.t3[i][0] != 0:
|
|---|
| 3706 | force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], 3, self.t3[i][1], self.t3[i][0])
|
|---|
| 3707 | break
|
|---|
| 3708 |
|
|---|
| 3709 | parsers["AmoebaTorsionForce"] = AmoebaTorsionGenerator.parseElement
|
|---|
| 3710 |
|
|---|
| 3711 | #=============================================================================================
|
|---|
| 3712 |
|
|---|
| 3713 | ## @private
|
|---|
| 3714 | class AmoebaPiTorsionGenerator(object):
|
|---|
| 3715 |
|
|---|
| 3716 | #=============================================================================================
|
|---|
| 3717 |
|
|---|
| 3718 | """An AmoebaPiTorsionGenerator constructs a AmoebaPiTorsionForce."""
|
|---|
| 3719 |
|
|---|
| 3720 | #=============================================================================================
|
|---|
| 3721 |
|
|---|
| 3722 | def __init__(self, piTorsionUnit):
|
|---|
| 3723 | self.piTorsionUnit = piTorsionUnit
|
|---|
| 3724 | self.types1 = []
|
|---|
| 3725 | self.types2 = []
|
|---|
| 3726 | self.k = []
|
|---|
| 3727 |
|
|---|
| 3728 | #=============================================================================================
|
|---|
| 3729 |
|
|---|
| 3730 | @staticmethod
|
|---|
| 3731 | def parseElement(element, forceField):
|
|---|
| 3732 |
|
|---|
| 3733 | # <AmoebaPiTorsionForce piTorsionUnit="1.0">
|
|---|
| 3734 | # <PiTorsion class1="1" class2="3" k="28.6604" />
|
|---|
| 3735 |
|
|---|
| 3736 | generator = AmoebaPiTorsionGenerator(float(element.attrib['piTorsionUnit']))
|
|---|
| 3737 | forceField._forces.append(generator)
|
|---|
| 3738 |
|
|---|
| 3739 | for piTorsion in element.findall('PiTorsion'):
|
|---|
| 3740 | types = forceField._findAtomTypes(piTorsion.attrib, 2)
|
|---|
| 3741 | if None not in types:
|
|---|
| 3742 | generator.types1.append(types[0])
|
|---|
| 3743 | generator.types2.append(types[1])
|
|---|
| 3744 | generator.k.append(float(piTorsion.attrib['k']))
|
|---|
| 3745 | else:
|
|---|
| 3746 | outputString = "AmoebaPiTorsionGenerator: error getting types: %s %s " % (
|
|---|
| 3747 | piTorsion.attrib['class1'],
|
|---|
| 3748 | piTorsion.attrib['class2'])
|
|---|
| 3749 | raise ValueError(outputString)
|
|---|
| 3750 |
|
|---|
| 3751 | #=============================================================================================
|
|---|
| 3752 |
|
|---|
| 3753 | def createForce(self, sys, data, nonpiTorsionedMethod, nonpiTorsionedCutoff, args):
|
|---|
| 3754 |
|
|---|
| 3755 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3756 | existing = [f for f in existing if type(f) == mm.AmoebaPiTorsionForce]
|
|---|
| 3757 |
|
|---|
| 3758 | if len(existing) == 0:
|
|---|
| 3759 | force = mm.AmoebaPiTorsionForce()
|
|---|
| 3760 | sys.addForce(force)
|
|---|
| 3761 | else:
|
|---|
| 3762 | force = existing[0]
|
|---|
| 3763 |
|
|---|
| 3764 | for bond in data.bonds:
|
|---|
| 3765 |
|
|---|
| 3766 | # search for bonds with both atoms in bond having covalency == 3
|
|---|
| 3767 |
|
|---|
| 3768 | atom1 = bond.atom1
|
|---|
| 3769 | atom2 = bond.atom2
|
|---|
| 3770 |
|
|---|
| 3771 | if (len(data.atomBonds[atom1]) == 3 and len(data.atomBonds[atom2]) == 3):
|
|---|
| 3772 |
|
|---|
| 3773 | type1 = data.atomType[data.atoms[atom1]]
|
|---|
| 3774 | type2 = data.atomType[data.atoms[atom2]]
|
|---|
| 3775 |
|
|---|
| 3776 | for i in range(len(self.types1)):
|
|---|
| 3777 |
|
|---|
| 3778 | types1 = self.types1[i]
|
|---|
| 3779 | types2 = self.types2[i]
|
|---|
| 3780 |
|
|---|
| 3781 | if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
|
|---|
| 3782 |
|
|---|
| 3783 | # piTorsionAtom1, piTorsionAtom2 are the atoms bonded to atom1, excluding atom2
|
|---|
| 3784 | # piTorsionAtom5, piTorsionAtom6 are the atoms bonded to atom2, excluding atom1
|
|---|
| 3785 |
|
|---|
| 3786 | piTorsionAtom1 = -1
|
|---|
| 3787 | piTorsionAtom2 = -1
|
|---|
| 3788 | piTorsionAtom3 = atom1
|
|---|
| 3789 |
|
|---|
| 3790 | piTorsionAtom4 = atom2
|
|---|
| 3791 | piTorsionAtom5 = -1
|
|---|
| 3792 | piTorsionAtom6 = -1
|
|---|
| 3793 |
|
|---|
| 3794 | for bond in data.atomBonds[atom1]:
|
|---|
| 3795 | bondedAtom1 = data.bonds[bond].atom1
|
|---|
| 3796 | bondedAtom2 = data.bonds[bond].atom2
|
|---|
| 3797 | if (bondedAtom1 != atom1):
|
|---|
| 3798 | b1 = bondedAtom1
|
|---|
| 3799 | else:
|
|---|
| 3800 | b1 = bondedAtom2
|
|---|
| 3801 | if (b1 != atom2):
|
|---|
| 3802 | if (piTorsionAtom1 == -1):
|
|---|
| 3803 | piTorsionAtom1 = b1
|
|---|
| 3804 | else:
|
|---|
| 3805 | piTorsionAtom2 = b1
|
|---|
| 3806 |
|
|---|
| 3807 | for bond in data.atomBonds[atom2]:
|
|---|
| 3808 | bondedAtom1 = data.bonds[bond].atom1
|
|---|
| 3809 | bondedAtom2 = data.bonds[bond].atom2
|
|---|
| 3810 | if (bondedAtom1 != atom2):
|
|---|
| 3811 | b1 = bondedAtom1
|
|---|
| 3812 | else:
|
|---|
| 3813 | b1 = bondedAtom2
|
|---|
| 3814 |
|
|---|
| 3815 | if (b1 != atom1):
|
|---|
| 3816 | if (piTorsionAtom5 == -1):
|
|---|
| 3817 | piTorsionAtom5 = b1
|
|---|
| 3818 | else:
|
|---|
| 3819 | piTorsionAtom6 = b1
|
|---|
| 3820 |
|
|---|
| 3821 | force.addPiTorsion(piTorsionAtom1, piTorsionAtom2, piTorsionAtom3, piTorsionAtom4, piTorsionAtom5, piTorsionAtom6, self.k[i])
|
|---|
| 3822 |
|
|---|
| 3823 | parsers["AmoebaPiTorsionForce"] = AmoebaPiTorsionGenerator.parseElement
|
|---|
| 3824 |
|
|---|
| 3825 | #=============================================================================================
|
|---|
| 3826 |
|
|---|
| 3827 | ## @private
|
|---|
| 3828 | class AmoebaTorsionTorsionGenerator(object):
|
|---|
| 3829 |
|
|---|
| 3830 | #=============================================================================================
|
|---|
| 3831 | """An AmoebaTorsionTorsionGenerator constructs a AmoebaTorsionTorsionForce."""
|
|---|
| 3832 | #=============================================================================================
|
|---|
| 3833 |
|
|---|
| 3834 | def __init__(self):
|
|---|
| 3835 |
|
|---|
| 3836 | self.types1 = []
|
|---|
| 3837 | self.types2 = []
|
|---|
| 3838 | self.types3 = []
|
|---|
| 3839 | self.types4 = []
|
|---|
| 3840 | self.types5 = []
|
|---|
| 3841 |
|
|---|
| 3842 | self.gridIndex = []
|
|---|
| 3843 |
|
|---|
| 3844 | self.grids = []
|
|---|
| 3845 |
|
|---|
| 3846 | #=============================================================================================
|
|---|
| 3847 |
|
|---|
| 3848 | @staticmethod
|
|---|
| 3849 | def parseElement(element, forceField):
|
|---|
| 3850 |
|
|---|
| 3851 | generator = AmoebaTorsionTorsionGenerator()
|
|---|
| 3852 | forceField._forces.append(generator)
|
|---|
| 3853 | maxGridIndex = -1
|
|---|
| 3854 |
|
|---|
| 3855 | # <AmoebaTorsionTorsionForce >
|
|---|
| 3856 | # <TorsionTorsion class1="3" class2="1" class3="2" class4="3" class5="1" grid="0" nx="25" ny="25" />
|
|---|
| 3857 |
|
|---|
| 3858 | for torsionTorsion in element.findall('TorsionTorsion'):
|
|---|
| 3859 | types = forceField._findAtomTypes(torsionTorsion.attrib, 5)
|
|---|
| 3860 | if None not in types:
|
|---|
| 3861 |
|
|---|
| 3862 | generator.types1.append(types[0])
|
|---|
| 3863 | generator.types2.append(types[1])
|
|---|
| 3864 | generator.types3.append(types[2])
|
|---|
| 3865 | generator.types4.append(types[3])
|
|---|
| 3866 | generator.types5.append(types[4])
|
|---|
| 3867 |
|
|---|
| 3868 | gridIndex = int(torsionTorsion.attrib['grid'])
|
|---|
| 3869 | if (gridIndex > maxGridIndex):
|
|---|
| 3870 | maxGridIndex = gridIndex
|
|---|
| 3871 |
|
|---|
| 3872 | generator.gridIndex.append(gridIndex)
|
|---|
| 3873 | else:
|
|---|
| 3874 | outputString = "AmoebaTorsionTorsionGenerator: error getting types: %s %s %s %s %s" % (
|
|---|
| 3875 | torsionTorsion.attrib['class1'],
|
|---|
| 3876 | torsionTorsion.attrib['class2'],
|
|---|
| 3877 | torsionTorsion.attrib['class3'],
|
|---|
| 3878 | torsionTorsion.attrib['class4'],
|
|---|
| 3879 | torsionTorsion.attrib['class5'] )
|
|---|
| 3880 | raise ValueError(outputString)
|
|---|
| 3881 |
|
|---|
| 3882 | # load grid
|
|---|
| 3883 |
|
|---|
| 3884 | # xml source
|
|---|
| 3885 |
|
|---|
| 3886 | # <TorsionTorsionGrid grid="0" nx="25" ny="25" >
|
|---|
| 3887 | # <Grid angle1="-180.00" angle2="-180.00" f="0.0" fx="2.31064374824e-05" fy="0.0" fxy="-0.0052801799672" />
|
|---|
| 3888 | # <Grid angle1="-165.00" angle2="-180.00" f="-0.66600912" fx="-0.06983370052" fy="-0.075058725744" fxy="-0.0044462732032" />
|
|---|
| 3889 |
|
|---|
| 3890 | # output grid:
|
|---|
| 3891 |
|
|---|
| 3892 | # grid[x][y][0] = x value
|
|---|
| 3893 | # grid[x][y][1] = y value
|
|---|
| 3894 | # grid[x][y][2] = function value
|
|---|
| 3895 | # grid[x][y][3] = dfdx value
|
|---|
| 3896 | # grid[x][y][4] = dfdy value
|
|---|
| 3897 | # grid[x][y][5] = dfd(xy) value
|
|---|
| 3898 |
|
|---|
| 3899 | maxGridIndex += 1
|
|---|
| 3900 | generator.grids = maxGridIndex*[]
|
|---|
| 3901 | for torsionTorsionGrid in element.findall('TorsionTorsionGrid'):
|
|---|
| 3902 |
|
|---|
| 3903 | gridIndex = int(torsionTorsionGrid.attrib[ "grid"])
|
|---|
| 3904 | nx = int(torsionTorsionGrid.attrib[ "nx"])
|
|---|
| 3905 | ny = int(torsionTorsionGrid.attrib[ "ny"])
|
|---|
| 3906 |
|
|---|
| 3907 | grid = []
|
|---|
| 3908 | gridCol = []
|
|---|
| 3909 |
|
|---|
| 3910 | gridColIndex = 0
|
|---|
| 3911 |
|
|---|
| 3912 | for gridEntry in torsionTorsionGrid.findall('Grid'):
|
|---|
| 3913 |
|
|---|
| 3914 | gridRow = []
|
|---|
| 3915 | gridRow.append(float(gridEntry.attrib['angle1']))
|
|---|
| 3916 | gridRow.append(float(gridEntry.attrib['angle2']))
|
|---|
| 3917 | gridRow.append(float(gridEntry.attrib['f']))
|
|---|
| 3918 | if 'fx' in gridEntry.attrib:
|
|---|
| 3919 | gridRow.append(float(gridEntry.attrib['fx']))
|
|---|
| 3920 | gridRow.append(float(gridEntry.attrib['fy']))
|
|---|
| 3921 | gridRow.append(float(gridEntry.attrib['fxy']))
|
|---|
| 3922 | gridCol.append(gridRow)
|
|---|
| 3923 |
|
|---|
| 3924 | gridColIndex += 1
|
|---|
| 3925 | if (gridColIndex == nx):
|
|---|
| 3926 | grid.append(gridCol)
|
|---|
| 3927 | gridCol = []
|
|---|
| 3928 | gridColIndex = 0
|
|---|
| 3929 |
|
|---|
| 3930 |
|
|---|
| 3931 | if (gridIndex == len(generator.grids)):
|
|---|
| 3932 | generator.grids.append(grid)
|
|---|
| 3933 | else:
|
|---|
| 3934 | while(len(generator.grids) < gridIndex):
|
|---|
| 3935 | generator.grids.append([])
|
|---|
| 3936 | generator.grids[gridIndex] = grid
|
|---|
| 3937 |
|
|---|
| 3938 | #=============================================================================================
|
|---|
| 3939 |
|
|---|
| 3940 | def getChiralAtomIndex(self, data, sys, atomB, atomC, atomD):
|
|---|
| 3941 |
|
|---|
| 3942 | chiralAtomIndex = -1
|
|---|
| 3943 |
|
|---|
| 3944 | # if atomC has four bonds, find the
|
|---|
| 3945 | # two bonds that do not include atomB and atomD
|
|---|
| 3946 | # set chiralAtomIndex to one of these, if they are
|
|---|
| 3947 | # not the same atom(type/mass)
|
|---|
| 3948 |
|
|---|
| 3949 | if (len(data.atomBonds[atomC]) == 4):
|
|---|
| 3950 | atomE = -1
|
|---|
| 3951 | atomF = -1
|
|---|
| 3952 | for bond in data.atomBonds[atomC]:
|
|---|
| 3953 | bondedAtom1 = data.bonds[bond].atom1
|
|---|
| 3954 | bondedAtom2 = data.bonds[bond].atom2
|
|---|
| 3955 | hit = -1
|
|---|
| 3956 | if ( bondedAtom1 == atomC and bondedAtom2 != atomB and bondedAtom2 != atomD):
|
|---|
| 3957 | hit = bondedAtom2
|
|---|
| 3958 | elif (bondedAtom2 == atomC and bondedAtom1 != atomB and bondedAtom1 != atomD):
|
|---|
| 3959 | hit = bondedAtom1
|
|---|
| 3960 |
|
|---|
| 3961 | if (hit > -1):
|
|---|
| 3962 | if (atomE == -1):
|
|---|
| 3963 | atomE = hit
|
|---|
| 3964 | else:
|
|---|
| 3965 | atomF = hit
|
|---|
| 3966 |
|
|---|
| 3967 | # raise error if atoms E or F not found
|
|---|
| 3968 |
|
|---|
| 3969 | if (atomE == -1 or atomF == -1):
|
|---|
| 3970 | outputString = "getChiralAtomIndex: error getting bonded partners of atomC=%s %d %s" % (atomC.name, atomC.resiude.index, atomC.resiude.name,)
|
|---|
| 3971 | raise ValueError(outputString)
|
|---|
| 3972 |
|
|---|
| 3973 | # check for different type/mass between atoms E & F
|
|---|
| 3974 |
|
|---|
| 3975 | typeE = int(data.atomType[data.atoms[atomE]])
|
|---|
| 3976 | typeF = int(data.atomType[data.atoms[atomF]])
|
|---|
| 3977 | if (typeE > typeF):
|
|---|
| 3978 | chiralAtomIndex = atomE
|
|---|
| 3979 | if (typeF > typeE):
|
|---|
| 3980 | chiralAtomIndex = atomF
|
|---|
| 3981 |
|
|---|
| 3982 | massE = sys.getParticleMass(atomE)/unit.dalton
|
|---|
| 3983 | massF = sys.getParticleMass(atomE)/unit.dalton
|
|---|
| 3984 | if (massE > massF):
|
|---|
| 3985 | chiralAtomIndex = massE
|
|---|
| 3986 | if (massF > massE):
|
|---|
| 3987 | chiralAtomIndex = massF
|
|---|
| 3988 |
|
|---|
| 3989 | return chiralAtomIndex
|
|---|
| 3990 |
|
|---|
| 3991 | #=============================================================================================
|
|---|
| 3992 |
|
|---|
| 3993 | def createForce(self, sys, data, nonpiTorsionedMethod, nonpiTorsionedCutoff, args):
|
|---|
| 3994 |
|
|---|
| 3995 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 3996 | existing = [f for f in existing if type(f) == mm.AmoebaTorsionTorsionForce]
|
|---|
| 3997 |
|
|---|
| 3998 | if len(existing) == 0:
|
|---|
| 3999 | force = mm.AmoebaTorsionTorsionForce()
|
|---|
| 4000 | sys.addForce(force)
|
|---|
| 4001 | else:
|
|---|
| 4002 | force = existing[0]
|
|---|
| 4003 |
|
|---|
| 4004 | for angle in data.angles:
|
|---|
| 4005 |
|
|---|
| 4006 | # search for bitorsions; based on TINKER subroutine bitors()
|
|---|
| 4007 |
|
|---|
| 4008 | ib = angle[0]
|
|---|
| 4009 | ic = angle[1]
|
|---|
| 4010 | id = angle[2]
|
|---|
| 4011 |
|
|---|
| 4012 | for bondIndex in data.atomBonds[ib]:
|
|---|
| 4013 | bondedAtom1 = data.bonds[bondIndex].atom1
|
|---|
| 4014 | bondedAtom2 = data.bonds[bondIndex].atom2
|
|---|
| 4015 | if (bondedAtom1 != ib):
|
|---|
| 4016 | ia = bondedAtom1
|
|---|
| 4017 | else:
|
|---|
| 4018 | ia = bondedAtom2
|
|---|
| 4019 |
|
|---|
| 4020 | if (ia != ic and ia != id):
|
|---|
| 4021 | for bondIndex in data.atomBonds[id]:
|
|---|
| 4022 | bondedAtom1 = data.bonds[bondIndex].atom1
|
|---|
| 4023 | bondedAtom2 = data.bonds[bondIndex].atom2
|
|---|
| 4024 | if (bondedAtom1 != id):
|
|---|
| 4025 | ie = bondedAtom1
|
|---|
| 4026 | else:
|
|---|
| 4027 | ie = bondedAtom2
|
|---|
| 4028 |
|
|---|
| 4029 | if (ie != ic and ie != ib and ie != ia):
|
|---|
| 4030 |
|
|---|
| 4031 | # found candidate set of atoms
|
|---|
| 4032 | # check if types match in order or reverse order
|
|---|
| 4033 |
|
|---|
| 4034 | type1 = data.atomType[data.atoms[ia]]
|
|---|
| 4035 | type2 = data.atomType[data.atoms[ib]]
|
|---|
| 4036 | type3 = data.atomType[data.atoms[ic]]
|
|---|
| 4037 | type4 = data.atomType[data.atoms[id]]
|
|---|
| 4038 | type5 = data.atomType[data.atoms[ie]]
|
|---|
| 4039 |
|
|---|
| 4040 | for i in range(len(self.types1)):
|
|---|
| 4041 |
|
|---|
| 4042 | types1 = self.types1[i]
|
|---|
| 4043 | types2 = self.types2[i]
|
|---|
| 4044 | types3 = self.types3[i]
|
|---|
| 4045 | types4 = self.types4[i]
|
|---|
| 4046 | types5 = self.types5[i]
|
|---|
| 4047 |
|
|---|
| 4048 | # match in order
|
|---|
| 4049 |
|
|---|
| 4050 | if (type1 in types1 and type2 in types2 and type3 in types3 and type4 in types4 and type5 in types5):
|
|---|
| 4051 | chiralAtomIndex = self.getChiralAtomIndex(data, sys, ib, ic, id)
|
|---|
| 4052 | force.addTorsionTorsion(ia, ib, ic, id, ie, chiralAtomIndex, self.gridIndex[i])
|
|---|
| 4053 |
|
|---|
| 4054 | # match in reverse order
|
|---|
| 4055 |
|
|---|
| 4056 | elif (type5 in types1 and type4 in types2 and type3 in types3 and type2 in types4 and type1 in types5):
|
|---|
| 4057 | chiralAtomIndex = self.getChiralAtomIndex(data, sys, ib, ic, id)
|
|---|
| 4058 | force.addTorsionTorsion(ie, id, ic, ib, ia, chiralAtomIndex, self.gridIndex[i])
|
|---|
| 4059 |
|
|---|
| 4060 | # set grids
|
|---|
| 4061 |
|
|---|
| 4062 | for (index, grid) in enumerate(self.grids):
|
|---|
| 4063 | force.setTorsionTorsionGrid(index, grid)
|
|---|
| 4064 |
|
|---|
| 4065 | parsers["AmoebaTorsionTorsionForce"] = AmoebaTorsionTorsionGenerator.parseElement
|
|---|
| 4066 |
|
|---|
| 4067 | #=============================================================================================
|
|---|
| 4068 |
|
|---|
| 4069 | ## @private
|
|---|
| 4070 | class AmoebaStretchBendGenerator(object):
|
|---|
| 4071 |
|
|---|
| 4072 | #=============================================================================================
|
|---|
| 4073 | """An AmoebaStretchBendGenerator constructs a AmoebaStretchBendForce."""
|
|---|
| 4074 | #=============================================================================================
|
|---|
| 4075 |
|
|---|
| 4076 | def __init__(self):
|
|---|
| 4077 |
|
|---|
| 4078 | self.types1 = []
|
|---|
| 4079 | self.types2 = []
|
|---|
| 4080 | self.types3 = []
|
|---|
| 4081 |
|
|---|
| 4082 | self.k1 = []
|
|---|
| 4083 | self.k2 = []
|
|---|
| 4084 |
|
|---|
| 4085 | #=============================================================================================
|
|---|
| 4086 |
|
|---|
| 4087 | @staticmethod
|
|---|
| 4088 | def parseElement(element, forceField):
|
|---|
| 4089 | generator = AmoebaStretchBendGenerator()
|
|---|
| 4090 | forceField._forces.append(generator)
|
|---|
| 4091 |
|
|---|
| 4092 | # <AmoebaStretchBendForce stretchBendUnit="1.0">
|
|---|
| 4093 | # <StretchBend class1="2" class2="1" class3="3" k1="5.25776946506" k2="5.25776946506" />
|
|---|
| 4094 | # <StretchBend class1="2" class2="1" class3="4" k1="3.14005676385" k2="3.14005676385" />
|
|---|
| 4095 |
|
|---|
| 4096 | for stretchBend in element.findall('StretchBend'):
|
|---|
| 4097 | types = forceField._findAtomTypes(stretchBend.attrib, 3)
|
|---|
| 4098 | if None not in types:
|
|---|
| 4099 |
|
|---|
| 4100 | generator.types1.append(types[0])
|
|---|
| 4101 | generator.types2.append(types[1])
|
|---|
| 4102 | generator.types3.append(types[2])
|
|---|
| 4103 |
|
|---|
| 4104 | generator.k1.append(float(stretchBend.attrib['k1']))
|
|---|
| 4105 | generator.k2.append(float(stretchBend.attrib['k2']))
|
|---|
| 4106 |
|
|---|
| 4107 | else:
|
|---|
| 4108 | outputString = "AmoebaStretchBendGenerator : error getting types: %s %s %s" % (
|
|---|
| 4109 | stretchBend.attrib['class1'],
|
|---|
| 4110 | stretchBend.attrib['class2'],
|
|---|
| 4111 | stretchBend.attrib['class3'])
|
|---|
| 4112 | raise ValueError(outputString)
|
|---|
| 4113 |
|
|---|
| 4114 | #=============================================================================================
|
|---|
| 4115 |
|
|---|
| 4116 | # The setup of this force is dependent on AmoebaBondForce and AmoebaAngleForce
|
|---|
| 4117 | # having been called since the ideal bond lengths and angle are needed here.
|
|---|
| 4118 | # As a conseqeunce, createForce() is not implemented since it is not guaranteed that the generator for
|
|---|
| 4119 | # AmoebaBondForce and AmoebaAngleForce have been called prior to AmoebaStretchBendGenerator().
|
|---|
| 4120 | # Instead, createForcePostAmoebaBondForce() is called
|
|---|
| 4121 | # after the generators for AmoebaBondForce and AmoebaAngleForce have been called
|
|---|
| 4122 |
|
|---|
| 4123 | #=============================================================================================
|
|---|
| 4124 |
|
|---|
| 4125 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 4126 | pass
|
|---|
| 4127 |
|
|---|
| 4128 | #=============================================================================================
|
|---|
| 4129 |
|
|---|
| 4130 | # Note: request for constrained bonds is ignored.
|
|---|
| 4131 |
|
|---|
| 4132 | #=============================================================================================
|
|---|
| 4133 |
|
|---|
| 4134 | def createForcePostAmoebaBondForce(self, sys, data, nonbondedMethod, nonbondedCutoff, angleList, args):
|
|---|
| 4135 |
|
|---|
| 4136 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 4137 | existing = [f for f in existing if type(f) == mm.AmoebaStretchBendForce]
|
|---|
| 4138 | if len(existing) == 0:
|
|---|
| 4139 | force = mm.AmoebaStretchBendForce()
|
|---|
| 4140 | sys.addForce(force)
|
|---|
| 4141 | else:
|
|---|
| 4142 | force = existing[0]
|
|---|
| 4143 |
|
|---|
| 4144 | for angleDict in angleList:
|
|---|
| 4145 |
|
|---|
| 4146 | angle = angleDict['angle']
|
|---|
| 4147 | if ('isConstrained' in angleDict):
|
|---|
| 4148 | isConstrained = angleDict['isConstrained']
|
|---|
| 4149 | else:
|
|---|
| 4150 | isConstrained = 0
|
|---|
| 4151 |
|
|---|
| 4152 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 4153 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 4154 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 4155 |
|
|---|
| 4156 | radian = 57.2957795130
|
|---|
| 4157 | for i in range(len(self.types1)):
|
|---|
| 4158 |
|
|---|
| 4159 | types1 = self.types1[i]
|
|---|
| 4160 | types2 = self.types2[i]
|
|---|
| 4161 | types3 = self.types3[i]
|
|---|
| 4162 |
|
|---|
| 4163 | # match types
|
|---|
| 4164 | # get ideal bond lengths, bondAB, bondCB
|
|---|
| 4165 | # get ideal angle
|
|---|
| 4166 |
|
|---|
| 4167 | if (type2 in types2 and ((type1 in types1 and type3 in types3) or (type3 in types1 and type1 in types3))):
|
|---|
| 4168 | bondAB = -1.0
|
|---|
| 4169 | bondCB = -1.0
|
|---|
| 4170 | swap = 0
|
|---|
| 4171 | for bond in data.atomBonds[angle[1]]:
|
|---|
| 4172 | atom1 = data.bonds[bond].atom1
|
|---|
| 4173 | atom2 = data.bonds[bond].atom2
|
|---|
| 4174 | length = data.bonds[bond].length
|
|---|
| 4175 | if (atom1 == angle[0]):
|
|---|
| 4176 | bondAB = length
|
|---|
| 4177 | if (atom1 == angle[2]):
|
|---|
| 4178 | bondCB = length
|
|---|
| 4179 | if (atom2 == angle[2]):
|
|---|
| 4180 | bondCB = length
|
|---|
| 4181 | if (atom2 == angle[0]):
|
|---|
| 4182 | bondAB = length
|
|---|
| 4183 |
|
|---|
| 4184 | # check that ideal angle and bonds are set
|
|---|
| 4185 |
|
|---|
| 4186 | if ('idealAngle' not in angleDict):
|
|---|
| 4187 |
|
|---|
| 4188 | outputString = "AmoebaStretchBendGenerator: ideal angle is not set for following entry:\n"
|
|---|
| 4189 | outputString += " types: %5s %5s %5s atoms: " % (type1, type2, type3)
|
|---|
| 4190 | outputString += getAtomPrint( data, angle[0] ) + ' '
|
|---|
| 4191 | outputString += getAtomPrint( data, angle[1] ) + ' '
|
|---|
| 4192 | outputString += getAtomPrint( data, angle[2] )
|
|---|
| 4193 | raise ValueError(outputString)
|
|---|
| 4194 |
|
|---|
| 4195 | elif (bondAB < 0 or bondCB < 0):
|
|---|
| 4196 |
|
|---|
| 4197 | outputString = "AmoebaStretchBendGenerator: bonds not set: %15.7e %15.7e. for following entry:" % (bondAB, bondCB)
|
|---|
| 4198 | outputString += " types: [%5s %5s %5s] atoms: " % (type1, type2, type3)
|
|---|
| 4199 | outputString += getAtomPrint( data, angle[0] ) + ' '
|
|---|
| 4200 | outputString += getAtomPrint( data, angle[1] ) + ' '
|
|---|
| 4201 | outputString += getAtomPrint( data, angle[2] )
|
|---|
| 4202 | raise ValueError(outputString)
|
|---|
| 4203 |
|
|---|
| 4204 | else:
|
|---|
| 4205 | force.addStretchBend(angle[0], angle[1], angle[2], bondAB, bondCB, angleDict['idealAngle']/radian, self.k1[i], self.k2[i])
|
|---|
| 4206 |
|
|---|
| 4207 | break
|
|---|
| 4208 |
|
|---|
| 4209 | parsers["AmoebaStretchBendForce"] = AmoebaStretchBendGenerator.parseElement
|
|---|
| 4210 |
|
|---|
| 4211 | #=============================================================================================
|
|---|
| 4212 |
|
|---|
| 4213 | ## @private
|
|---|
| 4214 | class AmoebaVdwGenerator(object):
|
|---|
| 4215 |
|
|---|
| 4216 | """A AmoebaVdwGenerator constructs a AmoebaVdwForce."""
|
|---|
| 4217 |
|
|---|
| 4218 | #=============================================================================================
|
|---|
| 4219 |
|
|---|
| 4220 | def __init__(self, type, radiusrule, radiustype, radiussize, epsilonrule, vdw13Scale, vdw14Scale, vdw15Scale):
|
|---|
| 4221 |
|
|---|
| 4222 | self.type = type
|
|---|
| 4223 |
|
|---|
| 4224 | self.radiusrule = radiusrule
|
|---|
| 4225 | self.radiustype = radiustype
|
|---|
| 4226 | self.radiussize = radiussize
|
|---|
| 4227 |
|
|---|
| 4228 | self.epsilonrule = epsilonrule
|
|---|
| 4229 |
|
|---|
| 4230 | self.vdw13Scale = vdw13Scale
|
|---|
| 4231 | self.vdw14Scale = vdw14Scale
|
|---|
| 4232 | self.vdw15Scale = vdw15Scale
|
|---|
| 4233 |
|
|---|
| 4234 | #=============================================================================================
|
|---|
| 4235 |
|
|---|
| 4236 | @staticmethod
|
|---|
| 4237 | def parseElement(element, forceField):
|
|---|
| 4238 |
|
|---|
| 4239 | # <AmoebaVdwForce type="BUFFERED-14-7" radiusrule="CUBIC-MEAN" radiustype="R-MIN" radiussize="DIAMETER" epsilonrule="HHG" vdw-13-scale="0.0" vdw-14-scale="1.0" vdw-15-scale="1.0" >
|
|---|
| 4240 | # <Vdw class="1" sigma="0.371" epsilon="0.46024" reduction="1.0" />
|
|---|
| 4241 | # <Vdw class="2" sigma="0.382" epsilon="0.422584" reduction="1.0" />
|
|---|
| 4242 |
|
|---|
| 4243 | existing = [f for f in forceField._forces if isinstance(f, AmoebaVdwGenerator)]
|
|---|
| 4244 | if len(existing) == 0:
|
|---|
| 4245 | generator = AmoebaVdwGenerator(element.attrib['type'], element.attrib['radiusrule'], element.attrib['radiustype'], element.attrib['radiussize'], element.attrib['epsilonrule'],
|
|---|
| 4246 | float(element.attrib['vdw-13-scale']), float(element.attrib['vdw-14-scale']), float(element.attrib['vdw-15-scale']))
|
|---|
| 4247 | forceField.registerGenerator(generator)
|
|---|
| 4248 | generator.params = ForceField._AtomTypeParameters(forceField, 'AmoebaVdwForce', 'Vdw', ('sigma', 'epsilon', 'reduction'))
|
|---|
| 4249 | else:
|
|---|
| 4250 | # Multiple <AmoebaVdwForce> tags were found, probably in different files. Simply add more types to the existing one.
|
|---|
| 4251 | generator = existing[0]
|
|---|
| 4252 | if abs(generator.vdw13Scale - float(element.attrib['vdw-13-scale'])) > NonbondedGenerator.SCALETOL or \
|
|---|
| 4253 | abs(generator.vdw14Scale - float(element.attrib['vdw-14-scale'])) > NonbondedGenerator.SCALETOL or \
|
|---|
| 4254 | abs(generator.vdw15Scale - float(element.attrib['vdw-15-scale'])) > NonbondedGenerator.SCALETOL:
|
|---|
| 4255 | raise ValueError('Found multiple AmoebaVdwForce tags with different scale factors')
|
|---|
| 4256 | if generator.radiusrule != element.attrib['radiusrule'] or generator.epsilonrule != element.attrib['epsilonrule'] or \
|
|---|
| 4257 | generator.radiustype != element.attrib['radiustype'] or generator.radiussize != element.attrib['radiussize']:
|
|---|
| 4258 | raise ValueError('Found multiple AmoebaVdwForce tags with different combining rules')
|
|---|
| 4259 | generator.params.parseDefinitions(element)
|
|---|
| 4260 | two_six = 1.122462048309372
|
|---|
| 4261 |
|
|---|
| 4262 | #=============================================================================================
|
|---|
| 4263 |
|
|---|
| 4264 | @staticmethod
|
|---|
| 4265 | def getBondedParticleSets(sys, data):
|
|---|
| 4266 |
|
|---|
| 4267 | bondedParticleSets = [set() for i in range(len(data.atoms))]
|
|---|
| 4268 | bondIndices = _findBondsForExclusions(data, sys)
|
|---|
| 4269 | for atom1, atom2 in bondIndices:
|
|---|
| 4270 | bondedParticleSets[atom1].add(atom2)
|
|---|
| 4271 | bondedParticleSets[atom2].add(atom1)
|
|---|
| 4272 | return bondedParticleSets
|
|---|
| 4273 |
|
|---|
| 4274 | #=============================================================================================
|
|---|
| 4275 |
|
|---|
| 4276 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 4277 |
|
|---|
| 4278 | sigmaMap = {'ARITHMETIC':1, 'GEOMETRIC':1, 'CUBIC-MEAN':1}
|
|---|
| 4279 | epsilonMap = {'ARITHMETIC':1, 'GEOMETRIC':1, 'HARMONIC':1, 'HHG':1}
|
|---|
| 4280 |
|
|---|
| 4281 | force = mm.AmoebaVdwForce()
|
|---|
| 4282 | sys.addForce(force)
|
|---|
| 4283 |
|
|---|
| 4284 | # sigma and epsilon combining rules
|
|---|
| 4285 |
|
|---|
| 4286 | if ('sigmaCombiningRule' in args):
|
|---|
| 4287 | sigmaRule = args['sigmaCombiningRule'].upper()
|
|---|
| 4288 | if (sigmaRule.upper() in sigmaMap):
|
|---|
| 4289 | force.setSigmaCombiningRule(sigmaRule.upper())
|
|---|
| 4290 | else:
|
|---|
| 4291 | stringList = ' ' . join(str(x) for x in sigmaMap.keys())
|
|---|
| 4292 | raise ValueError( "AmoebaVdwGenerator: sigma combining rule %s not recognized; valid values are %s; using default." % (sigmaRule, stringList) )
|
|---|
| 4293 | else:
|
|---|
| 4294 | force.setSigmaCombiningRule(self.radiusrule)
|
|---|
| 4295 |
|
|---|
| 4296 | if ('epsilonCombiningRule' in args):
|
|---|
| 4297 | epsilonRule = args['epsilonCombiningRule'].upper()
|
|---|
| 4298 | if (epsilonRule.upper() in epsilonMap):
|
|---|
| 4299 | force.setEpsilonCombiningRule(epsilonRule.upper())
|
|---|
| 4300 | else:
|
|---|
| 4301 | stringList = ' ' . join(str(x) for x in epsilonMap.keys())
|
|---|
| 4302 | raise ValueError( "AmoebaVdwGenerator: epsilon combining rule %s not recognized; valid values are %s; using default." % (epsilonRule, stringList) )
|
|---|
| 4303 | else:
|
|---|
| 4304 | force.setEpsilonCombiningRule(self.epsilonrule)
|
|---|
| 4305 |
|
|---|
| 4306 | # cutoff
|
|---|
| 4307 |
|
|---|
| 4308 | if ('vdwCutoff' in args):
|
|---|
| 4309 | force.setCutoff(args['vdwCutoff'])
|
|---|
| 4310 | else:
|
|---|
| 4311 | force.setCutoff(nonbondedCutoff)
|
|---|
| 4312 |
|
|---|
| 4313 | # dispersion correction
|
|---|
| 4314 |
|
|---|
| 4315 | if ('useDispersionCorrection' in args):
|
|---|
| 4316 | force.setUseDispersionCorrection(bool(args['useDispersionCorrection']))
|
|---|
| 4317 |
|
|---|
| 4318 | if (nonbondedMethod == PME):
|
|---|
| 4319 | force.setNonbondedMethod(mm.AmoebaVdwForce.CutoffPeriodic)
|
|---|
| 4320 |
|
|---|
| 4321 | # add particles to force
|
|---|
| 4322 |
|
|---|
| 4323 | sigmaScale = 1
|
|---|
| 4324 | if self.radiustype == 'SIGMA':
|
|---|
| 4325 | sigmaScale = 1.122462048309372
|
|---|
| 4326 | if self.radiussize == 'DIAMETER':
|
|---|
| 4327 | sigmaScale = 0.5
|
|---|
| 4328 | for (i, atom) in enumerate(data.atoms):
|
|---|
| 4329 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 4330 | # ivIndex = index of bonded partner for hydrogens; otherwise ivIndex = particle index
|
|---|
| 4331 |
|
|---|
| 4332 | ivIndex = i
|
|---|
| 4333 | if atom.element == elem.hydrogen and len(data.atomBonds[i]) == 1:
|
|---|
| 4334 | bondIndex = data.atomBonds[i][0]
|
|---|
| 4335 | if (data.bonds[bondIndex].atom1 == i):
|
|---|
| 4336 | ivIndex = data.bonds[bondIndex].atom2
|
|---|
| 4337 | else:
|
|---|
| 4338 | ivIndex = data.bonds[bondIndex].atom1
|
|---|
| 4339 |
|
|---|
| 4340 | force.addParticle(ivIndex, values[0]*sigmaScale, values[1], values[2])
|
|---|
| 4341 |
|
|---|
| 4342 | # set combining rules
|
|---|
| 4343 |
|
|---|
| 4344 | # set particle exclusions: self, 1-2 and 1-3 bonds
|
|---|
| 4345 | # (1) collect in bondedParticleSets[i], 1-2 indices for all bonded partners of particle i
|
|---|
| 4346 | # (2) add 1-2,1-3 and self to exclusion set
|
|---|
| 4347 |
|
|---|
| 4348 | bondedParticleSets = AmoebaVdwGenerator.getBondedParticleSets(sys, data)
|
|---|
| 4349 |
|
|---|
| 4350 | for (i,atom) in enumerate(data.atoms):
|
|---|
| 4351 |
|
|---|
| 4352 | # 1-2 partners
|
|---|
| 4353 |
|
|---|
| 4354 | exclusionSet = bondedParticleSets[i].copy()
|
|---|
| 4355 |
|
|---|
| 4356 | # 1-3 partners
|
|---|
| 4357 |
|
|---|
| 4358 | if (self.vdw13Scale == 0.0):
|
|---|
| 4359 | for bondedParticle in bondedParticleSets[i]:
|
|---|
| 4360 | exclusionSet = exclusionSet.union(bondedParticleSets[bondedParticle])
|
|---|
| 4361 |
|
|---|
| 4362 | # self
|
|---|
| 4363 |
|
|---|
| 4364 | exclusionSet.add(i)
|
|---|
| 4365 |
|
|---|
| 4366 | force.setParticleExclusions(i, tuple(exclusionSet))
|
|---|
| 4367 |
|
|---|
| 4368 | parsers["AmoebaVdwForce"] = AmoebaVdwGenerator.parseElement
|
|---|
| 4369 |
|
|---|
| 4370 | #=============================================================================================
|
|---|
| 4371 |
|
|---|
| 4372 | ## @private
|
|---|
| 4373 | class AmoebaMultipoleGenerator(object):
|
|---|
| 4374 |
|
|---|
| 4375 | #=============================================================================================
|
|---|
| 4376 |
|
|---|
| 4377 | """A AmoebaMultipoleGenerator constructs a AmoebaMultipoleForce."""
|
|---|
| 4378 |
|
|---|
| 4379 | #=============================================================================================
|
|---|
| 4380 |
|
|---|
| 4381 | def __init__(self, forceField):
|
|---|
| 4382 | self.forceField = forceField
|
|---|
| 4383 | self.typeMap = {}
|
|---|
| 4384 |
|
|---|
| 4385 | #=============================================================================================
|
|---|
| 4386 | # Set axis type
|
|---|
| 4387 | #=============================================================================================
|
|---|
| 4388 |
|
|---|
| 4389 | @staticmethod
|
|---|
| 4390 | def setAxisType(kIndices):
|
|---|
| 4391 |
|
|---|
| 4392 | # set axis type
|
|---|
| 4393 |
|
|---|
| 4394 | kIndicesLen = len(kIndices)
|
|---|
| 4395 | if (kIndicesLen > 3):
|
|---|
| 4396 | ky = kIndices[3]
|
|---|
| 4397 | else:
|
|---|
| 4398 | ky = 0
|
|---|
| 4399 |
|
|---|
| 4400 | if (kIndicesLen > 2):
|
|---|
| 4401 | kx = kIndices[2]
|
|---|
| 4402 | else:
|
|---|
| 4403 | kx = 0
|
|---|
| 4404 |
|
|---|
| 4405 | if (kIndicesLen > 1):
|
|---|
| 4406 | kz = kIndices[1]
|
|---|
| 4407 | else:
|
|---|
| 4408 | kz = 0
|
|---|
| 4409 |
|
|---|
| 4410 | while(len(kIndices) < 4):
|
|---|
| 4411 | kIndices.append(0)
|
|---|
| 4412 |
|
|---|
| 4413 | axisType = mm.AmoebaMultipoleForce.ZThenX
|
|---|
| 4414 | if (kz == 0):
|
|---|
| 4415 | axisType = mm.AmoebaMultipoleForce.NoAxisType
|
|---|
| 4416 | if (kz != 0 and kx == 0):
|
|---|
| 4417 | axisType = mm.AmoebaMultipoleForce.ZOnly
|
|---|
| 4418 | if (kz < 0 or kx < 0):
|
|---|
| 4419 | axisType = mm.AmoebaMultipoleForce.Bisector
|
|---|
| 4420 | if (kx < 0 and ky < 0):
|
|---|
| 4421 | axisType = mm.AmoebaMultipoleForce.ZBisect
|
|---|
| 4422 | if (kz < 0 and kx < 0 and ky < 0):
|
|---|
| 4423 | axisType = mm.AmoebaMultipoleForce.ThreeFold
|
|---|
| 4424 |
|
|---|
| 4425 | kIndices[1] = abs(kz)
|
|---|
| 4426 | kIndices[2] = abs(kx)
|
|---|
| 4427 | kIndices[3] = abs(ky)
|
|---|
| 4428 |
|
|---|
| 4429 | return axisType
|
|---|
| 4430 |
|
|---|
| 4431 | #=============================================================================================
|
|---|
| 4432 |
|
|---|
| 4433 | @staticmethod
|
|---|
| 4434 | def parseElement(element, forceField):
|
|---|
| 4435 |
|
|---|
| 4436 | # <AmoebaMultipoleForce direct11Scale="0.0" direct12Scale="1.0" direct13Scale="1.0" direct14Scale="1.0" mpole12Scale="0.0" mpole13Scale="0.0" mpole14Scale="0.4" mpole15Scale="0.8" mutual11Scale="1.0" mutual12Scale="1.0" mutual13Scale="1.0" mutual14Scale="1.0" polar12Scale="0.0" polar13Scale="0.0" polar14Intra="0.5" polar14Scale="1.0" polar15Scale="1.0" >
|
|---|
| 4437 | # <Multipole class="1" kz="2" kx="4" c0="-0.22620" d1="0.08214" d2="0.00000" d3="0.34883" q11="0.11775" q21="0.00000" q22="-1.02185" q31="-0.17555" q32="0.00000" q33="0.90410" />
|
|---|
| 4438 | # <Multipole class="2" kz="1" kx="3" c0="-0.15245" d1="0.19517" d2="0.00000" d3="0.19687" q11="-0.20677" q21="0.00000" q22="-0.48084" q31="-0.01672" q32="0.00000" q33="0.68761" />
|
|---|
| 4439 |
|
|---|
| 4440 | existing = [f for f in forceField._forces if isinstance(f, AmoebaMultipoleGenerator)]
|
|---|
| 4441 | if len(existing) == 0:
|
|---|
| 4442 | generator = AmoebaMultipoleGenerator(forceField)
|
|---|
| 4443 | forceField.registerGenerator(generator)
|
|---|
| 4444 | else:
|
|---|
| 4445 | # Multiple <AmoebaMultipoleForce> tags were found, probably in different files. Simply add more types to the existing one.
|
|---|
| 4446 | generator = existing[0]
|
|---|
| 4447 |
|
|---|
| 4448 | # set type map: [ kIndices, multipoles, AMOEBA/OpenMM axis type]
|
|---|
| 4449 |
|
|---|
| 4450 | for atom in element.findall('Multipole'):
|
|---|
| 4451 | types = forceField._findAtomTypes(atom.attrib, 1)
|
|---|
| 4452 | if None not in types:
|
|---|
| 4453 |
|
|---|
| 4454 | # k-indices not provided default to 0
|
|---|
| 4455 |
|
|---|
| 4456 | kIndices = [int(atom.attrib['type'])]
|
|---|
| 4457 |
|
|---|
| 4458 | kStrings = [ 'kz', 'kx', 'ky' ]
|
|---|
| 4459 | for kString in kStrings:
|
|---|
| 4460 | try:
|
|---|
| 4461 | if (atom.attrib[kString]):
|
|---|
| 4462 | kIndices.append(int(atom.attrib[kString]))
|
|---|
| 4463 | except:
|
|---|
| 4464 | pass
|
|---|
| 4465 |
|
|---|
| 4466 | # set axis type based on k-Indices
|
|---|
| 4467 |
|
|---|
| 4468 | axisType = AmoebaMultipoleGenerator.setAxisType(kIndices)
|
|---|
| 4469 |
|
|---|
| 4470 | # set multipole
|
|---|
| 4471 |
|
|---|
| 4472 | charge = float(atom.attrib['c0'])
|
|---|
| 4473 |
|
|---|
| 4474 | conversion = 1.0
|
|---|
| 4475 | dipole = [ conversion*float(atom.attrib['d1']), conversion*float(atom.attrib['d2']), conversion*float(atom.attrib['d3'])]
|
|---|
| 4476 |
|
|---|
| 4477 | quadrupole = []
|
|---|
| 4478 | quadrupole.append(conversion*float(atom.attrib['q11']))
|
|---|
| 4479 | quadrupole.append(conversion*float(atom.attrib['q21']))
|
|---|
| 4480 | quadrupole.append(conversion*float(atom.attrib['q31']))
|
|---|
| 4481 | quadrupole.append(conversion*float(atom.attrib['q21']))
|
|---|
| 4482 | quadrupole.append(conversion*float(atom.attrib['q22']))
|
|---|
| 4483 | quadrupole.append(conversion*float(atom.attrib['q32']))
|
|---|
| 4484 | quadrupole.append(conversion*float(atom.attrib['q31']))
|
|---|
| 4485 | quadrupole.append(conversion*float(atom.attrib['q32']))
|
|---|
| 4486 | quadrupole.append(conversion*float(atom.attrib['q33']))
|
|---|
| 4487 |
|
|---|
| 4488 | for t in types[0]:
|
|---|
| 4489 | if (t not in generator.typeMap):
|
|---|
| 4490 | generator.typeMap[t] = []
|
|---|
| 4491 |
|
|---|
| 4492 | valueMap = dict()
|
|---|
| 4493 | valueMap['classIndex'] = atom.attrib['type']
|
|---|
| 4494 | valueMap['kIndices'] = kIndices
|
|---|
| 4495 | valueMap['charge'] = charge
|
|---|
| 4496 | valueMap['dipole'] = dipole
|
|---|
| 4497 | valueMap['quadrupole'] = quadrupole
|
|---|
| 4498 | valueMap['axisType'] = axisType
|
|---|
| 4499 | generator.typeMap[t].append(valueMap)
|
|---|
| 4500 |
|
|---|
| 4501 | else:
|
|---|
| 4502 | outputString = "AmoebaMultipoleGenerator: error getting type for multipole: %s" % (atom.attrib['class'])
|
|---|
| 4503 | raise ValueError(outputString)
|
|---|
| 4504 |
|
|---|
| 4505 | # polarization parameters
|
|---|
| 4506 |
|
|---|
| 4507 | for atom in element.findall('Polarize'):
|
|---|
| 4508 | types = forceField._findAtomTypes(atom.attrib, 1)
|
|---|
| 4509 | if None not in types:
|
|---|
| 4510 |
|
|---|
| 4511 | classIndex = atom.attrib['type']
|
|---|
| 4512 | polarizability = float(atom.attrib['polarizability'])
|
|---|
| 4513 | thole = float(atom.attrib['thole'])
|
|---|
| 4514 | if (thole == 0):
|
|---|
| 4515 | pdamp = 0
|
|---|
| 4516 | else:
|
|---|
| 4517 | pdamp = pow(polarizability, 1.0/6.0)
|
|---|
| 4518 |
|
|---|
| 4519 | pgrpMap = dict()
|
|---|
| 4520 | for index in range(1, 7):
|
|---|
| 4521 | pgrp = 'pgrp' + str(index)
|
|---|
| 4522 | if (pgrp in atom.attrib):
|
|---|
| 4523 | pgrpMap[int(atom.attrib[pgrp])] = -1
|
|---|
| 4524 |
|
|---|
| 4525 | for t in types[0]:
|
|---|
| 4526 | if (t not in generator.typeMap):
|
|---|
| 4527 | outputString = "AmoebaMultipoleGenerator: polarize type not present: %s" % (atom.attrib['type'])
|
|---|
| 4528 | raise ValueError(outputString)
|
|---|
| 4529 | else:
|
|---|
| 4530 | typeMapList = generator.typeMap[t]
|
|---|
| 4531 | hit = 0
|
|---|
| 4532 | for (ii, typeMap) in enumerate(typeMapList):
|
|---|
| 4533 |
|
|---|
| 4534 | if (typeMap['classIndex'] == classIndex):
|
|---|
| 4535 | typeMap['polarizability'] = polarizability
|
|---|
| 4536 | typeMap['thole'] = thole
|
|---|
| 4537 | typeMap['pdamp'] = pdamp
|
|---|
| 4538 | typeMap['pgrpMap'] = pgrpMap
|
|---|
| 4539 | typeMapList[ii] = typeMap
|
|---|
| 4540 | hit = 1
|
|---|
| 4541 |
|
|---|
| 4542 | if (hit == 0):
|
|---|
| 4543 | outputString = "AmoebaMultipoleGenerator: error getting type for polarize: class index=%s not in multipole list?" % (atom.attrib['class'])
|
|---|
| 4544 | raise ValueError(outputString)
|
|---|
| 4545 |
|
|---|
| 4546 | else:
|
|---|
| 4547 | outputString = "AmoebaMultipoleGenerator: error getting type for polarize: %s" % (atom.attrib['class'])
|
|---|
| 4548 | raise ValueError(outputString)
|
|---|
| 4549 |
|
|---|
| 4550 | #=============================================================================================
|
|---|
| 4551 |
|
|---|
| 4552 | def setPolarGroups(self, data, bonded12ParticleSets, force):
|
|---|
| 4553 |
|
|---|
| 4554 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4555 |
|
|---|
| 4556 | # assign multipole parameters via only 1-2 connected atoms
|
|---|
| 4557 |
|
|---|
| 4558 | multipoleDict = atom.multipoleDict
|
|---|
| 4559 | pgrpMap = multipoleDict['pgrpMap']
|
|---|
| 4560 | bondedAtomIndices = bonded12ParticleSets[atomIndex]
|
|---|
| 4561 | atom.stage = -1
|
|---|
| 4562 | atom.polarizationGroupSet = list()
|
|---|
| 4563 | atom.polarizationGroups[atomIndex] = 1
|
|---|
| 4564 | for bondedAtomIndex in bondedAtomIndices:
|
|---|
| 4565 | bondedAtomType = int(data.atomType[data.atoms[bondedAtomIndex]])
|
|---|
| 4566 | bondedAtom = data.atoms[bondedAtomIndex]
|
|---|
| 4567 | if (bondedAtomType in pgrpMap):
|
|---|
| 4568 | atom.polarizationGroups[bondedAtomIndex] = 1
|
|---|
| 4569 | bondedAtom.polarizationGroups[atomIndex] = 1
|
|---|
| 4570 |
|
|---|
| 4571 | # pgrp11
|
|---|
| 4572 |
|
|---|
| 4573 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4574 |
|
|---|
| 4575 | if (len( data.atoms[atomIndex].polarizationGroupSet) > 0):
|
|---|
| 4576 | continue
|
|---|
| 4577 |
|
|---|
| 4578 | group = set()
|
|---|
| 4579 | visited = set()
|
|---|
| 4580 | notVisited = set()
|
|---|
| 4581 | for pgrpAtomIndex in atom.polarizationGroups:
|
|---|
| 4582 | group.add(pgrpAtomIndex)
|
|---|
| 4583 | notVisited.add(pgrpAtomIndex)
|
|---|
| 4584 | visited.add(atomIndex)
|
|---|
| 4585 | while(len(notVisited) > 0):
|
|---|
| 4586 | nextAtom = notVisited.pop()
|
|---|
| 4587 | if (nextAtom not in visited):
|
|---|
| 4588 | visited.add(nextAtom)
|
|---|
| 4589 | for ii in data.atoms[nextAtom].polarizationGroups:
|
|---|
| 4590 | group.add(ii)
|
|---|
| 4591 | if (ii not in visited):
|
|---|
| 4592 | notVisited.add(ii)
|
|---|
| 4593 |
|
|---|
| 4594 | pGroup = group
|
|---|
| 4595 | for pgrpAtomIndex in group:
|
|---|
| 4596 | data.atoms[pgrpAtomIndex].polarizationGroupSet.append(pGroup)
|
|---|
| 4597 |
|
|---|
| 4598 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4599 | atom.polarizationGroupSet[0] = sorted(atom.polarizationGroupSet[0])
|
|---|
| 4600 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.PolarizationCovalent11, atom.polarizationGroupSet[0])
|
|---|
| 4601 |
|
|---|
| 4602 | # pgrp12
|
|---|
| 4603 |
|
|---|
| 4604 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4605 |
|
|---|
| 4606 | if (len( data.atoms[atomIndex].polarizationGroupSet) > 1):
|
|---|
| 4607 | continue
|
|---|
| 4608 |
|
|---|
| 4609 | pgrp11 = set(atom.polarizationGroupSet[0])
|
|---|
| 4610 | pgrp12 = set()
|
|---|
| 4611 | for pgrpAtomIndex in pgrp11:
|
|---|
| 4612 | for bonded12 in bonded12ParticleSets[pgrpAtomIndex]:
|
|---|
| 4613 | pgrp12 = pgrp12.union(data.atoms[bonded12].polarizationGroupSet[0])
|
|---|
| 4614 | pgrp12 = pgrp12 - pgrp11
|
|---|
| 4615 | for pgrpAtomIndex in pgrp11:
|
|---|
| 4616 | data.atoms[pgrpAtomIndex].polarizationGroupSet.append(pgrp12)
|
|---|
| 4617 |
|
|---|
| 4618 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4619 | atom.polarizationGroupSet[1] = sorted(atom.polarizationGroupSet[1])
|
|---|
| 4620 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.PolarizationCovalent12, atom.polarizationGroupSet[1])
|
|---|
| 4621 |
|
|---|
| 4622 | # pgrp13
|
|---|
| 4623 |
|
|---|
| 4624 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4625 |
|
|---|
| 4626 | if (len(data.atoms[atomIndex].polarizationGroupSet) > 2):
|
|---|
| 4627 | continue
|
|---|
| 4628 |
|
|---|
| 4629 | pgrp11 = set(atom.polarizationGroupSet[0])
|
|---|
| 4630 | pgrp12 = set(atom.polarizationGroupSet[1])
|
|---|
| 4631 | pgrp13 = set()
|
|---|
| 4632 | for pgrpAtomIndex in pgrp12:
|
|---|
| 4633 | for bonded12 in bonded12ParticleSets[pgrpAtomIndex]:
|
|---|
| 4634 | pgrp13 = pgrp13.union(data.atoms[bonded12].polarizationGroupSet[0])
|
|---|
| 4635 | pgrp13 = pgrp13 - pgrp12
|
|---|
| 4636 | pgrp13 = pgrp13 - set(pgrp11)
|
|---|
| 4637 | for pgrpAtomIndex in pgrp11:
|
|---|
| 4638 | data.atoms[pgrpAtomIndex].polarizationGroupSet.append(pgrp13)
|
|---|
| 4639 |
|
|---|
| 4640 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4641 | atom.polarizationGroupSet[2] = sorted(atom.polarizationGroupSet[2])
|
|---|
| 4642 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.PolarizationCovalent13, atom.polarizationGroupSet[2])
|
|---|
| 4643 |
|
|---|
| 4644 | # pgrp14
|
|---|
| 4645 |
|
|---|
| 4646 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4647 |
|
|---|
| 4648 | if (len(data.atoms[atomIndex].polarizationGroupSet) > 3):
|
|---|
| 4649 | continue
|
|---|
| 4650 |
|
|---|
| 4651 | pgrp11 = set(atom.polarizationGroupSet[0])
|
|---|
| 4652 | pgrp12 = set(atom.polarizationGroupSet[1])
|
|---|
| 4653 | pgrp13 = set(atom.polarizationGroupSet[2])
|
|---|
| 4654 | pgrp14 = set()
|
|---|
| 4655 | for pgrpAtomIndex in pgrp13:
|
|---|
| 4656 | for bonded12 in bonded12ParticleSets[pgrpAtomIndex]:
|
|---|
| 4657 | pgrp14 = pgrp14.union(data.atoms[bonded12].polarizationGroupSet[0])
|
|---|
| 4658 |
|
|---|
| 4659 | pgrp14 = pgrp14 - pgrp13
|
|---|
| 4660 | pgrp14 = pgrp14 - pgrp12
|
|---|
| 4661 | pgrp14 = pgrp14 - set(pgrp11)
|
|---|
| 4662 |
|
|---|
| 4663 | for pgrpAtomIndex in pgrp11:
|
|---|
| 4664 | data.atoms[pgrpAtomIndex].polarizationGroupSet.append(pgrp14)
|
|---|
| 4665 |
|
|---|
| 4666 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4667 | atom.polarizationGroupSet[3] = sorted(atom.polarizationGroupSet[3])
|
|---|
| 4668 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.PolarizationCovalent14, atom.polarizationGroupSet[3])
|
|---|
| 4669 |
|
|---|
| 4670 | #=============================================================================================
|
|---|
| 4671 |
|
|---|
| 4672 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 4673 |
|
|---|
| 4674 | methodMap = {NoCutoff:mm.AmoebaMultipoleForce.NoCutoff,
|
|---|
| 4675 | PME:mm.AmoebaMultipoleForce.PME}
|
|---|
| 4676 |
|
|---|
| 4677 | force = mm.AmoebaMultipoleForce()
|
|---|
| 4678 | sys.addForce(force)
|
|---|
| 4679 | if (nonbondedMethod not in methodMap):
|
|---|
| 4680 | raise ValueError( "AmoebaMultipoleForce: input cutoff method not available." )
|
|---|
| 4681 | else:
|
|---|
| 4682 | force.setNonbondedMethod(methodMap[nonbondedMethod])
|
|---|
| 4683 | force.setCutoffDistance(nonbondedCutoff)
|
|---|
| 4684 |
|
|---|
| 4685 | if ('ewaldErrorTolerance' in args):
|
|---|
| 4686 | force.setEwaldErrorTolerance(float(args['ewaldErrorTolerance']))
|
|---|
| 4687 |
|
|---|
| 4688 | if ('polarization' in args):
|
|---|
| 4689 | polarizationType = args['polarization']
|
|---|
| 4690 | if (polarizationType.lower() == 'direct'):
|
|---|
| 4691 | force.setPolarizationType(mm.AmoebaMultipoleForce.Direct)
|
|---|
| 4692 | elif (polarizationType.lower() == 'extrapolated'):
|
|---|
| 4693 | force.setPolarizationType(mm.AmoebaMultipoleForce.Extrapolated)
|
|---|
| 4694 | else:
|
|---|
| 4695 | force.setPolarizationType(mm.AmoebaMultipoleForce.Mutual)
|
|---|
| 4696 |
|
|---|
| 4697 | if ('aEwald' in args):
|
|---|
| 4698 | force.setAEwald(float(args['aEwald']))
|
|---|
| 4699 |
|
|---|
| 4700 | if ('pmeGridDimensions' in args):
|
|---|
| 4701 | force.setPmeGridDimensions(args['pmeGridDimensions'])
|
|---|
| 4702 |
|
|---|
| 4703 | if ('mutualInducedMaxIterations' in args):
|
|---|
| 4704 | force.setMutualInducedMaxIterations(int(args['mutualInducedMaxIterations']))
|
|---|
| 4705 |
|
|---|
| 4706 | if ('mutualInducedTargetEpsilon' in args):
|
|---|
| 4707 | force.setMutualInducedTargetEpsilon(float(args['mutualInducedTargetEpsilon']))
|
|---|
| 4708 |
|
|---|
| 4709 | # add particles to force
|
|---|
| 4710 | # throw error if particle type not available
|
|---|
| 4711 |
|
|---|
| 4712 | # get 1-2, 1-3, 1-4, 1-5 bonded sets
|
|---|
| 4713 |
|
|---|
| 4714 | # 1-2
|
|---|
| 4715 |
|
|---|
| 4716 | bonded12ParticleSets = AmoebaVdwGenerator.getBondedParticleSets(sys, data)
|
|---|
| 4717 |
|
|---|
| 4718 | # 1-3
|
|---|
| 4719 |
|
|---|
| 4720 | bonded13ParticleSets = []
|
|---|
| 4721 | for i in range(len(data.atoms)):
|
|---|
| 4722 | bonded13Set = set()
|
|---|
| 4723 | bonded12ParticleSet = bonded12ParticleSets[i]
|
|---|
| 4724 | for j in bonded12ParticleSet:
|
|---|
| 4725 | bonded13Set = bonded13Set.union(bonded12ParticleSets[j])
|
|---|
| 4726 |
|
|---|
| 4727 | # remove 1-2 and self from set
|
|---|
| 4728 |
|
|---|
| 4729 | bonded13Set = bonded13Set - bonded12ParticleSet
|
|---|
| 4730 | selfSet = set()
|
|---|
| 4731 | selfSet.add(i)
|
|---|
| 4732 | bonded13Set = bonded13Set - selfSet
|
|---|
| 4733 | bonded13Set = set(sorted(bonded13Set))
|
|---|
| 4734 | bonded13ParticleSets.append(bonded13Set)
|
|---|
| 4735 |
|
|---|
| 4736 | # 1-4
|
|---|
| 4737 |
|
|---|
| 4738 | bonded14ParticleSets = []
|
|---|
| 4739 | for i in range(len(data.atoms)):
|
|---|
| 4740 | bonded14Set = set()
|
|---|
| 4741 | bonded13ParticleSet = bonded13ParticleSets[i]
|
|---|
| 4742 | for j in bonded13ParticleSet:
|
|---|
| 4743 | bonded14Set = bonded14Set.union(bonded12ParticleSets[j])
|
|---|
| 4744 |
|
|---|
| 4745 | # remove 1-3, 1-2 and self from set
|
|---|
| 4746 |
|
|---|
| 4747 | bonded14Set = bonded14Set - bonded12ParticleSets[i]
|
|---|
| 4748 | bonded14Set = bonded14Set - bonded13ParticleSet
|
|---|
| 4749 | selfSet = set()
|
|---|
| 4750 | selfSet.add(i)
|
|---|
| 4751 | bonded14Set = bonded14Set - selfSet
|
|---|
| 4752 | bonded14Set = set(sorted(bonded14Set))
|
|---|
| 4753 | bonded14ParticleSets.append(bonded14Set)
|
|---|
| 4754 |
|
|---|
| 4755 | # 1-5
|
|---|
| 4756 |
|
|---|
| 4757 | bonded15ParticleSets = []
|
|---|
| 4758 | for i in range(len(data.atoms)):
|
|---|
| 4759 | bonded15Set = set()
|
|---|
| 4760 | bonded14ParticleSet = bonded14ParticleSets[i]
|
|---|
| 4761 | for j in bonded14ParticleSet:
|
|---|
| 4762 | bonded15Set = bonded15Set.union(bonded12ParticleSets[j])
|
|---|
| 4763 |
|
|---|
| 4764 | # remove 1-4, 1-3, 1-2 and self from set
|
|---|
| 4765 |
|
|---|
| 4766 | bonded15Set = bonded15Set - bonded12ParticleSets[i]
|
|---|
| 4767 | bonded15Set = bonded15Set - bonded13ParticleSets[i]
|
|---|
| 4768 | bonded15Set = bonded15Set - bonded14ParticleSet
|
|---|
| 4769 | selfSet = set()
|
|---|
| 4770 | selfSet.add(i)
|
|---|
| 4771 | bonded15Set = bonded15Set - selfSet
|
|---|
| 4772 | bonded15Set = set(sorted(bonded15Set))
|
|---|
| 4773 | bonded15ParticleSets.append(bonded15Set)
|
|---|
| 4774 |
|
|---|
| 4775 | for (atomIndex, atom) in enumerate(data.atoms):
|
|---|
| 4776 | t = data.atomType[atom]
|
|---|
| 4777 | if t in self.typeMap:
|
|---|
| 4778 |
|
|---|
| 4779 | multipoleList = self.typeMap[t]
|
|---|
| 4780 | hit = 0
|
|---|
| 4781 | savedMultipoleDict = 0
|
|---|
| 4782 |
|
|---|
| 4783 | # assign multipole parameters via only 1-2 connected atoms
|
|---|
| 4784 |
|
|---|
| 4785 | for multipoleDict in multipoleList:
|
|---|
| 4786 |
|
|---|
| 4787 | if (hit != 0):
|
|---|
| 4788 | break
|
|---|
| 4789 |
|
|---|
| 4790 | kIndices = multipoleDict['kIndices']
|
|---|
| 4791 |
|
|---|
| 4792 | kz = kIndices[1]
|
|---|
| 4793 | kx = kIndices[2]
|
|---|
| 4794 | ky = kIndices[3]
|
|---|
| 4795 |
|
|---|
| 4796 | # assign multipole parameters
|
|---|
| 4797 | # (1) get bonded partners
|
|---|
| 4798 | # (2) match parameter types
|
|---|
| 4799 |
|
|---|
| 4800 | bondedAtomIndices = bonded12ParticleSets[atomIndex]
|
|---|
| 4801 | zaxis = -1
|
|---|
| 4802 | xaxis = -1
|
|---|
| 4803 | yaxis = -1
|
|---|
| 4804 | for bondedAtomZIndex in bondedAtomIndices:
|
|---|
| 4805 |
|
|---|
| 4806 | if (hit != 0):
|
|---|
| 4807 | break
|
|---|
| 4808 |
|
|---|
| 4809 | bondedAtomZType = int(data.atomType[data.atoms[bondedAtomZIndex]])
|
|---|
| 4810 | bondedAtomZ = data.atoms[bondedAtomZIndex]
|
|---|
| 4811 | if (bondedAtomZType == kz):
|
|---|
| 4812 | for bondedAtomXIndex in bondedAtomIndices:
|
|---|
| 4813 | if (bondedAtomXIndex == bondedAtomZIndex or hit != 0):
|
|---|
| 4814 | continue
|
|---|
| 4815 | bondedAtomXType = int(data.atomType[data.atoms[bondedAtomXIndex]])
|
|---|
| 4816 | if (bondedAtomXType == kx):
|
|---|
| 4817 | if (ky == 0):
|
|---|
| 4818 | zaxis = bondedAtomZIndex
|
|---|
| 4819 | xaxis = bondedAtomXIndex
|
|---|
| 4820 | if( bondedAtomXType == bondedAtomZType and xaxis < zaxis ):
|
|---|
| 4821 | swapI = zaxis
|
|---|
| 4822 | zaxis = xaxis
|
|---|
| 4823 | xaxis = swapI
|
|---|
| 4824 | else:
|
|---|
| 4825 | for bondedAtomXIndex in bondedAtomIndices:
|
|---|
| 4826 | bondedAtomX1Type = int(data.atomType[data.atoms[bondedAtomXIndex]])
|
|---|
| 4827 | if( bondedAtomX1Type == kx and bondedAtomXIndex != bondedAtomZIndex and bondedAtomXIndex < xaxis ):
|
|---|
| 4828 | xaxis = bondedAtomXIndex
|
|---|
| 4829 |
|
|---|
| 4830 | savedMultipoleDict = multipoleDict
|
|---|
| 4831 | hit = 1
|
|---|
| 4832 | else:
|
|---|
| 4833 | for bondedAtomYIndex in bondedAtomIndices:
|
|---|
| 4834 | if (bondedAtomYIndex == bondedAtomZIndex or bondedAtomYIndex == bondedAtomXIndex or hit != 0):
|
|---|
| 4835 | continue
|
|---|
| 4836 | bondedAtomYType = int(data.atomType[data.atoms[bondedAtomYIndex]])
|
|---|
| 4837 | if (bondedAtomYType == ky):
|
|---|
| 4838 | zaxis = bondedAtomZIndex
|
|---|
| 4839 | xaxis = bondedAtomXIndex
|
|---|
| 4840 | yaxis = bondedAtomYIndex
|
|---|
| 4841 | savedMultipoleDict = multipoleDict
|
|---|
| 4842 | hit = 2
|
|---|
| 4843 |
|
|---|
| 4844 | # assign multipole parameters via 1-2 and 1-3 connected atoms
|
|---|
| 4845 |
|
|---|
| 4846 | for multipoleDict in multipoleList:
|
|---|
| 4847 |
|
|---|
| 4848 | if (hit != 0):
|
|---|
| 4849 | break
|
|---|
| 4850 |
|
|---|
| 4851 | kIndices = multipoleDict['kIndices']
|
|---|
| 4852 |
|
|---|
| 4853 | kz = kIndices[1]
|
|---|
| 4854 | kx = kIndices[2]
|
|---|
| 4855 | ky = kIndices[3]
|
|---|
| 4856 |
|
|---|
| 4857 | # assign multipole parameters
|
|---|
| 4858 | # (1) get bonded partners
|
|---|
| 4859 | # (2) match parameter types
|
|---|
| 4860 |
|
|---|
| 4861 | bondedAtom12Indices = bonded12ParticleSets[atomIndex]
|
|---|
| 4862 | bondedAtom13Indices = bonded13ParticleSets[atomIndex]
|
|---|
| 4863 |
|
|---|
| 4864 | zaxis = -1
|
|---|
| 4865 | xaxis = -1
|
|---|
| 4866 | yaxis = -1
|
|---|
| 4867 |
|
|---|
| 4868 | for bondedAtomZIndex in bondedAtom12Indices:
|
|---|
| 4869 |
|
|---|
| 4870 | if (hit != 0):
|
|---|
| 4871 | break
|
|---|
| 4872 |
|
|---|
| 4873 | bondedAtomZType = int(data.atomType[data.atoms[bondedAtomZIndex]])
|
|---|
| 4874 | bondedAtomZ = data.atoms[bondedAtomZIndex]
|
|---|
| 4875 |
|
|---|
| 4876 | if (bondedAtomZType == kz):
|
|---|
| 4877 | for bondedAtomXIndex in bondedAtom13Indices:
|
|---|
| 4878 |
|
|---|
| 4879 | if (bondedAtomXIndex == bondedAtomZIndex or hit != 0):
|
|---|
| 4880 | continue
|
|---|
| 4881 | bondedAtomXType = int(data.atomType[data.atoms[bondedAtomXIndex]])
|
|---|
| 4882 | if (bondedAtomXType == kx and bondedAtomZIndex in bonded12ParticleSets[bondedAtomXIndex]):
|
|---|
| 4883 | if (ky == 0):
|
|---|
| 4884 | zaxis = bondedAtomZIndex
|
|---|
| 4885 | xaxis = bondedAtomXIndex
|
|---|
| 4886 |
|
|---|
| 4887 | # select xaxis w/ smallest index
|
|---|
| 4888 |
|
|---|
| 4889 | for bondedAtomXIndex in bondedAtom13Indices:
|
|---|
| 4890 | bondedAtomX1Type = int(data.atomType[data.atoms[bondedAtomXIndex]])
|
|---|
| 4891 | if( bondedAtomX1Type == kx and bondedAtomXIndex != bondedAtomZIndex and bondedAtomZIndex in bonded12ParticleSets[bondedAtomXIndex] and bondedAtomXIndex < xaxis ):
|
|---|
| 4892 | xaxis = bondedAtomXIndex
|
|---|
| 4893 |
|
|---|
| 4894 | savedMultipoleDict = multipoleDict
|
|---|
| 4895 | hit = 3
|
|---|
| 4896 | else:
|
|---|
| 4897 | for bondedAtomYIndex in bondedAtom13Indices:
|
|---|
| 4898 | if (bondedAtomYIndex == bondedAtomZIndex or bondedAtomYIndex == bondedAtomXIndex or hit != 0):
|
|---|
| 4899 | continue
|
|---|
| 4900 | bondedAtomYType = int(data.atomType[data.atoms[bondedAtomYIndex]])
|
|---|
| 4901 | if (bondedAtomYType == ky and bondedAtomZIndex in bonded12ParticleSets[bondedAtomYIndex]):
|
|---|
| 4902 | zaxis = bondedAtomZIndex
|
|---|
| 4903 | xaxis = bondedAtomXIndex
|
|---|
| 4904 | yaxis = bondedAtomYIndex
|
|---|
| 4905 | savedMultipoleDict = multipoleDict
|
|---|
| 4906 | hit = 4
|
|---|
| 4907 |
|
|---|
| 4908 | # assign multipole parameters via only a z-defining atom
|
|---|
| 4909 |
|
|---|
| 4910 | for multipoleDict in multipoleList:
|
|---|
| 4911 |
|
|---|
| 4912 | if (hit != 0):
|
|---|
| 4913 | break
|
|---|
| 4914 |
|
|---|
| 4915 | kIndices = multipoleDict['kIndices']
|
|---|
| 4916 |
|
|---|
| 4917 | kz = kIndices[1]
|
|---|
| 4918 | kx = kIndices[2]
|
|---|
| 4919 |
|
|---|
| 4920 | zaxis = -1
|
|---|
| 4921 | xaxis = -1
|
|---|
| 4922 | yaxis = -1
|
|---|
| 4923 |
|
|---|
| 4924 | for bondedAtomZIndex in bondedAtom12Indices:
|
|---|
| 4925 |
|
|---|
| 4926 | if (hit != 0):
|
|---|
| 4927 | break
|
|---|
| 4928 |
|
|---|
| 4929 | bondedAtomZType = int(data.atomType[data.atoms[bondedAtomZIndex]])
|
|---|
| 4930 | bondedAtomZ = data.atoms[bondedAtomZIndex]
|
|---|
| 4931 |
|
|---|
| 4932 | if (kx == 0 and kz == bondedAtomZType):
|
|---|
| 4933 | zaxis = bondedAtomZIndex
|
|---|
| 4934 | savedMultipoleDict = multipoleDict
|
|---|
| 4935 | hit = 5
|
|---|
| 4936 |
|
|---|
| 4937 | # assign multipole parameters via no connected atoms
|
|---|
| 4938 |
|
|---|
| 4939 | for multipoleDict in multipoleList:
|
|---|
| 4940 |
|
|---|
| 4941 | if (hit != 0):
|
|---|
| 4942 | break
|
|---|
| 4943 |
|
|---|
| 4944 | kIndices = multipoleDict['kIndices']
|
|---|
| 4945 |
|
|---|
| 4946 | kz = kIndices[1]
|
|---|
| 4947 |
|
|---|
| 4948 | zaxis = -1
|
|---|
| 4949 | xaxis = -1
|
|---|
| 4950 | yaxis = -1
|
|---|
| 4951 |
|
|---|
| 4952 | if (kz == 0):
|
|---|
| 4953 | savedMultipoleDict = multipoleDict
|
|---|
| 4954 | hit = 6
|
|---|
| 4955 |
|
|---|
| 4956 | # add particle if there was a hit
|
|---|
| 4957 |
|
|---|
| 4958 | if (hit != 0):
|
|---|
| 4959 |
|
|---|
| 4960 | atom.multipoleDict = savedMultipoleDict
|
|---|
| 4961 | atom.polarizationGroups = dict()
|
|---|
| 4962 | newIndex = force.addMultipole(savedMultipoleDict['charge'], savedMultipoleDict['dipole'], savedMultipoleDict['quadrupole'], savedMultipoleDict['axisType'],
|
|---|
| 4963 | zaxis, xaxis, yaxis, savedMultipoleDict['thole'], savedMultipoleDict['pdamp'], savedMultipoleDict['polarizability'])
|
|---|
| 4964 | if (atomIndex == newIndex):
|
|---|
| 4965 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.Covalent12, tuple(bonded12ParticleSets[atomIndex]))
|
|---|
| 4966 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.Covalent13, tuple(bonded13ParticleSets[atomIndex]))
|
|---|
| 4967 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.Covalent14, tuple(bonded14ParticleSets[atomIndex]))
|
|---|
| 4968 | force.setCovalentMap(atomIndex, mm.AmoebaMultipoleForce.Covalent15, tuple(bonded15ParticleSets[atomIndex]))
|
|---|
| 4969 | else:
|
|---|
| 4970 | raise ValueError("Atom %s of %s %d is out of sync!." %(atom.name, atom.residue.name, atom.residue.index))
|
|---|
| 4971 | else:
|
|---|
| 4972 | raise ValueError("Atom %s of %s %d was not assigned." %(atom.name, atom.residue.name, atom.residue.index))
|
|---|
| 4973 | else:
|
|---|
| 4974 | raise ValueError('No multipole type for atom %s %s %d' % (atom.name, atom.residue.name, atom.residue.index))
|
|---|
| 4975 |
|
|---|
| 4976 | # set polar groups
|
|---|
| 4977 |
|
|---|
| 4978 | self.setPolarGroups(data, bonded12ParticleSets, force)
|
|---|
| 4979 |
|
|---|
| 4980 | parsers["AmoebaMultipoleForce"] = AmoebaMultipoleGenerator.parseElement
|
|---|
| 4981 |
|
|---|
| 4982 | #=============================================================================================
|
|---|
| 4983 |
|
|---|
| 4984 | ## @private
|
|---|
| 4985 | class AmoebaWcaDispersionGenerator(object):
|
|---|
| 4986 |
|
|---|
| 4987 | """A AmoebaWcaDispersionGenerator constructs a AmoebaWcaDispersionForce."""
|
|---|
| 4988 |
|
|---|
| 4989 | #=========================================================================================
|
|---|
| 4990 |
|
|---|
| 4991 | def __init__(self, epso, epsh, rmino, rminh, awater, slevy, dispoff, shctd):
|
|---|
| 4992 |
|
|---|
| 4993 | self.epso = epso
|
|---|
| 4994 | self.epsh = epsh
|
|---|
| 4995 | self.rmino = rmino
|
|---|
| 4996 | self.rminh = rminh
|
|---|
| 4997 | self.awater = awater
|
|---|
| 4998 | self.slevy = slevy
|
|---|
| 4999 | self.dispoff = dispoff
|
|---|
| 5000 | self.shctd = shctd
|
|---|
| 5001 |
|
|---|
| 5002 | #=========================================================================================
|
|---|
| 5003 |
|
|---|
| 5004 | @staticmethod
|
|---|
| 5005 | def parseElement(element, forceField):
|
|---|
| 5006 |
|
|---|
| 5007 | # <AmoebaWcaDispersionForce epso="0.46024" epsh="0.056484" rmino="0.17025" rminh="0.13275" awater="33.428" slevy="1.0" dispoff="0.026" shctd="0.81" >
|
|---|
| 5008 | # <WcaDispersion class="1" radius="0.1855" epsilon="0.46024" />
|
|---|
| 5009 | # <WcaDispersion class="2" radius="0.191" epsilon="0.422584" />
|
|---|
| 5010 |
|
|---|
| 5011 | generator = AmoebaWcaDispersionGenerator(element.attrib['epso'],
|
|---|
| 5012 | element.attrib['epsh'],
|
|---|
| 5013 | element.attrib['rmino'],
|
|---|
| 5014 | element.attrib['rminh'],
|
|---|
| 5015 | element.attrib['awater'],
|
|---|
| 5016 | element.attrib['slevy'],
|
|---|
| 5017 | element.attrib['dispoff'],
|
|---|
| 5018 | element.attrib['shctd'])
|
|---|
| 5019 | forceField._forces.append(generator)
|
|---|
| 5020 | generator.params = ForceField._AtomTypeParameters(forceField, 'AmoebaWcaDispersionForce', 'WcaDispersion', ('radius', 'epsilon'))
|
|---|
| 5021 | generator.params.parseDefinitions(element)
|
|---|
| 5022 |
|
|---|
| 5023 | #=========================================================================================
|
|---|
| 5024 |
|
|---|
| 5025 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 5026 |
|
|---|
| 5027 | # get or create force depending on whether it has already been added to the system
|
|---|
| 5028 |
|
|---|
| 5029 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 5030 | existing = [f for f in existing if type(f) == mm.AmoebaWcaDispersionForce]
|
|---|
| 5031 | if len(existing) == 0:
|
|---|
| 5032 | force = mm.AmoebaWcaDispersionForce()
|
|---|
| 5033 | sys.addForce(force)
|
|---|
| 5034 | else:
|
|---|
| 5035 | force = existing[0]
|
|---|
| 5036 |
|
|---|
| 5037 | # add particles to force
|
|---|
| 5038 | # throw error if particle type not available
|
|---|
| 5039 |
|
|---|
| 5040 | force.setEpso( float(self.epso ))
|
|---|
| 5041 | force.setEpsh( float(self.epsh ))
|
|---|
| 5042 | force.setRmino( float(self.rmino ))
|
|---|
| 5043 | force.setRminh( float(self.rminh ))
|
|---|
| 5044 | force.setDispoff(float(self.dispoff))
|
|---|
| 5045 | force.setSlevy( float(self.slevy ))
|
|---|
| 5046 | force.setAwater( float(self.awater ))
|
|---|
| 5047 | force.setShctd( float(self.shctd ))
|
|---|
| 5048 |
|
|---|
| 5049 | for atom in data.atoms:
|
|---|
| 5050 | values = self.params.getAtomParameters(atom, data)
|
|---|
| 5051 | force.addParticle(values[0], values[1])
|
|---|
| 5052 |
|
|---|
| 5053 | parsers["AmoebaWcaDispersionForce"] = AmoebaWcaDispersionGenerator.parseElement
|
|---|
| 5054 |
|
|---|
| 5055 | #=============================================================================================
|
|---|
| 5056 |
|
|---|
| 5057 | ## @private
|
|---|
| 5058 | class AmoebaGeneralizedKirkwoodGenerator(object):
|
|---|
| 5059 |
|
|---|
| 5060 | """A AmoebaGeneralizedKirkwoodGenerator constructs a AmoebaGeneralizedKirkwoodForce."""
|
|---|
| 5061 |
|
|---|
| 5062 | #=========================================================================================
|
|---|
| 5063 |
|
|---|
| 5064 | def __init__(self, forceField, solventDielectric, soluteDielectric, includeCavityTerm, probeRadius, surfaceAreaFactor):
|
|---|
| 5065 |
|
|---|
| 5066 | self.forceField = forceField
|
|---|
| 5067 | self.solventDielectric = solventDielectric
|
|---|
| 5068 | self.soluteDielectric = soluteDielectric
|
|---|
| 5069 | self.includeCavityTerm = includeCavityTerm
|
|---|
| 5070 | self.probeRadius = probeRadius
|
|---|
| 5071 | self.surfaceAreaFactor = surfaceAreaFactor
|
|---|
| 5072 |
|
|---|
| 5073 | self.radiusTypeMap = {}
|
|---|
| 5074 | self.radiusTypeMap['Bondi'] = {}
|
|---|
| 5075 | bondiMap = self.radiusTypeMap['Bondi']
|
|---|
| 5076 | rscale = 1.03
|
|---|
| 5077 |
|
|---|
| 5078 | bondiMap[0] = 0.00
|
|---|
| 5079 | bondiMap[1] = 0.12*rscale
|
|---|
| 5080 | bondiMap[2] = 0.14*rscale
|
|---|
| 5081 | bondiMap[5] = 0.18*rscale
|
|---|
| 5082 |
|
|---|
| 5083 | bondiMap[6] = 0.170*rscale
|
|---|
| 5084 | bondiMap[7] = 0.155*rscale
|
|---|
| 5085 | bondiMap[8] = 0.152*rscale
|
|---|
| 5086 | bondiMap[9] = 0.147*rscale
|
|---|
| 5087 |
|
|---|
| 5088 | bondiMap[10] = 0.154*rscale
|
|---|
| 5089 | bondiMap[14] = 0.210*rscale
|
|---|
| 5090 | bondiMap[15] = 0.180*rscale
|
|---|
| 5091 | bondiMap[16] = 0.180*rscale
|
|---|
| 5092 |
|
|---|
| 5093 | bondiMap[17] = 0.175 *rscale
|
|---|
| 5094 | bondiMap[18] = 0.188*rscale
|
|---|
| 5095 | bondiMap[34] = 0.190*rscale
|
|---|
| 5096 | bondiMap[35] = 0.185*rscale
|
|---|
| 5097 |
|
|---|
| 5098 | bondiMap[36] = 0.202*rscale
|
|---|
| 5099 | bondiMap[53] = 0.198*rscale
|
|---|
| 5100 | bondiMap[54] = 0.216*rscale
|
|---|
| 5101 |
|
|---|
| 5102 | #=========================================================================================
|
|---|
| 5103 |
|
|---|
| 5104 | def getObcShct(self, data, atomIndex):
|
|---|
| 5105 |
|
|---|
| 5106 | atom = data.atoms[atomIndex]
|
|---|
| 5107 | atomicNumber = atom.element.atomic_number
|
|---|
| 5108 | shct = -1.0
|
|---|
| 5109 |
|
|---|
| 5110 | # shct
|
|---|
| 5111 |
|
|---|
| 5112 | if (atomicNumber == 1): # H(1)
|
|---|
| 5113 | shct = 0.85
|
|---|
| 5114 | elif (atomicNumber == 6): # C(6)
|
|---|
| 5115 | shct = 0.72
|
|---|
| 5116 | elif (atomicNumber == 7): # N(7)
|
|---|
| 5117 | shct = 0.79
|
|---|
| 5118 | elif (atomicNumber == 8): # O(8)
|
|---|
| 5119 | shct = 0.85
|
|---|
| 5120 | elif (atomicNumber == 9): # F(9)
|
|---|
| 5121 | shct = 0.88
|
|---|
| 5122 | elif (atomicNumber == 15): # P(15)
|
|---|
| 5123 | shct = 0.86
|
|---|
| 5124 | elif (atomicNumber == 16): # S(16)
|
|---|
| 5125 | shct = 0.96
|
|---|
| 5126 | elif (atomicNumber == 26): # Fe(26)
|
|---|
| 5127 | shct = 0.88
|
|---|
| 5128 |
|
|---|
| 5129 | if (shct < 0.0):
|
|---|
| 5130 | raise ValueError( "getObcShct: no GK overlap scale factor for atom %s of %s %d" % (atom.name, atom.residue.name, atom.residue.index) )
|
|---|
| 5131 |
|
|---|
| 5132 | return shct
|
|---|
| 5133 |
|
|---|
| 5134 | #=========================================================================================
|
|---|
| 5135 |
|
|---|
| 5136 | def getAmoebaTypeRadius(self, data, bondedAtomIndices, atomIndex):
|
|---|
| 5137 |
|
|---|
| 5138 | atom = data.atoms[atomIndex]
|
|---|
| 5139 | atomicNumber = atom.element.atomic_number
|
|---|
| 5140 | radius = -1.0
|
|---|
| 5141 |
|
|---|
| 5142 | if (atomicNumber == 1): # H(1)
|
|---|
| 5143 |
|
|---|
| 5144 | radius = 0.132
|
|---|
| 5145 |
|
|---|
| 5146 | if (len(bondedAtomIndices) < 1):
|
|---|
| 5147 | raise ValueError( "AmoebaGeneralizedKirkwoodGenerator: error getting atom bonded to %s of %s %d " % (atom.name, atom.residue.name, atom.residue.index) )
|
|---|
| 5148 |
|
|---|
| 5149 | for bondedAtomIndex in bondedAtomIndices:
|
|---|
| 5150 | bondedAtomAtomicNumber = data.atoms[bondedAtomIndex].element.atomic_number
|
|---|
| 5151 |
|
|---|
| 5152 | if (bondedAtomAtomicNumber == 7):
|
|---|
| 5153 | radius = 0.11
|
|---|
| 5154 | if (bondedAtomAtomicNumber == 8):
|
|---|
| 5155 | radius = 0.105
|
|---|
| 5156 |
|
|---|
| 5157 | elif (atomicNumber == 3): # Li(3)
|
|---|
| 5158 | radius = 0.15
|
|---|
| 5159 | elif (atomicNumber == 6): # C(6)
|
|---|
| 5160 |
|
|---|
| 5161 | radius = 0.20
|
|---|
| 5162 | if (len(bondedAtomIndices) == 3):
|
|---|
| 5163 | radius = 0.205
|
|---|
| 5164 |
|
|---|
| 5165 | elif (len(bondedAtomIndices) == 4):
|
|---|
| 5166 | for bondedAtomIndex in bondedAtomIndices:
|
|---|
| 5167 | bondedAtomAtomicNumber = data.atoms[bondedAtomIndex].element.atomic_number
|
|---|
| 5168 | if (bondedAtomAtomicNumber == 7 or bondedAtomAtomicNumber == 8):
|
|---|
| 5169 | radius = 0.175
|
|---|
| 5170 |
|
|---|
| 5171 | elif (atomicNumber == 7): # N(7)
|
|---|
| 5172 | radius = 0.16
|
|---|
| 5173 | elif (atomicNumber == 8): # O(8)
|
|---|
| 5174 | radius = 0.155
|
|---|
| 5175 | if (len(bondedAtomIndices) == 2):
|
|---|
| 5176 | radius = 0.145
|
|---|
| 5177 | elif (atomicNumber == 9): # F(9)
|
|---|
| 5178 | radius = 0.154
|
|---|
| 5179 | elif (atomicNumber == 10):
|
|---|
| 5180 | radius = 0.146
|
|---|
| 5181 | elif (atomicNumber == 11):
|
|---|
| 5182 | radius = 0.209
|
|---|
| 5183 | elif (atomicNumber == 12):
|
|---|
| 5184 | radius = 0.179
|
|---|
| 5185 | elif (atomicNumber == 14):
|
|---|
| 5186 | radius = 0.189
|
|---|
| 5187 | elif (atomicNumber == 15): # P(15)
|
|---|
| 5188 | radius = 0.196
|
|---|
| 5189 | elif (atomicNumber == 16): # S(16)
|
|---|
| 5190 | radius = 0.186
|
|---|
| 5191 | elif (atomicNumber == 17):
|
|---|
| 5192 | radius = 0.182
|
|---|
| 5193 | elif (atomicNumber == 18):
|
|---|
| 5194 | radius = 0.179
|
|---|
| 5195 | elif (atomicNumber == 19):
|
|---|
| 5196 | radius = 0.223
|
|---|
| 5197 | elif (atomicNumber == 20):
|
|---|
| 5198 | radius = 0.191
|
|---|
| 5199 | elif (atomicNumber == 35):
|
|---|
| 5200 | radius = 2.00
|
|---|
| 5201 | elif (atomicNumber == 36):
|
|---|
| 5202 | radius = 0.190
|
|---|
| 5203 | elif (atomicNumber == 37):
|
|---|
| 5204 | radius = 0.226
|
|---|
| 5205 | elif (atomicNumber == 53):
|
|---|
| 5206 | radius = 0.237
|
|---|
| 5207 | elif (atomicNumber == 54):
|
|---|
| 5208 | radius = 0.207
|
|---|
| 5209 | elif (atomicNumber == 55):
|
|---|
| 5210 | radius = 0.263
|
|---|
| 5211 | elif (atomicNumber == 56):
|
|---|
| 5212 | radius = 0.230
|
|---|
| 5213 |
|
|---|
| 5214 | if (radius < 0.0):
|
|---|
| 5215 | outputString = "No GK radius for atom %s of %s %d" % (atom.name, atom.residue.name, atom.residue.index)
|
|---|
| 5216 | raise ValueError( outputString )
|
|---|
| 5217 |
|
|---|
| 5218 | return radius
|
|---|
| 5219 |
|
|---|
| 5220 | #=========================================================================================
|
|---|
| 5221 |
|
|---|
| 5222 | def getBondiTypeRadius(self, data, bondedAtomIndices, atomIndex):
|
|---|
| 5223 |
|
|---|
| 5224 | bondiMap = self.radiusTypeMap['Bondi']
|
|---|
| 5225 | atom = data.atoms[atomIndex]
|
|---|
| 5226 | atomicNumber = atom.element.atomic_number
|
|---|
| 5227 | if (atomicNumber in bondiMap):
|
|---|
| 5228 | radius = bondiMap[atomicNumber]
|
|---|
| 5229 | else:
|
|---|
| 5230 | outputString = "Warning no Bondi radius for atom %s of %s %d using default value=%f" % (atom.name, atom.residue.name, atom.residue.index, radius)
|
|---|
| 5231 | raise ValueError( outputString )
|
|---|
| 5232 |
|
|---|
| 5233 | return radius
|
|---|
| 5234 |
|
|---|
| 5235 | #=========================================================================================
|
|---|
| 5236 |
|
|---|
| 5237 | @staticmethod
|
|---|
| 5238 | def parseElement(element, forceField):
|
|---|
| 5239 |
|
|---|
| 5240 | # <AmoebaGeneralizedKirkwoodForce solventDielectric="78.3" soluteDielectric="1.0" includeCavityTerm="1" probeRadius="0.14" surfaceAreaFactor="-170.351730663">
|
|---|
| 5241 | # <GeneralizedKirkwood type="1" charge="-0.22620" shct="0.79" />
|
|---|
| 5242 | # <GeneralizedKirkwood type="2" charge="-0.15245" shct="0.72" />
|
|---|
| 5243 |
|
|---|
| 5244 | generator = AmoebaGeneralizedKirkwoodGenerator(forceField, element.attrib['solventDielectric'], element.attrib['soluteDielectric'],
|
|---|
| 5245 | element.attrib['includeCavityTerm'],
|
|---|
| 5246 | element.attrib['probeRadius'], element.attrib['surfaceAreaFactor'])
|
|---|
| 5247 | forceField._forces.append(generator)
|
|---|
| 5248 |
|
|---|
| 5249 | #=========================================================================================
|
|---|
| 5250 |
|
|---|
| 5251 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 5252 |
|
|---|
| 5253 | if( nonbondedMethod != NoCutoff ):
|
|---|
| 5254 | raise ValueError( "Only the nonbondedMethod=NoCutoff option is available for implicit solvent simulations." )
|
|---|
| 5255 |
|
|---|
| 5256 | # check if AmoebaMultipoleForce exists since charges needed
|
|---|
| 5257 | # if it has not been created, raise an error
|
|---|
| 5258 |
|
|---|
| 5259 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 5260 | amoebaMultipoleForceList = [f for f in existing if type(f) == mm.AmoebaMultipoleForce]
|
|---|
| 5261 | if (len(amoebaMultipoleForceList) > 0):
|
|---|
| 5262 | amoebaMultipoleForce = amoebaMultipoleForceList[0]
|
|---|
| 5263 | else:
|
|---|
| 5264 | # call AmoebaMultipoleForceGenerator.createForce() to ensure charges have been set
|
|---|
| 5265 |
|
|---|
| 5266 | for force in self.forceField._forces:
|
|---|
| 5267 | if (force.__class__.__name__ == 'AmoebaMultipoleGenerator'):
|
|---|
| 5268 | force.createForce(sys, data, nonbondedMethod, nonbondedCutoff, args)
|
|---|
| 5269 |
|
|---|
| 5270 | # get or create force depending on whether it has already been added to the system
|
|---|
| 5271 |
|
|---|
| 5272 | existing = [f for f in existing if type(f) == mm.AmoebaGeneralizedKirkwoodForce]
|
|---|
| 5273 | if len(existing) == 0:
|
|---|
| 5274 |
|
|---|
| 5275 | force = mm.AmoebaGeneralizedKirkwoodForce()
|
|---|
| 5276 | sys.addForce(force)
|
|---|
| 5277 |
|
|---|
| 5278 | if ('solventDielectric' in args):
|
|---|
| 5279 | force.setSolventDielectric(float(args['solventDielectric']))
|
|---|
| 5280 | else:
|
|---|
| 5281 | force.setSolventDielectric( float(self.solventDielectric))
|
|---|
| 5282 |
|
|---|
| 5283 | if ('soluteDielectric' in args):
|
|---|
| 5284 | force.setSoluteDielectric(float(args['soluteDielectric']))
|
|---|
| 5285 | else:
|
|---|
| 5286 | force.setSoluteDielectric( float(self.soluteDielectric))
|
|---|
| 5287 |
|
|---|
| 5288 | if ('includeCavityTerm' in args):
|
|---|
| 5289 | force.setIncludeCavityTerm(int(args['includeCavityTerm']))
|
|---|
| 5290 | else:
|
|---|
| 5291 | force.setIncludeCavityTerm( int(self.includeCavityTerm))
|
|---|
| 5292 |
|
|---|
| 5293 | else:
|
|---|
| 5294 | force = existing[0]
|
|---|
| 5295 |
|
|---|
| 5296 | # add particles to force
|
|---|
| 5297 | # throw error if particle type not available
|
|---|
| 5298 |
|
|---|
| 5299 | force.setProbeRadius( float(self.probeRadius))
|
|---|
| 5300 | force.setSurfaceAreaFactor( float(self.surfaceAreaFactor))
|
|---|
| 5301 |
|
|---|
| 5302 | # 1-2
|
|---|
| 5303 |
|
|---|
| 5304 | bonded12ParticleSets = AmoebaVdwGenerator.getBondedParticleSets(sys, data)
|
|---|
| 5305 |
|
|---|
| 5306 | radiusType = 'Bondi'
|
|---|
| 5307 | for atomIndex in range(0, amoebaMultipoleForce.getNumMultipoles()):
|
|---|
| 5308 | multipoleParameters = amoebaMultipoleForce.getMultipoleParameters(atomIndex)
|
|---|
| 5309 | if (radiusType == 'Amoeba'):
|
|---|
| 5310 | radius = self.getAmoebaTypeRadius(data, bonded12ParticleSets[atomIndex], atomIndex)
|
|---|
| 5311 | else:
|
|---|
| 5312 | radius = self.getBondiTypeRadius(data, bonded12ParticleSets[atomIndex], atomIndex)
|
|---|
| 5313 | #shct = self.getObcShct(data, atomIndex)
|
|---|
| 5314 | shct = 0.69
|
|---|
| 5315 | force.addParticle(multipoleParameters[0], radius, shct)
|
|---|
| 5316 |
|
|---|
| 5317 | parsers["AmoebaGeneralizedKirkwoodForce"] = AmoebaGeneralizedKirkwoodGenerator.parseElement
|
|---|
| 5318 |
|
|---|
| 5319 | #=============================================================================================
|
|---|
| 5320 |
|
|---|
| 5321 | ## @private
|
|---|
| 5322 | class AmoebaUreyBradleyGenerator(object):
|
|---|
| 5323 |
|
|---|
| 5324 | #=============================================================================================
|
|---|
| 5325 | """An AmoebaUreyBradleyGenerator constructs a AmoebaUreyBradleyForce."""
|
|---|
| 5326 | #=============================================================================================
|
|---|
| 5327 |
|
|---|
| 5328 | def __init__(self):
|
|---|
| 5329 |
|
|---|
| 5330 | self.types1 = []
|
|---|
| 5331 | self.types2 = []
|
|---|
| 5332 | self.types3 = []
|
|---|
| 5333 |
|
|---|
| 5334 | self.length = []
|
|---|
| 5335 | self.k = []
|
|---|
| 5336 |
|
|---|
| 5337 | #=============================================================================================
|
|---|
| 5338 |
|
|---|
| 5339 | @staticmethod
|
|---|
| 5340 | def parseElement(element, forceField):
|
|---|
| 5341 |
|
|---|
| 5342 | # <AmoebaUreyBradleyForce>
|
|---|
| 5343 | # <UreyBradley class1="74" class2="73" class3="74" k="16003.8" d="0.15537" />
|
|---|
| 5344 |
|
|---|
| 5345 | generator = AmoebaUreyBradleyGenerator()
|
|---|
| 5346 | forceField._forces.append(generator)
|
|---|
| 5347 | for bond in element.findall('UreyBradley'):
|
|---|
| 5348 | types = forceField._findAtomTypes(bond.attrib, 3)
|
|---|
| 5349 | if None not in types:
|
|---|
| 5350 |
|
|---|
| 5351 | generator.types1.append(types[0])
|
|---|
| 5352 | generator.types2.append(types[1])
|
|---|
| 5353 | generator.types3.append(types[2])
|
|---|
| 5354 |
|
|---|
| 5355 | generator.length.append(float(bond.attrib['d']))
|
|---|
| 5356 | generator.k.append(float(bond.attrib['k']))
|
|---|
| 5357 |
|
|---|
| 5358 | else:
|
|---|
| 5359 | outputString = "AmoebaUreyBradleyGenerator: error getting types: %s %s %s" % (
|
|---|
| 5360 | bond.attrib['class1'], bond.attrib['class2'], bond.attrib['class3'])
|
|---|
| 5361 | raise ValueError(outputString)
|
|---|
| 5362 |
|
|---|
| 5363 | #=============================================================================================
|
|---|
| 5364 |
|
|---|
| 5365 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 5366 |
|
|---|
| 5367 | existing = [sys.getForce(i) for i in range(sys.getNumForces())]
|
|---|
| 5368 | existing = [f for f in existing if type(f) == mm.HarmonicBondForce]
|
|---|
| 5369 |
|
|---|
| 5370 | if len(existing) == 0:
|
|---|
| 5371 | force = mm.HarmonicBondForce()
|
|---|
| 5372 | sys.addForce(force)
|
|---|
| 5373 | else:
|
|---|
| 5374 | force = existing[0]
|
|---|
| 5375 |
|
|---|
| 5376 | for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
|
|---|
| 5377 | if (isConstrained):
|
|---|
| 5378 | continue
|
|---|
| 5379 | type1 = data.atomType[data.atoms[angle[0]]]
|
|---|
| 5380 | type2 = data.atomType[data.atoms[angle[1]]]
|
|---|
| 5381 | type3 = data.atomType[data.atoms[angle[2]]]
|
|---|
| 5382 | for i in range(len(self.types1)):
|
|---|
| 5383 | types1 = self.types1[i]
|
|---|
| 5384 | types2 = self.types2[i]
|
|---|
| 5385 | types3 = self.types3[i]
|
|---|
| 5386 | if ((type1 in types1 and type2 in types2 and type3 in types3) or (type3 in types1 and type2 in types2 and type1 in types3)):
|
|---|
| 5387 | force.addBond(angle[0], angle[2], self.length[i], 2*self.k[i])
|
|---|
| 5388 | break
|
|---|
| 5389 |
|
|---|
| 5390 | parsers["AmoebaUreyBradleyForce"] = AmoebaUreyBradleyGenerator.parseElement
|
|---|
| 5391 |
|
|---|
| 5392 | #=============================================================================================
|
|---|
| 5393 |
|
|---|
| 5394 |
|
|---|
| 5395 | ## @private
|
|---|
| 5396 | class DrudeGenerator(object):
|
|---|
| 5397 | """A DrudeGenerator constructs a DrudeForce."""
|
|---|
| 5398 |
|
|---|
| 5399 | def __init__(self, forcefield):
|
|---|
| 5400 | self.ff = forcefield
|
|---|
| 5401 | self.typeMap = {}
|
|---|
| 5402 |
|
|---|
| 5403 | @staticmethod
|
|---|
| 5404 | def parseElement(element, ff):
|
|---|
| 5405 | existing = [f for f in ff._forces if isinstance(f, DrudeGenerator)]
|
|---|
| 5406 | if len(existing) == 0:
|
|---|
| 5407 | generator = DrudeGenerator(ff)
|
|---|
| 5408 | ff.registerGenerator(generator)
|
|---|
| 5409 | else:
|
|---|
| 5410 | # Multiple <DrudeForce> tags were found, probably in different files. Simply add more types to the existing one.
|
|---|
| 5411 | generator = existing[0]
|
|---|
| 5412 | for particle in element.findall('Particle'):
|
|---|
| 5413 | types = ff._findAtomTypes(particle.attrib, 5)
|
|---|
| 5414 | if None not in types[:2]:
|
|---|
| 5415 | aniso12 = 0.0
|
|---|
| 5416 | aniso34 = 0.0
|
|---|
| 5417 | if 'aniso12' in particle.attrib:
|
|---|
| 5418 | aniso12 = float(particle.attrib['aniso12'])
|
|---|
| 5419 | if 'aniso34' in particle.attrib:
|
|---|
| 5420 | aniso34 = float(particle.attrib['aniso34'])
|
|---|
| 5421 | values = (types[1], types[2], types[3], types[4], float(particle.attrib['charge']), float(particle.attrib['polarizability']), aniso12, aniso34, float(particle.attrib['thole']))
|
|---|
| 5422 | for t in types[0]:
|
|---|
| 5423 | generator.typeMap[t] = values
|
|---|
| 5424 |
|
|---|
| 5425 | def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
|
|---|
| 5426 | force = mm.DrudeForce()
|
|---|
| 5427 | if not any(isinstance(f, mm.NonbondedForce) for f in sys.getForces()):
|
|---|
| 5428 | raise ValueError('<DrudeForce> must come after <NonbondedForce> in XML file')
|
|---|
| 5429 |
|
|---|
| 5430 | # Add Drude particles.
|
|---|
| 5431 |
|
|---|
| 5432 | for atom in data.atoms:
|
|---|
| 5433 | t = data.atomType[atom]
|
|---|
| 5434 | if t in self.typeMap:
|
|---|
| 5435 | # Find other atoms in the residue that affect the Drude particle.
|
|---|
| 5436 | p = [-1, -1, -1, -1]
|
|---|
| 5437 | values = self.typeMap[t]
|
|---|
| 5438 | for atom2 in atom.residue.atoms():
|
|---|
| 5439 | type2 = data.atomType[atom2]
|
|---|
| 5440 | if type2 in values[0]:
|
|---|
| 5441 | p[0] = atom2.index
|
|---|
| 5442 | elif values[1] is not None and type2 in values[1]:
|
|---|
| 5443 | p[1] = atom2.index
|
|---|
| 5444 | elif values[2] is not None and type2 in values[2]:
|
|---|
| 5445 | p[2] = atom2.index
|
|---|
| 5446 | elif values[3] is not None and type2 in values[3]:
|
|---|
| 5447 | p[3] = atom2.index
|
|---|
| 5448 | force.addParticle(atom.index, p[0], p[1], p[2], p[3], values[4], values[5], values[6], values[7])
|
|---|
| 5449 | data.excludeAtomWith[p[0]].append(atom.index)
|
|---|
| 5450 | sys.addForce(force)
|
|---|
| 5451 |
|
|---|
| 5452 | def postprocessSystem(self, sys, data, args):
|
|---|
| 5453 | # For every nonbonded exclusion between Drude particles, add a screened pair.
|
|---|
| 5454 |
|
|---|
| 5455 | drude = [f for f in sys.getForces() if isinstance(f, mm.DrudeForce)][0]
|
|---|
| 5456 | nonbonded = [f for f in sys.getForces() if isinstance(f, mm.NonbondedForce)][0]
|
|---|
| 5457 | particleMap = {}
|
|---|
| 5458 | for i in range(drude.getNumParticles()):
|
|---|
| 5459 | particleMap[drude.getParticleParameters(i)[0]] = i
|
|---|
| 5460 | for i in range(nonbonded.getNumExceptions()):
|
|---|
| 5461 | (particle1, particle2, charge, sigma, epsilon) = nonbonded.getExceptionParameters(i)
|
|---|
| 5462 | if charge._value == 0 and epsilon._value == 0:
|
|---|
| 5463 | # This is an exclusion.
|
|---|
| 5464 | if particle1 in particleMap and particle2 in particleMap:
|
|---|
| 5465 | # It connects two Drude particles, so add a screened pair.
|
|---|
| 5466 | drude1 = particleMap[particle1]
|
|---|
| 5467 | drude2 = particleMap[particle2]
|
|---|
| 5468 | type1 = data.atomType[data.atoms[particle1]]
|
|---|
| 5469 | type2 = data.atomType[data.atoms[particle2]]
|
|---|
| 5470 | thole1 = self.typeMap[type1][8]
|
|---|
| 5471 | thole2 = self.typeMap[type2][8]
|
|---|
| 5472 | drude.addScreenedPair(drude1, drude2, thole1+thole2)
|
|---|
| 5473 |
|
|---|
| 5474 | parsers["DrudeForce"] = DrudeGenerator.parseElement
|
|---|
| 5475 |
|
|---|
| 5476 | #=============================================================================================
|
|---|