Ring Perception

Problem

You want to determine whether two atoms belong to the same ring. See examples in Table 1.

Ring perception is one of the fundamental algorithms when handling chemical structures. While OEChem TK does not provide a solution for SSSR (Smallest Set of Smallest Rings) it includes wide range of functions (see Table 2) that answer related questions. This recipe will illustrate how to utilize these functions to determine whether two atoms belong to the same ring.

See also

Table 1. Example of depiction of atoms (pink) and those that are perceived to be in the same ring(s) (blue)
../_images/samering2img-01.png ../_images/samering2img-02.png ../_images/samering2img-03.png
Table 2. Ring perception API in OEChem TK
API See subsection
OEFindRingAtomsAndBonds Ring Perception section in OEChem TK
OEDetermineRingSystems Identifying Ring Systems of a Molecule
OEDetermineAromaticRingSystems Identifying Aromatic Ring Systems of a Molecule
OEAtomIsInRingSize / OEBondIsInRingSize Identifying Atom in Certain Ring Size
OEAtomIsInAromaticRingSize / OEBondIsInAromaticRingSize Identifying Atom in Certain Aromatic Ring Size
OEAtomGetSmallestRingSize / OEBondGetSmallestRingSize Identifying Atoms’ Smallest Ring Size

Ingredients

Difficulty level

../_images/chilly.png ../_images/chilly.png

Download

Download code

samering.py

See also Usage subsection.

Solution

The algorithm implemented in AtomsInSameRing is based on a simple concept: if two atoms belong to the same ring then there has to at least two alternative ring paths between them. These paths are identified by using the OEShortestPath function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def AtomsInSameRing(atomA, atomB):

    if not atomA.IsInRing() or not atomB.IsInRing():
        return False

    if atomA == atomB:
        return True

    firstpath = [a for a in oechem.OEShortestPath(atomA, atomB, oechem.OEAtomIsInChain())]
    firstpathlength = len(firstpath)

    if firstpathlength == 2:
        return True  # neighbors

    if firstpathlength == 0:
        return False  # not is same ring system

    smallestA = oechem.OEAtomGetSmallestRingSize(atomA)
    smallestB = oechem.OEAtomGetSmallestRingSize(atomB)

    if firstpathlength > smallestA and firstpathlength > smallestB:
        return False  # too far away

    # try to find the second shortest different path
    excludepred = ChainAtomOrAlreadyTraversed(firstpath[1:-1])
    secondpath = [a for a in oechem.OEShortestPath(atomA, atomB, excludepred)]
    secondpathlength = len(secondpath)

    if secondpathlength == 0:
        return False  # can not be in the same ring

    if secondpathlength > smallestA and secondpathlength > smallestB:
        return False  # too far away

    sumringsize = len(firstpath) + len(secondpath) - 2
    if sumringsize > smallestA and sumringsize > smallestB:
        return False

    inringA = oechem.OEAtomIsInRingSize(atomA, sumringsize)
    inringB = oechem.OEAtomIsInRingSize(atomB, sumringsize)
    return inringA and inringB


#############################################################################
# INTERFACE
#############################################################################

ChainAtomOrAlreadyTraversed is the atom predicate that is used with OEShortestPath to identify a second alternative path between two atoms.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class ChainAtomOrAlreadyTraversed(oechem.OEUnaryAtomPred):
    def __init__(self, exclude):
        oechem.OEUnaryAtomPred.__init__(self)
        self.exclude = exclude

    def __call__(self, atom):
        if not atom.IsInRing():
            return False
        return (atom in self.exclude)

    def CreateCopy(self):
        return ChainAtomOrAlreadyTraversed(self.exclude).__disown__()

Usage

Usage

samering.py

prompt > python3 samering.py .ism
c1ccc2c(c1)cc[nH]2

atom  0 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C
atom  1 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C
atom  2 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C
atom  3 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C  6 C  7 C  8 N
atom  4 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C  6 C  7 C  8 N
atom  5 C in the same ring as  0 C  1 C  2 C  3 C  4 C  5 C
atom  6 C in the same ring as  3 C  4 C  6 C  7 C  8 N
atom  7 C in the same ring as  3 C  4 C  6 C  7 C  8 N
atom  8 N in the same ring as  3 C  4 C  6 C  7 C  8 N

Discussion

The following examples show how to use ring perception functions available in OEChem TK.

Identifying Ring Systems of a Molecule

nrrings, ringlist = oechem.OEDetermineRingSystems(mol)

disp = oedepict.OE2DMolDisplay(mol, opts)

highlight = oedepict.OEHighlightByLasso(oechem.OEBlack)
highlight.SetConsiderAtomLabelBoundingBox(True)

ringpred = oechem.OEPartPredAtom(ringlist)
for ringidx, color in zip(range(1, nrrings + 1), oechem.OEGetVividColors()):
    ringpred.SelectPart(ringidx)
    ringset = oechem.OEAtomBondSet(mol.GetAtoms(ringpred))
    highlight.SetColor(color)
    oedepict.OEAddHighlighting(disp, highlight, ringset)

oedepict.OERenderMolecule(image, disp)

Download code

depict-ringsystem.py

../_images/depict-ringsystem.png

See also

Identifying Aromatic Ring Systems of a Molecule

nrrings, ringlist = oechem.OEDetermineAromaticRingSystems(mol)

disp = oedepict.OE2DMolDisplay(mol, opts)

highlight = oedepict.OEHighlightByLasso(oechem.OEBlack)
highlight.SetConsiderAtomLabelBoundingBox(True)

ringpred = oechem.OEPartPredAtom(ringlist)
for ringidx, color in zip(range(1, nrrings + 1), oechem.OEGetVividColors()):
    ringpred.SelectPart(ringidx)
    ringset = oechem.OEAtomBondSet(mol.GetAtoms(ringpred))
    highlight.SetColor(color)
    oedepict.OEAddHighlighting(disp, highlight, ringset)

oedepict.OERenderMolecule(image, disp)

Download code

depict-aromringsystem.py

../_images/depict-aromringsystem.png

Identifying Atom in Certain Ring Size

class LabelRingSize(oedepict.OEDisplayAtomPropBase):
    def __init__(self, maxringsize):
        self.maxringsize = maxringsize
        oedepict.OEDisplayAtomPropBase.__init__(self)

    def __call__(self, atom):
        if not atom.IsInRing():
            return ""
        rings = []
        for ringsize in range(3, self.maxringsize):
            if oechem.OEAtomIsInRingSize(atom, ringsize):
                rings.append(ringsize)
        if len(rings) == 0:
            return ""
        return "(" + ",".join([str(r) for r in rings]) + ")"

    def CreateCopy(self):
        return LabelRingSize(self.maxringsize).__disown__()


scale = oedepict.OEScale_AutoScale
opts = oedepict.OE2DMolDisplayOptions(width, height, scale)
opts.SetAtomColorStyle(oedepict.OEAtomColorStyle_WhiteMonochrome)
opts.SetAtomPropertyFunctor(LabelRingSize(maxringsize=10))

disp = oedepict.OE2DMolDisplay(mol, opts)

Download code

depict-ringsize.py

../_images/depict-ringsize.png

Identifying Atom in Certain Aromatic Ring Size

class LabelAromaticRingSize(oedepict.OEDisplayAtomPropBase):
    def __init__(self, maxringsize):
        self.maxringsize = maxringsize
        oedepict.OEDisplayAtomPropBase.__init__(self)

    def __call__(self, atom):
        if not atom.IsInRing():
            return ""
        rings = []
        for ringsize in range(3, self.maxringsize):
            if oechem.OEAtomIsInAromaticRingSize(atom, ringsize):
                rings.append(ringsize)
        if len(rings) == 0:
            return ""
        return "(" + ",".join([str(r) for r in rings]) + ")"

    def CreateCopy(self):
        return LabelAromaticRingSize(self.maxringsize).__disown__()


scale = oedepict.OEScale_AutoScale
opts = oedepict.OE2DMolDisplayOptions(width, height, scale)
opts.SetAtomColorStyle(oedepict.OEAtomColorStyle_WhiteMonochrome)
opts.SetAtomPropertyFunctor(LabelAromaticRingSize(maxringsize=10))

disp = oedepict.OE2DMolDisplay(mol, opts)

Download code

depict-aromringsize.py

../_images/depict-aromringsize.png

Identifying Atoms’ Smallest Ring Size

class LabelSmallestRingSize(oedepict.OEDisplayAtomPropBase):
    def __init__(self):
        oedepict.OEDisplayAtomPropBase.__init__(self)

    def __call__(self, atom):
        if not atom.IsInRing():
            return ""
        smallest = oechem.OEAtomGetSmallestRingSize(atom)
        return "(" + str(smallest) + ")"

    def CreateCopy(self):
        return LabelSmallestRingSize().__disown__()


scale = oedepict.OEScale_AutoScale
opts = oedepict.OE2DMolDisplayOptions(width, height, scale)
opts.SetAtomColorStyle(oedepict.OEAtomColorStyle_WhiteMonochrome)
opts.SetAtomPropertyFunctor(LabelSmallestRingSize())

disp = oedepict.OE2DMolDisplay(mol, opts)
../_images/depict-smallestringsize.png

See also in OEChem TK manual

Theory

API