Source code for qsarkit.chemistry.glycans._remover
"""Carbohydrate (glycan) removal, preserving the aglycone."""
from __future__ import annotations
from qsarkit.base.exceptions import RDKIT_MOLECULE_ERRORS
from typing import Any, List, Optional
from qsarkit.chemistry.glycans._detector import GlycanDetector
[docs]
class GlycanRemover:
"""Remove carbohydrate (sugar) moieties, keeping the aglycone core.
Detects sugar rings with :class:`GlycanDetector`, cleaves the
glycosidic (single) bonds linking sugar atoms to the rest of the
molecule, and returns the largest non-sugar fragment as the
"aglycone" - e.g. quercetin-3-glucoside -> quercetin.
Parameters
----------
detector : GlycanDetector, optional
Detector used to locate sugar rings. A default instance is
created if not supplied.
Examples
--------
>>> from rdkit import Chem
>>> from qsarkit.chemistry import GlycanRemover
>>> q3g = Chem.MolFromSmiles(
... "OC[C@H]1O[C@@H](Oc2c(-c3ccc(O)c(O)c3)oc3cc(O)cc(O)c3c2=O)"
... "[C@H](O)[C@@H](O)[C@@H]1O")
>>> result = GlycanRemover().remove(q3g)
>>> Chem.MolToSmiles(result["aglycone"])
'O=c1cc(-c2ccc(O)c(O)c2)oc2cc(O)cc(O)c12'
The result keeps the input alongside what was cut away, so a curation
step can record why a structure changed:
>>> sorted(result)
['aglycone', 'original', 'removed_fragments']
Deglycosylation matters for QSAR because the sugar usually carries no
activity of its own but dominates the fingerprint, so two glycosides
of unrelated aglycones look more similar to each other than either
does to its own aglycone. A molecule with no sugar passes through
unchanged:
>>> aspirin = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")
>>> Chem.MolToSmiles(GlycanRemover().remove(aspirin)["aglycone"])
'CC(=O)Oc1ccccc1C(=O)O'
References
----------
- Fischer et al. (2020). "The Sugar Removal Utility (SRU)." Molecules,
25(8), 1988. https://doi.org/10.3390/molecules25081988
- RDKit fragmentation documentation (``Chem.GetMolFrags``,
``Chem.RWMol``): https://www.rdkit.org/docs/GettingStartedInPython.html
"""
def __init__(self, detector: Optional[GlycanDetector] = None):
self.detector = detector or GlycanDetector()
[docs]
def remove(self, mol: Any) -> dict:
"""Remove glycan moieties from a single molecule.
Returns
-------
dict
``original``: the input Mol.
``aglycone``: the largest non-sugar fragment, or ``None`` if
the whole molecule was consumed by sugar rings.
``removed_fragments``: list of removed sugar (and any other
minor) fragments as Mol objects.
"""
from rdkit import Chem
matches = self.detector.find_glycans(mol)
if not matches:
return {"original": mol, "aglycone": mol, "removed_fragments": []}
glycan_atoms = set()
for m in matches:
glycan_atoms |= m.all_atoms
rw = Chem.RWMol(mol)
bonds_to_break = []
for bond in mol.GetBonds():
a1, a2 = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
in1, in2 = a1 in glycan_atoms, a2 in glycan_atoms
if in1 != in2 and bond.GetBondType() == Chem.BondType.SINGLE:
bonds_to_break.append((a1, a2))
for a1, a2 in bonds_to_break:
rw.RemoveBond(a1, a2)
frag_mol = rw.GetMol()
for atom in frag_mol.GetAtoms():
atom.SetNoImplicit(False)
try:
Chem.SanitizeMol(frag_mol)
except RDKIT_MOLECULE_ERRORS:
# Fragmentation is followed by unsanitized GetMolFrags below,
# which works regardless; sanitizing here only improves the
# aglycone when it happens to succeed.
pass
frag_atom_indices = Chem.GetMolFrags(frag_mol, asMols=False, sanitizeFrags=False)
frags = Chem.GetMolFrags(frag_mol, asMols=True, sanitizeFrags=False)
aglycone = None
aglycone_size = -1
removed: List[Any] = []
non_glycan_frags = []
for frag, atom_idx in zip(frags, frag_atom_indices):
try:
Chem.SanitizeMol(frag)
except RDKIT_MOLECULE_ERRORS:
# An unsanitizable fragment is still classifiable by its
# atom indices, which is all the loop below needs.
pass
if all(idx in glycan_atoms for idx in atom_idx):
removed.append(frag)
else:
non_glycan_frags.append(frag)
for frag in non_glycan_frags:
if frag.GetNumAtoms() > aglycone_size:
if aglycone is not None:
removed.append(aglycone)
aglycone = frag
aglycone_size = frag.GetNumAtoms()
else:
removed.append(frag)
return {"original": mol, "aglycone": aglycone, "removed_fragments": removed}