Identifying Acceptor and Donor Atoms๏
Problem โโโโโโโโโโโโlโโโโโโโโโโโโโโโ
You want to identify acceptor and donor atoms in a molecule.
Ingredients๏
|
Difficulty Level๏
๐ถ๏ธ
Solution๏
While the MolProp TK provides the functions to count the number of acceptor and donor atoms in the molecules, it does not identify which atoms are counted.
Lipinski [Lipinski-1997] provides a simple way to identify them. A Lipinski acceptor is either an oxygen or a nitrogen:
class IsLipinskiAcceptor(oechem.OEUnaryAtomPred):
"""
Predicate class to identify Lipinski acceptor atoms.
See Also
--------
* num_lipinsky_acceptors_
* :ref:`section_cheminfo_acceptor_donor` section
"""
def __call__(self, atom: oechem.OEAtomBase) -> bool:
"""Evaluate the atom."""
return atom.GetAtomicNum() in [oechem.OEElemNo_O, oechem.OEElemNo_N]
A Lipinski donor is either an oxygen or a nitrogen with at least one hydrogen.
class IsLipinskiDonor(oechem.OEUnaryAtomPred):
"""
Predicate class to identify Lipinski donor atoms.
See Also
--------
* num_lipinsky_donors_
* :ref:`section_cheminfo_acceptor_donor` section
"""
def __call__(self, atom: oechem.OEAtomBase) -> bool:
"""Evaluate the atom."""
if atom.GetAtomicNum() not in [oechem.OEElemNo_O, oechem.OEElemNo_N]:
return False
return has_hydrogen(atom)
Where has_hydrogen is defined as the following to handle both implicit and explicit hydrogens:
def has_hydrogen(atom: oechem.OEAtomBase) -> bool:
"""Return true if the atom has implicit or explicit hydrogens."""
if atom.GetImplicitHCount() > 0:
return True
return any(neigh.IsHydrogen() for neigh in atom.GetAtoms())
After implementing the definitions as predicates we can use them in any APIs that takes atom predicates.
See Examples
num_lipinsky_acceptors function
num_lipinsky_donors function
def print_lipinsky_donors(mol: oechem.OEMolBase) -> None:
"""Print Lipinsky donor atoms to console."""
oechem.OETriposAtomNames(mol)
console = rich.console.Console()
console.print(f"Acceptor atoms in molecule '{oechem.OEMolToSmiles(mol)}'")
for atom in mol.GetAtoms(IsLipinskiDonor()):
console.print(atom.GetName())
For more information see Predicate Functors chapter of the OEChem TK manual.
Discussion๏
Another possible implementation is based on Daylight SMARTS definitions.
This following predicate only identifies hydrogen bond acceptor atoms in carbonyl and nitroso functional groups:
class IsHBondAcceptorCarbonylNitroso(oechem.OEUnaryAtomPred):
"""Predicate class to identify atoms in [#6,#7;R0]=[#8]."""
def __call__(self, atom: oechem.OEAtomBase) -> bool:
"""Evaluate the atom."""
if not atom.IsOxygen():
return False
if atom.GetHvyDegree() != 1:
return False
for bond in atom.GetBonds():
if bond.GetOrder() != 2: # noqa: PLR2004
return False
return all(
neigh.GetAtomicNum in [oechem.OEElemNo_O, oechem.OEElemNo_N]
for neigh in atom.GetAtoms()
)
The following predicate identifies hydrogen bond donor as either a nitrogen, oxygen or fluorine atom with at least one hydrogen:
class IsHBondDonor(oechem.OEUnaryAtomPred):
"""Predicate class to identify atoms in [!H0;#7,#8,#9]."""
def __call__(self, atom: oechem.OEAtomBase) -> bool:
"""Evaluate the atom."""
if atom.GetAtomicNum() not in [
oechem.OEElemNo_O,
oechem.OEElemNo_N,
oechem.OEElemNo_F,
]:
return False
return has_hydrogen(atom)
The following predicate identifies hydrogen bond donor as a non-negatively charged hetero-atom with at least one hydrogen:
class IsHBondDonorInclusive(oechem.OEUnaryAtomPred):
"""Predicate class to identify atoms in [!$([#6,H0,-,-2,-3])]."""
def __call__(self, atom: oechem.OEAtomBase) -> bool:
"""Evaluate the atom."""
if atom.GetAtomicNum() in [oechem.OEElemNo_C, oechem.OEElemNo_H]:
return False
if atom.GetFormalCharge() != 0:
return False
return has_hydrogen(atom)
Also if a SMARTS pattern exists, it is very easy to create a predicate using the OEMatchAtom built-in predicate:
def print_custom_donors(mol: oechem.OEMolBase) -> None:
"""Print donor atoms to console."""
oechem.OETriposAtomNames(mol)
console = rich.console.Console()
donor_pred = oechem.OEMatchAtom("[!H0;#7,#8,#9]")
console.print(f"Donor atoms in molecule '{oechem.OEMolToSmiles(mol)}'")
for atom in mol.GetAtoms(donor_pred):
console.print(atom.GetName())
For comparison, the simplified SMARTS definitions of acceptor and donor atoms in OpenEyeโs ROCS application are the following:
donor:
[$([#7,#8,#15,#16]);H]acceptor:
[#8&!$(\*~N~[OD1]),#7&H0;!$([D4]);!$([D3]-\*=,:[$([#7,#8,#15,#16])])]
Please note that the built-in Mills and Dean [MillsDean-1996] definitions used in ROCS are significantly more precise and elaborate.
Figure 1. Example of color definition in ROCS
See also in OEChem TK manual๏
Theory
Predicate Functors chapter
API
OEMatchAtom atom predicate
OEUnaryAtomPred base atom predicate
See also in OEMolProp TK manual๏
OEGetLipinskiAcceptorCount function
OEGetLipinskiDonorCount function
OEGetHBondAcceptorCount function
OEGetHBondDonorCount function
See also๏
Theory