Source code for neurosnap.io.pqr

"""PQR writer for Structures annotated with partial charge and radius."""

from __future__ import annotations

import io
import pathlib
from typing import Union

from neurosnap.structure.structure import Structure

__all__ = ["save_pqr"]


[docs] def save_pqr( structure: Structure, pqr: Union[str, pathlib.Path, io.IOBase], *, include_header: bool = False, keep_chain: bool = True, ): """Save an annotated Structure as a PQR file. Parameters: structure: Single-model structure annotated with ``partial_charge`` and ``radius``. pqr: Output filepath or open file handle. include_header: Whether to include the stored ``pdb2pqr_header`` metadata when present. keep_chain: Whether to include chain IDs in the written PQR records. Example: Save a structure returned by :func:`neurosnap.algos.pdb2pqr.assign_pqr`:: from neurosnap.io.pdb import parse_pdb from neurosnap.algos.pdb2pqr import assign_pqr from neurosnap.io.pqr import save_pqr structure = parse_pdb("tests/files/1BTL.pdb", return_type="ensemble").first() pqr_structure = assign_pqr(structure, forcefield="AMBER", assign_only=True) save_pqr(pqr_structure, "test_output.pqr", include_header=True) """ if not isinstance(structure, Structure): raise TypeError(f"save_pqr() expects a Structure, found {type(structure).__name__}.") if "partial_charge" not in structure.atom_annotations.dtype.names: raise ValueError('Structure is missing required "partial_charge" annotation for PQR output.') if "radius" not in structure.atom_annotations.dtype.names: raise ValueError('Structure is missing required "radius" annotation for PQR output.') lines = [] header = structure.metadata.get("pdb2pqr_header") if include_header and isinstance(header, str) and header: lines.append(header if header.endswith("\n") else f"{header}\n") current_chain = None for atom_index in range(len(structure)): chain_id = str(structure.atom_annotations["chain_id"][atom_index]) if current_chain is None: current_chain = chain_id elif chain_id != current_chain: current_chain = chain_id lines.append("TER\n") lines.append(_format_pqr_atom_record(structure, atom_index, keep_chain=keep_chain)) lines.append("TER\n") lines.append("END\n") text = "".join(lines) if isinstance(pqr, io.IOBase): pqr.write(text) return with open(pqr, "w", encoding="utf-8") as handle: handle.write(text)
def _format_pqr_atom_record(structure: Structure, atom_index: int, *, keep_chain: bool) -> str: record = "HETATM" if bool(structure.atom_annotations["hetero"][atom_index]) else "ATOM " atom_id = int(structure.atom_annotations["atom_id"][atom_index]) or (atom_index + 1) atom_name = str(structure.atom_annotations["atom_name"][atom_index]) res_name = str(structure.atom_annotations["res_name"][atom_index]) chain_id = str(structure.atom_annotations["chain_id"][atom_index]) if keep_chain else "" res_id = int(structure.atom_annotations["res_id"][atom_index]) ins_code = str(structure.atom_annotations["ins_code"][atom_index]) x = float(structure.atoms["x"][atom_index]) y = float(structure.atoms["y"][atom_index]) z = float(structure.atoms["z"][atom_index]) partial_charge = float(structure.atom_annotations["partial_charge"][atom_index]) radius = float(structure.atom_annotations["radius"][atom_index]) line = f"{record}{atom_id:5d} " if len(atom_name) == 4 or len(atom_name.strip('FLIP')) == 4: line += f"{atom_name:<4}"[:4] else: line += f" {atom_name:<3}"[:4] if len(res_name) == 4: line += f"{res_name:<4}"[:4] else: line += f" {res_name:<3}"[:4] line += " " line += f"{chain_id:<1}"[:1] line += f"{res_id:4d}"[:4] line += f"{ins_code} " if ins_code else " " line += f"{x:8.3f}"[:8] line += f"{y:8.3f}"[:8] line += f"{z:8.3f}"[:8] line += f"{partial_charge:8.4f}"[-8:] line += f"{radius:7.4f}"[-7:] return f"{line}\n"