def _process_rb_torsion_forces(openff_sys, openmm_sys): """Process Ryckaert-Bellemans torsions""" rb_force = openmm.RBTorsionForce() openmm_sys.addForce(rb_force) rb_torsion_handler = openff_sys.handlers["RBTorsions"] for top_key, pot_key in rb_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = rb_torsion_handler.potentials[pot_key].parameters c0 = params["c0"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c1 = params["c1"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c2 = params["c2"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c3 = params["c3"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c4 = params["c4"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c5 = params["c5"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol rb_force.addTorsion( indices[0], indices[1], indices[2], indices[3], c0, c1, c2, c3, c4, c5, )
def _process_rb_torsion_forces(openff_sys, openmm_sys): """Process Ryckaert-Bellemans torsions""" rb_force = openmm.RBTorsionForce() openmm_sys.addForce(rb_force) rb_torsion_handler = openff_sys.handlers["RBTorsions"] for top_key, pot_key in rb_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = rb_torsion_handler.potentials[pot_key].parameters c0 = params["C0"].m_as(off_unit.kilojoule / off_unit.mol) c1 = params["C1"].m_as(off_unit.kilojoule / off_unit.mol) c2 = params["C2"].m_as(off_unit.kilojoule / off_unit.mol) c3 = params["C3"].m_as(off_unit.kilojoule / off_unit.mol) c4 = params["C4"].m_as(off_unit.kilojoule / off_unit.mol) c5 = params["C5"].m_as(off_unit.kilojoule / off_unit.mol) rb_force.addTorsion( indices[0], indices[1], indices[2], indices[3], c0, c1, c2, c3, c4, c5, )
def test_setting(self, ethane_system_topology): rb_torsion = openmm.RBTorsionForce() rb_torsion.addTorsion(0, 1, 2, 3, 10, 20, 30, 40, 50, 60) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_dih_ommp = RBTorsionForceOmmperator(my_ommp, rb_torsion, 0) my_dih_ommp.particle1 = 10 my_dih_ommp.particle2 = 20 my_dih_ommp.particle3 = 30 my_dih_ommp.particle4 = 40 my_dih_ommp.c0 = 100 my_dih_ommp.c1 = 200 my_dih_ommp.c2 = 300 my_dih_ommp.c3 = 400 my_dih_ommp.c4 = 500 my_dih_ommp.c5 = 600 assert my_dih_ommp.particle1 == rb_torsion.getTorsionParameters(0)[0] assert my_dih_ommp.particle1 == 10 assert my_dih_ommp.particle2 == rb_torsion.getTorsionParameters(0)[1] assert my_dih_ommp.particle2 == 20 assert my_dih_ommp.particle3 == rb_torsion.getTorsionParameters(0)[2] assert my_dih_ommp.particle3 == 30 assert my_dih_ommp.particle4 == rb_torsion.getTorsionParameters(0)[3] assert my_dih_ommp.particle4 == 40 assert my_dih_ommp.c0 == rb_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.c1 == rb_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.c2 == rb_torsion.getTorsionParameters(0)[6] assert my_dih_ommp.c3 == rb_torsion.getTorsionParameters(0)[7] assert my_dih_ommp.c4 == rb_torsion.getTorsionParameters(0)[8] assert my_dih_ommp.c5 == rb_torsion.getTorsionParameters(0)[9] my_dih_ommp.set_params(p1=100, p2=200, p3=300, p4=400, c0=1000, c1=2000, c2=3000, c3=4000, c4=5000, c5=6000) assert my_dih_ommp.particle1 == rb_torsion.getTorsionParameters(0)[0] assert my_dih_ommp.particle1 == 100 assert my_dih_ommp.particle2 == rb_torsion.getTorsionParameters(0)[1] assert my_dih_ommp.particle2 == 200 assert my_dih_ommp.particle3 == rb_torsion.getTorsionParameters(0)[2] assert my_dih_ommp.particle3 == 300 assert my_dih_ommp.particle4 == rb_torsion.getTorsionParameters(0)[3] assert my_dih_ommp.particle4 == 400 assert my_dih_ommp.c0 == rb_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.c1 == rb_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.c2 == rb_torsion.getTorsionParameters(0)[6] assert my_dih_ommp.c3 == rb_torsion.getTorsionParameters(0)[7] assert my_dih_ommp.c4 == rb_torsion.getTorsionParameters(0)[8] assert my_dih_ommp.c5 == rb_torsion.getTorsionParameters(0)[9]
def test_parsing(self, ethane_system_topology): rb_torsion = openmm.RBTorsionForce() rb_torsion.addTorsion(0, 1, 2, 3, 10, 20, 30, 40, 50, 60) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_dih_ommp = RBTorsionForceOmmperator(my_ommp, rb_torsion, 0) assert my_dih_ommp.particle1 == rb_torsion.getTorsionParameters(0)[0] assert my_dih_ommp.particle2 == rb_torsion.getTorsionParameters(0)[1] assert my_dih_ommp.particle3 == rb_torsion.getTorsionParameters(0)[2] assert my_dih_ommp.particle4 == rb_torsion.getTorsionParameters(0)[3] assert my_dih_ommp.c0 == rb_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.c1 == rb_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.c2 == rb_torsion.getTorsionParameters(0)[6] assert my_dih_ommp.c3 == rb_torsion.getTorsionParameters(0)[7] assert my_dih_ommp.c4 == rb_torsion.getTorsionParameters(0)[8] assert my_dih_ommp.c5 == rb_torsion.getTorsionParameters(0)[9]
def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer, constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None): """Construct an OpenMM System representing the topology described by this prmtop file. Parameters ---------- nonbondedMethod : object=NoCutoff The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME. nonbondedCutoff : distance=1*nanometer The cutoff distance to use for nonbonded interactions constraints : object=None Specifies which bonds and angles should be implemented with constraints. Allowed values are None, HBonds, AllBonds, or HAngles. rigidWater : boolean=True If true, water molecules will be fully rigid regardless of the value passed for the constraints argument implicitSolvent : object=None If not None, the implicit solvent model to use. The only allowed value is OBC2. soluteDielectric : float=1.0 The solute dielectric constant to use in the implicit solvent model. solventDielectric : float=78.5 The solvent dielectric constant to use in the implicit solvent model. ewaldErrorTolerance : float=0.0005 The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME. removeCMMotion : boolean=True If true, a CMMotionRemover will be added to the System hydrogenMass : mass=None The mass to use for hydrogen atoms bound to heavy atoms. Any mass added to a hydrogen is subtracted from the heavy atom to keep their total mass the same. Returns ------- System the newly created System """ # Create the System. sys = mm.System() boxVectors = self.topology.getPeriodicBoxVectors() if boxVectors is not None: sys.setDefaultPeriodicBoxVectors(*boxVectors) elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME): raise ValueError('Illegal nonbonded method for a non-periodic system') nb = mm.NonbondedForce() sys.addForce(nb) if implicitSolvent is OBC2: gb = mm.GBSAOBCForce() gb.setSoluteDielectric(soluteDielectric) gb.setSolventDielectric(solventDielectric) sys.addForce(gb) nb.setReactionFieldDielectric(1.0) elif implicitSolvent is not None: raise ValueError('Illegal value for implicitSolvent') bonds = None angles = None periodic = None rb = None harmonicTorsion = None cmap = None mapIndices = {} bondIndices = [] topologyAtoms = list(self.topology.atoms()) exceptions = [] fudgeQQ = float(self._defaults[4]) # Build a lookup table to let us process dihedrals more quickly. dihedralTypeTable = {} for key in self._dihedralTypes: if key[1] != 'X' and key[2] != 'X': if (key[1], key[2]) not in dihedralTypeTable: dihedralTypeTable[(key[1], key[2])] = [] dihedralTypeTable[(key[1], key[2])].append(key) if (key[2], key[1]) not in dihedralTypeTable: dihedralTypeTable[(key[2], key[1])] = [] dihedralTypeTable[(key[2], key[1])].append(key) wildcardDihedralTypes = [] for key in self._dihedralTypes: if key[1] == 'X' or key[2] == 'X': wildcardDihedralTypes.append(key) for types in dihedralTypeTable.values(): types.append(key) # Loop over molecules and create the specified number of each type. for moleculeName, moleculeCount in self._molecules: moleculeType = self._moleculeTypes[moleculeName] for i in range(moleculeCount): # Record the types of all atoms. baseAtomIndex = sys.getNumParticles() atomTypes = [atom[1] for atom in moleculeType.atoms] try: bondedTypes = [self._atomTypes[t][1] for t in atomTypes] except KeyError as e: raise ValueError('Unknown atom type: ' + e.message) bondedTypes = [b if b is not None else a for a, b in zip(atomTypes, bondedTypes)] # Add atoms. for fields in moleculeType.atoms: if len(fields) >= 8: mass = float(fields[7]) else: mass = float(self._atomTypes[fields[1]][3]) sys.addParticle(mass) # Add bonds. atomBonds = [{} for x in range(len(moleculeType.atoms))] for fields in moleculeType.bonds: atoms = [int(x)-1 for x in fields[:2]] types = tuple(bondedTypes[i] for i in atoms) if len(fields) >= 5: params = fields[3:5] elif types in self._bondTypes: params = self._bondTypes[types][3:5] elif types[::-1] in self._bondTypes: params = self._bondTypes[types[::-1]][3:5] else: raise ValueError('No parameters specified for bond: '+fields[0]+', '+fields[1]) # Decide whether to use a constraint or a bond. useConstraint = False if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH': useConstraint = True if constraints in (AllBonds, HAngles): useConstraint = True elif constraints is HBonds: elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms] if elem.hydrogen in elements: useConstraint = True # Add the bond or constraint. length = float(params[0]) if useConstraint: sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length) else: if bonds is None: bonds = mm.HarmonicBondForce() sys.addForce(bonds) bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length, float(params[1])) # Record information that will be needed for constraining angles. atomBonds[atoms[0]][atoms[1]] = length atomBonds[atoms[1]][atoms[0]] = length # Add angles. degToRad = math.pi/180 for fields in moleculeType.angles: atoms = [int(x)-1 for x in fields[:3]] types = tuple(bondedTypes[i] for i in atoms) if len(fields) >= 6: params = fields[4:] elif types in self._angleTypes: params = self._angleTypes[types][4:] elif types[::-1] in self._angleTypes: params = self._angleTypes[types[::-1]][4:] else: raise ValueError('No parameters specified for angle: '+fields[0]+', '+fields[1]+', '+fields[2]) # Decide whether to use a constraint or a bond. useConstraint = False if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH': useConstraint = True if constraints is HAngles: elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms] if elements[0] == elem.hydrogen and elements[2] == elem.hydrogen: useConstraint = True elif elements[1] == elem.oxygen and (elements[0] == elem.hydrogen or elements[2] == elem.hydrogen): useConstraint = True # Add the bond or constraint. theta = float(params[0])*degToRad if useConstraint: # Compute the distance between atoms and add a constraint if atoms[0] in atomBonds[atoms[1]] and atoms[2] in atomBonds[atoms[1]]: l1 = atomBonds[atoms[1]][atoms[0]] l2 = atomBonds[atoms[1]][atoms[2]] length = math.sqrt(l1*l1 + l2*l2 - 2*l1*l2*math.cos(theta)) sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], length) else: if angles is None: angles = mm.HarmonicAngleForce() sys.addForce(angles) angles.addAngle(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], theta, float(params[1])) if fields[3] == '5': # This is a Urey-Bradley term, so add the bond. if bonds is None: bonds = mm.HarmonicBondForce() sys.addForce(bonds) k = float(params[3]) if k != 0: bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], float(params[2]), k) # Add torsions. for fields in moleculeType.dihedrals: atoms = [int(x)-1 for x in fields[:4]] types = tuple(bondedTypes[i] for i in atoms) dihedralType = fields[4] reversedTypes = types[::-1]+(dihedralType,) types = types+(dihedralType,) if (dihedralType in ('1', '2', '4', '9') and len(fields) > 7) or (dihedralType == '3' and len(fields) > 10): paramsList = [fields] else: # Look for a matching dihedral type. paramsList = None if (types[1], types[2]) in dihedralTypeTable: dihedralTypes = dihedralTypeTable[(types[1], types[2])] else: dihedralTypes = wildcardDihedralTypes for key in dihedralTypes: if all(a == b or a == 'X' for a, b in zip(key, types)) or all(a == b or a == 'X' for a, b in zip(key, reversedTypes)): paramsList = self._dihedralTypes[key] if 'X' not in key: break if paramsList is None: raise ValueError('No parameters specified for dihedral: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3]) for params in paramsList: if dihedralType in ('1', '4', '9'): # Periodic torsion k = float(params[6]) if k != 0: if periodic is None: periodic = mm.PeriodicTorsionForce() sys.addForce(periodic) periodic.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], int(float(params[7])), float(params[5])*degToRad, k) elif dihedralType == '2': # Harmonic torsion k = float(params[6]) if k != 0: if harmonicTorsion is None: harmonicTorsion = mm.CustomTorsionForce('0.5*k*(theta-theta0)^2') harmonicTorsion.addPerTorsionParameter('theta0') harmonicTorsion.addPerTorsionParameter('k') sys.addForce(harmonicTorsion) harmonicTorsion.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], (float(params[5])*degToRad, k)) else: # RB Torsion c = [float(x) for x in params[5:11]] if any(x != 0 for x in c): if rb is None: rb = mm.RBTorsionForce() sys.addForce(rb) rb.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], c[0], c[1], c[2], c[3], c[4], c[5]) # Add CMAP terms. for fields in moleculeType.cmaps: atoms = [int(x)-1 for x in fields[:5]] types = tuple(bondedTypes[i] for i in atoms) if len(fields) >= 8 and len(fields) >= 8+int(fields[6])*int(fields[7]): params = fields elif types in self._cmapTypes: params = self._cmapTypes[types] elif types[::-1] in self._cmapTypes: params = self._cmapTypes[types[::-1]] else: raise ValueError('No parameters specified for cmap: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3]+', '+fields[4]) if cmap is None: cmap = mm.CMAPTorsionForce() sys.addForce(cmap) mapSize = int(params[6]) if mapSize != int(params[7]): raise ValueError('Non-square CMAPs are not supported') map = [] for i in range(mapSize): for j in range(mapSize): map.append(float(params[8+mapSize*((j+mapSize//2)%mapSize)+((i+mapSize//2)%mapSize)])) map = tuple(map) if map not in mapIndices: mapIndices[map] = cmap.addMap(mapSize, map) cmap.addTorsion(mapIndices[map], baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], baseAtomIndex+atoms[4]) # Set nonbonded parameters for particles. for fields in moleculeType.atoms: params = self._atomTypes[fields[1]] if len(fields) > 6: q = float(fields[6]) else: q = float(params[4]) nb.addParticle(q, float(params[6]), float(params[7])) if implicitSolvent is OBC2: if fields[1] not in self._implicitTypes: raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1]) gbparams = self._implicitTypes[fields[1]] gb.addParticle(q, float(gbparams[4]), float(gbparams[5])) for fields in moleculeType.bonds: atoms = [int(x)-1 for x in fields[:2]] bondIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1])) # Record nonbonded exceptions. for fields in moleculeType.pairs: atoms = [int(x)-1 for x in fields[:2]] types = tuple(atomTypes[i] for i in atoms) if len(fields) >= 5: params = fields[3:5] elif types in self._pairTypes: params = self._pairTypes[types][3:5] elif types[::-1] in self._pairTypes: params = self._pairTypes[types[::-1]][3:5] elif not self._genpairs: raise ValueError('No pair parameters defined for atom ' 'types %s and gen-pairs is "no"' % types) else: continue # We'll use the automatically generated parameters atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0]) atom2params = nb.getParticleParameters(baseAtomIndex+atoms[1]) exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], atom1params[0]*atom2params[0]*fudgeQQ, params[0], params[1])) for fields in moleculeType.exclusions: atoms = [int(x)-1 for x in fields] for atom in atoms[1:]: if atom > atoms[0]: exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0)) # Create nonbonded exceptions. nb.createExceptionsFromBonds(bondIndices, fudgeQQ, float(self._defaults[3])) for exception in exceptions: nb.addException(exception[0], exception[1], exception[2], float(exception[3]), float(exception[4]), True) # Finish configuring the NonbondedForce. methodMap = {ff.NoCutoff:mm.NonbondedForce.NoCutoff, ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic, ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic, ff.Ewald:mm.NonbondedForce.Ewald, ff.PME:mm.NonbondedForce.PME, ff.LJPME:mm.NonbondedForce.LJPME} nb.setNonbondedMethod(methodMap[nonbondedMethod]) nb.setCutoffDistance(nonbondedCutoff) nb.setEwaldErrorTolerance(ewaldErrorTolerance) # Adjust masses. if hydrogenMass is not None: for atom1, atom2 in self.topology.bonds(): if atom1.element == elem.hydrogen: (atom1, atom2) = (atom2, atom1) if atom2.element == elem.hydrogen and atom1.element not in (elem.hydrogen, None): transferMass = hydrogenMass-sys.getParticleMass(atom2.index) sys.setParticleMass(atom2.index, hydrogenMass) sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass) # Add a CMMotionRemover. if removeCMMotion: sys.addForce(mm.CMMotionRemover()) return sys
def _addTorsionToSystem(self, syst, moleculeType, bondedTypes, dihedralTypeTable, wildcardDihedralTypes, baseAtomIndex): degToRad = math.pi / 180 for fields in moleculeType.dihedrals: atoms = [int(x) - 1 for x in fields[:4]] types = tuple(bondedTypes[i] for i in atoms) dihedralType = fields[4] reversedTypes = types[::-1] + (dihedralType, ) types = types + (dihedralType, ) if (dihedralType in ('1', '2', '4', '9') and len(fields) > 6) or (dihedralType == '3' and len(fields) > 10): paramsList = [fields] else: # Look for a matching dihedral type. paramsList = None if (types[1], types[2]) in dihedralTypeTable: dihedralTypes = dihedralTypeTable[(types[1], types[2])] else: dihedralTypes = wildcardDihedralTypes for key in dihedralTypes: if all(a == b or a == 'X' for a, b in zip(key, types)) or all( a == b or a == 'X' for a, b in zip(key, reversedTypes)): paramsList = self.dihedralTypes[key] if 'X' not in key: break if paramsList is None: raise ValueError('No parameters specified for dihedral: ' + fields[0] + ', ' + fields[1] + ', ' + fields[2] + ', ' + fields[3]) for params in paramsList: if dihedralType in ('1', '4', '9'): # Periodic torsion k = float(params[6]) if k != 0: periodic = mm.PeriodicTorsionForce() syst.addForce(periodic) periodic.addTorsion(baseAtomIndex + atoms[0], baseAtomIndex + atoms[1], baseAtomIndex + atoms[2], baseAtomIndex + atoms[3], int(params[7]), float(params[5]) * degToRad, k) elif dihedralType == '2': # Harmonic torsion k = float(params[6]) if k != 0: harmonicTorsion = mm.CustomTorsionForce( '0.5*k*(theta-theta0)^2') harmonicTorsion.addPerTorsionParameter('theta0') harmonicTorsion.addPerTorsionParameter('k') syst.addForce(harmonicTorsion) harmonicTorsion.addTorsion( baseAtomIndex + atoms[0], baseAtomIndex + atoms[1], baseAtomIndex + atoms[2], baseAtomIndex + atoms[3], (float(params[5]) * degToRad, k)) else: # RB Torsion c = [float(x) for x in params[5:11]] if any(x != 0 for x in c): rb = mm.RBTorsionForce() syst.addForce(rb) rb.addTorsion(baseAtomIndex + atoms[0], baseAtomIndex + atoms[1], baseAtomIndex + atoms[2], baseAtomIndex + atoms[3], c[0], c[1], c[2], c[3], c[4], c[5])