def _addVirtualSitesToSystem(self, sys): """Create any virtual sites in the systempy """ if not any(t.startswith('virtual_') for t in self._tables.keys()): return if 'virtual_lc2_term' in self._tables: q = """SELECT p0, p1, p2, c1 FROM virtual_lc2_term INNER JOIN virtual_lc2_param ON virtual_lc2_term.param=virtual_lc2_param.id""" for p0, p1, p2, c1 in self._conn.execute(q): vsite = mm.TwoParticleAverageSite(p1, p2, (1-c1), c1) sys.setVirtualSite(p0, vsite) if 'virtual_lc3_term' in self._tables: q = """SELECT p0, p1, p2, p3, c1, c2 FROM virtual_lc3_term INNER JOIN virtual_lc3_param ON virtual_lc3_term.param=virtual_lc3_param.id""" for p0, p1, p2, p3, c1, c2 in self._conn.execute(q): vsite = mm.ThreeParticleAverageSite(p1, p2, p3, (1-c1-c2), c1, c2) sys.setVirtualSite(p0, vsite) if 'virtual_out3_term' in self._tables: q = """SELECT p0, p1, p2, p3, c1, c2, c3 FROM virtual_out3_term INNER JOIN virtual_out3_param ON virtual_out3_term.param=virtual_out3_param.id""" for p0, p1, p2, p3, c1, c2, c3 in self._conn.execute(q): vsite = mm.OutOfPlaneSite(p1, p2, p3, c1, c2, c3) sys.setVirtualSite(p0, vsite) if 'virtual_fdat3_term' in self._tables: raise NotImplementedError('OpenMM does not currently support ' 'fdat3-style virtual sites')
def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*u.nanometer, constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, **args): """Construct an OpenMM System representing a Topology with this force field. Parameters ---------- topology : Topology The Topology for which to create a System nonbondedMethod : object=NoCutoff The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME. 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 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. args Arbitrary additional keyword arguments may also be specified. This allows extra parameters to be specified that are specific to particular force fields. Returns ------- system the newly created System """ # Atomtype the system. G = nx.Graph() G.add_nodes_from(topology.atoms()) G.add_edges_from(topology.bonds()) cycles = nx.cycle_basis(G) for atom in topology.atoms(): atom.cycles = set() for cycle in cycles: for atom in cycle: atom.cycles.add(tuple(cycle)) find_atomtypes(atoms=list(topology.atoms()), forcefield=self) data = app.ForceField._SystemData() data.atoms = list(topology.atoms()) for atom in data.atoms: data.excludeAtomWith.append([]) # Make a list of all bonds for bond in topology.bonds(): data.bonds.append(app.ForceField._BondData(bond[0].index, bond[1].index)) # Record which atoms are bonded to each other atom bondedToAtom = [] for i in range(len(data.atoms)): bondedToAtom.append(set()) data.atomBonds.append([]) for i in range(len(data.bonds)): bond = data.bonds[i] bondedToAtom[bond.atom1].add(bond.atom2) bondedToAtom[bond.atom2].add(bond.atom1) data.atomBonds[bond.atom1].append(i) data.atomBonds[bond.atom2].append(i) # TODO: Better way to lookup nonbonded parameters...? nonbonded_params = None for generator in self.getGenerators(): if isinstance(generator, NonbondedGenerator): nonbonded_params = generator.params.paramsForType break for chain in topology.chains(): for res in chain.residues(): for atom in res.atoms(): data.atomType[atom] = atom.id if nonbonded_params: params = nonbonded_params[atom.id] data.atomParameters[atom] = params # Create the System and add atoms sys = mm.System() for atom in topology.atoms(): # Look up the atom type name, returning a helpful error message if it cannot be found. if atom not in data.atomType: raise Exception("Could not identify atom type for atom '%s'." % str(atom)) typename = data.atomType[atom] # Look up the type name in the list of registered atom types, returning a helpful error message if it cannot be found. if typename not in self._atomTypes: msg = "Could not find typename '%s' for atom '%s' in list of known atom types.\n" % (typename, str(atom)) msg += "Known atom types are: %s" % str(self._atomTypes.keys()) raise Exception(msg) # Add the particle to the OpenMM system. mass = self._atomTypes[typename].mass sys.addParticle(mass) # Adjust hydrogen masses if requested. if hydrogenMass is not None: if not u.is_quantity(hydrogenMass): hydrogenMass *= u.dalton for atom1, atom2 in 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) # Set periodic boundary conditions. boxVectors = topology.getPeriodicBoxVectors() if boxVectors is not None: sys.setDefaultPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]) elif nonbondedMethod not in [NoCutoff, CutoffNonPeriodic]: raise ValueError('Requested periodic boundary conditions for a Topology that does not specify periodic box dimensions') # Make a list of all unique angles uniqueAngles = set() for bond in data.bonds: for atom in bondedToAtom[bond.atom1]: if atom != bond.atom2: if atom < bond.atom2: uniqueAngles.add((atom, bond.atom1, bond.atom2)) else: uniqueAngles.add((bond.atom2, bond.atom1, atom)) for atom in bondedToAtom[bond.atom2]: if atom != bond.atom1: if atom > bond.atom1: uniqueAngles.add((bond.atom1, bond.atom2, atom)) else: uniqueAngles.add((atom, bond.atom2, bond.atom1)) data.angles = sorted(list(uniqueAngles)) # Make a list of all unique proper torsions uniquePropers = set() for angle in data.angles: for atom in bondedToAtom[angle[0]]: if atom not in angle: if atom < angle[2]: uniquePropers.add((atom, angle[0], angle[1], angle[2])) else: uniquePropers.add((angle[2], angle[1], angle[0], atom)) for atom in bondedToAtom[angle[2]]: if atom not in angle: if atom > angle[0]: uniquePropers.add((angle[0], angle[1], angle[2], atom)) else: uniquePropers.add((atom, angle[2], angle[1], angle[0])) data.propers = sorted(list(uniquePropers)) # Make a list of all unique improper torsions for atom in range(len(bondedToAtom)): bondedTo = bondedToAtom[atom] if len(bondedTo) > 2: for subset in itertools.combinations(bondedTo, 3): data.impropers.append((atom, subset[0], subset[1], subset[2])) # Identify bonds that should be implemented with constraints if constraints == AllBonds or constraints == HAngles: for bond in data.bonds: bond.isConstrained = True elif constraints == HBonds: for bond in data.bonds: atom1 = data.atoms[bond.atom1] atom2 = data.atoms[bond.atom2] bond.isConstrained = atom1.name.startswith('H') or atom2.name.startswith('H') if rigidWater: for bond in data.bonds: atom1 = data.atoms[bond.atom1] atom2 = data.atoms[bond.atom2] if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH': bond.isConstrained = True # Identify angles that should be implemented with constraints if constraints == HAngles: for angle in data.angles: atom1 = data.atoms[angle[0]] atom2 = data.atoms[angle[1]] atom3 = data.atoms[angle[2]] numH = 0 if atom1.name.startswith('H'): numH += 1 if atom3.name.startswith('H'): numH += 1 data.isAngleConstrained.append(numH == 2 or (numH == 1 and atom2.name.startswith('O'))) else: data.isAngleConstrained = len(data.angles)*[False] if rigidWater: for i in range(len(data.angles)): angle = data.angles[i] atom1 = data.atoms[angle[0]] atom2 = data.atoms[angle[1]] atom3 = data.atoms[angle[2]] if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH' and atom3.residue.name == 'HOH': data.isAngleConstrained[i] = True # Add virtual sites for atom in data.virtualSites: (site, atoms, excludeWith) = data.virtualSites[atom] index = atom.index data.excludeAtomWith[excludeWith].append(index) if site.type == 'average2': sys.setVirtualSite(index, mm.TwoParticleAverageSite(atoms[0], atoms[1], site.weights[0], site.weights[1])) elif site.type == 'average3': sys.setVirtualSite(index, mm.ThreeParticleAverageSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2])) elif site.type == 'outOfPlane': sys.setVirtualSite(index, mm.OutOfPlaneSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2])) elif site.type == 'localCoords': sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms[0], atoms[1], atoms[2], mm.Vec3(site.originWeights[0], site.originWeights[1], site.originWeights[2]), mm.Vec3(site.xWeights[0], site.xWeights[1], site.xWeights[2]), mm.Vec3(site.yWeights[0], site.yWeights[1], site.yWeights[2]), mm.Vec3(site.localPos[0], site.localPos[1], site.localPos[2]))) # Add forces to the System for force in self._forces: force.createForce(sys, data, nonbondedMethod, nonbondedCutoff, args) if removeCMMotion: sys.addForce(mm.CMMotionRemover()) # Let force generators do postprocessing for force in self._forces: if 'postprocessSystem' in dir(force): force.postprocessSystem(sys, data, args) # Execute scripts found in the XML files. for script in self._scripts: exec(script, locals()) return sys
def _addVirtualSitesToSystem(self, sys): """Create any virtual sites in the system """ go = [] for (fcounter, conn, tables, offset) in self._localVars(): if not any(t.startswith('virtual_') for t in list(tables.keys())): go.append(False) else: go.append(True) if not any(go): return for (fcounter, conn, tables, offset) in self._localVars(): if not go[fcounter]: continue if 'virtual_lc2_term' in tables: q = """SELECT p0, p1, p2, c1 FROM virtual_lc2_term INNER JOIN virtual_lc2_param ON virtual_lc2_term.param=virtual_lc2_param.id""" for p0, p1, p2, c1 in conn.execute(q): p0 += offset p1 += offset p2 += offset vsite = mm.TwoParticleAverageSite(p1, p2, (1 - c1), c1) sys.setVirtualSite(p0, vsite) for (fcounter, conn, tables, offset) in self._localVars(): if not go[fcounter]: continue if 'virtual_lc3_term' in tables: q = """SELECT p0, p1, p2, p3, c1, c2 FROM virtual_lc3_term INNER JOIN virtual_lc3_param ON virtual_lc3_term.param=virtual_lc3_param.id""" for p0, p1, p2, p3, c1, c2 in conn.execute(q): p0 += offset p1 += offset p2 += offset p3 += offset vsite = mm.ThreeParticleAverageSite( p1, p2, p3, (1 - c1 - c2), c1, c2) sys.setVirtualSite(p0, vsite) for (fcounter, conn, tables, offset) in self._localVars(): if not go[fcounter]: continue if 'virtual_out3_term' in tables: q = """SELECT p0, p1, p2, p3, c1, c2, c3 FROM virtual_out3_term INNER JOIN virtual_out3_param ON virtual_out3_term.param=virtual_out3_param.id""" for p0, p1, p2, p3, c1, c2, c3 in conn.execute(q): p0 += offset p1 += offset p2 += offset p3 += offset vsite = mm.OutOfPlaneSite(p1, p2, p3, c1, c2, c3) sys.setVirtualSite(p0, vsite) for (fcounter, conn, tables, offset) in self._localVars(): if not go[fcounter]: continue if 'virtual_fdat3_term' in tables: raise NotImplementedError('OpenMM does not currently support ' 'fdat3-style virtual sites')
def createRigidBodies(system, positions, bodies): """Modify a System to turn specified sets of particles into rigid bodies. For every rigid body, four particles are selected as "real" particles whose positions are integrated. Constraints are added between them to make them move as a rigid body. All other particles in the body are then turned into virtual sites whose positions are computed based on the "real" particles. Because virtual sites are massless, the mass properties of the rigid bodies will be slightly different from the corresponding sets of particles in the original system. The masses of the non-virtual particles are chosen to guarantee that the total mass and center of mass of each rigid body exactly match those of the original particles. The moment of inertia will be similar to that of the original particles, but not identical. Care is needed when using constraints, since virtual particles cannot participate in constraints. If the input system includes any constraints, this function will automatically remove ones that connect two particles in the same rigid body. But if there is a constraint beween a particle in a rigid body and another particle not in that body, it will likely lead to an exception when you try to create a context. Parameters: - system (System) the System to modify - positions (list) the positions of all particles in the system - bodies (list) each element of this list defines one rigid body. Each element should itself be a list of the indices of all particles that make up that rigid body. """ # Remove any constraints involving particles in rigid bodies. for i in range(system.getNumConstraints() - 1, -1, -1): p1, p2, distance = system.getConstraintParameters(i) if (any(p1 in body and p2 in body for body in bodies)): system.removeConstraint(i) # Loop over rigid bodies and process them. for particles in bodies: if len(particles) < 5: # All the particles will be "real" particles. realParticles = particles realParticleMasses = [system.getParticleMass(i) for i in particles] else: # Select four particles to use as the "real" particles. All others will be virtual sites. pos = [positions[i] for i in particles] mass = [system.getParticleMass(i) for i in particles] cm = unit.sum([p * m for p, m in zip(pos, mass)]) / unit.sum(mass) r = [p - cm for p in pos] avgR = unit.sqrt( unit.sum([unit.dot(x, x) for x in r]) / len(particles)) rank = sorted(range(len(particles)), key=lambda i: abs(unit.norm(r[i]) - avgR)) for p in combinations(rank, 4): # Select masses for the "real" particles. If any is negative, reject this set of particles # and keep going. matrix = np.zeros((4, 4)) for i in range(4): particleR = r[p[i]].value_in_unit(unit.nanometers) matrix[0][i] = particleR[0] matrix[1][i] = particleR[1] matrix[2][i] = particleR[2] matrix[3][i] = 1.0 rhs = np.array( [0.0, 0.0, 0.0, unit.sum(mass).value_in_unit(unit.amu)]) weights = lin.solve(matrix, rhs) if all(w > 0.0 for w in weights): # We have a good set of particles. realParticles = [particles[i] for i in p] realParticleMasses = [float(w) for w in weights] * unit.amu break # Set particle masses. for i, m in zip(realParticles, realParticleMasses): system.setParticleMass(i, m) # Add constraints between the real particles. for p1, p2 in combinations(realParticles, 2): distance = unit.norm(positions[p1] - positions[p2]) key = (min(p1, p2), max(p1, p2)) system.addConstraint(p1, p2, distance) # Select which three particles to use for defining virtual sites. bestNorm = 0 for p1, p2, p3 in combinations(realParticles, 3): d12 = (positions[p2] - positions[p1]).value_in_unit(unit.nanometer) d13 = (positions[p3] - positions[p1]).value_in_unit(unit.nanometer) crossNorm = unit.norm((d12[1] * d13[2] - d12[2] * d13[1], d12[2] * d13[0] - d12[0] * d13[2], d12[0] * d13[1] - d12[1] * d13[0])) if crossNorm > bestNorm: bestNorm = crossNorm vsiteParticles = (p1, p2, p3) # Create virtual sites. d12 = (positions[vsiteParticles[1]] - positions[vsiteParticles[0]]).value_in_unit(unit.nanometer) d13 = (positions[vsiteParticles[2]] - positions[vsiteParticles[0]]).value_in_unit(unit.nanometer) cross = mm.Vec3(d12[1] * d13[2] - d12[2] * d13[1], d12[2] * d13[0] - d12[0] * d13[2], d12[0] * d13[1] - d12[1] * d13[0]) matrix = np.zeros((3, 3)) for i in range(3): matrix[i][0] = d12[i] matrix[i][1] = d13[i] matrix[i][2] = cross[i] for i in particles: if i not in realParticles: system.setParticleMass(i, 0) rhs = np.array((positions[i] - positions[vsiteParticles[0]]).value_in_unit( unit.nanometer)) weights = lin.solve(matrix, rhs) system.setVirtualSite( i, mm.OutOfPlaneSite(vsiteParticles[0], vsiteParticles[1], vsiteParticles[2], weights[0], weights[1], weights[2]))