def test_setting(self, ethane_system_topology): nb_force = openmm.NonbondedForce() nb_force.addParticle(1, 2, 3) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_nb_ommp = NonbondedForceOmmperator(my_ommp, nb_force, 0) my_nb_ommp.charge = 10*unit.elementary_charge my_nb_ommp.sigma = 20*unit.nanometer my_nb_ommp.epsilon = 30*unit.kilojoule_per_mole assert my_nb_ommp.charge == nb_force.getParticleParameters(0)[0] assert my_nb_ommp.sigma == nb_force.getParticleParameters(0)[1] assert my_nb_ommp.epsilon == nb_force.getParticleParameters(0)[2] assert is_close(my_nb_ommp.charge, 10*unit.elementary_charge) assert is_close(my_nb_ommp.sigma, 20*unit.nanometer) assert is_close(my_nb_ommp.epsilon, 30*unit.kilojoule_per_mole) my_nb_ommp.set_params(charge=100*unit.elementary_charge, sigma=200*unit.nanometer, epsilon=300*unit.kilojoule_per_mole) assert my_nb_ommp.charge == nb_force.getParticleParameters(0)[0] assert my_nb_ommp.sigma == nb_force.getParticleParameters(0)[1] assert my_nb_ommp.epsilon == nb_force.getParticleParameters(0)[2] assert is_close(my_nb_ommp.charge, 100*unit.elementary_charge) assert is_close(my_nb_ommp.sigma, 200*unit.nanometer) assert is_close(my_nb_ommp.epsilon, 300*unit.kilojoule_per_mole)
def calc_energy_forces_with_omm(self, ewald=True, short=True, long=True): from simtk import openmm as mm, unit system = mm.System() system.setDefaultPeriodicBoxVectors(*np.diag(self.box)) nbforce = mm.NonbondedForce() system.addForce(nbforce) if ewald: nbforce.setNonbondedMethod(mm.NonbondedForce.Ewald) else: nbforce.setNonbondedMethod(mm.NonbondedForce.NoCutoff) nbforce.setCutoffDistance(self.cutoff) nbforce.setForceGroup(1) nbforce.setReciprocalSpaceForceGroup(2) for i in range(self.n_atom): system.addParticle(0) nbforce.addParticle(self.charges[i], 1.0, 0) integrator = mm.VerletIntegrator(0.001) platform = mm.Platform.getPlatformByName('Reference') context = mm.Context(system, integrator, platform) context.setPositions(self.positions) groups = set() if short: groups.add(1) if long: groups.add(2) state = context.getState(getEnergy=True, getForces=True, groups=groups) energy = state.getPotentialEnergy().value_in_unit( unit.kilojoule_per_mole) forces = state.getForces(asNumpy=True).value_in_unit( unit.kilojoule_per_mole / unit.nanometer) return energy, forces
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 _add_nonbonded_force_terms(self): standard_nonbonded_force = openmm.NonbondedForce() self._out_system.addForce(standard_nonbonded_force) self._out_system_forces[standard_nonbonded_force.__class__. __name__] = standard_nonbonded_force #set the appropriate parameters epsilon_solvent = self._og_system_forces[ 'NonbondedForce'].getReactionFieldDielectric() r_cutoff = self._og_system_forces['NonbondedForce'].getCutoffDistance() switch_bool = self._og_system_forces[ 'NonbondedForce'].getUseSwitchingFunction() standard_nonbonded_force.setUseSwitchingFunction(switch_bool) if switch_bool: switching_distance = self._og_system_forces[ 'NonbondedForce'].getSwitchingDistance() standard_nonbonded_force.setSwitchingDistance(switching_distance) if self._nonbonded_method != openmm.NonbondedForce.NoCutoff: standard_nonbonded_force.setReactionFieldDielectric( epsilon_solvent) standard_nonbonded_force.setCutoffDistance(r_cutoff) if self._nonbonded_method in [ openmm.NonbondedForce.PME, openmm.NonbondedForce.Ewald ]: [alpha_ewald, nx, ny, nz] = self._og_system_forces['NonbondedForce'].getPMEParameters() delta = self._og_system_forces[ 'NonbondedForce'].getEwaldErrorTolerance() standard_nonbonded_force.setPMEParameters(alpha_ewald, nx, ny, nz) standard_nonbonded_force.setEwaldErrorTolerance(delta) standard_nonbonded_force.setNonbondedMethod(self._nonbonded_method) if self._og_system_forces['NonbondedForce'].getUseDispersionCorrection( ) and self._use_dispersion_correction: self._out_system_forces[ 'NonbondedForce'].setUseDispersionCorrection(True) else: self._out_system_forces[ 'NonbondedForce'].setUseDispersionCorrection(False) #add the global value self._out_system_forces['NonbondedForce'].addGlobalParameter( 'electrostatic_scale', 0.) self._out_system_forces['NonbondedForce'].addGlobalParameter( 'steric_scale', 0.)
def _addNonbondedForceToSystem(self, sys): """Create the nonbonded force """ nb = mm.NonbondedForce() sys.addForce(nb) q = """SELECT charge, sigma, epsilon FROM particle INNER JOIN nonbonded_param ON particle.nbtype=nonbonded_param.id""" for charge, sigma, epsilon in self._conn.execute(q): nb.addParticle(charge, sigma * angstrom, epsilon * kilocalorie_per_mole) for p0, p1 in self._conn.execute('SELECT p0, p1 FROM exclusion'): nb.addException(p0, p1, 0.0, 1.0, 0.0) q = """SELECT p0, p1, aij, bij, qij FROM pair_12_6_es_term INNER JOIN pair_12_6_es_param ON pair_12_6_es_term.param=pair_12_6_es_param.id;""" for p0, p1, a_ij, b_ij, q_ij in self._conn.execute(q): a_ij = (a_ij * kilocalorie_per_mole * (angstrom**12)).in_units_of( kilojoule_per_mole * (nanometer**12)) b_ij = (b_ij * kilocalorie_per_mole * (angstrom**6)).in_units_of( kilojoule_per_mole * (nanometer**6)) q_ij = q_ij * elementary_charge**2 if (b_ij._value == 0.0) or (a_ij._value == 0.0): new_epsilon = 0 new_sigma = 1 else: new_epsilon = b_ij**2 / (4 * a_ij) new_sigma = (a_ij / b_ij)**(1.0 / 6.0) nb.addException(p0, p1, q_ij, new_sigma, new_epsilon, True) n_total = self._conn.execute( """SELECT COUNT(*) FROM pair_12_6_es_term""").fetchone() n_in_exclusions = self._conn.execute("""SELECT COUNT(*) FROM exclusion INNER JOIN pair_12_6_es_term ON exclusion.p0==pair_12_6_es_term.p0 AND exclusion.p1==pair_12_6_es_term.p1""" ).fetchone() if not n_total == n_in_exclusions: raise NotImplementedError( 'All pair_12_6_es_terms must have a corresponding exclusion') return nb
def perform_test(sigma, epsilon, charge0, charge1, rs, rc): nonbonded = openmm.NonbondedForce() nonbonded.addParticle(charge0, sigma, epsilon) nonbonded.addParticle(charge1, sigma, epsilon) nonbonded.setNonbondedMethod(nonbonded.CutoffNonPeriodic) platform = openmm.Platform.getPlatformByName('Reference') system = openmm.System() system.addParticle(1) system.addParticle(1) system.addForce(nonbonded) ufedmm.add_inner_nonbonded_force(system, rs, rc, 1) context = openmm.Context(system, openmm.CustomIntegrator(0), platform) ONE_4PI_EPS0 = 138.93545764438198 for r in np.linspace(sigma, rc, 101): context.setPositions([[0, 0, 0], [r, 0, 0]]) state = context.getState(getForces=True, groups={1}) force = state.getForces()[1].x F = 24 * epsilon * ( 2 * (sigma / r)**12 - (sigma / r)**6) / r + ONE_4PI_EPS0 * charge0 * charge1 / r**2 assert force == pytest.approx(F * S((r - rs) / (rc - rs)))
def test_nonbonded_kernel(): pairs = list(itertools.combinations(list(range(top.n_atom)), 2)) parameters = [(i, j / 1000, i / 10 + j / 10) for i, j in pairs] kernel = NonbondedKernel(top.positions, pairs, parameters) r, energy, forces = kernel.evaluate() print() print(r) print(energy) print(sum(energy)) print(forces) force = mm.NonbondedForce() force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) force.setForceGroup(5) for i in range(top.n_atom): force.addParticle(0, 1, 0) for (i, j), parameter in zip(pairs, parameters): force.addException(i, j, parameter[2], parameter[1], parameter[0]) 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 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)
mass = 39.9 * units.amu charge = 0.05 * units.elementary_charge sigma = 3.350 * units.angstrom epsilon = 100 * 0.001603 * units.kilojoule_per_mole # Simulation parameters. temperature = 300.0 * units.kelvin kT = kB * temperature beta = 1.0 / kT import simtk.openmm as openmm # Create receptor. receptor_system = openmm.System() receptor_system.addParticle(10 * mass) force = openmm.NonbondedForce() force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) charge = +charge * units.elementary_charge # DEBUG force.addParticle(charge, sigma, epsilon) receptor_system.addForce(force) # Create ligand. ligand_system = openmm.System() ligand_system.addParticle(mass) force = openmm.NonbondedForce() force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) charge = -charge * units.elementary_charge # DEBUG force.addParticle(charge, sigma, epsilon) ligand_system.addForce(force) # Prevent receptor from diffusing away by imposing a spring.
1]) # lambda starts at 0, we will increase it to 1 elif particle_index not in [1728, 1729]: # Add nonbonded LJ parameters for a water sigma = waterbox_nbforce.getParticleParameters(particle_index)[1] epsilon = waterbox_nbforce.getParticleParameters(particle_index)[2] alchemical_nbforce.addParticle([sigma, epsilon, 0]) # lambda is ALWAYS 0 system.addForce(alchemical_nbforce) # use a switching function to smoothly truncate forces to zero from 10-12 angstrom alchemical_nbforce.setUseSwitchingFunction(use=True) alchemical_nbforce.setSwitchingDistance(2.0 * unit.angstrom) # and now we're going to build out a "normal", non-alchemical NonbondedForce. # Even though O2 will not be part of this force, any NonbondedForce object # must contain parameters for EVERY atom in the system, even if the parameters are zero nbforce = mm.NonbondedForce() nbforce.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) nbforce.setCutoffDistance(12.0 * unit.angstrom) nbforce.setUseSwitchingFunction(use=True) nbforce.setSwitchingDistance(2.0 * unit.angstrom) # use the long-range dispersion correction for isotropic fluids in NPT # Michael R. Shirts, David L. Mobley, John D. Chodera, and Vijay S. Pande. # Accurate and efficient corrections for missing dispersion interactions in molecular simulations. # Journal of Physical Chemistry B, 111:13052–13063, 2007. nbforce.setUseDispersionCorrection(True) for particle_index in range(system.getNumParticles()): # set LJ parameters of each paricle if particle_index in [1728, 1729]: # O2 has 0 -- the only way it interats with water is via alchemical_nbforce charge = 0.0 * unit.coulomb sigma = 0.0 * unit.nanometer
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 distributeAtoms(boxsize=[34, 34, 34], nparticles=1000, reduced_density=0.05, mass=39.9 * unit.amu, # argon sigma=3.4 * unit.angstrom, # argon, epsilon=0.238 * unit.kilocalories_per_mole, # argon, cutoff=None, switch_width=3.4 * unit.angstrom, # argon dispersion_correction=True, lattice=False, charge=None, **kwargs): # Determine Lennard-Jones cutoff. if cutoff is None: cutoff = 3.0 * sigma charge = 0.0 * unit.elementary_charge cutoff_type = openmm.NonbondedForce.CutoffPeriodic # Create an empty system object. system = openmm.System() # Periodic box vectors. a = unit.Quantity((boxsize[0] * unit.angstrom, 0 * unit.angstrom, 0 * unit.angstrom)) b = unit.Quantity((0 * unit.angstrom, boxsize[1] * unit.angstrom, 0 * unit.angstrom)) c = unit.Quantity((0 * unit.angstrom, 0 * unit.angstrom, boxsize[2] * unit.angstrom)) system.setDefaultPeriodicBoxVectors(a, b, c) # Set up periodic nonbonded interactions with a cutoff. nb = openmm.NonbondedForce() nb.setNonbondedMethod(cutoff_type) nb.setCutoffDistance(cutoff) nb.setUseDispersionCorrection(dispersion_correction) nb.setUseSwitchingFunction(False) if (switch_width is not None): nb.setUseSwitchingFunction(True) nb.setSwitchingDistance(cutoff - switch_width) for particle_index in range(nparticles): system.addParticle(mass) nb.addParticle(charge, sigma, epsilon) positions = subrandom_particle_positions(nparticles, system.getDefaultPeriodicBoxVectors(), 2) # Add the nonbonded force. system.addForce(nb) # Add a restrining potential to keep atoms in z=0 energy_expression = 'k * (z^2)' force = openmm.CustomExternalForce(energy_expression) force.addGlobalParameter('k', 100) for particle_index in range(nparticles): force.addParticle(particle_index, []) system.addForce(force) # Create topology. topology = app.Topology() element = app.Element.getBySymbol('Ar') chain = topology.addChain() for particle in range(system.getNumParticles()): residue = topology.addResidue('Ar', chain) topology.addAtom('Ar', element, residue) topology.setUnitCellDimensions(unit.Quantity(boxsize, unit.angstrom)) # Simulate it from simtk.openmm import LangevinIntegrator, VerletIntegrator from simtk.openmm.app import Simulation, PDBReporter, StateDataReporter, PDBFile from simtk.unit import kelvin, picoseconds, picosecond, angstrom from sys import stdout from mdtraj.reporters import DCDReporter #from dcdreporter import DCDReporter nsteps = 10000 freq = 1 #integrator = LangevinIntegrator(300 * kelvin, 1 / picosecond, 0.002 * picoseconds) integrator = VerletIntegrator(0.002 * picoseconds) simulation = Simulation(topology, system, integrator) simulation.context.setPositions(positions) simulation.minimizeEnergy() simulation.reporters.append(DCDReporter('output.dcd', 1)) simulation.reporters.append(StateDataReporter(stdout, 1000, potentialEnergy=True, totalEnergy=True, step=True, separator=' ')) simulation.step(nsteps) state = simulation.context.getState(getPositions=True) finalpos = state.getPositions(asNumpy=True).value_in_unit(angstrom) with open('topology.pdb', 'w') as f: PDBFile.writeFile(topology, positions, f) from htmd.molecule.molecule import Molecule mol = Molecule('topology.pdb') mol.read('output.dcd') return finalpos, mol, system, simulation
def _process_nonbonded_forces(openff_sys, openmm_sys): """Process the vdW and Electrostatics sections of an OpenFF System into a corresponding openmm.NonbondedForce""" # Store the pairings, not just the supported methods for each supported_cutoff_methods = [["cutoff", "pme"]] vdw_handler = openff_sys.handlers["vdW"] if vdw_handler.method not in [val[0] for val in supported_cutoff_methods]: raise UnsupportedCutoffMethodError() vdw_cutoff = vdw_handler.cutoff * unit.angstrom electrostatics_handler = openff_sys.handlers[ "Electrostatics"] # Split this out if electrostatics_handler.method.lower() not in [ v[1] for v in supported_cutoff_methods ]: raise UnsupportedCutoffMethodError() non_bonded_force = openmm.NonbondedForce() openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.topology_particles: non_bonded_force.addParticle(0.0, 1.0, 0.0) if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) else: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME) non_bonded_force.setUseDispersionCorrection(True) non_bonded_force.setCutoffDistance(vdw_cutoff) for vdw_atom, vdw_smirks in vdw_handler.slot_map.items(): atom_idx = eval(vdw_atom)[0] partial_charge = electrostatics_handler.charge_map[vdw_atom] partial_charge = (partial_charge / off_unit.elementary_charge).magnitude vdw_potential = vdw_handler.potentials[vdw_smirks] # these are floats, implicitly angstrom and kcal/mol sigma, epsilon = _lj_params_from_potential(vdw_potential) sigma = sigma * unit.angstrom / unit.nanometer epsilon = epsilon * unit.kilocalorie_per_mole / unit.kilojoule_per_mole non_bonded_force.setParticleParameters(atom_idx, partial_charge, sigma, epsilon) # from vdWHandler.postprocess_system bond_particle_indices = [] for topology_molecule in openff_sys.topology.topology_molecules: top_mol_particle_start_index = topology_molecule.atom_start_topology_index for topology_bond in topology_molecule.bonds: top_index_1 = topology_molecule._ref_to_top_index[ topology_bond.bond.atom1_index] top_index_2 = topology_molecule._ref_to_top_index[ topology_bond.bond.atom2_index] top_index_1 += top_mol_particle_start_index top_index_2 += top_mol_particle_start_index bond_particle_indices.append((top_index_1, top_index_2)) non_bonded_force.createExceptionsFromBonds( bond_particle_indices, electrostatics_handler.scale_14, vdw_handler.scale_14, ) non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME) non_bonded_force.setCutoffDistance(9.0 * unit.angstrom) non_bonded_force.setEwaldErrorTolerance(1.0e-4) # It's not clear why this needs to happen here, but it cannot be set above # and satisfy vdW/Electrostatics methods Cutoff and PME; see create_force # and postprocess_system methods in toolkit if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
def _process_nonbonded_forces(openff_sys, openmm_sys): """Process the vdW and Electrostatics sections of an OpenFF System into a corresponding openmm.NonbondedForce""" # Store the pairings, not just the supported methods for each supported_cutoff_methods = [["cutoff", "pme"]] if "vdW" in openff_sys.handlers: vdw_handler = openff_sys.handlers["vdW"] if vdw_handler.method not in [ val[0] for val in supported_cutoff_methods ]: raise UnsupportedCutoffMethodError() vdw_cutoff = vdw_handler.cutoff * unit.angstrom electrostatics_handler = openff_sys.handlers[ "Electrostatics"] # Split this out if electrostatics_handler.method.lower() not in [ v[1] for v in supported_cutoff_methods ]: raise UnsupportedCutoffMethodError() non_bonded_force = openmm.NonbondedForce() openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.topology_particles: non_bonded_force.addParticle(0.0, 1.0, 0.0) if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) else: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME) non_bonded_force.setUseDispersionCorrection(True) non_bonded_force.setCutoffDistance(vdw_cutoff) for top_key, pot_key in vdw_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] partial_charge = electrostatics_handler.charges[top_key] partial_charge = (partial_charge / off_unit.elementary_charge).magnitude vdw_potential = vdw_handler.potentials[pot_key] # these are floats, implicitly angstrom and kcal/mol sigma, epsilon = _lj_params_from_potential(vdw_potential) sigma = sigma * unit.angstrom / unit.nanometer epsilon = epsilon * unit.kilocalorie_per_mole / unit.kilojoule_per_mole non_bonded_force.setParticleParameters(atom_idx, partial_charge, sigma, epsilon) elif "Buckingham-6" in openff_sys.handlers: buck_handler = openff_sys.handlers["Buckingham-6"] non_bonded_force = openmm.CustomNonbondedForce( "A * exp(-B * r) - C * r ^ -6; A = sqrt(A1 * A2); B = 2 / (1 / B1 + 1 / B2); C = sqrt(C1 * C2)" ) non_bonded_force.addPerParticleParameter("A") non_bonded_force.addPerParticleParameter("B") non_bonded_force.addPerParticleParameter("C") openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.topology_particles: non_bonded_force.addParticle([0.0, 0.0, 0.0]) if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) else: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.CutoffPeriodic) non_bonded_force.setCutoffDistance(buck_handler.cutoff * unit.angstrom) for top_key, pot_key in buck_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] # TODO: Add electrostatics params = buck_handler.potentials[pot_key].parameters a = pint_to_simtk(params["A"]) b = pint_to_simtk(params["B"]) c = pint_to_simtk(params["C"]) non_bonded_force.setParticleParameters(atom_idx, [a, b, c]) return # TODO: Figure out all of this post-processing with CustomNonbondedForce # from vdWHandler.postprocess_system bond_particle_indices = [] for topology_molecule in openff_sys.topology.topology_molecules: top_mol_particle_start_index = topology_molecule.atom_start_topology_index for topology_bond in topology_molecule.bonds: top_index_1 = topology_molecule._ref_to_top_index[ topology_bond.bond.atom1_index] top_index_2 = topology_molecule._ref_to_top_index[ topology_bond.bond.atom2_index] top_index_1 += top_mol_particle_start_index top_index_2 += top_mol_particle_start_index bond_particle_indices.append((top_index_1, top_index_2)) non_bonded_force.createExceptionsFromBonds( bond_particle_indices, electrostatics_handler.scale_14, vdw_handler.scale_14, ) non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME) non_bonded_force.setCutoffDistance(9.0 * unit.angstrom) non_bonded_force.setEwaldErrorTolerance(1.0e-4) # It's not clear why this needs to happen here, but it cannot be set above # and satisfy vdW/Electrostatics methods Cutoff and PME; see create_force # and postprocess_system methods in toolkit if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
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 createSystem(self, nonbondedMethod=None, nonbondedCutoff=None, removeCMMotion=True, constraints=None): """Construct an OpenMM System representing the topology described by this prmtop infile. Parameters: - nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME. - nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions - constraints (object=None) Specifies which bonds and angles should be implemented with constraints. Allowed values are None, HBonds, AllBonds or HAngles. - rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument - 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 or PME. - 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: the newly created System """ # Create the System. syst = mm.System() nb = mm.NonbondedForce() nb.setNonbondedMethod(nonbondedMethod) nb.setCutoffDistance(nonbondedCutoff) syst.addForce(nb) boxSize = self.topology.getUnitCellDimensions() if boxSize is not None: syst.setDefaultPeriodicBoxVectors( (boxSize[0], 0, 0), (0, boxSize[1], 0), (0, 0, boxSize[2])) # Build a lookup table to let us process dihedrals more quickly. dihedralTypeTable, wildcardDihedralTypes = self._buildDihLookupTable() # Loop over molecules and create the specified number of each type. allAtomTypes = [] allcharges = [] allExceptions = [] for moleculeName, moleculeCount in self.molecules: moleculeType = self.moleculeTypes[moleculeName] for i in range(moleculeCount): # Record the types of all atoms. baseAtomIndex = syst.getNumParticles() atomTypes = [atom[1] for atom in moleculeType.atoms] charges = [atom[6] for atom in moleculeType.atoms] for charge in charges: allcharges.append(charge) for atomType in atomTypes: allAtomTypes.append(atomType) 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. self._addAtomsToSystem(syst, moleculeType) # Add bonds. atomBonds = self._addBondsToSystem(syst, moleculeType, bondedTypes, constraints, baseAtomIndex) # Add constraints self._addConstraintsToSystem(syst, moleculeType, bondedTypes, constraints, baseAtomIndex) # Add angles. self._addAngleToSystem(syst, moleculeType, bondedTypes, atomBonds, baseAtomIndex) # Add torsions. self._addTorsionToSystem(syst, moleculeType, bondedTypes, dihedralTypeTable, wildcardDihedralTypes, baseAtomIndex) # Set nonbonded parameters for particles. exceptions = self._setnonbondedParams(nb, moleculeType, baseAtomIndex, atomTypes) for exception in exceptions: allExceptions.append(exception) # Add pairInteractions first as exceptions, followed by the rest # This way other exceptions can override pairInteractions for i in range(syst.getNumParticles() - 1): atomType1 = allAtomTypes[i] for j in range(i + 1, syst.getNumParticles()): atomType2 = allAtomTypes[j] try: sig, eps = self.nonbondParams[(atomType1, atomType2)] except KeyError: try: sig, eps = self.nonbondParams[(atomType2, atomType1)] except KeyError(): msg = "%s,%s pair interactions not found" % (atomType2, atomType1) raise KeyError(msg) chargeProd = float(allcharges[i]) * float(allcharges[j]) nb.addException(i, j, chargeProd, sig, eps, True) for exception in allExceptions: nb.addException(exception[0], exception[1], exception[2], float(exception[3]), float(exception[4]), True) # Add a CMMotionRemover. if removeCMMotion: syst.addForce(mm.CMMotionRemover()) return syst
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 create_system(self, parameters=None, nmolecules=512, verbose=False): """ Construct a flexible TIP3P system. RETURNS system (simtk.openmm.System) - TIP3P system with given parameters EXAMPLES Create with default parameters. >>> system = TIP3P.create_system() Create with specified parameters. >>> parameters = TIP3P.get_default_parameters() >>> system = TIP3P.create_system(parameters) """ initial_time = time.time() # Fixed parameters. massO = 16.0 * units.amu # oxygen mass massH = 1.0 * units.amu # hydrogen mass cutoff = None # override for nonbonded cutoff nonbonded_method = mm.NonbondedForce.PME # nonbonded method unit_sigma = 1.0 * units.angstrom # hydrogen sigma zero_epsilon = 0.0 * units.kilocalories_per_mole # hydrogen epsilon # Set parameters if not provided. if parameters is None: parameters = TIP3P.get_default_parameters() # Create system. system = mm.System() # Masses. for molecule_index in range(nmolecules): system.addParticle(massO) system.addParticle(massH) system.addParticle(massH) # Nonbonded interactions. nb = mm.NonbondedForce() nb.setNonbondedMethod(nonbonded_method) if cutoff is not None: nb.setCutoffDistance(cutoff) for molecule_index in range(nmolecules): # Nonbonded parameters. nb.addParticle(parameters['qO'], parameters['sigma'], parameters['epsilon']) nb.addParticle(parameters['qH'], unit_sigma, zero_epsilon) nb.addParticle(parameters['qH'], unit_sigma, zero_epsilon) # Nonbonded exceptions. nb.addException(molecule_index * 3, molecule_index * 3 + 1, 0.0, unit_sigma, zero_epsilon) nb.addException(molecule_index * 3, molecule_index * 3 + 2, 0.0, unit_sigma, zero_epsilon) nb.addException(molecule_index * 3 + 1, molecule_index * 3 + 2, 0.0, unit_sigma, zero_epsilon) system.addForce(nb) # Bonds. bonds = mm.HarmonicBondForce() for molecule_index in range(nmolecules): bonds.addBond(3 * molecule_index + 0, 3 * molecule_index + 1, parameters['rOH'], parameters['kOH']) bonds.addBond(3 * molecule_index + 0, 3 * molecule_index + 2, parameters['rOH'], parameters['kOH']) system.addForce(bonds) # Angles. angles = mm.HarmonicAngleForce() for molecule_index in range(nmolecules): angles.addAngle(3 * molecule_index + 1, 3 * molecule_index + 0, 3 * molecule_index + 2, parameters['aHOH'], parameters['kHOH']) system.addForce(angles) final_time = time.time() elapsed_time = final_time - initial_time if verbose: print "%.3f s elapsed" % elapsed_time return system
def lennard_jones_force( sim_object, cutoff=2.5, domains=False, epsilonRep=0.24, epsilonAttr=0.27, blindFraction=(-1), sigmaRep=None, sigmaAttr=None, ): """ Adds a lennard-jones force, that allows for mutual attraction. This is the slowest force out of all repulsive. .. note :: This is the only force that allows for so-called "exceptions'. Exceptions allow you to change parameters of the force for a specific pair of particles. This can be used to create short-range attraction between pairs of particles. See manual for Openmm.NonbondedForce.addException. Parameters ---------- cutoff : float, optional Radius cutoff value. Default is good. domains : bool, optional Use domains, defined by :py:func:'setDomains <Simulation.setDomains>' epsilonRep : float, optional Epsilon (attraction strength) for LJ-force for all particles (except for domain) in kT epsilonAttr : float, optional Epsilon for attractive domain (if domains are used) in kT blindFraction : float, 0<x<1 Fraction of particles that are "transparent" - used here instead of truncation sigmaRep, sigmaAttr: float, optional Radius of particles in the LJ force. For advanced fine-tuning. """ sim_object.metadata["LennardJonesForce"] = repr({ "cutoff": cutoff, "domains": domains, "epsilonRep": epsilonRep, "epsilonAttr": epsilonAttr, "blindFraction": blindFraction, }) if blindFraction > 0.99: sim_object._exitProgram( "why do you need this force without particles???" " set blindFraction between 0 and 1") if (sigmaRep is None) and (sigmaAttr is None): sigmaAttr = sigmaRep = sim_object.conlen else: sigmaAttr = sigmaAttr * sim_object.conlen sigmaRep = sigmaRep * sim_object.conlen epsilonRep = epsilonRep * sim_object.kT epsilonAttr = epsilonAttr * sim_object.kT nbCutOffDist = sim_object.conlen * cutoff sim_object.epsilonRep = epsilonRep repulforce = openmm.NonbondedForce() sim_object.force_dict["Nonbonded"] = repulforce for i in range(sim_object.N): particleParameters = [0.0, 0.0, 0.0] if np.random.random() > blindFraction: particleParameters[1] = sigmaRep particleParameters[2] = epsilonRep if domains == True: if sim_object.domains[i] != 0: particleParameters[1] = sigmaAttr particleParameters[2] = epsilonAttr repulforce.addParticle(*particleParameters) repulforce.setCutoffDistance(nbCutOffDist)
def _process_nonbonded_forces(openff_sys, openmm_sys, combine_nonbonded_forces=False): """Process the vdW and Electrostatics sections of an OpenFF Interchange into a corresponding openmm.NonbondedForce or a collection of other forces (NonbondedForce, CustomNonbondedForce, CustomBondForce)""" if "vdW" in openff_sys.handlers: vdw_handler = openff_sys.handlers["vdW"] vdw_cutoff = vdw_handler.cutoff.m_as(off_unit.angstrom) * unit.angstrom vdw_method = vdw_handler.method.lower() electrostatics_handler = openff_sys.handlers["Electrostatics"] electrostatics_method = electrostatics_handler.method.lower() if vdw_handler.mixing_rule != "lorentz-berthelot": if combine_nonbonded_forces: raise UnsupportedExportError( "OpenMM's default NonbondedForce only supports Lorentz-Berthelot mixing rules." "Try setting `combine_nonbonded_forces=False`.") else: raise NotImplementedError( f"Mixing rule `{vdw_handler.mixing_rule}` not compatible with current OpenMM export." "The only supported values is `lorentez-berthelot`.") if vdw_handler.mixing_rule == "lorentz-berthelot": if not combine_nonbonded_forces: mixing_rule_expression = ( "sigma=(sigma1+sigma2)/2; epsilon=sqrt(epsilon1*epsilon2); " ) if combine_nonbonded_forces: non_bonded_force = openmm.NonbondedForce() openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.mdtop.atoms: non_bonded_force.addParticle(0.0, 1.0, 0.0) if vdw_method == "cutoff" and electrostatics_method == "pme": if openff_sys.box is not None: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.PME) non_bonded_force.setUseDispersionCorrection(True) non_bonded_force.setCutoffDistance(vdw_cutoff) non_bonded_force.setEwaldErrorTolerance(1.0e-4) else: raise UnsupportedCutoffMethodError elif vdw_method == "pme" and electrostatics_method == "pme": if openff_sys.box is not None: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.LJPME) non_bonded_force.setEwaldErrorTolerance(1.0e-4) else: raise UnsupportedCutoffMethodError else: raise UnimplementedCutoffMethodError( f"Combination of non-bonded cutoff methods {vdw_cutoff} (vdW) and " f"{electrostatics_method} (Electrostatics) not currently supported with " f"`combine_nonbonded_forces={combine_nonbonded_forces}") else: vdw_expression = vdw_handler.expression vdw_expression = vdw_expression.replace("**", "^") vdw_force = openmm.CustomNonbondedForce(vdw_expression + "; " + mixing_rule_expression) openmm_sys.addForce(vdw_force) vdw_force.addPerParticleParameter("sigma") vdw_force.addPerParticleParameter("epsilon") # TODO: Add virtual particles for _ in openff_sys.topology.mdtop.atoms: vdw_force.addParticle([1.0, 0.0]) if vdw_method == "cutoff": if openff_sys.box is None: vdw_force.setNonbondedMethod( openmm.NonbondedForce.CutoffNonPeriodic) else: vdw_force.setNonbondedMethod( openmm.NonbondedForce.CutoffPeriodic) vdw_force.setUseLongRangeCorrection(True) vdw_force.setCutoffDistance(vdw_cutoff) if getattr(vdw_handler, "switch_width", None) is not None: if vdw_handler.switch_width == 0.0: vdw_force.setUseSwitchingFunction(False) else: switching_distance = (vdw_handler.cutoff - vdw_handler.switch_width) if switching_distance.m < 0: raise UnsupportedCutoffMethodError( "Found a 'switch_width' greater than the cutoff distance. It's not clear " "what this means and it's probably invalid. Found " f"switch_width{vdw_handler.switch_width} and cutoff {vdw_handler.cutoff}" ) switching_distance = ( switching_distance.m_as(off_unit.angstrom) * unit.angstrom) vdw_force.setUseSwitchingFunction(True) vdw_force.setSwitchingDistance(switching_distance) elif vdw_method == "pme": if openff_sys.box is None: raise UnsupportedCutoffMethodError( "vdW method pme/ljpme is not valid for non-periodic systems." ) else: # TODO: Fully flesh out this implementation - cutoffs, other settings vdw_force.setNonbondedMethod(openmm.NonbondedForce.PME) electrostatics_force = openmm.NonbondedForce() openmm_sys.addForce(electrostatics_force) for _ in openff_sys.topology.mdtop.atoms: electrostatics_force.addParticle(0.0, 1.0, 0.0) if electrostatics_method == "reaction-field": if openff_sys.box is None: # TODO: Should this state be prevented from happening? raise UnsupportedCutoffMethodError( f"Electrostatics method {electrostatics_method} is not valid for a non-periodic interchange." ) else: raise UnimplementedCutoffMethodError( f"Electrostatics method {electrostatics_method} is not yet implemented." ) elif electrostatics_method == "pme": electrostatics_force.setNonbondedMethod( openmm.NonbondedForce.PME) electrostatics_force.setEwaldErrorTolerance(1.0e-4) electrostatics_force.setUseDispersionCorrection(True) elif electrostatics_method == "cutoff": raise UnsupportedCutoffMethodError( "OpenMM does not clearly support cut-off electrostatics with no reaction-field attenuation." ) else: raise UnsupportedCutoffMethodError( f"Electrostatics method {electrostatics_method} not supported" ) partial_charges = electrostatics_handler.charges for top_key, pot_key in vdw_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] partial_charge = partial_charges[top_key] # partial_charge = partial_charge.m_as(off_unit.elementary_charge) vdw_potential = vdw_handler.potentials[pot_key] # these are floats, implicitly angstrom and kcal/mol sigma, epsilon = _lj_params_from_potential(vdw_potential) sigma = sigma.m_as(off_unit.nanometer) epsilon = epsilon.m_as(off_unit.kilojoule / off_unit.mol) if combine_nonbonded_forces: non_bonded_force.setParticleParameters( atom_idx, partial_charge.m_as(off_unit.e), sigma, epsilon, ) else: vdw_force.setParticleParameters(atom_idx, [sigma, epsilon]) electrostatics_force.setParticleParameters( atom_idx, partial_charge.m_as(off_unit.e), 0.0, 0.0) elif "Buckingham-6" in openff_sys.handlers: buck_handler = openff_sys.handlers["Buckingham-6"] non_bonded_force = openmm.CustomNonbondedForce( "A * exp(-B * r) - C * r ^ -6; A = sqrt(A1 * A2); B = 2 / (1 / B1 + 1 / B2); C = sqrt(C1 * C2)" ) non_bonded_force.addPerParticleParameter("A") non_bonded_force.addPerParticleParameter("B") non_bonded_force.addPerParticleParameter("C") openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.mdtop.atoms: non_bonded_force.addParticle([0.0, 0.0, 0.0]) if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) else: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.CutoffPeriodic) non_bonded_force.setCutoffDistance(buck_handler.cutoff * unit.angstrom) for top_key, pot_key in buck_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] # TODO: Add electrostatics params = buck_handler.potentials[pot_key].parameters a = pint_to_simtk(params["A"]) b = pint_to_simtk(params["B"]) c = pint_to_simtk(params["C"]) non_bonded_force.setParticleParameters(atom_idx, [a, b, c]) return if not combine_nonbonded_forces: # Attempting to match the value used internally by OpenMM; The source of this value is likely # https://github.com/openmm/openmm/issues/1149#issuecomment-250299854 # 1 / * (4pi * eps0) * elementary_charge ** 2 / nanometer ** 2 coul_const = 138.935456 # kJ/nm vdw_14_force = openmm.CustomBondForce( "4*epsilon*((sigma/r)^12-(sigma/r)^6)") vdw_14_force.addPerBondParameter("sigma") vdw_14_force.addPerBondParameter("epsilon") vdw_14_force.setUsesPeriodicBoundaryConditions(True) coul_14_force = openmm.CustomBondForce(f"{coul_const}*qq/r") coul_14_force.addPerBondParameter("qq") coul_14_force.setUsesPeriodicBoundaryConditions(True) openmm_sys.addForce(vdw_14_force) openmm_sys.addForce(coul_14_force) # Need to create 1-4 exceptions, just to have a baseline for splitting out/modifying # It might be simpler to iterate over 1-4 pairs directly bonds = [(b.atom1.index, b.atom2.index) for b in openff_sys.topology.mdtop.bonds] if combine_nonbonded_forces: non_bonded_force.createExceptionsFromBonds( bonds=bonds, coulomb14Scale=electrostatics_handler.scale_14, lj14Scale=vdw_handler.scale_14, ) else: electrostatics_force.createExceptionsFromBonds( bonds=bonds, coulomb14Scale=electrostatics_handler.scale_14, lj14Scale=vdw_handler.scale_14, ) for i in range(electrostatics_force.getNumExceptions()): (p1, p2, q, sig, eps) = electrostatics_force.getExceptionParameters(i) # If the interactions are both zero, assume this is a 1-2 or 1-3 interaction if q._value == 0 and eps._value == 0: pass else: # Assume this is a 1-4 interaction # Look up the vdW parameters for each particle sig1, eps1 = vdw_force.getParticleParameters(p1) sig2, eps2 = vdw_force.getParticleParameters(p2) q1, _, _ = electrostatics_force.getParticleParameters(p1) q2, _, _ = electrostatics_force.getParticleParameters(p2) # manually compute and set the 1-4 interactions sig_14 = (sig1 + sig2) * 0.5 eps_14 = (eps1 * eps2)**0.5 * vdw_handler.scale_14 qq = q1 * q2 * electrostatics_handler.scale_14 vdw_14_force.addBond(p1, p2, [sig_14, eps_14]) coul_14_force.addBond(p1, p2, [qq]) vdw_force.addExclusion(p1, p2) # electrostatics_force.addExclusion(p1, p2) electrostatics_force.setExceptionParameters( i, p1, p2, 0.0, 0.0, 0.0)
def __init__(self, atom_1='Ar', atom_2='Xe', mass_1=None, sigma_1=None, epsilon_1=None, mass_2=None, sigma_2=None, epsilon_2=None, cutoff_distance=None, switching_distance=None, box=None, coordinates=None): super().__init__() # Parameters if (mass_1 is not None) or (sigma_1 is not None) or (epsilon_1 is not None): if mass_1 is None: raise ValueError( 'A value for the input argument "mass_1" is needed.') if sigma_1 is None: raise ValueError( 'A value for the input argument "sigma_1" is needed.') if epsilon_1 is None: raise ValueError( 'A value for the input argument "epsilon_1" is needed.') elif atom_1 is not None: mass_1 = atoms_LJ[atom_1]['mass'] sigma_1 = atoms_LJ[atom_1]['sigma'] epsilon_1 = atoms_LJ[atom_1]['epsilon'] if (mass_2 is not None) or (sigma_2 is not None) or (epsilon_2 is not None): if mass_2 is None: raise ValueError( 'A value for the input argument "mass_2" is needed.') if sigma_2 is None: raise ValueError( 'A value for the input argument "sigma_2" is needed.') if epsilon_2 is None: raise ValueError( 'A value for the input argument "epsilon_2" is needed.') elif atom_2 is not None: mass_2 = atoms_LJ[atom_2]['mass'] sigma_2 = atoms_LJ[atom_2]['sigma'] epsilon_2 = atoms_LJ[atom_2]['epsilon'] self.parameters['mass_1'] = mass_1 self.parameters['sigma_1'] = sigma_1 self.parameters['epsilon_1'] = epsilon_1 self.parameters['mass_2'] = mass_2 self.parameters['sigma_2'] = sigma_2 self.parameters['epsilon_2'] = epsilon_2 if box is not None: reduced_sigma = self.get_reduced_sigma() if cutoff_distance is None: cutoff_distance = 4.0 * reduced_sigma if switching_distance is None: switching_distance = 3.0 * reduced_sigma self.parameters['box'] = box self.parameters['cutoff_distance'] = cutoff_distance self.parameters['switching_distance'] = switching_distance # OpenMM topology self.topology = app.Topology() try: dummy_element = app.element.get_by_symbol('DUM') except: dummy_element = app.Element(0, 'DUM', 'DUM', 0.0 * unit.amu) chain = self.topology.addChain('A') residue = self.topology.addResidue('DUM_0', chain) atom = self.topology.addAtom(name='DUM_0', element=dummy_element, residue=residue) residue = self.topology.addResidue('DUM_1', chain) atom = self.topology.addAtom(name='DUM_1', element=dummy_element, residue=residue) # OpenMM system self.system = mm.System() non_bonded_force = mm.NonbondedForce() if box is not None: non_bonded_force.setNonbondedMethod( mm.NonbondedForce.CutoffPeriodic) non_bonded_force.setUseSwitchingFunction(True) non_bonded_force.setCutoffDistance(cutoff_distance) non_bonded_force.setSwitchingDistance(switching_distance) else: non_bonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) self.system.addParticle(mass_1) charge_1 = 0.0 * unit.elementary_charge non_bonded_force.addParticle(charge_1, sigma_1, epsilon_1) self.system.addParticle(mass_2) charge_2 = 0.0 * unit.elementary_charge non_bonded_force.addParticle(charge_2, sigma_2, epsilon_2) _ = self.system.addForce(non_bonded_force) # Coordinates if coordinates is not None: self.set_coordinates(coordinates) # Box if box is not None: self.set_box(box) # Potential expresion d, eps_r, sigma_r = symbols('d eps_r sigma_r') self.potential_expression = 4.0 * eps_r * ((sigma_r / d)**12 - (sigma_r / d)**6) del (d, eps_r, sigma_r)
mass_1 = 39.948 * unit.amu sigma_1 = 3.404 * unit.angstroms epsilon_1 = 0.238 * unit.kilocalories_per_mole charge_1 = 0.0 * unit.elementary_charge ## Segundo átomo: Xenón mass_2 = 131.293 * unit.amu sigma_2 = 3.961 * unit.angstroms epsilon_2 = 0.459 * unit.kilocalories_per_mole charge_2 = 0.0 * unit.elementary_charge # Creación del sistema. system = mm.System() non_bonded_force = mm.NonbondedForce() non_bonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) # Átomo 1 system.addParticle(mass_1) non_bonded_force.addParticle(charge_1, sigma_1, epsilon_1) # Átomo 2 system.addParticle(mass_2) non_bonded_force.addParticle(charge_2, sigma_2, epsilon_2) # Caja periódica system.setDefaultPeriodicBoxVectors([2.0, 0.0, 0.0] * unit.nanometers, [0.0, 2.0, 0.0] * unit.nanometers, [0.0, 0.0, 2.0] * unit.nanometers)
def test_add_dummy_atoms(tmp_path, dummy_complex): import mdtraj from simtk import openmm from simtk import unit as simtk_unit # Create an empty system to add the dummy atoms to. system_path = os.path.join(tmp_path, "input.xml") system = openmm.System() system.addForce(openmm.NonbondedForce()) with open(system_path, "w") as file: file.write(openmm.XmlSerializer.serialize(system)) protocol = AddDummyAtoms("release_add_dummy_atoms") protocol.substance = dummy_complex protocol.input_coordinate_path = get_data_filename( os.path.join("test", "molecules", "methanol_methane.pdb") ) protocol.input_system = ParameterizedSystem( substance=dummy_complex, force_field=None, topology_path=get_data_filename( os.path.join("test", "molecules", "methanol_methane.pdb") ), system_path=system_path, ) protocol.offset = 6.0 * unit.angstrom protocol.execute(str(tmp_path)) # Validate that dummy atoms have been added to the configuration file # and the structure has been correctly shifted. trajectory = mdtraj.load_pdb(protocol.output_coordinate_path) assert trajectory.topology.n_atoms == 14 assert numpy.allclose(trajectory.xyz[0][11:12, :2], 2.5) assert numpy.isclose(trajectory.xyz[0][11, 2], 0.62) assert numpy.isclose(trajectory.xyz[0][12, 2], 0.32) assert numpy.isclose(trajectory.xyz[0][13, 0], 2.5) assert numpy.isclose(trajectory.xyz[0][13, 1], 2.72) assert numpy.isclose(trajectory.xyz[0][13, 2], 0.1) # Validate the atom / residue names. all_atoms = [*trajectory.topology.atoms] dummy_atoms = all_atoms[11:14] assert all(atom.name == "DUM" for atom in dummy_atoms) assert all(dummy_atoms[i].residue.name == f"DM{i + 1}" for i in range(3)) # Validate that the dummy atoms got added to the system with open(protocol.output_system.system_path) as file: system: openmm.System = openmm.XmlSerializer.deserialize(file.read()) assert system.getNumParticles() == 3 assert all( numpy.isclose(system.getParticleMass(i).value_in_unit(simtk_unit.dalton), 207.0) for i in range(3) ) assert system.getNumForces() == 1 assert system.getForce(0).getNumParticles() == 3
def _addNonbondedForceToSystem(self, sys, OPLS): """Create the nonbonded force """ cnb = None nb = mm.NonbondedForce() sys.addForce(nb) if OPLS: cnb = mm.CustomNonbondedForce( "4.0*epsilon12*((sigma12/r)^12 - (sigma12/r)^6); sigma12=sqrt(sigma1*sigma2); epsilon12=sqrt(epsilon1*epsilon2)" ) cnb.addPerParticleParameter("sigma") cnb.addPerParticleParameter("epsilon") sys.addForce(cnb) if OPLS: q = """SELECT sigma, epsilon FROM particle INNER JOIN nonbonded_param ON particle.nbtype=nonbonded_param.id ORDER BY particle.id""" for (fcounter, conn, tables, offset) in self._localVars(): for sigma, epsilon in conn.execute(q): cnb.addParticle( [sigma * angstrom, epsilon * kilocalorie_per_mole]) q = """SELECT charge, sigma, epsilon FROM particle INNER JOIN nonbonded_param ON particle.nbtype=nonbonded_param.id ORDER BY particle.id""" for (fcounter, conn, tables, offset) in self._localVars(): for charge, sigma, epsilon in conn.execute(q): if OPLS: epsilon = 0 nb.addParticle(charge, sigma * angstrom, epsilon * kilocalorie_per_mole) for (fcounter, conn, tables, offset) in self._localVars(): for p0, p1 in conn.execute('SELECT p0, p1 FROM exclusion'): p0 += offset p1 += offset nb.addException(p0, p1, 0.0, 1.0, 0.0) if OPLS: cnb.addExclusion(p0, p1) q = """SELECT p0, p1, aij, bij, qij FROM pair_12_6_es_term INNER JOIN pair_12_6_es_param ON pair_12_6_es_term.param=pair_12_6_es_param.id""" for (fcounter, conn, tables, offset) in self._localVars(): for p0, p1, a_ij, b_ij, q_ij in conn.execute(q): p0 += offset p1 += offset a_ij = (a_ij * kilocalorie_per_mole * (angstrom**12)).in_units_of(kilojoule_per_mole * (nanometer**12)) b_ij = (b_ij * kilocalorie_per_mole * (angstrom**6)).in_units_of(kilojoule_per_mole * (nanometer**6)) q_ij = q_ij * elementary_charge**2 if (b_ij._value == 0.0) or (a_ij._value == 0.0): new_epsilon = 0 new_sigma = 1 else: new_epsilon = b_ij**2 / (4 * a_ij) new_sigma = (a_ij / b_ij)**(1.0 / 6.0) nb.addException(p0, p1, q_ij, new_sigma, new_epsilon, True) n_total = conn.execute( """SELECT COUNT(*) FROM pair_12_6_es_term""").fetchone() n_in_exclusions = conn.execute("""SELECT COUNT(*) FROM exclusion INNER JOIN pair_12_6_es_term ON ( ( exclusion.p0==pair_12_6_es_term.p0 AND exclusion.p1==pair_12_6_es_term.p1) OR ( exclusion.p0==pair_12_6_es_term.p1 AND exclusion.p1==pair_12_6_es_term.p0) )""").fetchone() if not n_total == n_in_exclusions: raise NotImplementedError( 'All pair_12_6_es_terms must have a corresponding exclusion' ) return nb, cnb
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 distributeLipids(boxsize, resnames, sigmas, cutoff, mass=39.9 * unit.amu, # argon epsilon=0.238 * unit.kilocalories_per_mole, # argon, switch_width=3.4 * unit.angstrom, # argon ): nparticles = len(resnames) # Determine Lennard-Jones cutoff. cutoff = cutoff * unit.angstrom cutoff_type = openmm.NonbondedForce.CutoffPeriodic # Create an empty system object. system = openmm.System() # Periodic box vectors. a = unit.Quantity((boxsize[0] * unit.angstrom, 0 * unit.angstrom, 0 * unit.angstrom)) b = unit.Quantity((0 * unit.angstrom, boxsize[1] * unit.angstrom, 0 * unit.angstrom)) c = unit.Quantity((0 * unit.angstrom, 0 * unit.angstrom, boxsize[2] * unit.angstrom)) system.setDefaultPeriodicBoxVectors(a, b, c) # Set up periodic nonbonded interactions with a cutoff. nb = openmm.NonbondedForce() nb.setNonbondedMethod(cutoff_type) nb.setCutoffDistance(cutoff) nb.setUseDispersionCorrection(True) nb.setUseSwitchingFunction(False) if (switch_width is not None): nb.setUseSwitchingFunction(True) nb.setSwitchingDistance(cutoff - switch_width) for s in sigmas: system.addParticle(mass) nb.addParticle(0.0 * unit.elementary_charge, s * unit.angstrom, epsilon) positions = subrandom_particle_positions(nparticles, system.getDefaultPeriodicBoxVectors(), 2) # Add the nonbonded force. system.addForce(nb) # Add a restraining potential to keep atoms in z=0 energy_expression = 'k * (z^2)' force = openmm.CustomExternalForce(energy_expression) force.addGlobalParameter('k', 10) for particle_index in range(nparticles): force.addParticle(particle_index, []) system.addForce(force) # Create topology. topology = app.Topology() chain = topology.addChain() elems = ['Ar', 'Cl', 'Na'] _, idx = np.unique(resnames, return_inverse=True) for i in idx: element = app.Element.getBySymbol(elems[i]) residue = topology.addResidue(elems[i], chain) topology.addAtom(elems[i], element, residue) topology.setUnitCellDimensions(unit.Quantity(boxsize, unit.angstrom)) # Simulate it from simtk.openmm import LangevinIntegrator, VerletIntegrator from simtk.openmm.app import Simulation, PDBReporter, StateDataReporter, PDBFile from simtk.unit import kelvin, picoseconds, picosecond, angstrom from sys import stdout from mdtraj.reporters import DCDReporter nsteps = 10000 freq = 1 integrator = VerletIntegrator(0.002 * picoseconds) simulation = Simulation(topology, system, integrator) simulation.context.setPositions(positions) simulation.minimizeEnergy() # simulation.reporters.append(DCDReporter('output.dcd', 1)) # simulation.reporters.append(StateDataReporter(stdout, 1000, potentialEnergy=True, totalEnergy=True, step=True, separator=' ')) simulation.step(nsteps) state = simulation.context.getState(getPositions=True, enforcePeriodicBox=True) allfinalpos = state.getPositions(asNumpy=True).value_in_unit(angstrom) # with open('topology.pdb', 'w') as f: # PDBFile.writeFile(topology, positions, f) # from htmd.molecule.molecule import Molecule # mol = Molecule('topology.pdb') # mol.read('output.dcd') return allfinalpos
import scipy.integrate import simtk.openmm as mm import pandas as pd box = 4.0 cutoff = box / 2. q = 0.1 epsilon = 0.0 system = mm.System() system.setDefaultPeriodicBoxVectors((box, 0, 0), (0, box, 0), (0, 0, box)) system.addParticle(1.0) system.addParticle(1.0) f = mm.NonbondedForce() f.setCutoffDistance(cutoff) f.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) f.setUseDispersionCorrection(False) f.setUseSwitchingFunction(False) f.setSwitchingDistance(0.9 * cutoff) f.addParticle(q, 1.0, epsilon) f.addParticle(-q, 1.0, epsilon) system.addForce(f) n = 1000 dt = (box) / n integrator = mm.CustomIntegrator(dt) integrator.addPerDofVariable("xcur", 0.0)
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 createOMMSys(my_top, verbose=False): """ Creates an OpenMM system Parameters ---------- my_top : chemlib topology object, should also include general system parameters (i.e. temperatures, etc.) verbose : bool verbosity of output Returns ------- system : OpenMM system topOMM : OpenMM topology topMDtraj : MDtraj topology Notes ----- Todo: 1) topology.BoxL """ if UNITS != "DimensionlessUnits": raise ValueError( "Danger! OMM export currently only for dimensionless units, but {} detected" .format(Units)) "takes in Sim 'Sys' object and returns an OpenMM System and Topology" print("\n=== OMM export currently uses LJ (Dimensionless) Units ===") print("mass: {} dalton".format(mass.value_in_unit(unit.dalton))) print("epsilon: {}".format(epsilon)) print("tau: {}".format(tau)) system_options = parsevalidate.parseSys( my_top.system_specs) #my_top.system_specs["SystemOptions"] #try: # parsevalidate.parseSys(system_options) # parsevalidate.parseFF(my_top) # --- box size --- Lx, Ly, Lz = parsevalidate.parseBox(system_options) #=================================== # Create a System and its Box Size # #=================================== print("\n=== Creating OMM System ===") system = openmm.System() # Set the periodic box vectors: box_edge = [Lx, Ly, Lz] box_vectors = np.diag(box_edge) * sigma system.setDefaultPeriodicBoxVectors(*box_vectors) #================== # Create Topology # #================== #Topology consists of a set of Chains #Each Chain contains a set of Residues, #and each Residue contains a set of Atoms. #We take the topology from the `sim System` object. Currently we do 1 residue per chain, and residue is effectively the Molecule class in sim. print("--- Creating Topology ---") # --- first get atom types and create dummy elements for openMM --- sim_atom_types = [value for value in my_top.atom_types.values()] elements = {} atom_type_index = {} atom_name_map = [] sim_atom_type_map = {} for ia, atom_type in enumerate(sim_atom_types): newsymbol = 'Z{}'.format(ia) if newsymbol not in app.element.Element._elements_by_symbol: elements[atom_type.name] = app.element.Element( 200 + ia, atom_type.name, newsymbol, atom_type.mass * mass) else: elements[atom_type.name] = app.element.Element.getBySymbol( newsymbol) atom_type_index[atom_type.name] = ia atom_name_map.append(atom_type.name) sim_atom_type_map[atom_type.name] = atom_type # --- next get the molecules and bond_list --- residues = [] """#not used molecule_atom_list = {} molecule_bond_list = {} for mname, m in my_top.molecule_types.items(): molname = m.name residues.append(molname) #molecule_atom_list[molname] = [my_top.Atoms[ia] for ia in my_top.atoms_in_mol[im]] #stores names of atoms in molecule molecule_atom_list[molname] = [a.name for r in m for a in r] #stores names of atoms in molecule molecule_bond_list[molname] = [ [b[0],b[1]] for b in m.bonds ] #molecule_bond_list[molname] = [ [b[0], b[1]] for b in my_top.bonds_in_mol[im] ] #stores bond indices in molecule """ # --- aggregate stuff, and add atoms to omm system --- # Particles are added one at a time # Their indices in the System will correspond with their indices in the Force objects we will add later atom_list = [a.name for a in my_top.atoms] #stores atom names res_list = [r.name for r in my_top.residues] #stores residue names mol_list = [m.name for m in my_top.molecules] #stores molecule names for a in my_top.atoms: system.addParticle(a.mass * mass) print("Total number of particles in system: {}".format( system.getNumParticles())) # --- the actual work of creating the topology --- top = app.topology.Topology() mdtrajtop = app.topology.Topology( ) #so that later can make molecules whole constrained_bonds = False constraint_lengths = [] for im, mol in enumerate(my_top.molecules): chain = top.addChain() #Create new chain for each molecule # add the atoms atoms_in_this_mol = [] mdt_atoms_in_this_mol = [] #atomsInThisMol = [my_top.AtomTypes[ my_top.Atoms[ia] ] for ia in my_top.AtomsInThisMol[im]] #list of atom objects for ir, r in enumerate(mol): res = top.addResidue( mol.name, chain ) #don't worry about actually specifying residues within a molecule mdt_chain = mdtrajtop.addChain( ) #Create new chain for each molecule mdt_res = mdtrajtop.addResidue(mol.name, mdt_chain) for ia, a in enumerate(r): el = elements[a.name] #if ia > 0: # previous_atom = atom atom = top.addAtom(a.name, el, res) #reference to openMM atom instance mdt_atom = mdtrajtop.addAtom( a.name, mdtraj.element.Element.getByAtomicNumber( atom_type_index[a.name]), mdt_res ) #use a dummy element by matching atomic number == cgAtomTypeIndex atoms_in_this_mol.append(atom) mdt_atoms_in_this_mol.append(mdt_atom) # add the bonds for bond_site in mol.bonds: #mol.bonds stores the intramolecular site indices of the bond a1 = atoms_in_this_mol[bond_site[ 0]] #the atoms_in_this_mol has the current, newly added atoms of this residue a2 = atoms_in_this_mol[bond_site[1]] if verbose: print("Adding bond ({},{}), absolute index {},{}".format( bond_site[0], bond_site[1], a1.index, a2.index)) #top.addBond( atoms_in_this_mol[bond.SType1.AInd], atoms_in_this_mol[bond.SType2.AInd] ) new_bond = top.addBond(a1, a2) mdtrajtop.addBond( a1, a2 ) #don't worry about adding constraint to the mdtraj topology if bond_site.rigid: constrained_bonds = True if verbose: print("Adding rigid constraint for {},{}".format(a1, a2)) system.addConstraint( a1.index, a2.index, bond.length) #constraint uses 0-based indexing of atoms constraint_lengths.append(bond.length) else: constraint_lengths.append(0.0) print("Total number of constraints: {}".format(system.getNumConstraints())) #==================== ##CREATE FORCEFIELD## #==================== #Currently only allow for some restricted interactions #a) harmonic bonds #b) Gaussian repulsion #c) external force #d) LJ #TODO: #-) spline/tabulated [would then need to implement a library of function evaluations...] #-) special bonds treatment (i.e. all fixed bonds, bending and dihedrals) #============================ #create Bonded Interactions # #============================ #Currently only support harmonic bonds, pairwise bonds bond_ffs = parsevalidate.parseBond(my_top.system_specs) if bond_ffs: #found bonds print("\n---> Found bonded interactions") bonded_force = openmm.HarmonicBondForce() #Check for bonds #Add bonds for mol in my_top.molecules: for bond in mol.bonds: ai = mol.atoms[bond[0]] aj = mol.atoms[bond[1]] applicable_potentials = [] for potential in bond_ffs: if potential['bond_type'] == 'harmonic': #atype_name1, atype_name2, bond_length, K = potential[1:] atype_name1 = potential['atype1'] atype_name2 = potential['atype2'] bond_length = potential['length'] K = potential['K'] #print('{},{},{},{}'.format(ai.name,aj.name,atype_name1,atype_name2)) if (ai.name, aj.name) in [(atype_name1, atype_name2), (atype_name2, atype_name1)]: applicable_potentials.append(potential) temp_label = 'for sites {},{} in mol {}: absolute atom index {},{}'.format( bond[0], bond[1], mol.name, ai.ind, aj.ind) if K == np.inf: bond.length = bond_length if verbose: print( "adding rigid constraint for {}, overriding harmonic bond" .format(temp_label)) #if bond_site.rigid: #should be True constrained_bonds = True system.addConstraint( ai.ind, aj.ind, bond.length ) #constraint uses 0-based indexing of atoms constraint_lengths.append(bond.length) else: bonded_force.addBond( ai.ind, aj.ind, bond_length * sigma, 2.0 * K * epsilon / sigma / sigma) if verbose: print("adding harmonic bond {}(r-{})^2 for {}". format(K, bond_length, temp_label)) else: raise ValueError( 'Bond type {} has not been implemented in openMM export yet' .format(potential)) # end for potential in bond_ffs if not applicable_potentials and not bond.rigid: raise OMMError( "no bonded potential nor constraint found for bond {} in mol {}" .format(bond, mol.name)) if bond_ffs: system.addForce(bonded_force) print('') #===================================================== #create custom nonbonded force: Gaussian + ShortRange# #===================================================== #--- iterate over all atom pair types --- #for aname1 in atomNameMap: # for aname2 in atomNameMap: # AType1 = simAtomTypeMap[aname1] # AType2 = simAtomTypeMap[aname2] # #AType1 = [atype for atype in Sys.World.AtomTypes if atype.Name==aname1] # #AType2 = [atype for atype in Sys.World.AtomTypes if atype.Name==aname2] # print([AType1,AType2]) # #TODO: spline aggregation of potentials # ag, u0, dist0, rcut, indv_gaussians = parsevalidate.parseGaussian( my_top.system_specs) if indv_gaussians: print("---> Found individual gaussians interactions") print( "CAUTION, Inefficient if manually layering a lot of interactions!") for ii, entry in enumerate(indv_gaussians): typ1, typ2, _ag, _u0, cut = entry[1:] print('...adding {}'.format(entry)) B_name = 'B{}'.format(ii) kappa_name = 'kappa{}'.format(ii) energy_function = 'LJ + Gaussian - CutoffShift;' energy_function += 'Gaussian = {B}*exp(-{kappa}*r^2);'.format( B=B_name, kappa=kappa_name) energy_function += 'LJ = 0;' #energy_function += 'LJ = 4*eps(type1,type2)*(rinv12 - rinv6);' #energy_function += 'rinv12 = rinv6^2;' energy_function += 'CutoffShift = {B}*exp(-{kappa}*({cut})^2);'.format( B=B_name, kappa=kappa_name, cut=cut) fcnbg = openmm.CustomNonbondedForce(energy_function) fcnbg.setCutoffDistance(cut) nonbondedMethod = 2 fcnbg.setNonbondedMethod(nonbondedMethod) #2 is cutoff periodic fcnbg.addGlobalParameter(B_name, _u0 / (4 * np.pi * _ag * _ag)**1.5) fcnbg.addGlobalParameter(kappa_name, 1 / 4 / _ag / _ag) typ1_inds = [] typ2_inds = [] for atom in top.atoms(): fcnbg.addParticle() if atom.name == typ1: typ1_inds.append(atom.index) if atom.name == typ2: typ2_inds.append(atom.index) fcnbg.addInteractionGroup(set(typ1_inds), set(typ2_inds)) print('......added {} interaction groups'.format( fcnbg.getNumInteractionGroups())) system.addForce(fcnbg) if len(ag) != 0 and len(u0) != 0 and len(dist0) != 0 and rcut is not None: print("---> Found gaussians interactions matrix") nspec = len(my_top.atom_types) if ag.shape[0] != nspec: raise ValueError("Gaussian Base widths matrix incorrectly sized") if u0.shape[0] != nspec: raise ValueError( "Gaussian Base prefactor matrix incorrectly sized") print("... Detected cutoff: {}".format(rcut)) GlobalCut = rcut nonbondedMethod = 2 print("... u0 matrix:\n{}".format(u0)) print("... ag matrix:\n{}".format(ag)) epsmatrix = np.zeros(ag.shape) sigmatrix = np.zeros(ag.shape) Bmatrix = u0 / (4 * np.pi * ag * ag)**1.5 kappamatrix = 1 / 4 / ag / ag dist0matrix = dist0 energy_function = 'LJ + Gaussian - CutoffShift;' energy_function += 'LJ = 0;' #energy_function += 'LJ = 4*eps(type1,type2)*(rinv12 - rinv6);' energy_function += 'Gaussian = B(type1,type2)*exp(-kappa(type1,type2)*(r - dist0(type1,type2))^2);' #energy_function += 'rinv12 = rinv6^2;' #energy_function += 'rinv6 = (sig(type1,type2)/r)^6;' energy_function += 'CutoffShift = 4*eps(type1,type2)*( (sig(type1,type2)/{0})^12 - (sig(type1,type2)/{0})^6 ) + B(type1,type2)*exp(-kappa(type1,type2)*({0}-dist0(type1,type2))^2);'.format( GlobalCut) fcnb = openmm.CustomNonbondedForce(energy_function) fcnb.addPerParticleParameter('type') fcnb.setCutoffDistance(GlobalCut) fcnb.setNonbondedMethod(nonbondedMethod) #2 is cutoff periodic fcnb.addTabulatedFunction( 'eps', openmm.Discrete2DFunction(nspec, nspec, epsmatrix.ravel(order='F'))) fcnb.addTabulatedFunction( 'sig', openmm.Discrete2DFunction(nspec, nspec, sigmatrix.ravel(order='F'))) fcnb.addTabulatedFunction( 'B', openmm.Discrete2DFunction(nspec, nspec, Bmatrix.ravel(order='F'))) fcnb.addTabulatedFunction( 'kappa', openmm.Discrete2DFunction(nspec, nspec, kappamatrix.ravel(order='F'))) fcnb.addTabulatedFunction( 'dist0', openmm.Discrete2DFunction(nspec, nspec, dist0matrix.ravel(order='F'))) for atom in top.atoms(): fcnb.addParticle([atom_type_index[atom.name]]) system.addForce(fcnb) # === LJ interactions === sig, eps, rcut, shift, long_range = parsevalidate.parseLJ( my_top.system_specs) if len(sig) > 0: print("---> Found LJ interactions matrix") nspec = len(my_top.atom_types) if sig.shape[0] != nspec: raise ValueError("LJ Base sigma matrix incorrectly sized") if eps.shape[0] != nspec: raise ValueError("LJ Base epsilon matrix incorrectly sized") print("... Detected cutoff: {}".format(rcut)) GlobalCut = rcut nonbondedMethod = 2 print("... sigma matrix:\n{}".format(sig)) print("... epsilon matrix:\n{}".format(eps)) epsmatrix = eps sigmatrix = sig energy_function = 'LJ - {}*CutoffShift;'.format(int(shift)) energy_function += 'LJ = 4*LJeps(type1,type2)*(rinv12 - rinv6);' energy_function += 'rinv12 = rinv6^2;' energy_function += 'rinv6 = (LJsig(type1,type2)/r)^6;' energy_function += 'CutoffShift = 4*LJeps(type1,type2)*( (LJsig(type1,type2)/{0})^12 - (LJsig(type1,type2)/{0})^6 );'.format( GlobalCut) fcnbLJ = openmm.CustomNonbondedForce(energy_function) fcnbLJ.addPerParticleParameter('type') fcnbLJ.setCutoffDistance(GlobalCut) fcnbLJ.setNonbondedMethod(nonbondedMethod) #2 is cutoff periodic fcnbLJ.setUseLongRangeCorrection(long_range) fcnbLJ.addTabulatedFunction( 'LJeps', openmm.Discrete2DFunction(nspec, nspec, epsmatrix.ravel(order='F'))) fcnbLJ.addTabulatedFunction( 'LJsig', openmm.Discrete2DFunction(nspec, nspec, sigmatrix.ravel(order='F'))) for atom in top.atoms(): fcnbLJ.addParticle([atom_type_index[atom.name]]) system.addForce(fcnbLJ) #===================================== #--- create the external potential --- #===================================== uext_ffs = parsevalidate.parseUExt(my_top.system_specs) if uext_ffs: print("\n---> Found External Force") #TODO: possibly allow the period length to be changed direction = ['x', 'y', 'z'] f_exts = [] for potential in uext_ffs: print(potential) type_names_in_potential = potential[1] Uext = potential[2] n_period = potential[3] axis = potential[4] offset = potential[5] if Uext != 0.: external = { "planeLoc": offset, "ax": axis, "U": Uext * epsilon.value_in_unit(unit.kilojoule / unit.mole), "NPeriod": n_period } print("...Adding external potential: Uext={}, NPeriod={}, axis={}". format(external["U"], external["NPeriod"], external["ax"])) print("......with atom types: {}".format(type_names_in_potential)) energy_function = 'U*sin(2*{pi}*NPeriod*(r-{r0})/{L}); r={axis};'.format( pi=np.pi, L=box_edge[external["ax"]], r0=external["planeLoc"], axis=direction[external["ax"]]) print('...{}'.format(energy_function)) f_exts.append(openmm.CustomExternalForce(energy_function)) f_exts[-1].addGlobalParameter("U", external["U"]) f_exts[-1].addGlobalParameter("NPeriod", external["NPeriod"]) for ia, atom in enumerate(top.atoms()): if atom.name in type_names_in_potential: print('adding atom {} {} to external force'.format( ia, atom.name)) f_exts[-1].addParticle(ia, []) system.addForce(f_exts[-1]) for f in f_exts: print("External potential with amplitude U={}, NPeriod={}".format( f.getGlobalParameterDefaultValue(0), f.getGlobalParameterDefaultValue(1))) #================================ #--- setup the electrostatics --- #================================ lb, ewald_cut, ewald_tolerance, a_born = parsevalidate.parseElec( my_top.system_specs) has_electrostatics = False if lb is not None: has_electrostatics = True if has_electrostatics and lb > 0.: nbfmethod = openmm.NonbondedForce.PME print("To implement in OMM, unit charge is now {}".format(q_factor)) charge_scale = q_factor * lb**0.5 nbf = openmm.NonbondedForce() nbf.setCutoffDistance(ewald_cut) nbf.setEwaldErrorTolerance(ewald_tolerance) nbf.setNonbondedMethod(nbfmethod) nbf.setUseDispersionCorrection(False) nbf.setUseSwitchingFunction(False) for i, atom in enumerate(my_top.atoms): charge = atom.charge * charge_scale #In dimensionless, EwaldPotential.Coef is typically 1, and usually change relative strength via temperature. But can also scale the coef, which then acts as lB in the unit length LJsigma = 1.0 LJepsilon = 0.0 nbf.addParticle(charge, LJsigma, LJepsilon) system.addForce(nbf) if has_electrostatics and lb > 0. and a_born is not None: #Need explicit analytical portion to cancel out 1/r; tabulation can't capture such a steep potential print("Ewald coefficient is: {}".format(lb)) #print( "Matrix of smearing correction coefficients is:" ) #print(smPrefactorMatrix) nspec = my_top.num_atom_types if nspec != len(a_born): raise ValueError("a_born has a different length than num_species!") a_born_matrix = np.zeros((nspec, nspec)) for ii in range(nspec): for jj in range(nspec): a_born_matrix[ii, jj] = np.sqrt(0.5 * a_born[ii]**2. + 0.5 * a_born[ii]**2.) energy_function = 'coef*q1*q2 * ( (erf(factor*r) - 1)/r - shift );' energy_function += 'shift = (erf(factor*rcut) -1)/rcut;' energy_function += 'factor = sqrt({:f})/2/aborn(type1,type2);'.format( np.pi) energy_function += 'coef = {:f};'.format(lb) energy_function += 'rcut = {:f};'.format(ewald_cut) fcnb = openmm.CustomNonbondedForce(energy_function) fcnb.addPerParticleParameter('type') fcnb.addPerParticleParameter('q') fcnb.setCutoffDistance(ewald_cut) fcnb.setNonbondedMethod(openmm.NonbondedForce.CutoffPeriodic) fcnb.addTabulatedFunction( 'aborn', openmm.Discrete2DFunction(nspec, nspec, a_born_matrix.ravel(order='F'))) for ia, atom in enumerate(top.atoms()): q = my_top.atoms[ia].charge if q != 0.0: print("atom {} has charge {}".format(ia, q)) fcnb.addParticle([atom_type_index[atom.name], q]) system.addForce(fcnb) # === Finished! === return system, top, mdtrajtop