Source code for molsysmt.element.group.amino_acid.get_expected_heavy_atoms
from .get_group_db import get_group_db
from .get_standard_name import get_standard_name
from .group_names import group_names
def _is_hydrogen(atom_name):
"""Return True if atom_name follows PDB hydrogen naming conventions."""
if not atom_name:
return False
if atom_name[0] == "H":
return True
# Legacy PDB format: digit-first names like '1HB', '2HB', '3H'
if len(atom_name) >= 2 and atom_name[0].isdigit() and atom_name[1] == "H":
return True
return False
[docs]
def get_expected_heavy_atoms(group_name, present_atom_names=None):
"""
Return the set of expected heavy (non-hydrogen) atom names for a residue.
Looks up the residue in MolSysMT's amino-acid topology database. When
``present_atom_names`` is supplied the function selects the topology
variant whose atom set is a superset of the given heavy atoms (matching
PDBFixer's template-selection strategy). When no variant matches, or when
``present_atom_names`` is ``None``, the first (CCD canonical) variant is
used.
Parameters
----------
group_name : str
Name of the chemical group (residue).
present_atom_names : object, default=None
Argument present_atom_names.
Returns
-------
set of str or None
Set of heavy-atom names expected for the residue, or ``None`` when the
residue is not found in the amino-acid database.
Notes
-----
Heavy atoms are identified by PDB naming convention: a name is a hydrogen
if it starts with ``'H'`` or with a digit followed by ``'H'`` (e.g.
``'1HB'``).
Examples
--------
>>> get_expected_heavy_atoms('ALA')
{'N', 'CA', 'C', 'O', 'CB', 'OXT'}
>>> get_expected_heavy_atoms('ALA', present_atom_names=['N', 'CA', 'C', 'O', 'CB'])
{'N', 'CA', 'C', 'O', 'CB', 'OXT'}
>>> get_expected_heavy_atoms('MSE') # selenomethionine → look up MET
{'N', 'CA', 'C', 'O', 'CB', 'CG', 'SD', 'CE', 'OXT'}
.. versionadded:: 1.0.0
"""
# Canonicalize: if group_name is a known non-standard amino acid, use its
# standard equivalent for the topology look-up.
canonical = get_standard_name(group_name)
lookup_name = canonical if canonical is not None else group_name
if lookup_name not in group_names:
return None
db = get_group_db(lookup_name)
if present_atom_names is not None:
present_heavy = {a for a in present_atom_names if not _is_hydrogen(a)}
# Among all variants that contain the present heavy atoms, pick the one
# with the fewest extra (unexpected) heavy atoms — the tightest fit.
# This avoids falsely reporting terminal-only atoms (e.g. OXT) as
# missing for internal residues.
best_heavy = None
best_extra = None
for variant in db["topology"]:
variant_heavy = {a for a in variant["atoms"] if not _is_hydrogen(a)}
if present_heavy <= variant_heavy:
extra = len(variant_heavy) - len(present_heavy)
if best_extra is None or extra < best_extra:
best_extra = extra
best_heavy = variant_heavy
if best_heavy is not None:
return best_heavy
# Fallback: first (CCD canonical) variant.
return {a for a in db["topology"][0]["atoms"] if not _is_hydrogen(a)}