Source code for pestifer.charmmff.segtype_classifier
# Author: Cameron F. Abrams, <cfa22@drexel.edu>
"""
Derive a residue's *segtype* from the CHARMM topology/stream file it is defined in.
The CHARMM force field already organizes residues by category across its topology and
stream files (``*_lipid_*`` for lipids, ``*_carb_*`` for carbohydrates, ``*_prot_*`` for
protein, ``*_na_*`` for nucleic acids, ``*_cgenff*`` for CGenFF ligands, ``water_ions``
for water and ions, ...). Classifying by the defining file therefore replaces a large
hand-maintained enumeration of residue names: a residue added to a CHARMM lipid stream
is a lipid automatically.
This module is deliberately dependency-free (it takes a plain ``resname -> topfile``
mapping) so it can be exercised in isolation and reused by the persist-time generator.
"""
# Ordered (substring, segtype) rules matched against the lowercased basename of the
# topology file a residue is defined in. First match wins, so the list runs from the
# most specific/unambiguous file family to the most general. ``water_ions`` is matched
# before the ion rules and split downstream (water resnames vs everything-else-is-an-ion).
_TOPFILE_RULES = [
('cgenff', 'ligand'), # top_all36_cgenff*.rtf, <resi>-cgenff.str
('water_ions', 'water_ions'), # toppar_water_ions.str -> split into water / ion
('moreions', 'ion'), # toppar_all36_moreions.str
('lipid', 'lipid'), # top_all36_lipid.rtf, toppar_all36_lipid_*.str
('carb', 'glycan'), # top_all36_carb.rtf, toppar_all36_carb_*.str
('36_na', 'nucleicacid'), # top_all36_na.rtf, toppar_all36_na_*.str
('prot', 'protein'), # top_all36_prot.rtf, toppar_all36_prot_*.str
]
[docs]
def segtype_of_topfile(topfile, water_resnames=frozenset(), resname=None):
"""
Return the segtype implied by ``topfile`` (a topology/stream file basename), or
``None`` if no rule matches (e.g. ``topfile`` is ``None`` because the name is a
PDB-style alias not defined directly in the force field).
``toppar_water_ions.str`` holds both water and ions; when a residue comes from it,
``resname`` is classified as ``water`` if it is in ``water_resnames`` and ``ion``
otherwise.
"""
if not topfile:
return None
t = topfile.lower()
for substr, segtype in _TOPFILE_RULES:
if substr in t:
if segtype == 'water_ions':
return 'water' if (resname and resname in water_resnames) else 'ion'
return segtype
return None
[docs]
def derive_segtypes(resi_to_topfile_map, curated_names=frozenset(), water_resnames=frozenset()):
"""
Classify every residue in ``resi_to_topfile_map`` by its defining topology file.
Parameters
----------
resi_to_topfile_map : dict
``resname -> topfile-basename`` (a CHARMMFFContent attribute).
curated_names : set
Residue names that are classified explicitly elsewhere (the curated core in
:mod:`pestifer.core.labels`); these are skipped so an explicit classification is
never overridden by a derived one.
water_resnames : set
Residue names treated as water when they come from ``water_ions``.
Returns
-------
dict
``segtype -> sorted list of resnames`` for every residue that a rule classified
and that is not already curated.
"""
curated = set(curated_names)
out: dict[str, list] = {}
for resname, topfile in resi_to_topfile_map.items():
if resname in curated:
continue
segtype = segtype_of_topfile(topfile, water_resnames=water_resnames, resname=resname)
if segtype is None:
continue
out.setdefault(segtype, []).append(resname)
return {segtype: sorted(names) for segtype, names in sorted(out.items())}