Source code for pestifer.objs.patch

# Author: Cameron F. Abrams, <cfa22@drexel.edu>
"""
A patch is a modification to a residue or residues defined in the CHARMM force field by ``PRES`` records.
It is used to apply specific modifications to residues in a molecular structure, such as adding or removing
functional groups or modifying the residue's properties.
"""
from __future__ import annotations

import logging
from typing import ClassVar, TYPE_CHECKING
from pydantic import Field
from ..core.baseobj import BaseObj, BaseObjList
from .resid import ResID

if TYPE_CHECKING:
    from ..molecule.residue import Residue, ResidueList

logger = logging.getLogger(__name__)

[docs] class Patch(BaseObj): """ A class for handing patch residues """ _required_fields = {'patchname', 'chainID', 'resid'} """ Required attributes for a Patch object. These attributes must be provided when creating a Patch object. - ``patchname``: The name of the patch as defined in the CHARMM force field. - ``chainID``: The chain identifier of the residue to be patched. - ``resid``: The residue ID of the residue to be patched. """ _optional_fields = {'residue', 'use_in_segment', 'use_after_regenerate'} """ Optional attributes for a Patch object. - ``residue``: The Residue object to be patched. Assigned by the PatchList.assign_residues method. - ``use_in_segment``: Specifies whether the patch is applied to the first or last residue in a segment, and therefore should be declared inside a psfgen ``segment``. - ``use_after_regenerate``: A boolean indicating whether the patch should be applied after the ``regenerate angles dihedrals`` psfgen command. """ patchname: str = Field(..., description="Name of the patch as defined in the CHARMM force field") chainID: str = Field(..., description="Chain identifier of the residue to be patched") resid: ResID = Field(..., description="Residue ID of the residue to be patched") residue: "Residue" = Field(None, description="Residue object to be patched") use_in_segment: str | None = Field(None, description="Specifies whether the patch is applied to the first or last residue in a segment, and therefore should be declared inside a psfgen `segment`") use_after_regenerate: bool | None = Field(False, description="If True, the patch is applied after the `regenerate angles dihedrals` psfgen command") _yaml_header: ClassVar[str] = 'patches' """ YAML header for Patch objects. This header is used to identify Patch objects in YAML files. """ _objcat: ClassVar[str] = 'seq' """ Category of the Patch object. This categorization is used to group Patch objects in the object manager. """ @staticmethod def _from_shortcode(raw: str) -> dict: parts = raw.split(':') if len(parts) < 3: raise ValueError(f'Invalid patch shortcode: {raw}') resid = ResID(parts[2]) if parts[0] in Patch._in_segment_Npatches: use_in_segment = 'first' elif parts[0] in Patch._in_segment_Cpatches: use_in_segment = 'last' else: use_in_segment = '' if parts[0] in Patch._after_regenerate_patches: use_after_regenerate = True else: use_after_regenerate = False return { 'patchname': parts[0], 'chainID': parts[1], 'resid': resid, 'use_in_segment': use_in_segment, 'use_after_regenerate': use_after_regenerate } @classmethod def _adapt(cls, *args, **kwargs) -> dict: """ Adapts the input to a dictionary format suitable for Patch instantiation. This method is used to convert various input types into a dictionary of parameters. """ if args and isinstance(args[0], str): return Patch._from_shortcode(args[0]) return super()._adapt(*args, **kwargs) _in_segment_Npatches: ClassVar[list[str]] = ['NTER', 'GLYP', 'PROP', 'ACE', 'ACED', 'ACP', 'ACPD', 'NNEU', 'NGNE'] """ List of patch names that are applied to the first residue in a segment. These patches are typically used for N-terminal modifications. """ _in_segment_Cpatches: ClassVar[list[str]] = ['CTER', 'CNEU', 'PCTE', 'CT1', 'CT2', 'CT3'] """ List of patch names that are applied to the last residue in a segment. These patches are typically used for C-terminal modifications. """ _after_regenerate_patches: ClassVar[list[str]] = ['PHEM', 'FHEM'] """ List of patch names that should be applied after the ``regenerate angles dihedrals`` psfgen command. """
[docs] def shortcode(self) -> str: """ Convert the Adapter instance to a shortcode string. Returns ------- str The shortcode string in the format patchname:chainID:resID. """ return f"{self.patchname}:{self.chainID}:{self.resid.resid}"
[docs] def return_TcL(self): """ Writes the patch command. """ return f'patch {self.patchname} {self.chainID}:{self.resid.resid}'
[docs] def assign_residue(self, residue: 'Residue'): """ Assigns a Residue object to the patch. """ self.residue = residue
[docs] class PatchList(BaseObjList[Patch]): """ A class for handling lists of Patch objects This class inherits from BaseObjList and provides methods to manage a list of Patch objects. """
[docs] def describe(self): """ Returns a string description of the PatchList. Returns ------- str A description of the PatchList, including the number of patches it contains. """ return f'<PatchList with {len(self)} patches>'
[docs] def assign_residues(self, Residues: "ResidueList") -> PatchList: """ Assigns a list of Residue objects to the patch residues. Parameters ---------- Residues : list of Residue A list of Residue objects to be assigned to the patch residues. Returns ------- PatchList A new PatchList object containing the residues that were not assigned because no applicable residues were found """ logger.debug(f'Patches: Assigning residues from list of {len(Residues)} residues') ignored = self.assign_objs_to_attr('residue', Residues, resid='resid', chainID='chainID') return self.__class__(ignored)