def _write_angles(lmp_file: IO, openff_sys: Interchange): """Write the Angles section of a LAMMPS data file""" from openff.interchange.components.mdtraj import ( _iterate_angles, _store_bond_partners, ) _store_bond_partners(openff_sys.topology.mdtop) lmp_file.write("\nAngles\n\n") angle_handler = openff_sys["Angles"] angle_type_map = dict(enumerate(angle_handler.potentials)) angle_type_map_inv = dict({v: k for k, v in angle_type_map.items()}) for angle_idx, angle in enumerate(_iterate_angles(openff_sys.topology.mdtop)): # These are "topology indices" indices = tuple(a.index for a in angle) top_key = TopologyKey(atom_indices=indices) pot_key = angle_handler.slot_map[top_key] angle_type = angle_type_map_inv[pot_key] lmp_file.write( "{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format( angle_idx + 1, angle_type + 1, indices[0] + 1, indices[1] + 1, indices[2] + 1, ) )
def _write_propers(lmp_file: IO, openff_sys: Interchange): """Write the Dihedrals section of a LAMMPS data file""" from openff.interchange.components.mdtraj import ( _iterate_propers, _store_bond_partners, ) _store_bond_partners(openff_sys.topology.mdtop) lmp_file.write("\nDihedrals\n\n") proper_handler = openff_sys["ProperTorsions"] proper_type_map = dict(enumerate(proper_handler.potentials)) proper_type_map_inv = dict({v: k for k, v in proper_type_map.items()}) for proper_idx, proper in enumerate(_iterate_propers(openff_sys.topology.mdtop)): # These are "topology indices" indices = tuple(a.index for a in proper) for top_key, pot_key in proper_handler.slot_map.items(): if indices == top_key.atom_indices: proper_type_idx = proper_type_map_inv[pot_key] lmp_file.write( "{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format( proper_idx + 1, proper_type_idx + 1, indices[0] + 1, indices[1] + 1, indices[2] + 1, indices[3] + 1, ) )
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 test_iterate_pairs_benzene(): """Check that bonds in rings are not double-counted with _iterate_pairs. This should be fixed by using Topology.nth_degree_neighbors directly""" benzene = Molecule.from_smiles("c1ccccc1") mdtop = md.Topology.from_openmm(benzene.to_topology().to_openmm()) _store_bond_partners(mdtop) assert len({*_iterate_pairs(mdtop)}) == 21
def test_iterate_pairs(): mol = Molecule.from_smiles("C1#CC#CC#C1") top = mol.to_topology() mdtop = md.Topology.from_openmm(top.to_openmm()) _store_bond_partners(mdtop) pairs = { tuple(sorted((atom1.index, atom2.index))) for atom1, atom2 in _iterate_pairs(mdtop) } assert len(pairs) == 3 assert len([*_iterate_propers(mdtop)]) > len(pairs)
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, ))