[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)