def test_modify_omm(self, ethane_system_topology): periodic_torsion = openmm.PeriodicTorsionForce() periodic_torsion.addTorsion(0, 1, 2, 3, 10, 20, 30) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_dih_ommp = PeriodicTorsionForceOmmperator(my_ommp, periodic_torsion, 0) periodic_torsion.setTorsionParameters(0, 1, 2, 3, 4, 20, 30, 40) assert my_dih_ommp.particle1 == periodic_torsion.getTorsionParameters( 0)[0] assert my_dih_ommp.particle1 == 1 assert my_dih_ommp.particle2 == periodic_torsion.getTorsionParameters( 0)[1] assert my_dih_ommp.particle2 == 2 assert my_dih_ommp.particle3 == periodic_torsion.getTorsionParameters( 0)[2] assert my_dih_ommp.particle3 == 3 assert my_dih_ommp.particle4 == periodic_torsion.getTorsionParameters( 0)[3] assert my_dih_ommp.particle4 == 4 assert my_dih_ommp.n == periodic_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.phase == periodic_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.k == periodic_torsion.getTorsionParameters(0)[6]
def _process_improper_torsion_forces(openff_sys, openmm_sys): """Process the Impropers section of an OpenFF Interchange into corresponding forces within an openmm.PeriodicTorsionForce""" if "ImproperTorsions" not in openff_sys.handlers.keys(): return for force in openmm_sys.getForces(): if type(force) == openmm.PeriodicTorsionForce: torsion_force = force break else: torsion_force = openmm.PeriodicTorsionForce() improper_torsion_handler = openff_sys.handlers["ImproperTorsions"] for top_key, pot_key in improper_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = improper_torsion_handler.potentials[pot_key].parameters k = params["k"].m_as(off_unit.kilojoule / off_unit.mol) periodicity = int(params["periodicity"]) phase = params["phase"].m_as(off_unit.radian) idivf = int(params["idivf"]) torsion_force.addTorsion( indices[0], indices[1], indices[2], indices[3], periodicity, phase, k / idivf, )
def _process_proper_torsion_forces(openff_sys, openmm_sys): """Process the Propers section of an OpenFF Interchange into corresponding forces within an openmm.PeriodicTorsionForce""" torsion_force = openmm.PeriodicTorsionForce() openmm_sys.addForce(torsion_force) proper_torsion_handler = openff_sys.handlers["ProperTorsions"] for top_key, pot_key in proper_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = proper_torsion_handler.potentials[pot_key].parameters k = params["k"].m_as(off_unit.kilojoule / off_unit.mol) periodicity = int(params["periodicity"]) phase = params["phase"].m_as(off_unit.radian) idivf = int(params["idivf"]) torsion_force.addTorsion( indices[0], indices[1], indices[2], indices[3], periodicity, phase, k / idivf, )
def _create_empty_system(cutoff): """Creates an empty system object with stub forces. Parameters ---------- cutoff: simtk.unit The non-bonded cutoff. Returns ------- simtk.openmm.System The created system object. """ system = openmm.System() system.addForce(openmm.HarmonicBondForce()) system.addForce(openmm.HarmonicAngleForce()) system.addForce(openmm.PeriodicTorsionForce()) nonbonded_force = openmm.NonbondedForce() nonbonded_force.setCutoffDistance(cutoff) nonbonded_force.setNonbondedMethod(openmm.NonbondedForce.PME) system.addForce(nonbonded_force) return system
def _process_proper_torsion_forces(openff_sys, openmm_sys): """Process the Propers section of an OpenFF System into corresponding forces within an openmm.PeriodicTorsionForce""" torsion_force = openmm.PeriodicTorsionForce() openmm_sys.addForce(torsion_force) proper_torsion_handler = openff_sys.handlers["ProperTorsions"] for top_key, pot_key in proper_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = proper_torsion_handler.potentials[pot_key].parameters k = params["k"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol periodicity = int(params["periodicity"]) phase = params["phase"].to(off_unit.degree).magnitude phase = phase * unit.degree / unit.radian idivf = int(params["idivf"]) torsion_force.addTorsion( indices[0], indices[1], indices[2], indices[3], periodicity, phase, k / idivf, )
def _addPeriodicTorsionsToSystem(self, sys, OPLS): """Create the torsion terms """ if OPLS: periodic = mm.CustomTorsionForce('f * cos(n * theta - phi0)') periodic.addPerTorsionParameter('n') periodic.addPerTorsionParameter('phi0') periodic.addPerTorsionParameter('f') else: periodic = mm.PeriodicTorsionForce() sys.addForce(periodic) q = """SELECT p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 FROM dihedral_trig_term INNER JOIN dihedral_trig_param ON dihedral_trig_term.param=dihedral_trig_param.id""" for (fcounter, conn, tables, offset) in self._localVars(): for p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 in conn.execute( q): p0 += offset p1 += offset p2 += offset p3 += offset for order, fc in enumerate([fc0, fc1, fc2, fc3, fc4, fc5, fc6]): if fc == 0: continue if OPLS: periodic.addTorsion( p0, p1, p2, p3, [order, phi0 * degree, fc * kilocalorie_per_mole]) else: periodic.addTorsion(p0, p1, p2, p3, order, phi0 * degree, fc * kilocalorie_per_mole)
def test_opls_torsion_kernel(): kernel = OplsTorsionKernel( top.positions, [dihedral.id_atoms for dihedral in top.dihedrals], [[1 + 0.1 * i, 2 + 0.1 * i, 3 + 0.1 * i, 4 + 0.1 * i] for i in range(top.n_dihedral)], ) phi, energy, forces = kernel.evaluate() print() for dihedral, val in zip(top.dihedrals, phi): print([i + 1 for i in dihedral.id_atoms], val * 180 / np.pi) print(energy) print(sum(energy)) print(forces) force = mm.PeriodicTorsionForce() force.setForceGroup(3) for i, dihedral in enumerate(top.dihedrals): for k in range(1, 5): force.addTorsion(*dihedral.id_atoms, k, ((k - 1) % 2) * np.pi, k + 0.1 * i) e, f = calc_energy_with_omm(force, top.positions) assert pytest.approx(sum(energy), rel=1E-6) == e assert pytest.approx(forces, rel=1E-6) == f
def _create_torsion_sim(periodicity: int = 2, phase=0 * omm_angle_unit, k=10.0 * omm_energy_unit) -> app.Simulation: """Create a 4-particle OpenMM Simulation containing only a PeriodicTorsionForce""" system = mm.System() # add 4 particles of unit mass for _ in range(4): system.addParticle(1) # add torsion force to system force = mm.PeriodicTorsionForce() force.addTorsion(0, 1, 2, 3, periodicity, phase, k) system.addForce(force) # create openmm Simulation, which requires a Topology and Integrator topology = app.Topology() chain = topology.addChain() residue = topology.addResidue("torsion", chain) for name in ["a", "b", "c", "d"]: topology.addAtom(name, "C", residue) integrator = mm.VerletIntegrator(1.0) sim = app.Simulation(topology, system, integrator) return sim
def test_setting(self, ethane_system_topology): periodic_torsion = openmm.PeriodicTorsionForce() periodic_torsion.addTorsion(0, 1, 2, 3, 10, 20, 30) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_dih_ommp = PeriodicTorsionForceOmmperator(my_ommp, periodic_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.n == 50 my_dih_ommp.phase == 60 my_dih_ommp.k == 70 assert my_dih_ommp.particle1 == periodic_torsion.getTorsionParameters( 0)[0] assert my_dih_ommp.particle1 == 10 assert my_dih_ommp.particle2 == periodic_torsion.getTorsionParameters( 0)[1] assert my_dih_ommp.particle2 == 20 assert my_dih_ommp.particle3 == periodic_torsion.getTorsionParameters( 0)[2] assert my_dih_ommp.particle3 == 30 assert my_dih_ommp.particle4 == periodic_torsion.getTorsionParameters( 0)[3] assert my_dih_ommp.particle4 == 40 assert my_dih_ommp.n == periodic_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.phase == periodic_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.k == periodic_torsion.getTorsionParameters(0)[6] my_dih_ommp.set_params(p1=100, p2=200, p3=300, p4=400, n=500, phase=600, k=700) assert my_dih_ommp.particle1 == periodic_torsion.getTorsionParameters( 0)[0] assert my_dih_ommp.particle1 == 100 assert my_dih_ommp.particle2 == periodic_torsion.getTorsionParameters( 0)[1] assert my_dih_ommp.particle2 == 200 assert my_dih_ommp.particle3 == periodic_torsion.getTorsionParameters( 0)[2] assert my_dih_ommp.particle3 == 300 assert my_dih_ommp.particle4 == periodic_torsion.getTorsionParameters( 0)[3] assert my_dih_ommp.particle4 == 400 assert my_dih_ommp.n == periodic_torsion.getTorsionParameters(0)[4] assert my_dih_ommp.phase == periodic_torsion.getTorsionParameters(0)[5] assert my_dih_ommp.k == periodic_torsion.getTorsionParameters(0)[6]
def create_force(self, system, topology, **kwargs): #force = super(ImproperTorsionHandler, self).create_force(system, topology, **kwargs) #force = super().create_force(system, topology, **kwargs) existing = [system.getForce(i) for i in range(system.getNumForces())] existing = [ f for f in existing if type(f) == openmm.PeriodicTorsionForce ] if len(existing) == 0: force = openmm.PeriodicTorsionForce() system.addForce(force) else: force = existing[0] # Add all improper torsions to the system improper_matches = self.find_matches(topology) # The atom indices in the key of the dictionary match have been rearranged and shouldn't be trusted for (_, improper_match) in improper_matches.items(): #for (atom_indices, improper_match) in improper_matches.items(): # Ensure atoms are actually bonded correct pattern in Topology # For impropers, central atom is atom 1 # for (i, j) in [(0, 1), (1, 2), (1, 3)]: # topology.assert_bonded(atom_indices[i], atom_indices[j]) self._assert_correct_connectivity(improper_match, [(0, 2), (1, 2), (2, 3)]) atom_indices = improper_match.environment_match.topology_atom_indices improper = improper_match.parameter_type # TODO: This is a lazy hack. idivf should be set according to the ParameterHandler's default_idivf attrib if improper.idivf is None: improper.idivf = [3 for item in improper.k] # Impropers are applied in three paths around the trefoil having the same handedness for (improper_periodicity, improper_phase, improper_k, improper_idivf) in zip(improper.periodicity, improper.phase, improper.k, improper.idivf): # TODO: Implement correct "auto" behavior if improper_idivf == 'auto': improper_idivf = 3 #logger.warning("The OpenForceField toolkit hasn't implemented " # "support for the torsion `idivf` value of 'auto'." # "Currently assuming a value of '3' for impropers.") # Permute non-central atoms #others = [atom_indices[0], atom_indices[2], atom_indices[3]] ## ((0, 1, 2), (1, 2, 0), and (2, 0, 1)) are the three paths around the trefoil #for p in [(others[i], others[j], others[k]) for (i, j, k) in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]]: # # The torsion force gets added three times, since the k is divided by three # force.addTorsion(atom_indices[1], p[0], p[1], p[2], # improper_periodicity, improper_phase, improper_k/improper_idivf) force.addTorsion(atom_indices[0], atom_indices[1], atom_indices[2], atom_indices[3], improper_periodicity, improper_phase, improper_k / improper_idivf)
def _addPeriodicTorsionsToSystem(self, sys): """Create the torsion terms """ periodic = mm.PeriodicTorsionForce() sys.addForce(periodic) q = """SELECT p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 FROM dihedral_trig_term INNER JOIN dihedral_trig_param ON dihedral_trig_term.param=dihedral_trig_param.id""" for p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 in self._conn.execute(q): for order, fc in enumerate([fc0, fc1, fc2, fc3, fc4, fc5, fc6]): if fc == 0: continue periodic.addTorsion(p0, p1, p2, p3, order, phi0*degree, fc*kilocalorie_per_mole)
def _addPeriodicTorsionsToSystem(self, sys, OPLS): """Create the torsion terms """ go = [] for (fcounter,conn,tables,offset) in self._localVars(): if not self._hasTable('dihedral_trig_term', tables): go.append(False) else: go.append(True) if any(go): if OPLS: periodic = mm.CustomTorsionForce('f * cos(n * theta - phi0)') periodic.addPerTorsionParameter('n') periodic.addPerTorsionParameter('phi0') periodic.addPerTorsionParameter('f') else: periodic = mm.PeriodicTorsionForce() sys.addForce(periodic) periodic.setForceGroup(self._bonded_force_group) else: return q = """SELECT p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 FROM dihedral_trig_term INNER JOIN dihedral_trig_param ON dihedral_trig_term.param=dihedral_trig_param.id""" for (fcounter,conn,tables,offset) in self._localVars(): if not go[fcounter]: continue for p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 in conn.execute(q): p0 += offset p1 += offset p2 += offset p3 += offset for order, fc in enumerate([fc0, fc1, fc2, fc3, fc4, fc5, fc6]): if fc == 0: continue if OPLS: periodic.addTorsion(p0, p1, p2, p3, [order, phi0*degree, fc*kilocalorie_per_mole]) else: periodic.addTorsion(p0, p1, p2, p3, order, phi0*degree, fc*kilocalorie_per_mole)
def _process_improper_torsion_forces(openff_sys, openmm_sys): """Process the Impropers section of an OpenFF System into corresponding forces within an openmm.PeriodicTorsionForce""" if "ImproperTorsions" not in openff_sys.handlers.keys(): return for force in openmm_sys.getForces(): if type(force) == openmm.PeriodicTorsionForce: torsion_force = force break else: torsion_force = openmm.PeriodicTorsionForce() improper_torsion_handler = openff_sys.handlers["ImproperTorsions"] for top_key, pot_key in improper_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = improper_torsion_handler.potentials[pot_key].parameters k = params["k"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol periodicity = int(params["periodicity"]) phase = params["phase"].to(off_unit.degree).magnitude phase = phase * unit.degree / unit.radian idivf = int(params["idivf"]) other_atoms = [indices[0], indices[2], indices[3]] for p in [(other_atoms[i], other_atoms[j], other_atoms[k]) for (i, j, k) in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]]: torsion_force.addTorsion( indices[1], p[0], p[1], p[2], periodicity, phase, k / idivf, )
def createPerturbedSystem(self, alchemical_state, mm=None, verbose=False): """ Create a perturbed copy of the system given the specified alchemical state. ARGUMENTS alchemical_state (AlchemicalState) - the alchemical state to create from the reference system TODO * Start from a deep copy of the system, rather than building copy through Python interface. * isinstance(mm.NonbondedForce) and related expressions won't work if reference system was created with a different OpenMM implemnetation. EXAMPLES Create alchemical intermediates for 'denihilating' one water in a water box. >>> # Create a reference system. >>> import testsystems >>> [reference_system, coordinates] = testsystems.WaterBox() >>> # Create a factory to produce alchemical intermediates. >>> factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=[0, 1, 2]) >>> # Create an alchemically-perturbed state corresponding to fully-interacting. >>> alchemical_state = AlchemicalState(0.00, 1.00, 1.00, 1.) >>> # Create the perturbed system. >>> alchemical_system = factory.createPerturbedSystem(alchemical_state) >>> # Compare energies. >>> import simtk.openmm as openmm >>> import simtk.unit as units >>> timestep = 1.0 * units.femtosecond >>> reference_integrator = openmm.VerletIntegrator(timestep) >>> reference_context = openmm.Context(reference_system, reference_integrator) >>> reference_state = reference_context.getState(getEnergy=True) >>> reference_potential = reference_state.getPotentialEnergy() >>> alchemical_integrator = openmm.VerletIntegrator(timestep) >>> alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) >>> alchemical_state = alchemical_context.getState(getEnergy=True) >>> alchemical_potential = alchemical_state.getPotentialEnergy() >>> delta = alchemical_potential - reference_potential >>> print delta 0.0 kJ/mol Create alchemical intermediates for 'denihilating' p-xylene in T4 lysozyme L99A in GBSA. >>> # Create a reference system. >>> import testsystems >>> [reference_system, coordinates] = testsystems.LysozymeImplicit() >>> # Compute reference potential. >>> timestep = 1.0 * units.femtosecond >>> reference_integrator = openmm.VerletIntegrator(timestep) >>> reference_context = openmm.Context(reference_system, reference_integrator) >>> reference_context.setPositions(coordinates) >>> reference_state = reference_context.getState(getEnergy=True) >>> reference_potential = reference_state.getPotentialEnergy() >>> # Create a factory to produce alchemical intermediates. >>> receptor_atoms = range(0,2603) # T4 lysozyme L99A >>> ligand_atoms = range(2603,2621) # p-xylene >>> factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) >>> # Create an alchemically-perturbed state corresponding to fully-interacting. >>> alchemical_state = AlchemicalState(0.00, 1.00, 1.00, 1.) >>> # Create the perturbed systems using this protocol. >>> alchemical_system = factory.createPerturbedSystem(alchemical_state) >>> # Compare energies. >>> alchemical_integrator = openmm.VerletIntegrator(timestep) >>> alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) >>> alchemical_context.setPositions(coordinates) >>> alchemical_state = alchemical_context.getState(getEnergy=True) >>> alchemical_potential = alchemical_state.getPotentialEnergy() >>> delta = alchemical_potential - reference_potential >>> print delta 0.0 kJ/mol NOTES If lambda = 1.0 is specified for some force terms, they will not be replaced with modified forms. """ # Record timing statistics. initial_time = time.time() if verbose: print "Creating alchemically modified intermediate..." reference_system = self.reference_system # Create new deep copy reference system to modify. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Modify forces as appropriate, copying other forces without modification. nforces = reference_system.getNumForces() for force_index in range(nforces): reference_force = reference_system.getForce(force_index) if isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Scale torsion barrier of alchemically-modified system. if set([particle1, particle2, particle3, particle4]).issubset(self.ligand_atomset): k *= alchemical_state.ligandTorsions force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # Copy NonbondedForce. force = copy.deepcopy(reference_force) system.addForce(force) # Modify electrostatics. if alchemical_state.ligandElectrostatics != 1.0: for particle_index in range(force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon] = force.getParticleParameters(particle_index) # Alchemically modify charges. if particle_index in self.ligand_atomset: charge *= alchemical_state.ligandElectrostatics # Set modified particle parameters. force.setParticleParameters(particle_index, charge, sigma, epsilon) for exception_index in range(force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = force.getExceptionParameters(exception_index) # Alchemically modify chargeprod. if (iatom in self.ligand_atomset) and ( jatom in self.ligand_atomset): if alchemical_state.annihilateElectrostatics: chargeprod *= alchemical_state.ligandElectrostatics**2 # Set modified exception parameters. force.setExceptionParameters(exception_index, iatom, jatom, chargeprod, sigma, epsilon) # Modify Lennard-Jones if required. if alchemical_state.ligandLennardJones != 1.0: # Create softcore Lennard-Jones interactions by modifying NonbondedForce and adding CustomNonbondedForce. #self._alchemicallyModifyLennardJones(system, force, self.ligand_atoms, alchemical_state) self._alchemicallyModifyLennardJonesGroup( system, force, self.ligand_atoms, alchemical_state) elif isinstance(reference_force, openmm.GBSAOBCForce) and ( alchemical_state.ligandElectrostatics != 1.0): # Create a CustomNonbondedForce to implement softcore interactions. particle_lambdas = numpy.ones([system.getNumParticles()], numpy.float32) particle_lambdas[ self.ligand_atoms] = alchemical_state.ligandElectrostatics custom_force = AbsoluteAlchemicalFactory._createCustomSoftcoreGBOBC( reference_force, particle_lambdas) system.addForce(custom_force) elif isinstance(reference_force, openmm.CustomExternalForce): force = openmm.CustomExternalForce( reference_force.getEnergyFunction()) for parameter_index in range( reference_force.getNumGlobalParameters()): name = reference_force.getGlobalParameterName( parameter_index) default_value = reference_force.getGlobalParameterDefaultValue( parameter_index) force.addGlobalParameter(name, default_value) for parameter_index in range( reference_force.getNumPerParticleParameters()): name = reference_force.getPerParticleParameterName( parameter_index) force.addPerParticleParameter(name) for index in range(reference_force.getNumParticles()): [particle_index, parameters] = reference_force.getParticleParameters(index) force.addParticle(particle_index, parameters) system.addForce(force) elif isinstance(reference_force, openmm.CustomBondForce): force = openmm.CustomBondForce( reference_force.getEnergyFunction()) for parameter_index in range( reference_force.getNumGlobalParameters()): name = reference_force.getGlobalParameterName( parameter_index) default_value = reference_force.getGlobalParameterDefaultValue( parameter_index) force.addGlobalParameter(name, default_value) for parameter_index in range( reference_force.getNumPerBondParameters()): name = reference_force.getPerBondParameterName( parameter_index) force.addPerBondParameter(name) for index in range(reference_force.getNumBonds()): [particle1, particle2, parameters] = reference_force.getBondParameters(index) force.addBond(particle1, particle2, parameters) system.addForce(force) else: # Copy force without modification. force = copy.deepcopy(reference_force) system.addForce(force) # Record timing statistics. final_time = time.time() elapsed_time = final_time - initial_time if verbose: print "Elapsed time %.3f s." % (elapsed_time) return system
def create_alchemical_intermediates(reference_system, bond_atoms, bond_lambda, kT, annihilate=False): """ Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes. ARGUMENTS reference_system (simtk.openmm.System) - reference System object from which alchemical derivatives will be made (will not be modified) bond_atoms (list of int) - atoms spanning bond to be eliminated bond_lambda (float) - lambda value for bond breaking (lambda = 1 is original system, lambda = 0 is broken-bond system) kT (simtk.unit.Quantity with units compatible with simtk.unit.kilocalories_per_mole) - thermal energy, used in constructing alchemical intermediates RETURNS system (simtk.openmm.System) - alchemical intermediate copy """ # Create new system. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) # Raise an exception if the specified bond_atoms are part of a constrained bond; we can't handle that. if (iatom in bond_atoms) and (jatom in bond_atoms): raise Exception("Bond to be broken is part of a constraint.") system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): # Dispatch forces based on reference force type. reference_force = reference_system.getForce(force_index) if bond_lambda == 1.0: # Just make a copy of the force if lambda = 1. force = copy.deepcopy(reference_force) system.addForce(force) continue if isinstance(reference_force, openmm.HarmonicBondForce): force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) if (iatom in bond_atoms) and (jatom in bond_atoms): if bond_lambda == 0.0: continue # eliminate this bond if broken # Replace this bond with a soft-core (Morse) bond. softcore_bond_force = create_softcore_bond( iatom, jatom, r0, K, kT, bond_lambda) system.addForce(softcore_bond_force) else: # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Turn off angle terms that span bond. if ((iatom in bond_atoms) and (jatom in bond_atoms)) or ((jatom in bond_atoms) and (katom in bond_atoms)): if bond_lambda == 0.0: continue # eliminate this angle if bond broken Ktheta *= bond_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Annihilate if torsion spans bond. if ((particle1 in bond_atoms) and (particle2 in bond_atoms)) or ( (particle2 in bond_atoms) and (particle3 in bond_atoms)) or ( (particle3 in bond_atoms) and (particle4 in bond_atoms)): if bond_lambda == 0.0: continue # eliminate this torsion if bond broken k *= bond_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce will handle charges and exception interactions. force = openmm.NonbondedForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Lennard-Jones and electrostatic interactions involving atoms in bond will be handled by CustomNonbondedForce except at lambda = 0 or 1. if ((bond_lambda > 0) and (bond_lambda < 1)) and (particle_index in bond_atoms): # TODO: We have to also add softcore electrostatics. epsilon *= 0.0 # Add modified particle parameters. force.addParticle(charge, sigma, epsilon) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Modify exception for bond atoms. if ((iatom in bond_atoms) and (jatom in bond_atoms)): if (bond_lambda == 0.0): continue # Omit exception if bond has been turned off. # Alchemically modify epsilon and chargeprod. if (iatom in bond_atoms) and (jatom in bond_atoms): # Attenuate exception interaction (since it will be covered by CustomNonbondedForce interactions). epsilon *= bond_lambda chargeprod *= bond_lambda # TODO: Compute restored (1,3) and (1,4) interactions across modified bond. # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric()) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance()) # Add force to new system. system.addForce(force) if (bond_lambda == 0.0) or (bond_lambda == 1.0): continue # don't need softcore if bond is turned off # CustomNonbondedForce will handle the softcore interactions with and among alchemically-modified atoms. # Softcore potential. # TODO: Add coulomb interaction. energy_expression = "4*epsilon*compute*x*(x-1.0);" energy_expression += "x = 1.0/(alpha*(bond_lambda*(1.0-bond_lambda)/0.25) + (r/sigma)^6);" energy_expression += "epsilon = sqrt(epsilon1*epsilon2);" energy_expression += "sigma = 0.5*(sigma1 + sigma2);" energy_expression += "compute = (1-bond_lambda)*alchemical1*alchemical2 + (alchemical1*(1-alchemical2) + (1-alchemical1)*alchemical2);" # only compute interactions with or between alchemically-modified atoms force = openmm.CustomNonbondedForce(energy_expression) alpha = 0.5 # softcore parameter force.addGlobalParameter("alpha", alpha) force.addGlobalParameter("bond_lambda", bond_lambda) force.addPerParticleParameter("charge") force.addPerParticleParameter("sigma") force.addPerParticleParameter("epsilon") force.addPerParticleParameter("alchemical") for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in bond_atoms: force.addParticle([charge, sigma, epsilon, 1]) else: force.addParticle([charge, sigma, epsilon, 0]) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Exclude exception for bonded atoms. if (iatom in bond_atoms) and (jatom in bond_atoms): continue # All exceptions are handled by NonbondedForce, so we exclude all these here. force.addExclusion(iatom, jatom) if reference_force.getNonbondedMethod() in [ openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME ]: force.setNonbondedMethod( openmm.CustomNonbondedForce.CutoffPeriodic) else: force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) system.addForce(force) else: # Add copy of force term. force = copy.deepcopy(reference_force) system.addForce(force) return system
def export(system): ''' Generate OpenMM system from a system Parameters ---------- system : System Returns ------- omm_system : simtk.openmm.System ''' try: import simtk.openmm as mm except ImportError: raise ImportError('Can not import OpenMM') supported_terms = { LJ126Term, MieTerm, HarmonicBondTerm, HarmonicAngleTerm, SDKAngleTerm, PeriodicDihedralTerm, OplsImproperTerm, HarmonicImproperTerm, DrudeTerm } unsupported = system.ff_classes - supported_terms if unsupported != set(): raise Exception( 'Unsupported FF terms: %s' % (', '.join(map(lambda x: x.__name__, unsupported)))) if system.vsite_types - {TIP4PSite} != set(): raise Exception( 'Virtual sites other than TIP4PSite haven\'t been implemented') top = system.topology ff = system.ff omm_system = mm.System() if system.use_pbc: omm_system.setDefaultPeriodicBoxVectors(*top.cell.vectors) for atom in top.atoms: omm_system.addParticle(atom.mass) ### Set up bonds ####################################################################### for bond_class in system.bond_classes: if bond_class == HarmonicBondTerm: logger.info('Setting up harmonic bonds...') bforce = mm.HarmonicBondForce() for bond in top.bonds: if bond.is_drude: # DrudeForce will handle the bond between Drude pair continue bterm = system.bond_terms[id(bond)] if type(bterm) != HarmonicBondTerm: continue bforce.addBond(bond.atom1.id, bond.atom2.id, bterm.length, bterm.k * 2) else: raise Exception('Bond terms other that HarmonicBondTerm ' 'haven\'t been implemented') bforce.setUsesPeriodicBoundaryConditions(system.use_pbc) bforce.setForceGroup(ForceGroup.BOND) omm_system.addForce(bforce) ### Set up angles ####################################################################### for angle_class in system.angle_classes: if angle_class == HarmonicAngleTerm: logger.info('Setting up harmonic angles...') aforce = mm.HarmonicAngleForce() for angle in top.angles: aterm = system.angle_terms[id(angle)] if type(aterm) == HarmonicAngleTerm: aforce.addAngle(angle.atom1.id, angle.atom2.id, angle.atom3.id, aterm.theta * PI / 180, aterm.k * 2) elif angle_class == SDKAngleTerm: logger.info('Setting up SDK angles...') aforce = mm.CustomCompoundBondForce( 3, 'k*(theta-theta0)^2+step(rmin-r)*LJ96;' 'LJ96=6.75*epsilon*((sigma/r)^9-(sigma/r)^6)+epsilon;' 'theta=angle(p1,p2,p3);' 'r=distance(p1,p3);' 'rmin=1.144714*sigma') aforce.addPerBondParameter('theta0') aforce.addPerBondParameter('k') aforce.addPerBondParameter('epsilon') aforce.addPerBondParameter('sigma') for angle in top.angles: aterm = system.angle_terms[id(angle)] if type(aterm) != SDKAngleTerm: continue vdw = ff.get_vdw_term(ff.atom_types[angle.atom1.type], ff.atom_types[angle.atom2.type]) if type( vdw ) != MieTerm or vdw.repulsion != 9 or vdw.attraction != 6: raise Exception( f'Corresponding 9-6 MieTerm for {aterm} not found in FF' ) aforce.addBond( [angle.atom1.id, angle.atom2.id, angle.atom3.id], [ aterm.theta * PI / 180, aterm.k, vdw.epsilon, vdw.sigma ]) else: raise Exception( 'Angle terms other that HarmonicAngleTerm and SDKAngleTerm ' 'haven\'t been implemented') aforce.setUsesPeriodicBoundaryConditions(system.use_pbc) aforce.setForceGroup(ForceGroup.ANGLE) omm_system.addForce(aforce) ### Set up constraints ################################################################# logger.info( f'Setting up {len(system.constrain_bonds)} bond constraints...') for bond in top.bonds: if id(bond) in system.constrain_bonds: omm_system.addConstraint(bond.atom1.id, bond.atom2.id, system.constrain_bonds[id(bond)]) logger.info( f'Setting up {len(system.constrain_angles)} angle constraints...') for angle in top.angles: if id(angle) in system.constrain_angles: omm_system.addConstraint(angle.atom1.id, angle.atom3.id, system.constrain_angles[id(angle)]) ### Set up dihedrals ################################################################### for dihedral_class in system.dihedral_classes: if dihedral_class == PeriodicDihedralTerm: logger.info('Setting up periodic dihedrals...') dforce = mm.PeriodicTorsionForce() for dihedral in top.dihedrals: dterm = system.dihedral_terms[id(dihedral)] ia1, ia2, ia3, ia4 = dihedral.atom1.id, dihedral.atom2.id, dihedral.atom3.id, dihedral.atom4.id if type(dterm) == PeriodicDihedralTerm: for par in dterm.parameters: dforce.addTorsion(ia1, ia2, ia3, ia4, par.n, par.phi * PI / 180, par.k) else: continue else: raise Exception( 'Dihedral terms other that PeriodicDihedralTerm ' 'haven\'t been implemented') dforce.setUsesPeriodicBoundaryConditions(system.use_pbc) dforce.setForceGroup(ForceGroup.DIHEDRAL) omm_system.addForce(dforce) ### Set up impropers #################################################################### for improper_class in system.improper_classes: if improper_class == OplsImproperTerm: logger.info('Setting up periodic impropers...') iforce = mm.CustomTorsionForce('k*(1-cos(2*theta))') iforce.addPerTorsionParameter('k') for improper in top.impropers: iterm = system.improper_terms[id(improper)] if type(iterm) == OplsImproperTerm: # in OPLS convention, the third atom is the central atom iforce.addTorsion(improper.atom2.id, improper.atom3.id, improper.atom1.id, improper.atom4.id, [iterm.k]) elif improper_class == HarmonicImproperTerm: logger.info('Setting up harmonic impropers...') iforce = mm.CustomTorsionForce(f'k*min(dtheta,2*pi-dtheta)^2;' f'dtheta=abs(theta-phi0);' f'pi={PI}') iforce.addPerTorsionParameter('phi0') iforce.addPerTorsionParameter('k') for improper in top.impropers: iterm = system.improper_terms[id(improper)] if type(iterm) == HarmonicImproperTerm: iforce.addTorsion(improper.atom1.id, improper.atom2.id, improper.atom3.id, improper.atom4.id, [iterm.phi * PI / 180, iterm.k]) else: raise Exception( 'Improper terms other that PeriodicImproperTerm and ' 'HarmonicImproperTerm haven\'t been implemented') iforce.setUsesPeriodicBoundaryConditions(system.use_pbc) iforce.setForceGroup(ForceGroup.IMPROPER) omm_system.addForce(iforce) ### Set up non-bonded interactions ######################################################### # NonbonedForce is not flexible enough. Use it only for Coulomb interactions (including 1-4 Coulomb exceptions) # CustomNonbondedForce handles vdW interactions (including 1-4 LJ exceptions) cutoff = ff.vdw_cutoff logger.info('Setting up Coulomb interactions...') nbforce = mm.NonbondedForce() if system.use_pbc: nbforce.setNonbondedMethod(mm.NonbondedForce.PME) nbforce.setEwaldErrorTolerance(5E-4) nbforce.setCutoffDistance(cutoff) # dispersion will be handled by CustomNonbondedForce nbforce.setUseDispersionCorrection(False) try: nbforce.setExceptionsUsePeriodicBoundaryConditions(True) except: logger.warning('Cannot apply PBC for Coulomb 1-4 exceptions') else: nbforce.setNonbondedMethod(mm.NonbondedForce.NoCutoff) nbforce.setForceGroup(ForceGroup.COULOMB) omm_system.addForce(nbforce) for atom in top.atoms: nbforce.addParticle(atom.charge, 1.0, 0.0) ### Set up vdW interactions ######################################################### atom_types = list(ff.atom_types.values()) type_names = list(ff.atom_types.keys()) n_type = len(atom_types) for vdw_class in system.vdw_classes: if vdw_class == LJ126Term: logger.info('Setting up LJ-12-6 vdW interactions...') if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT: invRc6 = 1 / cutoff**6 cforce = mm.CustomNonbondedForce( f'A(type1,type2)*(invR6*invR6-{invRc6 * invRc6})-' f'B(type1,type2)*(invR6-{invRc6});' f'invR6=1/r^6') else: cforce = mm.CustomNonbondedForce( 'A(type1,type2)*invR6*invR6-B(type1,type2)*invR6;' 'invR6=1/r^6') cforce.addPerParticleParameter('type') A_list = [0.0] * n_type * n_type B_list = [0.0] * n_type * n_type for i, atype1 in enumerate(atom_types): for j, atype2 in enumerate(atom_types): vdw = ff.get_vdw_term(atype1, atype2) if type(vdw) == LJ126Term: A = 4 * vdw.epsilon * vdw.sigma**12 B = 4 * vdw.epsilon * vdw.sigma**6 else: A = B = 0 A_list[i + n_type * j] = A B_list[i + n_type * j] = B cforce.addTabulatedFunction( 'A', mm.Discrete2DFunction(n_type, n_type, A_list)) cforce.addTabulatedFunction( 'B', mm.Discrete2DFunction(n_type, n_type, B_list)) for atom in top.atoms: id_type = type_names.index(atom.type) cforce.addParticle([id_type]) elif vdw_class == MieTerm: logger.info('Setting up Mie vdW interactions...') if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT: cforce = mm.CustomNonbondedForce( 'A(type1,type2)/r^REP(type1,type2)-' 'B(type1,type2)/r^ATT(type1,type2)-' 'SHIFT(type1,type2)') else: cforce = mm.CustomNonbondedForce( 'A(type1,type2)/r^REP(type1,type2)-' 'B(type1,type2)/r^ATT(type1,type2)') cforce.addPerParticleParameter('type') A_list = [0.0] * n_type * n_type B_list = [0.0] * n_type * n_type REP_list = [0.0] * n_type * n_type ATT_list = [0.0] * n_type * n_type SHIFT_list = [0.0] * n_type * n_type for i, atype1 in enumerate(atom_types): for j, atype2 in enumerate(atom_types): vdw = ff.get_vdw_term(atype1, atype2) if type(vdw) == MieTerm: A = vdw.factor_energy( ) * vdw.epsilon * vdw.sigma**vdw.repulsion B = vdw.factor_energy( ) * vdw.epsilon * vdw.sigma**vdw.attraction REP = vdw.repulsion ATT = vdw.attraction SHIFT = A / cutoff**REP - B / cutoff**ATT else: A = B = REP = ATT = SHIFT = 0 A_list[i + n_type * j] = A B_list[i + n_type * j] = B REP_list[i + n_type * j] = REP ATT_list[i + n_type * j] = ATT SHIFT_list[i + n_type * j] = SHIFT cforce.addTabulatedFunction( 'A', mm.Discrete2DFunction(n_type, n_type, A_list)) cforce.addTabulatedFunction( 'B', mm.Discrete2DFunction(n_type, n_type, B_list)) cforce.addTabulatedFunction( 'REP', mm.Discrete2DFunction(n_type, n_type, REP_list)) cforce.addTabulatedFunction( 'ATT', mm.Discrete2DFunction(n_type, n_type, ATT_list)) if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT: cforce.addTabulatedFunction( 'SHIFT', mm.Discrete2DFunction(n_type, n_type, SHIFT_list)) for atom in top.atoms: id_type = type_names.index(atom.type) cforce.addParticle([id_type]) else: raise Exception('vdW terms other than LJ126Term and MieTerm ' 'haven\'t been implemented') if system.use_pbc: cforce.setNonbondedMethod( mm.CustomNonbondedForce.CutoffPeriodic) cforce.setCutoffDistance(cutoff) if ff.vdw_long_range == ForceField.VDW_LONGRANGE_CORRECT: cforce.setUseLongRangeCorrection(True) else: cforce.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff) cforce.setForceGroup(ForceGroup.VDW) omm_system.addForce(cforce) ### Set up 1-2, 1-3 and 1-4 exceptions ################################################## logger.info('Setting up 1-2, 1-3 and 1-4 exceptions...') custom_nb_forces = [ f for f in omm_system.getForces() if type(f) == mm.CustomNonbondedForce ] pair12, pair13, pair14 = top.get_12_13_14_pairs() for atom1, atom2 in pair12 + pair13: nbforce.addException(atom1.id, atom2.id, 0.0, 1.0, 0.0) for f in custom_nb_forces: f.addExclusion(atom1.id, atom2.id) # As long as 1-4 LJ OR Coulomb need to be scaled, then this pair should be excluded from ALL non-bonded forces. # This is required by OpenMM's internal implementation. # Even though NonbondedForce can handle 1-4 vdW, we use it only for 1-4 Coulomb. # And use CustomBondForce to handle 1-4 vdW, which makes it more clear for energy decomposition. if ff.scale_14_vdw != 1 or ff.scale_14_coulomb != 1: pair14_forces = {} # {VdwTerm: mm.NbForce} for atom1, atom2 in pair14: charge_prod = atom1.charge * atom2.charge * ff.scale_14_coulomb nbforce.addException(atom1.id, atom2.id, charge_prod, 1.0, 0.0) for f in custom_nb_forces: f.addExclusion(atom1.id, atom2.id) if ff.scale_14_vdw == 0: continue vdw = ff.get_vdw_term(ff.atom_types[atom1.type], ff.atom_types[atom2.type]) # We generalize LJ126Term and MieTerm because of minimal computational cost for 1-4 vdW if type(vdw) in (LJ126Term, MieTerm): cbforce = pair14_forces.get(MieTerm) if cbforce is None: cbforce = mm.CustomBondForce( 'C*epsilon*((sigma/r)^n-(sigma/r)^m);' 'C=n/(n-m)*(n/m)^(m/(n-m))') cbforce.addPerBondParameter('epsilon') cbforce.addPerBondParameter('sigma') cbforce.addPerBondParameter('n') cbforce.addPerBondParameter('m') cbforce.setUsesPeriodicBoundaryConditions( system.use_pbc) cbforce.setForceGroup(ForceGroup.VDW) omm_system.addForce(cbforce) pair14_forces[MieTerm] = cbforce epsilon = vdw.epsilon * ff.scale_14_vdw if type(vdw) == LJ126Term: cbforce.addBond(atom1.id, atom2.id, [epsilon, vdw.sigma, 12, 6]) elif type(vdw) == MieTerm: cbforce.addBond(atom1.id, atom2.id, [ epsilon, vdw.sigma, vdw.repulsion, vdw.attraction ]) else: raise Exception( '1-4 scaling for vdW terms other than LJ126Term and MieTerm ' 'haven\'t been implemented') ### Set up Drude particles ############################################################## for polar_class in system.polarizable_classes: if polar_class == DrudeTerm: logger.info('Setting up Drude polarizations...') pforce = mm.DrudeForce() pforce.setForceGroup(ForceGroup.DRUDE) omm_system.addForce(pforce) parent_idx_thole = { } # {parent: (index in DrudeForce, thole)} for addScreenPair for parent, drude in system.drude_pairs.items(): pterm = system.polarizable_terms[parent] n_H = len([ atom for atom in parent.bond_partners if atom.symbol == 'H' ]) alpha = pterm.alpha + n_H * pterm.merge_alpha_H idx = pforce.addParticle(drude.id, parent.id, -1, -1, -1, drude.charge, alpha, 0, 0) parent_idx_thole[parent] = (idx, pterm.thole) # exclude the non-boned interactions between Drude and parent # and those concerning Drude particles in 1-2 and 1-3 pairs # pairs formed by real atoms have already been handled above # also apply thole screening between 1-2 and 1-3 Drude dipole pairs drude_exclusions = list(system.drude_pairs.items()) for atom1, atom2 in pair12 + pair13: drude1 = system.drude_pairs.get(atom1) drude2 = system.drude_pairs.get(atom2) if drude1 is not None: drude_exclusions.append((drude1, atom2)) if drude2 is not None: drude_exclusions.append((atom1, drude2)) if drude1 is not None and drude2 is not None: drude_exclusions.append((drude1, drude2)) idx1, thole1 = parent_idx_thole[atom1] idx2, thole2 = parent_idx_thole[atom2] pforce.addScreenedPair(idx1, idx2, (thole1 + thole2) / 2) for a1, a2 in drude_exclusions: nbforce.addException(a1.id, a2.id, 0, 1.0, 0) for f in custom_nb_forces: f.addExclusion(a1.id, a2.id) # scale the non-boned interactions concerning Drude particles in 1-4 pairs # pairs formed by real atoms have already been handled above drude_exceptions14 = [] for atom1, atom2 in pair14: drude1 = system.drude_pairs.get(atom1) drude2 = system.drude_pairs.get(atom2) if drude1 is not None: drude_exceptions14.append((drude1, atom2)) if drude2 is not None: drude_exceptions14.append((atom1, drude2)) if drude1 is not None and drude2 is not None: drude_exceptions14.append((drude1, drude2)) for a1, a2 in drude_exceptions14: charge_prod = a1.charge * a2.charge * ff.scale_14_coulomb nbforce.addException(a1.id, a2.id, charge_prod, 1.0, 0.0) for f in custom_nb_forces: f.addExclusion(a1.id, a2.id) else: raise Exception( 'Polarizable terms other that DrudeTerm haven\'t been implemented' ) ### Set up virtual sites ################################################################ if top.has_virtual_site: logger.info('Setting up virtual sites...') for atom in top.atoms: vsite = atom.virtual_site if type(vsite) == TIP4PSite: O, H1, H2 = vsite.parents coeffs = system.get_TIP4P_linear_coeffs(atom) omm_vsite = mm.ThreeParticleAverageSite( O.id, H1.id, H2.id, *coeffs) omm_system.setVirtualSite(atom.id, omm_vsite) elif vsite is not None: raise Exception( 'Virtual sites other than TIP4PSite haven\'t been implemented' ) # exclude the non-boned interactions between virtual sites and parents # and particles (atoms, drude particles, virtual sites) in 1-2 and 1-3 pairs # TODO Assume no more than one virtual site is attached to each atom vsite_exclusions = list(system.vsite_pairs.items()) for atom, vsite in system.vsite_pairs.items(): drude = system.drude_pairs.get(atom) if drude is not None: vsite_exclusions.append((vsite, drude)) for atom1, atom2 in pair12 + pair13: vsite1 = system.vsite_pairs.get(atom1) vsite2 = system.vsite_pairs.get(atom2) drude1 = system.drude_pairs.get(atom1) drude2 = system.drude_pairs.get(atom2) if vsite1 is not None: vsite_exclusions.append((vsite1, atom2)) if drude2 is not None: vsite_exclusions.append((vsite1, drude2)) if vsite2 is not None: vsite_exclusions.append((vsite2, atom1)) if drude1 is not None: vsite_exclusions.append((vsite2, drude1)) if None not in [vsite1, vsite2]: vsite_exclusions.append((vsite1, vsite2)) for a1, a2 in vsite_exclusions: nbforce.addException(a1.id, a2.id, 0, 1.0, 0) for f in custom_nb_forces: f.addExclusion(a1.id, a2.id) # scale the non-boned interactions between virtual sites and particles in 1-4 pairs # TODO Assume no 1-4 LJ interactions on virtual sites vsite_exceptions14 = [] for atom1, atom2 in pair14: vsite1 = system.vsite_pairs.get(atom1) vsite2 = system.vsite_pairs.get(atom2) drude1 = system.drude_pairs.get(atom1) drude2 = system.drude_pairs.get(atom2) if vsite1 is not None: vsite_exceptions14.append((vsite1, atom2)) if drude2 is not None: vsite_exceptions14.append((vsite1, drude2)) if vsite2 is not None: vsite_exceptions14.append((vsite2, atom1)) if drude1 is not None: vsite_exceptions14.append((vsite2, drude1)) if None not in [vsite1, vsite2]: vsite_exceptions14.append((vsite1, vsite2)) for a1, a2 in vsite_exceptions14: charge_prod = a1.charge * a2.charge * ff.scale_14_coulomb nbforce.addException(a1.id, a2.id, charge_prod, 1.0, 0.0) for f in custom_nb_forces: f.addExclusion(a1.id, a2.id) ### Remove COM motion ################################################################### logger.info('Setting up COM motion remover...') omm_system.addForce(mm.CMMotionRemover(10)) return omm_system
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])
def create_system(self, topology, **kwargs): """ Create a System object with simple parameters from the provided Topology Any kwargs are ignored. Parameters ---------- topology : openforcefield.topology.Topology The Topology to be parameterized Returns ------- system : simtk.openmm.System The System object """ # TODO: Allow periodicity to be determined from topology from openmmtools.constants import kB kT = kB * 300 * unit.kelvin # hard-coded temperature for setting energy scales # Create a System system = openmm.System() # Add particles mass = 12.0 * unit.amu for atom in topology.atoms: system.addParticle(mass) # Add simple repulsive interactions # TODO: Use softcore repulsive interaction; Gaussian times switch? nonbonded = openmm.CustomNonbondedForce('100/(r/0.1)^4') nonbonded.setNonbondedMethod( openmm.CustomNonbondedForce.CutoffNonPeriodic) nonbonded.setCutoffDistance(1 * unit.nanometer) system.addForce(nonbonded) for atom in topology.atoms: nonbonded.addParticle([]) # Build a list of which atom indices are bonded to each atom bondedToAtom = [] for atom in topology.atoms(): bondedToAtom.append(set()) for (atom1, atom2) in topology.bonds(): bondedToAtom[atom1.index].add(atom2.index) bondedToAtom[atom2.index].add(atom1.index) return bondedToAtom # Add bonds bond_force = openmm.HarmonicBondForce() r0 = 1.0 * unit.angstroms sigma_r = 0.1 * unit.angstroms Kr = kT / sigma_r**2 for atom1, atom2 in topology.bonds(): bond_force.addBond(atom1.index, atom2.index, r0, Kr) system.addForce(bond_force) # Add angles uniqueAngles = set() for bond in topology.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)) angles = sorted(list(uniqueAngles)) theta0 = 109.5 * unit.degrees # TODO: Adapt based on number of bonds to each atom? sigma_theta = 10 * unit.degrees Ktheta = kT / sigma_theta**2 angle_force = openmm.HarmonicAngleForce() for (atom1, atom2, atom3) in angles: angles.addAngle(atom1.index, atom2.index, atom3.index, theta0, Ktheta) system.addForce(angle_force) # Make a list of all unique proper torsions uniquePropers = set() for angle in 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])) propers = sorted(list(uniquePropers)) torsion_force = openmm.PeriodicTorsionForce() periodicity = 3 phase = 0.0 * unit.degrees Kphi = 0.0 * kT for (atom1, atom2, atom3, atom4) in propers: torsion_force.add_torsion(atom1.index, atom2.index, atom3.index, atom4.index, periodicity, phase, Kphi) system.addForce(torsion_force) return system
def add_force(cgmodel, force_type=None): """ Given a 'cgmodel' and 'force_type' as input, this function adds the OpenMM force corresponding to 'force_type' to 'cgmodel.system'. :param cgmodel: CGModel() class object. :param type: class :param force_type: Designates the kind of 'force' provided. (Valid options include: "Bond", "Nonbonded", "Angle", and "Torsion") :type force_type: str :returns: - cgmodel (class) - 'foldamers' CGModel() class object - force (class) - An OpenMM `Force() <https://simtk.org/api_docs/openmm/api4_1/python/classsimtk_1_1openmm_1_1openmm_1_1Force.html>`_ object. :Example: >>> from foldamers.cg_model.cgmodel import CGModel >>> cgmodel = CGModel() >>> force_type = "Bond" >>> cgmodel,force = add_force(cgmodel,force_type=force_type) """ if force_type == "Bond": bond_force = mm.HarmonicBondForce() bond_list = [] for bond_indices in cgmodel.get_bond_list(): bond_list.append([bond_indices[0], bond_indices[1]]) bond_force_constant = cgmodel.get_bond_force_constant( bond_indices[0], bond_indices[1]) bond_length = cgmodel.get_bond_length(bond_indices[0], bond_indices[1]) if cgmodel.constrain_bonds: cgmodel.system.addConstraint(bond_indices[0], bond_indices[1], bond_length) bond_length = bond_length.in_units_of(unit.nanometer)._value bond_force.addBond(bond_indices[0], bond_indices[1], bond_length, bond_force_constant) if len(bond_list) != bond_force.getNumBonds(): print( "ERROR: The number of bonds in the coarse grained model is different\n" ) print("from the number of bonds in its OpenMM System object\n") print("There are " + str(len(bond_list)) + " bonds in the coarse grained model\n") print("and " + str(bond_force.getNumBonds()) + " bonds in the OpenMM system object.") exit() cgmodel.system.addForce(bond_force) force = bond_force if force_type == "Nonbonded": nonbonded_force = mm.NonbondedForce() nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) for particle in range(cgmodel.num_beads): charge = cgmodel.get_particle_charge(particle) sigma = cgmodel.get_sigma(particle) epsilon = cgmodel.get_epsilon(particle) nonbonded_force.addParticle(charge, sigma, epsilon) if len(cgmodel.bond_list) >= 1: nonbonded_force.createExceptionsFromBonds(cgmodel.bond_list, 1.0, 1.0) cgmodel.system.addForce(nonbonded_force) force = nonbonded_force #for particle in range(cgmodel.num_beads): #print(force.getParticleParameters(particle)) if force_type == "Angle": angle_force = mm.HarmonicAngleForce() for angle in cgmodel.bond_angle_list: bond_angle_force_constant = cgmodel.get_bond_angle_force_constant( angle[0], angle[1], angle[2]) equil_bond_angle = cgmodel.get_equil_bond_angle( angle[0], angle[1], angle[2]) angle_force.addAngle(angle[0], angle[1], angle[2], equil_bond_angle, bond_angle_force_constant) cgmodel.system.addForce(angle_force) force = angle_force if force_type == "Torsion": torsion_force = mm.PeriodicTorsionForce() for torsion in cgmodel.torsion_list: torsion_force_constant = cgmodel.get_torsion_force_constant( torsion) equil_torsion_angle = cgmodel.get_equil_torsion_angle(torsion) periodicity = 0 #print(torsion) #print(equil_torsion_angle) #print(torsion_force_constant) torsion_force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], periodicity, equil_torsion_angle, torsion_force_constant) #print(torsion_force.getNumTorsions()) cgmodel.system.addForce(torsion_force) force = torsion_force return (cgmodel, force)
def build_alchemically_modified_system(reference_system, receptor_atoms, ligand_atoms, annihilate=True): """ Build alchemically-modified system where ligand is decoupled or annihilated. """ # Create new system. system = openmm.System() # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): reference_force = reference_system.getForce(force_index) # Dispatch forces if isinstance(reference_force, openmm.HarmonicBondForce): # HarmonicBondForce force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) # Annihilate if directed. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): K *= 0.0 # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): # HarmonicAngleForce force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Annihilate if directed: if annihilate and (iatom in ligand_atoms) and ( jatom in ligand_atoms) and (katom in ligand_atoms): Ktheta *= 0.0 # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Annihilate if directed: if annihilate and (particle1 in ligand_atoms) and ( particle2 in ligand_atoms) and ( particle3 in ligand_atoms) and (particle4 in ligand_atoms): k *= 0.0 # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce force = openmm.NonbondedSoftcoreForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. alchemical_lambda = 1.0 if particle_index in ligand_atoms: alchemical_lambda = 0.0 charge *= 0.0 if annihilate: epsilon *= 0.0 # Add modified particle parameters. force.addParticle(charge, sigma, epsilon, alchemical_lambda) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # TODO: Alchemically modify parameters. if (iatom in ligand_atoms) and (jatom in ligand_atoms): chargeprod *= 0.0 if annihilate: epsilon *= 0.0 # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric()) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance()) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.GBSAOBCForce): # GBSAOBCForce force = openmm.GBSAOBCSoftcoreForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. nonpolar_scaling_factor = 1.0 if particle_index in ligand_atoms: charge *= 0.0 #radius *= 0.0 #scaling_factor *= 0.0 nonpolar_scaling_factor = 0.0 pass # Add parameters. force.addParticle(charge, radius, scaling_factor, nonpolar_scaling_factor) force.setSolventDielectric(reference_force.getSolventDielectric()) force.setSoluteDielectric(reference_force.getSoluteDielectric()) #force.setCutoffDistance( reference_force.getCutoffDistance() ) #force.setNonbondedMethod( reference_force.getNonbondedMethod() ) # Add force to new system. system.addForce(force) else: # Don't add unrecognized forces. pass return system
def build_custom_system(reference_system, receptor_atoms, ligand_atoms, valence_lambda, coulomb_lambda, vdw_lambda, annihilate=False): """ Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes. """ # Create new system. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): reference_force = reference_system.getForce(force_index) # Dispatch forces if isinstance(reference_force, openmm.HarmonicBondForce): # HarmonicBondForce force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) # Annihilate if directed. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): K *= valence_lambda # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): # HarmonicAngleForce force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Annihilate if directed: if annihilate and (iatom in ligand_atoms) and ( jatom in ligand_atoms) and (katom in ligand_atoms): Ktheta *= valence_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Annihilate if directed: if annihilate and (particle1 in ligand_atoms) and ( particle2 in ligand_atoms) and ( particle3 in ligand_atoms) and (particle4 in ligand_atoms): k *= valence_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce will handle charges and exception interactions. force = openmm.NonbondedForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Remove Lennard-Jones interactions, which will be handled by CustomNonbondedForce. epsilon *= 0.0 # Alchemically modify charges. if particle_index in ligand_atoms: charge *= coulomb_lambda # Add modified particle parameters. force.addParticle(charge, sigma, epsilon) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Alchemically modify epsilon and chargeprod. # Note that exceptions are handled by NonbondedForce and not CustomNonbondedForce. if (iatom in ligand_atoms) and (jatom in ligand_atoms): if annihilate: epsilon *= vdw_lambda chargeprod *= coulomb_lambda # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric()) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance()) # Add force to new system. system.addForce(force) # CustomNonbondedForce # Softcore potential. energy_expression = "4*epsilon*lambda*x*(x-1.0);" energy_expression += "x = 1.0/(alpha*(1.0-lambda) + (r/sigma)^6);" energy_expression += "epsilon = sqrt(epsilon1*epsilon2);" energy_expression += "sigma = 0.5*(sigma1 + sigma2);" energy_expression += "lambda = lambda1*lambda2;" force = openmm.CustomNonbondedForce(energy_expression) alpha = 0.5 # softcore parameter force.addGlobalParameter("alpha", alpha) force.addPerParticleParameter("sigma") force.addPerParticleParameter("epsilon") force.addPerParticleParameter("lambda") for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: force.addParticle([sigma, epsilon, vdw_lambda]) else: force.addParticle([sigma, epsilon, 1.0]) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # All exceptions are handled by NonbondedForce, so we exclude all these here. force.addExclusion(iatom, jatom) if reference_force.getNonbondedMethod() in [ openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME ]: force.setNonbondedMethod( openmm.CustomNonbondedForce.CutoffPeriodic) else: force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) system.addForce(force) elif isinstance(reference_force, openmm.GBSAOBCForce): # GBSAOBCForce solvent_dielectric = reference_force.getSolventDielectric() solute_dielectric = reference_force.getSoluteDielectric() force = createCustomSoftcoreGBOBC(solvent_dielectric, solute_dielectric, igb=5) for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: # Scale charge and contribution to GB integrals. force.addParticle([ charge * coulomb_lambda, radius, scaling_factor, coulomb_lambda ]) else: # Don't modulate GB. force.addParticle([charge, radius, scaling_factor, 1.0]) # Add force to new system. system.addForce(force) else: # Don't add unrecognized forces. pass return system
def build_softcore_system(reference_system, receptor_atoms, ligand_atoms, valence_lambda, coulomb_lambda, vdw_lambda, annihilate=False): """ Build alchemically-modified system where ligand is decoupled or annihilated using *SoftcoreForce classes. """ # Create new system. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): reference_force = reference_system.getForce(force_index) # Dispatch forces if isinstance(reference_force, openmm.HarmonicBondForce): # HarmonicBondForce force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) # Annihilate if directed. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): K *= valence_lambda # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): # HarmonicAngleForce force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Annihilate if directed: if annihilate and (iatom in ligand_atoms) and ( jatom in ligand_atoms) and (katom in ligand_atoms): Ktheta *= valence_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Annihilate if directed: if annihilate and (particle1 in ligand_atoms) and ( particle2 in ligand_atoms) and ( particle3 in ligand_atoms) and (particle4 in ligand_atoms): k *= valence_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce force = openmm.NonbondedSoftcoreForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: charge *= coulomb_lambda epsilon *= vdw_lambda # Add modified particle parameters. force.addParticle(charge, sigma, epsilon, vdw_lambda) else: # Add unmodified particle parameters. force.addParticle(charge, sigma, epsilon, 1.0) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Alchemically modify epsilon and chargeprod. if (iatom in ligand_atoms) and (jatom in ligand_atoms): if annihilate: epsilon *= vdw_lambda chargeprod *= coulomb_lambda # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon, vdw_lambda) else: # Add unmodified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon, 1.0) # Set parameters. force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric()) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance()) # Add force to new system. #system.addForce(force) elif isinstance(reference_force, openmm.GBSAOBCForce): # GBSAOBCForce force = openmm.GBSAOBCSoftcoreForce() force.setSolventDielectric(reference_force.getSolventDielectric()) force.setSoluteDielectric(reference_force.getSoluteDielectric()) for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: # Scale charge and contribution to GB integrals. force.addParticle(charge * coulomb_lambda, radius, scaling_factor, coulomb_lambda) else: # Don't modulate GB. force.addParticle(charge, radius, scaling_factor, 1.0) # Add force to new system. #system.addForce(force) else: # Don't add unrecognized forces. pass return system
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 run(self): prmtop = self._get_prmtop() system = prmtop.createSystem() groups = {force.getForceGroup() for force in system.getForces()} if self.optimize: if self.restrained_dihedrals is not None: restraint = openmm.PeriodicTorsionForce() restraint.setForceGroup(max(groups) + 1) for dihedral in self.restrained_dihedrals: restraint.addTorsion(*tuple(map(int, dihedral)), periodicity=1, phase=0, k=-1000 * unit.kilocalorie_per_mole) system.addForce(restraint) simulation = app.Simulation( prmtop.topology, system, openmm.VerletIntegrator(1 * unit.femtosecond), openmm.Platform.getPlatformByName('CPU')) results = [] molecule_copy = self.molecule.copy() for iframe in range(self.molecule.numFrames): self.molecule.frame = iframe molecule_copy.frame = iframe directory = os.path.join(self.directory, '%05d' % iframe) os.makedirs(directory, exist_ok=True) pickleFile = os.path.join(directory, 'data.pkl') if self._completed(directory): with open(pickleFile, 'rb') as fd: results.append(pickle.load(fd)) logger.info('Loading QM data from %s' % pickleFile) continue simulation.context.setPositions( self.molecule.coords[:, :, iframe] * unit.angstrom) if self.optimize: if self.restrained_dihedrals is not None: for i, dihedral in enumerate(self.restrained_dihedrals): ref_angle = dihedralAngle( self.molecule.coords[dihedral, :, iframe]) parameters = restraint.getTorsionParameters(i) parameters[5] = ref_angle * unit.degree restraint.setTorsionParameters(i, *parameters) restraint.updateParametersInContext(simulation.context) simulation.minimizeEnergy(tolerance=0.001 * unit.kilocalorie_per_mole) state = simulation.context.getState(getEnergy=True, getPositions=True, groups=groups) result = QMResult() result.errored = False result.energy = state.getPotentialEnergy().value_in_unit( unit.kilocalorie_per_mole) result.coords = state.getPositions(asNumpy=True).value_in_unit( unit.angstrom).reshape((-1, 3, 1)) result.dipole = self.molecule.getDipole() if self.esp_points is not None: assert self.molecule.numFrames == 1 result.esp_points = self.esp_points distances = cdist(result.esp_points, result.coords[:, :, 0]) # Angstrom distances *= const.physical_constants['Bohr radius'][ 0] / const.angstrom # Angstrom --> Bohr result.esp_values = np.dot( np.reciprocal(distances), self.molecule.charge) # Hartree/Bohr results.append(result) with open(pickleFile, 'wb') as fd: pickle.dump(result, fd) self.molecule.write(os.path.join( directory, 'mol-init.mol2')) # Write an optimiz molecule_copy.coords[:, :, iframe] = result.coords[:, :, 0] molecule_copy.write(os.path.join(directory, 'mol.mol2')) # Write an optimiz return results
def add_force(cgmodel, force_type=None, rosetta_functional_form=False): """ Given a 'cgmodel' and 'force_type' as input, this function adds the OpenMM force corresponding to 'force_type' to 'cgmodel.system'. :param cgmodel: CGModel() class object. :param type: class :param force_type: Designates the kind of 'force' provided. (Valid options include: "Bond", "Nonbonded", "Angle", and "Torsion") :type force_type: str :returns: - cgmodel (class) - 'foldamers' CGModel() class object - force (class) - An OpenMM `Force() <https://simtk.org/api_docs/openmm/api4_1/python/classsimtk_1_1openmm_1_1openmm_1_1Force.html>`_ object. :Example: >>> from foldamers.cg_model.cgmodel import CGModel >>> cgmodel = CGModel() >>> force_type = "Bond" >>> cgmodel,force = add_force(cgmodel,force_type=force_type) """ if force_type == "Bond": bond_force = mm.HarmonicBondForce() bond_list = [] for bond_indices in cgmodel.get_bond_list(): bond_list.append([bond_indices[0], bond_indices[1]]) if cgmodel.include_bond_forces: bond_force_constant = cgmodel.get_bond_force_constant( bond_indices) bond_length = cgmodel.get_bond_length(bond_indices) bond_force.addBond( bond_indices[0], bond_indices[1], bond_length.value_in_unit(unit.nanometer), bond_force_constant.value_in_unit(unit.kilojoule_per_mole / unit.nanometer**2), ) if cgmodel.constrain_bonds: bond_length = cgmodel.get_bond_length(bond_indices) if not cgmodel.include_bond_forces: bond_force.addBond( bond_indices[0], bond_indices[1], bond_length.value_in_unit(unit.nanometer), 0.0, ) cgmodel.system.addConstraint(bond_indices[0], bond_indices[1], bond_length) if len(bond_list) != bond_force.getNumBonds(): print( "ERROR: The number of bonds in the coarse grained model is different\n" ) print("from the number of bonds in its OpenMM System object\n") print("There are " + str(len(bond_list)) + " bonds in the coarse grained model\n") print("and " + str(bond_force.getNumBonds()) + " bonds in the OpenMM system object.") exit() cgmodel.system.addForce(bond_force) force = bond_force if force_type == "Nonbonded": if cgmodel.binary_interaction_parameters: # If not an empty dictionary, use the parameters within for key, value in cgmodel.binary_interaction_parameters.items(): # TODO: make kappa work for systems with more than 2 bead types kappa = value # Use custom nonbonded force with binary interaction parameter nonbonded_force = mm.CustomNonbondedForce( f"4*epsilon*((sigma/r)^12-(sigma/r)^6); sigma=0.5*(sigma1+sigma2); epsilon=(1-kappa)*sqrt(epsilon1*epsilon2)" ) nonbonded_force.addPerParticleParameter("sigma") nonbonded_force.addPerParticleParameter("epsilon") # We need to specify a default value of kappa when adding global parameter nonbonded_force.addGlobalParameter("kappa", kappa) # TODO: add the rosetta_function_form switching function nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) for particle in range(cgmodel.num_beads): # We don't need to define charge here, though we should add it in the future # We also don't need to define kappa since it is a global parameter sigma = cgmodel.get_particle_sigma(particle) epsilon = cgmodel.get_particle_epsilon(particle) nonbonded_force.addParticle((sigma, epsilon)) if len(cgmodel.bond_list) >= 1: #***Note: customnonbonded force uses 'Exclusion' rather than 'Exception' # Each of these also takes different arguments if not rosetta_functional_form: # This should not be applied if there are no angle forces. if cgmodel.include_bond_angle_forces: bond_cut = 2 # Particles separated by this many bonds or fewer are excluded # A value of 2 means that 1-2, 1-3 interactions are 0, 1-4 interactions are 1 nonbonded_force.createExclusionsFromBonds( cgmodel.bond_list, bond_cut) else: # Just remove the 1-2 nonbonded interactions. # For customNonbondedForce, don't need to set charge product and epsilon here for bond in cgmodel.bond_list: nonbonded_force.addExclusion(bond[0], bond[1]) else: nonbonded_force = mm.NonbondedForce() if rosetta_functional_form: # rosetta has a 4.5-6 A vdw cutoff. Note the OpenMM cutoff may not be quite the same # functional form as the Rosetta cutoff, but it should be somewhat close. nonbonded_force.setNonbondedMethod( mm.NonbondedForce.CutoffNonPeriodic) nonbonded_force.setCutoffDistance( 0.6) # rosetta cutoff distance in nm nonbonded_force.setUseSwitchingFunction(True) nonbonded_force.setSwitchingDistance( 0.45) # start of rosetta switching distance in nm else: nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) for particle in range(cgmodel.num_beads): charge = cgmodel.get_particle_charge(particle) sigma = cgmodel.get_particle_sigma(particle) epsilon = cgmodel.get_particle_epsilon(particle) nonbonded_force.addParticle(charge, sigma, epsilon) if len(cgmodel.bond_list) >= 1: if not rosetta_functional_form: # This should not be applied if there are no angle forces. if cgmodel.include_bond_angle_forces: nonbonded_force.createExceptionsFromBonds( cgmodel.bond_list, 1.0, 1.0) else: # Just remove the 1-2 nonbonded interactions. # If charge product and epsilon are 0, the interaction is omitted. for bond in cgmodel.bond_list: nonbonded_force.addException( bond[0], bond[1], 0.0, 1.0, 0.0) if rosetta_functional_form: # Remove i+3 interactions nonbonded_force.createExceptionsFromBonds( cgmodel.bond_list, 0.0, 0.0) # Reduce the strength of i+4 interactions for torsion in cgmodel.torsion_list: for bond in cgmodel.bond_list: if bond[0] not in torsion: if bond[1] == torsion[0]: nonbonded_force = add_rosetta_exception_parameters( cgmodel, nonbonded_force, bond[0], torsion[3]) if bond[1] == torsion[3]: nonbonded_force = add_rosetta_exception_parameters( cgmodel, nonbonded_force, bond[0], torsion[0]) if bond[1] not in torsion: if bond[0] == torsion[0]: nonbonded_force = add_rosetta_exception_parameters( cgmodel, nonbonded_force, bond[1], torsion[3]) if bond[0] == torsion[3]: nonbonded_force = add_rosetta_exception_parameters( cgmodel, nonbonded_force, bond[1], torsion[0]) cgmodel.system.addForce(nonbonded_force) force = nonbonded_force if force_type == "Angle": angle_force = mm.HarmonicAngleForce() for angle in cgmodel.bond_angle_list: bond_angle_force_constant = cgmodel.get_bond_angle_force_constant( angle) equil_bond_angle = cgmodel.get_equil_bond_angle(angle) angle_force.addAngle( angle[0], angle[1], angle[2], equil_bond_angle.value_in_unit(unit.radian), bond_angle_force_constant.value_in_unit( unit.kilojoule_per_mole / unit.radian**2), ) cgmodel.system.addForce(angle_force) force = angle_force if force_type == "Torsion": torsion_force = mm.PeriodicTorsionForce() for torsion in cgmodel.torsion_list: torsion_force_constant = cgmodel.get_torsion_force_constant( torsion) torsion_phase_angle = cgmodel.get_torsion_phase_angle(torsion) periodicity = cgmodel.get_torsion_periodicity(torsion) if type(periodicity) == list: # Check periodic torsion parameter lists: # These can be either a list of quantities, or a quantity with a list as its value # Check torsion_phase_angle parameters: if type(torsion_phase_angle) == unit.quantity.Quantity: # This is either a single quantity, or quantity with a list value if type(torsion_phase_angle.value_in_unit( unit.radian)) == list: # Check if there are either 1 or len(periodicity) elements if len(torsion_phase_angle) != len( periodicity) and len(torsion_phase_angle) != 1: # Mismatch is list lengths print( 'ERROR: incompatible periodic torsion parameter lists' ) exit() if len(torsion_phase_angle) == 1: # This happens when input is '[value]*unit.radian' torsion_phase_angle_list = [] for i in range(len(periodicity)): torsion_phase_angle_list.append( torsion_phase_angle[0]) # This is a list of quantities torsion_phase_angle = torsion_phase_angle_list else: # Single quantity - apply same angle to all periodic terms: torsion_phase_angle_list = [] for i in range(len(periodicity)): torsion_phase_angle_list.append( torsion_phase_angle) # This is a list of quantities torsion_phase_angle = torsion_phase_angle_list else: # This is a list of quantities or incorrect input if len(torsion_phase_angle) == 1: # This is a list containing a single quantity torsion_phase_angle_list = [] for i in range(len(periodicity)): torsion_phase_angle_list.append( torsion_phase_angle[0]) # This is a list of quantities torsion_phase_angle = torsion_phase_angle_list # Check torsion_force_constant parameters: if type(torsion_force_constant) == unit.quantity.Quantity: # This is either a single quantity, or quantity with a list value if type( torsion_force_constant.value_in_unit( unit.kilojoule_per_mole)) == list: # Check if there are either 1 or len(periodicity) elements if len(torsion_force_constant) != len( periodicity) and len( torsion_force_constant) != 1: # Mismatch is list lengths print( 'ERROR: incompatible periodic torsion parameter lists' ) exit() if len(torsion_force_constant) == 1: # This happens when input is '[value]*unit.kilojoule_per_mole' torsion_force_constant_list = [] for i in range(len(periodicity)): torsion_force_constant_list.append( torsion_force_constant[0]) # This is a list of quantities torsion_force_constant = torsion_force_constant_list else: # Single quantity - apply same angle to all periodic terms: torsion_force_constant_list = [] for i in range(len(periodicity)): torsion_force_constant_list.append( torsion_force_constant) # This is a list of quantities torsion_force_constant = torsion_force_constant_list else: # This is a list of quantities or incorrect input if len(torsion_force_constant) == 1: # This is a list containing a single quantity torsion_force_constant_list = [] for i in range(len(periodicity)): torsion_force_constant_list.append( torsion_force_constant[0]) # This is a list of quantities torsion_force_constant = torsion_force_constant_list # Add torsion force: for i in range(len(periodicity)): # print(f'Adding torsion term to particles [{torsion[0]} {torsion[1]} {torsion[2]} {torsion[3]}]') # print(f'periodicity: {periodicity[i]}') # print(f'torsion_phase_angle: {torsion_phase_angle[i]}') # print(f'torsion_force_constant: {torsion_force_constant[i]}\n') torsion_force.addTorsion( torsion[0], torsion[1], torsion[2], torsion[3], periodicity[i], torsion_phase_angle[i].value_in_unit(unit.radian), torsion_force_constant[i].value_in_unit( unit.kilojoule_per_mole), ) else: # Single periodic torsion term: torsion_force.addTorsion( torsion[0], torsion[1], torsion[2], torsion[3], periodicity, torsion_phase_angle.value_in_unit(unit.radian), torsion_force_constant.value_in_unit( unit.kilojoule_per_mole), ) cgmodel.system.addForce(torsion_force) # print(f"Number of torsion forces: {cgmodel.system.getForces()[3].getNumTorsions()}") force = torsion_force return (cgmodel, force)