def _write_atomtypes_buck(openff_sys: "System", top_file: IO, typemap: Dict): top_file.write("[ atomtypes ]\n") top_file.write( ";type, bondingtype, atomic_number, mass, charge, ptype, sigma, epsilon\n" ) for atom_idx, atom_type in typemap.items(): atom = openff_sys.topology.atom(atom_idx) # type: ignore element = ele.element_from_atomic_number(atom.atomic_number) parameters = _get_buck_parameters(openff_sys, atom_idx) a = parameters["A"].to(unit.Unit("kilojoule / mol")).magnitude b = parameters["B"].to(1 / unit.nanometer).magnitude c = parameters["C"].to(unit.Unit("kilojoule / mol * nanometer ** 6")).magnitude top_file.write( "{:<11s} {:6d} {:.16g} {:.16g} {:5s} {:.16g} {:.16g} {:.16g}".format( atom_type, # atom type # "XX", # atom "bonding type", i.e. bond class atom.atomic_number, element.mass, 0.0, # charge, overriden later in [ atoms ] "A", # ptype a, b, c, ) ) top_file.write("\n")
def _process_proper_torsion_forces(openff_sys, openmm_sys): """Process the Propers section of an OpenFF System into corresponding forces within an openmm.PeriodicTorsionForce""" torsion_force = openmm.PeriodicTorsionForce() openmm_sys.addForce(torsion_force) proper_torsion_handler = openff_sys.handlers["ProperTorsions"] for top_key, pot_key in proper_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = proper_torsion_handler.potentials[pot_key].parameters k = params["k"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol periodicity = int(params["periodicity"]) phase = params["phase"].to(off_unit.degree).magnitude phase = phase * unit.degree / unit.radian idivf = int(params["idivf"]) torsion_force.addTorsion( indices[0], indices[1], indices[2], indices[3], periodicity, phase, k / idivf, )
def _process_bond_forces(openff_sys, openmm_sys): """Process the Bonds section of an OpenFF System into a corresponding openmm.HarmonicBondForce""" harmonic_bond_force = openmm.HarmonicBondForce() openmm_sys.addForce(harmonic_bond_force) try: bond_handler = openff_sys.handlers["Bonds"] except KeyError: return try: constraint_handler = openff_sys.handlers["Constraints"] has_constraint_handler = True except KeyError: has_constraint_handler = False for top_key, pot_key in bond_handler.slot_map.items(): if has_constraint_handler: # If this bond show up in the constraints ... if top_key in constraint_handler.slot_map: # ... don't add it as an interacting bond continue indices = top_key.atom_indices params = bond_handler.potentials[pot_key].parameters k = params["k"].to(off_unit.Unit( str(kcal_ang))).magnitude * kcal_ang / kj_nm length = params["length"].to(off_unit.nanometer).magnitude harmonic_bond_force.addBond( particle1=indices[0], particle2=indices[1], length=length, k=k, )
def _from_omm_quantity(val): """Helper function to convert float quantities tagged with SimTK/OpenMM units to a Pint-compatible quantity""" assert type(val.value_in_unit(val.unit)) in {float, int} unit_ = val.unit quantity_ = val.value_in_unit(unit_) return quantity_ * unit.Unit(str(unit_))
def _write_improper_coeffs(lmp_file: IO, openff_sys: System): lmp_file.write("\nImproper Coeffs\n\n") improper_handler = openff_sys.handlers["ImproperTorsions"] improper_type_map = dict(enumerate(improper_handler.potentials)) for improper_type_idx, smirks in improper_type_map.items(): params = improper_handler.potentials[smirks].parameters k = params["k"].to(unit.Unit("kilocalorie / mole")).magnitude n = int(params["periodicity"]) phase = params["phase"].to(unit.degree).magnitude idivf = int(params["idivf"]) k = k / idivf if (phase != 180) or (n != 2): raise UnsupportedExportError( "Improper exports to LAMMPS are funky and not well-supported " "at the moment, see PR #126") # See https://lammps.sandia.gov/doc/improper_fourier.html # cos(n * x - pi) == - cos(n * x) # k * (1 + cos(n * phi - pi / 2)) == k * (1 - cos(n * phi)) d = -1 lmp_file.write( f"{improper_type_idx+1:d} cvff {k:.16g}\t{d:d}\t{n:.16g}\n") lmp_file.write("\n")
def _process_angle_forces(openff_sys, openmm_sys): """Process the Angles section of an OpenFF System into a corresponding openmm.HarmonicAngleForce""" harmonic_angle_force = openmm.HarmonicAngleForce() openmm_sys.addForce(harmonic_angle_force) try: angle_handler = openff_sys.handlers["Angles"] except KeyError: return for top_key, pot_key in angle_handler.slot_map.items(): indices = top_key.atom_indices params = angle_handler.potentials[pot_key].parameters k = params["k"].to(off_unit.Unit(str(kcal_rad))).magnitude k = k * kcal_rad / kj_rad angle = params["angle"].to(off_unit.degree).magnitude angle = angle * unit.degree / unit.radian harmonic_angle_force.addAngle( particle1=indices[0], particle2=indices[1], particle3=indices[2], angle=angle, k=k, )
def test_argon_buck(): mol = Molecule.from_smiles("[#18]") top = Topology.from_molecules([mol, mol]) A = 1.69e-8 * unit.Unit("erg / mol") * Avogadro B = 1 / (0.273 * unit.angstrom) C = 102e-12 * unit.Unit("erg / mol") * unit.angstrom**6 * Avogadro A = A.to(unit.Unit("kilojoule/mol")) B = B.to(unit.Unit("1 / nanometer")) C = C.to(unit.Unit("kilojoule / mol * nanometer ** 6")) r = 0.3 * unit.nanometer buck = BuckinghamvdWHandler() coul = ElectrostaticsMetaHandler() # Just to pass compatibility checks pot_key = PotentialKey(id="[#18]") pot = Potential(parameters={"A": A, "B": B, "C": C}) for top_atom in top.topology_atoms: top_key = TopologyKey(atom_indices=(top_atom.topology_atom_index, )) buck.slot_map.update({top_key: pot_key}) coul.charges.update({top_key: 0 * unit.elementary_charge}) buck.potentials[pot_key] = pot from openff.system.components.system import System out = System() out.handlers["Buckingham-6"] = buck out.handlers["Electrostatics"] = coul out.topology = top out.box = [10, 10, 10] * unit.nanometer out.positions = [[0, 0, 0], [0.3, 0, 0]] * unit.nanometer out.to_gro("out.gro", writer="internal") out.to_top("out.top", writer="internal") gmx_energies = get_gromacs_energies(out, mdp="cutoff_buck") omm_energies = get_openmm_energies(out) by_hand = A * exp(-B * r) - C * r**-6 gmx_energies.compare(omm_energies) resid = simtk_to_pint(gmx_energies.energies["Nonbonded"]) - by_hand assert resid < 1e-5 * unit.kilojoule / unit.mol
def _process_rb_torsion_forces(openff_sys, openmm_sys): """Process Ryckaert-Bellemans torsions""" rb_force = openmm.RBTorsionForce() openmm_sys.addForce(rb_force) rb_torsion_handler = openff_sys.handlers["RBTorsions"] for top_key, pot_key in rb_torsion_handler.slot_map.items(): indices = top_key.atom_indices params = rb_torsion_handler.potentials[pot_key].parameters c0 = params["c0"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c1 = params["c1"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c2 = params["c2"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c3 = params["c3"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c4 = params["c4"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol c5 = params["c5"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol rb_force.addTorsion( indices[0], indices[1], indices[2], indices[3], c0, c1, c2, c3, c4, c5, )
def _write_pair_coeffs(lmp_file: IO, openff_sys: System, atom_type_map: Dict): lmp_file.write("Pair Coeffs\n\n") vdw_handler = openff_sys["vdW"] for atom_type_idx, smirks in atom_type_map.items(): params = vdw_handler.potentials[smirks].parameters sigma = params["sigma"].to(unit.angstrom).magnitude epsilon = params["epsilon"].to( unit.Unit("kilocalorie / mole")).magnitude lmp_file.write("{:d}\t{:.8g}\t{:.8g}\n".format(atom_type_idx + 1, epsilon, sigma)) lmp_file.write("\n")
def simtk_to_pint(simtk_quantity): """ Convert a SimTK Quantity (OpenMM Quantity) to a pint Quantity. Note: This function is adapted from evaluator.utils.openmm.openmm_quantity_to_pint, as part of the OpenFF Evaluator, Copyright (c) 2019 Open Force Field Consortium. """ if isinstance(simtk_quantity, List): simtk_quantity = omm_unit.Quantity(simtk_quantity) openmm_unit = simtk_quantity.unit openmm_value = simtk_quantity.value_in_unit(openmm_unit) target_unit = unit_to_string(openmm_unit) target_unit = unit.Unit(target_unit) return openmm_value * target_unit
def _write_bond_coeffs(lmp_file: IO, openff_sys: System): lmp_file.write("Bond Coeffs\n\n") bond_handler = openff_sys.handlers["Bonds"] bond_type_map = dict(enumerate(bond_handler.potentials)) for bond_type_idx, smirks in bond_type_map.items(): params = bond_handler.potentials[smirks].parameters k = params["k"].to( unit.Unit("kilocalorie / mole / angstrom ** 2")).magnitude k = k * 0.5 # Account for LAMMPS wrapping 1/2 into k length = params["length"].to(unit.angstrom).magnitude lmp_file.write( f"{bond_type_idx+1:d} harmonic\t{k:.16g}\t{length:.16g}\n") lmp_file.write("\n")
def _write_angle_coeffs(lmp_file: IO, openff_sys: System): lmp_file.write("\nAngle Coeffs\n\n") angle_handler = openff_sys.handlers["Angles"] angle_type_map = dict(enumerate(angle_handler.potentials)) for angle_type_idx, smirks in angle_type_map.items(): params = angle_handler.potentials[smirks].parameters k = params["k"].to( unit.Unit("kilocalorie / mole / radian ** 2")).magnitude k = k * 0.5 # Account for LAMMPS wrapping 1/2 into k theta = params["angle"].to(unit.degree).magnitude lmp_file.write( f"{angle_type_idx+1:d} harmonic\t{k:.16g}\t{theta:.16g}\n") lmp_file.write("\n")
def _process_bond_forces(openff_sys, openmm_sys): """Process the Bonds section of an OpenFF System into a corresponding openmm.HarmonicBondForce""" harmonic_bond_force = openmm.HarmonicBondForce() openmm_sys.addForce(harmonic_bond_force) bond_handler = openff_sys.handlers["Bonds"] for bond, key in bond_handler.slot_map.items(): indices = eval(bond) params = bond_handler.potentials[key].parameters k = params["k"].to(off_unit.Unit( str(kcal_ang))).magnitude * kcal_ang / kj_nm length = params["length"].to(off_unit.nanometer).magnitude harmonic_bond_force.addBond( particle1=indices[0], particle2=indices[1], length=length, k=k, )
def _write_proper_coeffs(lmp_file: IO, openff_sys: System): lmp_file.write("\nDihedral Coeffs\n\n") proper_handler = openff_sys.handlers["ProperTorsions"] proper_type_map = dict(enumerate(proper_handler.potentials)) for proper_type_idx, smirks in proper_type_map.items(): params = proper_handler.potentials[smirks].parameters k = params["k"].to(unit.Unit("kilocalorie / mole")).magnitude n = int(params["periodicity"]) phase = params["phase"].to(unit.degree).magnitude idivf = int(params["idivf"]) k = k / idivf lmp_file.write( f"{proper_type_idx+1:d} fourier 1\t{k:.16g}\t{n:d}\t{phase:.16g}\n" ) lmp_file.write("\n")
def _write_bonds(top_file: IO, openff_sys: "System", ref_mol: FrozenMolecule): if "Bonds" not in openff_sys.handlers.keys(): return top_file.write("[ bonds ]\n") top_file.write("; ai\taj\tfunc\tr\tk\n") bond_handler = openff_sys.handlers["Bonds"] top_mol = openff_sys.topology._reference_molecule_to_topology_molecules[ref_mol][0] # type: ignore offset = top_mol.atom_start_topology_index for bond in top_mol.bonds: # These are "topology indices" indices = tuple(sorted(a.topology_atom_index for a in bond.atoms)) for top_key in bond_handler.slot_map: if top_key.atom_indices == indices: pot_key = bond_handler.slot_map[top_key] # "reference" indices ref_indices = [idx - offset for idx in indices] params = bond_handler.potentials[pot_key].parameters k = params["k"].to(unit.Unit("kilojoule / mole / nanometer ** 2")).magnitude length = params["length"].to(unit.nanometer).magnitude top_file.write( "{:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format( ref_indices[0] + 1, # atom i ref_indices[1] + 1, # atom j str(1), # bond type (functional form) length, k, ) ) del pot_key top_file.write("\n\n")
def _process_improper_torsion_forces(openff_sys, openmm_sys): """Process the Impropers section of an OpenFF System into corresponding forces within an openmm.PeriodicTorsionForce""" if "ImproperTorsions" not in openff_sys.handlers.keys(): raise Exception for force in openmm_sys.getForces(): if type(force) == openmm.PeriodicTorsionForce: torsion_force = force break else: # TODO: Support case of no propers but some impropers? raise Exception improper_torsion_handler = openff_sys.handlers["ImproperTorsions"] for torsion_key, key in improper_torsion_handler.slot_map.items(): torsion, idx = torsion_key.split("_") indices = eval(torsion) params = improper_torsion_handler.potentials[key].parameters k = params["k"].to(off_unit.Unit( str(kcal_mol))).magnitude * kcal_mol / kj_mol periodicity = int(params["periodicity"]) phase = params["phase"].to(off_unit.degree).magnitude phase = phase * unit.degree / unit.radian idivf = int(params["idivf"]) other_atoms = [indices[0], indices[2], indices[3]] for p in [(other_atoms[i], other_atoms[j], other_atoms[k]) for (i, j, k) in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]]: torsion_force.addTorsion( indices[1], p[0], p[1], p[2], periodicity, phase, k / idivf, )
def _write_angles(top_file: IO, openff_sys: "System", ref_mol: FrozenMolecule): if "Angles" not in openff_sys.handlers.keys(): return top_file.write("[ angles ]\n") top_file.write("; ai\taj\tak\tfunc\tr\tk\n") top_mol = openff_sys.topology._reference_molecule_to_topology_molecules[ref_mol][0] # type: ignore offset = top_mol.atom_start_topology_index angle_handler = openff_sys.handlers["Angles"] for angle in top_mol.angles: indices = tuple(a.topology_atom_index for a in angle) for top_key in angle_handler.slot_map: if top_key.atom_indices == indices: pot_key = angle_handler.slot_map[top_key] # "reference" indices ref_indices = [idx - offset for idx in indices] params = angle_handler.potentials[pot_key].parameters k = params["k"].to(unit.Unit("kilojoule / mole / radian ** 2")).magnitude theta = params["angle"].to(unit.degree).magnitude top_file.write( "{:7d} {:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format( ref_indices[0] + 1, # atom i ref_indices[1] + 1, # atom j ref_indices[2] + 1, # atom k str(1), # angle type (functional form) theta, k, ) ) top_file.write("\n\n")
def test_bond_potential_handler(self): top = Topology.from_molecules(Molecule.from_smiles("O=O")) bond_handler = BondHandler(version=0.3) bond_parameter = BondHandler.BondType( smirks="[*:1]~[*:2]", k=1.5 * omm_unit.kilocalorie_per_mole / omm_unit.angstrom**2, length=1.5 * omm_unit.angstrom, id="b1000", ) bond_handler.add_parameter(bond_parameter.to_dict()) from openff.system.stubs import ForceField forcefield = ForceField() forcefield.register_parameter_handler(bond_handler) bond_potentials = forcefield["Bonds"].create_potential(top) pot = bond_potentials.potentials[bond_potentials.slot_map["(0, 1)"]] kcal_mol_a2 = unit.Unit("kilocalorie / (angstrom ** 2 * mole)") assert pot.parameters["k"].to(kcal_mol_a2).magnitude == pytest.approx( 1.5)
def test_angle_potential_handler(self): top = Topology.from_molecules(Molecule.from_smiles("CCC")) angle_handler = AngleHandler(version=0.3) angle_parameter = AngleHandler.AngleType( smirks="[*:1]~[*:2]~[*:3]", k=2.5 * omm_unit.kilocalorie_per_mole / omm_unit.radian**2, angle=100 * omm_unit.degree, id="b1000", ) angle_handler.add_parameter(angle_parameter.to_dict()) from openff.system.stubs import ForceField forcefield = ForceField() forcefield.register_parameter_handler(angle_handler) angle_potentials = forcefield["Angles"].create_potential(top) top_key = TopologyKey(atom_indices=(0, 1, 2)) pot = angle_potentials.potentials[angle_potentials.slot_map[top_key]] kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)") assert pot.parameters["k"].to( kcal_mol_rad2).magnitude == pytest.approx(2.5)
from typing import TYPE_CHECKING, Dict import numpy as np import parmed as pmd from openff.system import unit from openff.system.components.potentials import Potential from openff.system.models import PotentialKey, TopologyKey if TYPE_CHECKING: from openff.system.components.system import System kcal_mol = unit.Unit("kilocalories / mol") kcal_mol_a2 = unit.Unit("kilocalories / mol / angstrom ** 2") kcal_mol_rad2 = unit.Unit("kilocalories / mol / rad ** 2") def to_parmed(off_system: "System") -> pmd.Structure: """Convert an OpenFF System to a ParmEd Structure""" structure = pmd.Structure() _convert_box(off_system.box, structure) if "Electrostatics" in off_system.handlers.keys(): has_electrostatics = True electrostatics_handler = off_system.handlers["Electrostatics"] else: has_electrostatics = False for topology_molecule in off_system.topology.topology_molecules: # type: ignore[union-attr] for atom in topology_molecule.atoms: atomic_number = atom.atomic_number
def _write_dihedrals(top_file: IO, openff_sys: "System", ref_mol: FrozenMolecule): if "ProperTorsions" not in openff_sys.handlers: if "RBTorsions" not in openff_sys.handlers: if "ImproperTorsions" not in openff_sys.handlers: return top_file.write("[ dihedrals ]\n") top_file.write("; i j k l func\n") top_mol = openff_sys.topology._reference_molecule_to_topology_molecules[ref_mol][0] # type: ignore offset = top_mol.atom_start_topology_index rb_torsion_handler = ( openff_sys.handlers["RBTorsions"] if "RBTorsions" in openff_sys.handlers else [] ) proper_torsion_handler = ( openff_sys.handlers["ProperTorsions"] if "ProperTorsions" in openff_sys.handlers else [] ) improper_torsion_handler = ( openff_sys.handlers["ImproperTorsions"] if "ImproperTorsions" in openff_sys.handlers else [] ) # TODO: Ensure number of torsions written matches what is expected for proper in top_mol.propers: if proper_torsion_handler: for top_key in proper_torsion_handler.slot_map: # type: ignore[attr-defined] indices = tuple(a.topology_atom_index for a in proper) if top_key.atom_indices == indices: pot_key = proper_torsion_handler.slot_map[top_key] # type: ignore[attr-defined] params = proper_torsion_handler.potentials[pot_key].parameters # type: ignore[attr-defined] # "reference" indices ref_indices = [idx - offset for idx in indices] k = params["k"].to(unit.Unit("kilojoule / mol")).magnitude periodicity = int(params["periodicity"]) phase = params["phase"].to(unit.degree).magnitude idivf = int(params["idivf"]) if "idivf" in params else 1 top_file.write( "{:7d} {:7d} {:7d} {:7d} {:6d} {:16g} {:16g} {:7d}\n".format( ref_indices[0] + 1, ref_indices[1] + 1, ref_indices[2] + 1, ref_indices[3] + 1, 1, phase, k / idivf, periodicity, ) ) # This should be `if` if a single quartet can be subject to both proper and RB torsions elif rb_torsion_handler: for top_key in rb_torsion_handler.slot_map: # type: ignore[attr-defined] indices = tuple(a.topology_atom_index for a in proper) if top_key.atom_indices == indices: pot_key = rb_torsion_handler.slot_map[top_key] # type: ignore[attr-defined] params = rb_torsion_handler.potentials[pot_key].parameters # type: ignore[attr-defined] # "reference" indices ref_indices = [idx - offset for idx in indices] c0 = params["c0"].to(unit.Unit("kilojoule / mol")).magnitude c1 = params["c1"].to(unit.Unit("kilojoule / mol")).magnitude c2 = params["c2"].to(unit.Unit("kilojoule / mol")).magnitude c3 = params["c3"].to(unit.Unit("kilojoule / mol")).magnitude c4 = params["c4"].to(unit.Unit("kilojoule / mol")).magnitude c5 = params["c5"].to(unit.Unit("kilojoule / mol")).magnitude top_file.write( "{:7d} {:7d} {:7d} {:7d} {:6d} " "{:16g} {:16g} {:16g} {:16g} {:16g} {:16g} \n".format( ref_indices[0] + 1, ref_indices[1] + 1, ref_indices[2] + 1, ref_indices[3] + 1, 3, c0, c1, c2, c3, c4, c5, ) ) # TODO: Ensure number of torsions written matches what is expected for improper in top_mol.impropers: if improper_torsion_handler: for top_key in improper_torsion_handler.slot_map: # type: ignore[attr-defined] indices = tuple(a.topology_atom_index for a in improper) if indices == top_key.atom_indices: key = improper_torsion_handler.slot_map[top_key] # type: ignore[attr-defined] params = improper_torsion_handler.potentials[key].parameters # type: ignore[attr-defined] k = params["k"].to(unit.Unit("kilojoule / mol")).magnitude periodicity = int(params["periodicity"]) phase = params["phase"].to(unit.degree).magnitude idivf = int(params["idivf"]) top_file.write( "{:7d} {:7d} {:7d} {:7d} {:6d} {:.16g} {:.16g} {:.16g}\n".format( indices[0] + 1, indices[1] + 1, indices[2] + 1, indices[3] + 1, 4, phase, k / idivf, periodicity, ) )
from openff.toolkit.topology.molecule import Molecule from simtk import unit as omm_unit from openff.system import unit from openff.system.components.misc import RBTorsionHandler from openff.system.components.potentials import Potential from openff.system.models import PotentialKey, TopologyKey from openff.system.stubs import ForceField from openff.system.tests.energy_tests.gromacs import ( get_gromacs_energies, get_mdp_file, run_gmx_energy, ) from openff.system.tests.energy_tests.openmm import get_openmm_energies kj_mol = unit.Unit("kilojoule / mol") def test_ethanol_opls(): mol = Molecule.from_smiles("CC") mol.generate_conformers(n_conformers=1) top = mol.to_topology() parsley = ForceField("openff-1.0.0.offxml") out = parsley.create_openff_system(top) out.box = [4, 4, 4] out.positions = mol.conformers[0] out.positions = np.round(out.positions, 2) rb_torsions = RBTorsionHandler() smirks = "[#1:1]-[#6X4:2]-[#6X4:3]-[#1:4]" pot_key = PotentialKey(id=smirks)