Source code for buildamol.extensions.molecular_dynamics.psf

[docs] class PSFMaker: """ A class to generate a PSF file from a molecule Parameters ---------- atom_typer : AtomTyper, optional The atom typer, by default None. If None, the atom type, mass, and charge must all be set as attributes of the atoms in the molecule. """ def __init__(self, atom_typer=None): self.atom_typer = atom_typer
[docs] def encode_psf( self, molecule, angles: bool = False, dihedrals: bool = False ) -> str: """ Encode a the content for a PSF file from a molecule Parameters ---------- molecule : Molecule The molecule angles : bool, optional Whether to include angle information, by default False dihedrals : bool, optional Whether to include dihedral information, by default False Returns ------- str The PSF content """ psf = [] self._psf_preamble(molecule, psf) psf.append("\n") self._encode_atoms(molecule, psf) psf.append("\n") self._encode_bonds(molecule, psf) psf.append("\n") if angles: self._encode_angles(molecule, psf) psf.append("\n") if dihedrals: self._encode_dihedrals(molecule, psf) psf.append("\n") return "".join(psf)
[docs] def write_psf( self, molecule, filename: str, angles: bool = False, dihedrals: bool = False ): """ Write a PSF file from a molecule Parameters ---------- molecule : Molecule The molecule filename : str The filename of the PSF file angles : bool, optional Whether to include angle information, by default False dihedrals : bool, optional Whether to include dihedral information, by default False """ with open(filename, "w") as f: f.write(self.encode_psf(molecule, angles=angles, dihedrals=dihedrals))
def _encode_dihedrals(self, molecule, psf): idx = len(psf) n = 0 for dihedral in molecule.get_atom_quartets(): psf.append( f" {dihedral[0].serial_number:<8}{dihedral[1].serial_number:<8}{dihedral[2].serial_number:<8}{dihedral[3].serial_number:<8}\n" ) n += 1 psf.insert(idx, f" {n} !NPHI\n") def _encode_angles(self, molecule, psf): idx = len(psf) n = 0 for angle in molecule.get_atom_triplets(): psf.append( f" {angle[0].serial_number:<8}{angle[1].serial_number:<8}{angle[2].serial_number:<8}\n" ) n += 1 psf.insert(idx, f" {n} !NTHETA\n") def _encode_bonds(self, molecule, psf): psf.append(f" {molecule.count_bonds()} !NBOND\n") for bond in molecule.get_bonds(): psf.append(f" {bond[0].serial_number:<8}{bond[1].serial_number:<8}\n") def _encode_atoms(self, molecule, psf): psf.append(f" {molecule.count_atoms()} !NATOM\n") atom = next(molecule.get_atoms()) if self.atom_typer is None: if not hasattr(atom, "type") or getattr(atom, "type") is None: raise ValueError("Atom type is not set and no atom typer is provided") type_getter = lambda atom: atom.type else: type_getter = lambda atom: self.atom_typer.get_data(atom)["type"] if self.atom_typer is None: if not hasattr(atom, "mass") or getattr(atom, "mass") is None: raise ValueError("Atom mass is not set and no atom typer is provided") mass_getter = lambda atom: atom.mass else: mass_getter = lambda atom: self.atom_typer.get_data(atom).get("mass", 0) if self.atom_typer is None: if not hasattr(atom, "pqr_charge") or getattr(atom, "pqr_charge") is None: raise ValueError("Atom charge is not set and no atom typer is provided") charge_getter = lambda atom: atom.pqr_charge else: charge_getter = lambda atom: self.atom_typer.get_data(atom).get("charge", 0) for i, atom in enumerate(molecule.get_atoms()): atom_type = type_getter(atom) mass = mass_getter(atom) charge = charge_getter(atom) psf.append( f" {i+1:<4} {atom.parent.parent.id:<3} {atom.parent.serial_number:<4} {atom.parent.resname:<4} {atom.id:<4} {atom_type:<10}{charge:>12.6f} {mass:>12.6f} {0 :>8}\n" ) @staticmethod def _psf_preamble(molecule, psf): p = f""" PSF 1 !NTITLE REMARKS [Automatically generated by BuildAMol] REMARKS molecule id: {molecule.id} """.lstrip() psf.append(p)
[docs] def write_psf(molecule, filename: str, angles=False, dihedrals=False, _atom_typer=None): """ Write a PSF file from a molecule Parameters ---------- molecule : Molecule The molecule filename : str The filename of the PSF file angles : bool, optional Whether to include angle information, by default False dihedrals : bool, optional Whether to include dihedral information, by default False _atom_typer : AtomTyper, optional The atom typer, by default None. If None, the atom type, mass, and charge must all be set as attributes of the atoms in the molecule. """ psf_maker = PSFMaker(_atom_typer) psf_maker.write_psf(molecule, filename, angles=angles, dihedrals=dihedrals)
if __name__ == "__main__": import buildamol as bam from buildamol.extensions.molecular_dynamics.atom_typing import CHARMMTyper bam.load_sugars() man = bam.get_compound("MAN") man.rename_chain("A", "AP1") typer = CHARMMTyper.from_file( "/Users/noahhk/GIT/glycosylator/support/toppar_charmm/carbohydrates.rtf" ) psf_maker = PSFMaker(typer) psf = psf_maker.encode_psf(man, dihedrals=True) print(psf)