def _write_atomtypes_buck(openff_sys: "Interchange", 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) 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 _from_omm_quantity(val: simtk_unit.Quantity): """Helper function to convert float or array quantities tagged with SimTK/OpenMM units to a Pint-compatible quantity""" unit_ = val.unit val_ = val.value_in_unit(unit_) if type(val_) in {float, int}: unit_ = val.unit return val_ * unit.Unit(str(unit_)) elif type(val_) in {tuple, list, np.ndarray}: array = np.asarray(val_) return array * unit.Unit(str(unit_)) else: raise UnitValidationError( "Found a simtk.unit.Unit wrapped around something other than a float-like " f"or np.ndarray-like. Found a unit wrapped around type {type(val_)}." )
def test_bond_potential_handler(self): top = OFFBioTop.from_molecules(Molecule.from_smiles("O=O")) bond_handler = BondHandler(version=0.3) bond_parameter = BondHandler.BondType( smirks="[*:1]~[*:2]", k=1.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.angstrom**2, length=1.5 * simtk_unit.angstrom, id="b1000", ) bond_handler.add_parameter(bond_parameter.to_dict()) from openff.toolkit.typing.engines.smirnoff import ForceField forcefield = ForceField() forcefield.register_parameter_handler(bond_handler) bond_potentials = SMIRNOFFBondHandler._from_toolkit( parameter_handler=forcefield["Bonds"], topology=top, ) top_key = TopologyKey(atom_indices=(0, 1)) pot_key = bond_potentials.slot_map[top_key] assert pot_key.associated_handler == "Bonds" pot = bond_potentials.potentials[pot_key] 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 = OFFBioTop.from_molecules(Molecule.from_smiles("CCC")) angle_handler = AngleHandler(version=0.3) angle_parameter = AngleHandler.AngleType( smirks="[*:1]~[*:2]~[*:3]", k=2.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.radian**2, angle=100 * simtk_unit.degree, id="b1000", ) angle_handler.add_parameter(angle_parameter.to_dict()) forcefield = ForceField() forcefield.register_parameter_handler(angle_handler) angle_potentials = SMIRNOFFAngleHandler._from_toolkit( parameter_handler=forcefield["Angles"], topology=top, ) top_key = TopologyKey(atom_indices=(0, 1, 2)) pot_key = angle_potentials.slot_map[top_key] assert pot_key.associated_handler == "Angles" pot = angle_potentials.potentials[pot_key] kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)") assert pot.parameters["k"].to( kcal_mol_rad2).magnitude == pytest.approx(2.5)
def _write_angles(top_file: IO, openff_sys: "Interchange"): if "Angles" not in openff_sys.handlers.keys(): return _store_bond_partners(openff_sys.topology.mdtop) top_file.write("[ angles ]\n") top_file.write("; ai\taj\tak\tfunc\tr\tk\n") angle_handler = openff_sys.handlers["Angles"] for angle in _iterate_angles(openff_sys.topology.mdtop): indices = ( angle[0].index, angle[1].index, angle[2].index, ) for top_key in angle_handler.slot_map: if top_key.atom_indices == indices: pot_key = angle_handler.slot_map[top_key] params = angle_handler.potentials[pot_key].parameters k = params["k"].m_as(unit.Unit("kilojoule / mole / radian ** 2")) theta = params["angle"].to(unit.degree).magnitude top_file.write("{:7d} {:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format( indices[0] + 1, # atom i indices[1] + 1, # atom j indices[2] + 1, # atom k str(1), # angle type (functional form) theta, k, )) top_file.write("\n\n")
def _write_bonds(top_file: IO, openff_sys: "Interchange"): 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"] for bond in openff_sys.topology.mdtop.bonds: indices = tuple(sorted((bond.atom1.index, bond.atom2.index))) for top_key in bond_handler.slot_map: if top_key.atom_indices == indices: pot_key = bond_handler.slot_map[top_key] elif top_key.atom_indices == indices[::-1]: pot_key = bond_handler.slot_map[top_key] params = bond_handler.potentials[pot_key].parameters k = params["k"].m_as(unit.Unit("kilojoule / mole / nanometer ** 2")) length = params["length"].to(unit.nanometer).magnitude top_file.write("{:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format( indices[0] + 1, # atom i indices[1] + 1, # atom j str(1), # bond type (functional form) length, k, )) del pot_key top_file.write("\n\n")
def _write_atomtypes_lj(openff_sys: "Interchange", 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.mdtop.atom(atom_idx) mass = atom.element.mass atomic_number = atom.element.atomic_number parameters = _get_lj_parameters(openff_sys, atom_idx) sigma = parameters["sigma"].to(unit.nanometer).magnitude epsilon = parameters["epsilon"].to( unit.Unit("kilojoule / mole")).magnitude top_file.write( "{:<11s} {:6d} {:.16g} {:.16g} {:5s} {:.16g} {:.16g}".format( atom_type, # atom type # "XX", # atom "bonding type", i.e. bond class atomic_number, mass, 0.0, # charge, overriden later in [ atoms ] "A", # ptype sigma, epsilon, )) top_file.write("\n")
def _write_pair_coeffs(lmp_file: IO, openff_sys: Interchange, atom_type_map: Dict): """Write the Pair Coeffs section of a LAMMPS data file""" 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(f"{atom_type_idx + 1:d}\t{epsilon:.8g}\t{sigma:.8g}\n") lmp_file.write("\n")
def _write_improper_coeffs(lmp_file: IO, openff_sys: Interchange): """Write the Improper Coeffs section of a LAMMPS data file""" 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 # See https://lammps.sandia.gov/doc/improper_cvff.html # E_periodic = k * (1 + cos(n * theta - phase)) # E_cvff = k * (1 + d * cos(n * theta)) # k_periodic = k_cvff # if phase = 0, # d_cvff = 1 # n_periodic = n_cvff # if phase = 180, # cos(n * x - pi) == - cos(n * x) # d_cvff = -1 # n_periodic = n_cvff # k * (1 + cos(n * phi - pi / 2)) == k * (1 - cos(n * phi)) if phase == 0: k_cvff = k d_cvff = 1 n_cvff = n elif phase == 180: k_cvff = k d_cvff = -1 n_cvff = n else: raise UnsupportedExportError( "Improper exports to LAMMPS are funky and not well-supported, the only compatibility" "found between periodidic impropers is with improper_style cvff when phase = 0 or 180 degrees" ) lmp_file.write( f"{improper_type_idx+1:d} {k_cvff:.16g}\t{d_cvff:d}\t{n_cvff:.16g}\n" ) lmp_file.write("\n")
def _write_angle_coeffs(lmp_file: IO, openff_sys: Interchange): """Write the Angle Coeffs section of a LAMMPS data file""" 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 _write_bond_coeffs(lmp_file: IO, openff_sys: Interchange): """Write the Bond Coeffs section of a LAMMPS data file""" 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_proper_coeffs(lmp_file: IO, openff_sys: Interchange): """Write the Dihedral Coeffs section of a LAMMPS data file""" 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")
from openff.interchange.models import PotentialKey, TopologyKey from openff.interchange.tests import BaseTest from openff.interchange.tests.utils import HAS_GROMACS, needs_gmx from openff.interchange.utils import get_test_files_dir_path if has_package("foyer"): import foyer if HAS_GROMACS: from openff.interchange.drivers.gromacs import ( _get_mdp_file, _run_gmx_energy, get_gromacs_energies, ) kj_mol = unit.Unit("kilojoule / mol") @skip_if_missing("foyer") class TestFoyer(BaseTest): @pytest.fixture(scope="session") def oplsaa(self): return foyer.forcefields.load_OPLSAA() @pytest.fixture(scope="session") def oplsaa_interchange_ethanol(self, oplsaa): molecule = Molecule.from_file( get_test_files_dir_path("foyer_test_molecules") + "/ethanol.sdf" ) molecule.name = "ETH"
def _write_dihedrals(top_file: IO, openff_sys: "Interchange"): if "ProperTorsions" not in openff_sys.handlers: if "RBTorsions" not in openff_sys.handlers: if "ImproperTorsions" not in openff_sys.handlers: return _store_bond_partners(openff_sys.topology.mdtop) top_file.write("[ dihedrals ]\n") top_file.write("; i j k l func\n") rb_torsion_handler = openff_sys.handlers.get("RBTorsions", []) proper_torsion_handler = openff_sys.handlers.get("ProperTorsions", []) improper_torsion_handler = openff_sys.handlers.get("ImproperTorsions", []) # TODO: Ensure number of torsions written matches what is expected for proper in _iterate_propers(openff_sys.topology.mdtop): if proper_torsion_handler: for top_key in proper_torsion_handler.slot_map: indices = tuple(a.index for a in proper) if top_key.atom_indices == indices: pot_key = proper_torsion_handler.slot_map[top_key] params = proper_torsion_handler.potentials[ pot_key].parameters 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( indices[0] + 1, indices[1] + 1, indices[2] + 1, 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 if rb_torsion_handler: for top_key in rb_torsion_handler.slot_map: indices = tuple(a.index for a in proper) if top_key.atom_indices == indices: pot_key = rb_torsion_handler.slot_map[top_key] params = rb_torsion_handler.potentials[pot_key].parameters 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( indices[0] + 1, indices[1] + 1, indices[2] + 1, indices[3] + 1, 3, c0, c1, c2, c3, c4, c5, )) # TODO: Ensure number of torsions written matches what is expected for improper in _iterate_impropers(openff_sys.topology.mdtop): if improper_torsion_handler: for top_key in improper_torsion_handler.slot_map: indices = tuple(a.index for a in improper) if indices == top_key.atom_indices: key = improper_torsion_handler.slot_map[top_key] params = improper_torsion_handler.potentials[ key].parameters 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, ))
def _write_atoms( top_file: IO, openff_sys: "Interchange", typemap: Dict, ): """Write the [ atoms ] and [ pairs ] sections for a molecule""" top_file.write("[ atoms ]\n") top_file.write(";num, type, resnum, resname, atomname, cgnr, q, m\n") charges = openff_sys.handlers["Electrostatics"].charges for atom in openff_sys.topology.mdtop.atoms: atom_idx = atom.index mass = atom.element.mass atom_type = typemap[atom.index] res_idx = atom.residue.index res_name = str(atom.residue) top_key = TopologyKey(atom_indices=(atom_idx, )) charge = charges[top_key].m_as(unit.e) top_file.write("{:6d} {:18s} {:6d} {:8s} {:8s} {:6d} " "{:18.8f} {:18.8f}\n".format( atom_idx + 1, atom_type, res_idx + 1, res_name, atom_type, atom_idx + 1, charge, mass, )) top_file.write("[ pairs ]\n") top_file.write("; ai\taj\tfunct\n") _store_bond_partners(openff_sys.topology.mdtop) try: mixing_rule = openff_sys["vdW"].mixing_rule scale_lj = openff_sys["vdW"].scale_14 except LookupError: mixing_rule = openff_sys["Buckingham-6"].mixing_rule scale_lj = openff_sys["Buckingham-6"].scale_14 # Use a set to de-duplicate pairs: Set[Tuple] = {*_iterate_pairs(openff_sys.topology.mdtop)} for pair in pairs: indices = [a.index for a in pair] indices = sorted(indices) parameters1 = _get_lj_parameters(openff_sys, indices[0]) sigma1 = parameters1["sigma"].to(unit.nanometer).magnitude epsilon1 = parameters1["epsilon"].to( unit.Unit("kilojoule / mole")).magnitude parameters2 = _get_lj_parameters(openff_sys, indices[1]) sigma2 = parameters2["sigma"].to(unit.nanometer).magnitude epsilon2 = parameters2["epsilon"].to( unit.Unit("kilojoule / mole")).magnitude epsilon_mix = (epsilon1 * epsilon2)**0.5 if mixing_rule == "lorentz-berthelot": sigma_mix = (sigma1 + sigma2) * 0.5 elif mixing_rule == "geometric": sigma_mix = (sigma1 * sigma2)**0.5 top_file.write("{:7d} {:7d} {:6d} {:16g} {:16g}\n".format( indices[0] + 1, indices[1] + 1, 1, sigma_mix, epsilon_mix * scale_lj, ))
from openff.interchange.components.potentials import Potential from openff.interchange.exceptions import UnsupportedBoxError, UnsupportedExportError from openff.interchange.models import PotentialKey, TopologyKey if TYPE_CHECKING: import parmed as pmd from openff.interchange.components.interchange import Interchange from openff.interchange.components.smirnoff import ( SMIRNOFFImproperTorsionHandler, SMIRNOFFProperTorsionHandler, ) 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: "Interchange") -> "pmd.Structure": """Convert an OpenFF Interchange to a ParmEd Structure""" import parmed as pmd 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: