def convert(self, obj): """Write selection at current trajectory frame to :class:`~parmed.structure.Structure`. Parameters ----------- obj : AtomGroup or Universe or :class:`Timestep` """ try: import parmed as pmd except ImportError: raise ImportError('ParmEd is required for ParmEdConverter but ' 'is not installed. Try installing it with \n' 'pip install parmed') try: # make sure to use atoms (Issue 46) ag_or_ts = obj.atoms except AttributeError: if isinstance(obj, base.Timestep): raise ValueError("Writing Timesteps to ParmEd " "objects is not supported") else: raise_from(TypeError("No atoms found in obj argument"), None) # Check for topology information missing_topology = [] try: names = ag_or_ts.names except (AttributeError, NoDataError): names = itertools.cycle(('X', )) missing_topology.append('names') try: resnames = ag_or_ts.resnames except (AttributeError, NoDataError): resnames = itertools.cycle(('UNK', )) missing_topology.append('resnames') if missing_topology: warnings.warn( "Supplied AtomGroup was missing the following attributes: " "{miss}. These will be written with default values. " "Alternatively these can be supplied as keyword arguments." "".format(miss=', '.join(missing_topology))) try: positions = ag_or_ts.positions except: positions = [None] * ag_or_ts.n_atoms try: velocities = ag_or_ts.velocities except: velocities = [None] * ag_or_ts.n_atoms atom_kwargs = [] for atom, name, resname, xyz, vel in zip(ag_or_ts, names, resnames, positions, velocities): akwargs = {'name': name} chain_seg = {'segid': atom.segid} for attrname in ('mass', 'charge', 'type', 'altLoc', 'tempfactor', 'occupancy', 'gbscreen', 'solventradius', 'nbindex', 'rmin', 'epsilon', 'rmin14', 'epsilon14', 'id'): try: akwargs[MDA2PMD.get(attrname, attrname)] = getattr(atom, attrname) except AttributeError: pass try: el = atom.element.lower().capitalize() akwargs['atomic_number'] = SYMB2Z[el] except (KeyError, AttributeError): try: tp = atom.type.lower().capitalize() akwargs['atomic_number'] = SYMB2Z[tp] except (KeyError, AttributeError): pass try: chain_seg['chain'] = atom.chainID except AttributeError: pass try: chain_seg['inscode'] = atom.icode except AttributeError: pass atom_kwargs.append( (akwargs, resname, atom.resid, chain_seg, xyz, vel)) struct = pmd.Structure() for akwarg, resname, resid, kw, xyz, vel in atom_kwargs: atom = pmd.Atom(**akwarg) if xyz is not None: atom.xx, atom.xy, atom.xz = xyz if vel is not None: atom.vx, atom.vy, atom.vz = vel atom.atom_type = pmd.AtomType( akwarg['name'], None, akwarg['mass'], atomic_number=akwargs.get('atomic_number')) struct.add_atom(atom, resname, resid, **kw) try: struct.box = ag_or_ts.dimensions except AttributeError: struct.box = None if hasattr(ag_or_ts, 'universe'): atomgroup = { atom: index for index, atom in enumerate(list(ag_or_ts)) } get_atom_indices = functools.partial(get_indices_from_subset, atomgroup=atomgroup, universe=ag_or_ts.universe) else: get_atom_indices = lambda x: x # bonds try: params = ag_or_ts.bonds.atomgroup_intersection(ag_or_ts, strict=True) except AttributeError: pass else: for p in params: atoms = [ struct.atoms[i] for i in map(get_atom_indices, p.indices) ] try: for obj in p.type: bond = pmd.Bond(*atoms, type=obj.type, order=obj.order) struct.bonds.append(bond) if isinstance(obj.type, pmd.BondType): struct.bond_types.append(bond.type) bond.type.list = struct.bond_types except (TypeError, AttributeError): order = p.order if p.order is not None else 1 btype = getattr(p.type, 'type', None) bond = pmd.Bond(*atoms, type=btype, order=order) struct.bonds.append(bond) if isinstance(bond.type, pmd.BondType): struct.bond_types.append(bond.type) bond.type.list = struct.bond_types # dihedrals try: params = ag_or_ts.dihedrals.atomgroup_intersection(ag_or_ts, strict=True) except AttributeError: pass else: for p in params: atoms = [ struct.atoms[i] for i in map(get_atom_indices, p.indices) ] try: for obj in p.type: imp = getattr(obj, 'improper', False) ign = getattr(obj, 'ignore_end', False) dih = pmd.Dihedral(*atoms, type=obj.type, ignore_end=ign, improper=imp) struct.dihedrals.append(dih) if isinstance(dih.type, pmd.DihedralType): struct.dihedral_types.append(dih.type) dih.type.list = struct.dihedral_types except (TypeError, AttributeError): btype = getattr(p.type, 'type', None) imp = getattr(p.type, 'improper', False) ign = getattr(p.type, 'ignore_end', False) dih = pmd.Dihedral(*atoms, type=btype, improper=imp, ignore_end=ign) struct.dihedrals.append(dih) if isinstance(dih.type, pmd.DihedralType): struct.dihedral_types.append(dih.type) dih.type.list = struct.dihedral_types for param, pmdtype, trackedlist, typelist, clstype in ( ('ureybradleys', pmd.UreyBradley, struct.urey_bradleys, struct.urey_bradley_types, pmd.BondType), ('angles', pmd.Angle, struct.angles, struct.angle_types, pmd.AngleType), ('impropers', pmd.Improper, struct.impropers, struct.improper_types, pmd.ImproperType), ('cmaps', pmd.Cmap, struct.cmaps, struct.cmap_types, pmd.CmapType)): try: params = getattr(ag_or_ts, param) values = params.atomgroup_intersection(ag_or_ts, strict=True) except AttributeError: pass else: for v in values: atoms = [ struct.atoms[i] for i in map(get_atom_indices, v.indices) ] try: for parmed_obj in v.type: p = pmdtype(*atoms, type=parmed_obj.type) trackedlist.append(p) if isinstance(p.type, clstype): typelist.append(p.type) p.type.list = typelist except (TypeError, AttributeError): vtype = getattr(v.type, 'type', None) p = pmdtype(*atoms, type=vtype) trackedlist.append(p) if isinstance(p.type, clstype): typelist.append(p.type) p.type.list = typelist return struct
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 element = pmd.periodic_table.Element[atomic_number] mass = pmd.periodic_table.Mass[element] structure.add_atom( pmd.Atom( atomic_number=atomic_number, mass=mass, ), resname="FOO", resnum=0, ) if "Bonds" in off_system.handlers.keys(): bond_handler = off_system.handlers["Bonds"] bond_type_map: Dict = dict() for pot_key, pot in bond_handler.potentials.items(): k = pot.parameters["k"].to(kcal_mol_a2).magnitude / 2 length = pot.parameters["length"].to(unit.angstrom).magnitude bond_type = pmd.BondType(k=k, req=length) bond_type_map[pot_key] = bond_type structure.bond_types.append(bond_type) for top_key, pot_key in bond_handler.slot_map.items(): idx_1, idx_2 = top_key.atom_indices bond_type = bond_type_map[pot_key] bond = pmd.Bond( atom1=structure.atoms[idx_1], atom2=structure.atoms[idx_2], type=bond_type, ) structure.bonds.append(bond) structure.bond_types.claim() if "Angles" in off_system.handlers.keys(): angle_handler = off_system.handlers["Angles"] angle_type_map: Dict = dict() for pot_key, pot in angle_handler.potentials.items(): k = pot.parameters["k"].to(kcal_mol_rad2).magnitude / 2 theta = pot.parameters["angle"].to(unit.degree).magnitude # TODO: Look up if AngleType already exists in struct angle_type = pmd.AngleType(k=k, theteq=theta) angle_type_map[pot_key] = angle_type structure.angle_types.append(angle_type) for top_key, pot_key in angle_handler.slot_map.items(): idx_1, idx_2, idx_3 = top_key.atom_indices angle_type = angle_type_map[pot_key] structure.angles.append( pmd.Angle( atom1=structure.atoms[idx_1], atom2=structure.atoms[idx_2], atom3=structure.atoms[idx_3], type=angle_type, )) structure.angle_types.append(angle_type) structure.angle_types.claim() # ParmEd treats 1-4 scaling factors at the level of each DihedralType, # whereas SMIRNOFF captures them at the level of the non-bonded handler, # so they need to be stored here for processing dihedrals vdw_14 = off_system.handlers["vdW"].scale_14 # type: ignore[attr-defined] if has_electrostatics: coul_14 = off_system.handlers[ "Electrostatics"].scale_14 # type: ignore[attr-defined] else: coul_14 = 1.0 vdw_handler = off_system.handlers["vdW"] if "ProperTorsions" in off_system.handlers.keys(): proper_torsion_handler = off_system.handlers["ProperTorsions"] proper_type_map: Dict = dict() for pot_key, pot in proper_torsion_handler.potentials.items(): k = pot.parameters["k"].to(kcal_mol).magnitude periodicity = pot.parameters["periodicity"] phase = pot.parameters["phase"].magnitude proper_type = pmd.DihedralType( phi_k=k, per=periodicity, phase=phase, scnb=1 / vdw_14, scee=1 / coul_14, ) proper_type_map[pot_key] = proper_type structure.dihedral_types.append(proper_type) for top_key, pot_key in proper_torsion_handler.slot_map.items(): idx_1, idx_2, idx_3, idx_4 = top_key.atom_indices dihedral_type = proper_type_map[pot_key] structure.dihedrals.append( pmd.Dihedral( atom1=structure.atoms[idx_1], atom2=structure.atoms[idx_2], atom3=structure.atoms[idx_3], atom4=structure.atoms[idx_4], type=dihedral_type, )) structure.dihedral_types.append(dihedral_type) key1 = TopologyKey(atom_indices=(idx_1, )) key4 = TopologyKey(atom_indices=(idx_4, )) vdw1 = vdw_handler.potentials[vdw_handler.slot_map[key1]] vdw4 = vdw_handler.potentials[vdw_handler.slot_map[key4]] sig1, eps1 = _lj_params_from_potential(vdw1) sig4, eps4 = _lj_params_from_potential(vdw4) sig = (sig1 + sig4) * 0.5 eps = (eps1 * eps4)**0.5 nbtype = pmd.NonbondedExceptionType(rmin=sig * 2**(1 / 6), epsilon=eps * vdw_14, chgscale=coul_14) structure.adjusts.append( pmd.NonbondedException(structure.atoms[idx_1], structure.atoms[idx_4], type=nbtype)) structure.adjust_types.append(nbtype) structure.dihedral_types.claim() structure.adjust_types.claim() # if False: # "ImroperTorsions" in off_system.term_collection.terms: # improper_term = off_system.term_collection.terms["ImproperTorsions"] # for improper, smirks in improper_term.smirks_map.items(): # idx_1, idx_2, idx_3, idx_4 = improper # pot = improper_term.potentials[improper_term.smirks_map[improper]] # # TODO: Better way of storing periodic data in generally, probably need to improve Potential # n = re.search(r"\d", "".join(pot.parameters.keys())).group() # k = pot.parameters["k" + n].m # kcal/mol # periodicity = pot.parameters["periodicity" + n].m # dimless # phase = pot.parameters["phase" + n].m # degree # # dihedral_type = pmd.DihedralType(per=periodicity, phi_k=k, phase=phase) # structure.dihedrals.append( # pmd.Dihedral( # atom1=structure.atoms[idx_1], # atom2=structure.atoms[idx_2], # atom3=structure.atoms[idx_3], # atom4=structure.atoms[idx_4], # type=dihedral_type, # ) # ) vdw_handler = off_system.handlers["vdW"] for pmd_idx, pmd_atom in enumerate(structure.atoms): top_key = TopologyKey(atom_indices=(pmd_idx, )) smirks = vdw_handler.slot_map[top_key] potential = vdw_handler.potentials[smirks] element = pmd.periodic_table.Element[pmd_atom.element] sigma, epsilon = _lj_params_from_potential(potential) atom_type = pmd.AtomType( name=element + str(pmd_idx + 1), number=pmd_idx, atomic_number=pmd_atom.atomic_number, mass=pmd.periodic_table.Mass[element], ) atom_type.set_lj_params(eps=epsilon, rmin=sigma * 2**(1 / 6) / 2) pmd_atom.atom_type = atom_type pmd_atom.type = atom_type.name pmd_atom.name = pmd_atom.type for pmd_idx, pmd_atom in enumerate(structure.atoms): if has_electrostatics: top_key = TopologyKey(atom_indices=(pmd_idx, )) partial_charge = electrostatics_handler.charges[ top_key] # type: ignore[attr-defined] unitless_ = partial_charge.to(unit.elementary_charge).magnitude pmd_atom.charge = float(unitless_) pmd_atom.atom_type.charge = float(unitless_) else: pmd_atom.charge = 0 # Assign dummy residue names, GROMACS will not accept empty strings for res in structure.residues: res.name = "FOO" structure.positions = off_system.positions.to( unit.angstrom).magnitude # type: ignore[attr-defined] for idx, pos in enumerate(structure.positions): structure.atoms[idx].xx = pos._value[0] structure.atoms[idx].xy = pos._value[1] structure.atoms[idx].xz = pos._value[2] return structure
def patch(self, top_string, crd_string): INTOP = "in.top" INRST = "in.rst" OUTTOP = "out.top" OUTRST = "out.rst" with util.in_temp_dir(): with open(INTOP, "wt") as outfile: outfile.write(top_string) with open(INRST, "wt") as outfile: outfile.write(crd_string) base = pmd.load_file(INTOP) crd = pmd.load_file(INRST) base.coordinates = crd.coordinates # create a new structure to add our dummy atoms to parm = pmd.Structure() # add in atom type for our dummy particles atype = pmd.AtomType("SDUM", 0, mass=12.0, charge=0.0) atype.set_lj_params(0.0, 0.0) for i in range(self.n_tensors): a1 = pmd.Atom( name="S1", atomic_number=atype.atomic_number, type=str(atype), charge=atype.charge, mass=atype.mass, solvent_radius=1.0, screen=0.5, ) a1.atom_type = atype a2 = pmd.Atom( name="S2", atomic_number=atype.atomic_number, type=str(atype), charge=atype.charge, mass=atype.mass, solvent_radius=1.0, screen=0.5, ) a2.atom_type = atype parm.add_atom(a1, resname="SDM", resnum=i) parm.add_atom(a2, resname="SDM", resnum=i) # we add noise here because we'll get NaN if the particles ever # end up exactly on top of each other parm.positions = np.zeros((2 * self.n_tensors, 3)) # combine the old system with the new dummy atoms comb = base + parm last_index = comb.residues[-1].idx self.resids = list(range(last_index - self.n_tensors + 2, last_index + 2)) comb.write_parm(OUTTOP) comb.write_rst7(OUTRST) with open(OUTTOP, "rt") as infile: top_string = infile.read() with open(OUTRST, "rt") as infile: crd_string = infile.read() return top_string, crd_string