Chem.Recap

Package Chem :: Module Recap |
[hide private]
[frames] | no frames]

# Source Code for Module Chem.Recap

      1  #  $Id: Recap.py 594 2008-04-06 10:41:14Z glandrum $
      2  #
      3  #  Copyright (c) 2007, Novartis Institutes for BioMedical Research Inc.
      4  #  All rights reserved.
      5  #
      6  # Redistribution and use in source and binary forms, with or without
      7  # modification, are permitted provided that the following conditions are
      8  # met:
      9  #
     10  #     * Redistributions of source code must retain the above copyright
     11  #       notice, this list of conditions and the following disclaimer.
     12  #     * Redistributions in binary form must reproduce the above
     13  #       copyright notice, this list of conditions and the following
     14  #       disclaimer in the documentation and/or other materials provided
     15  #       with the distribution.
     16  #     * Neither the name of Novartis Institutes for BioMedical Research Inc.
     17  #       nor the names of its contributors may be used to endorse or promote
     18  #       products derived from this software without specific prior written permission.
     19  #
     20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24  # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25  # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26  # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  #
     32  """ Implementation of the RECAP algorithm from Lewell et al. JCICS *38* 511-522 (1998)
     33
     34  The published algorithm is implemented more or less without
     35  modification. The results are returned as a hierarchy of nodes instead
     36  of just as a set of fragments. The hope is that this will allow a bit
     37  more flexibility in working with the results.
     38
     39  For example:
     40  >>> m = Chem.MolFromSmiles('C1CC1Oc1ccccc1-c1ncc(OC)cc1')
     41  >>> res = Recap.RecapDecompose(m)
     42  >>> res
     43  <Chem.Recap.RecapHierarchyNode object at 0x00CDB5D0>
     44  >>> res.children.keys()
     45  ['[*]C1CC1', '[*]c1ccccc1-c1ncc(OC)cc1']
     46  >>> res.GetAllChildren().keys()
     47  ['[*]c1ccccc1[*]', '[*]c1ccc(OC)cn1', '[*]C1CC1', '[*]c1ccccc1-c1ncc(OC)cc1']
     48
     49  To get the standard set of RECAP results, use GetLeaves():
     50  >>> leaves=res.GetLeaves()
     51  >>> leaves.keys()
     52  ['[*]c1ccccc1[*]', '[*]c1ccc(OC)cn1', '[*]C1CC1']
     53  >>> leaf = leaves['[*]C1CC1']
     54  >>> leaf.mol
     55  <Chem.rdchem.Mol object at 0x00CBE0F0>
     56
     57
     58  """
     59  import weakref
     60  import Chem
     61  from Chem import rdChemReactions as Reactions
     62
     63  # These are the definitions that will be applied to fragment molecules:
     64  reactionDefs = (
     65    "[#7:1;+0;D2,D3]!@C(!@=O)!@[#7:2;+0;D2,D3]>>[*][#7:1].[#7:2][*]", # urea
     66
     67    "[C:1](=!@[O:2])!@[#7:3;+0;!D1]>>[*][C:1]=[O:2].[*][#7:3]", # amide
     68
     69    "[C:1](=!@[O:2])!@[O:3;+0]>>[*][C:1]=[O:2].[O:3][*]", # ester
     70
     71    "[N;!D1;+0](-!@[*:1])-!@[*:2]>>[*][*:1].[*:2][*]", # amines
     72    #"[N;!D1](!@[*:1])!@[*:2]>>[*][*:1].[*:2][*]", # amines
     73
     74    # again: what about aromatics?
     75    "[#7:1;R;D3;+0]-!@[*:2]>>[*][#7:1].[*:2][*]", # cyclic amines
     76
     77    "[#6:1]-!@[O;+0]-!@[#6:2]>>[#6:1][*].[*][#6:2]", # ether
     78
     79    "[C:1]=!@[C:2]>>[C:1][*].[*][C:2]", # olefin
     80
     81    "[n:1;+0]-!@[C:2]>>[n:1][*].[C:2][*]", # aromatic nitrogen - aliphatic carbon
     82
     83    "O=C-@[N:1;+0]-!@[C:2]>>[N:1][*].[C:2][*]", # lactam nitrogen - aliphatic carbon
     84
     85    "[c:1]-!@[c:2]>>[c:1][*].[*][c:2]", # aromatic carbon - aromatic carbon
     86
     87    "[n:1;+0]-!@[c:2]>>[n:1][*].[*][c:2]", # aromatic nitrogen - aromatic carbon *NOTE* this is not part of the standard recap set.
     88
     89    "[#7:1;+0;D2,D3]-!@[S:2](=[O:3])=[O:4]>>[#7:1][*].[*][S:2](=[O:3])=[O:4]", # sulphonamide
     90    )
     91
     92  reactions = tuple([Reactions.ReactionFromSmarts(x) for x in reactionDefs])
     93
     94
     95 -class RecapHierarchyNode(object):
     96    """ This class is used to hold the Recap hiearchy
     97    """
     98    mol=None
     99    children=None
    100    parents=None
    101    smiles = None
    102 -  def __init__(self,mol):
    103      self.mol=mol
    104      self.children = {}
    105      self.parents = {}
    106
    107 -  def GetAllChildren(self):
    108      " returns a dictionary, keyed by SMILES, of children "
    109      res = {}
    110      for smi,child in self.children.iteritems():
    111        res[smi] = child
    112        child._gacRecurse(res,terminalOnly=False)
    113      return res
    114
    115 -  def GetLeaves(self):
    116      " returns a dictionary, keyed by SMILES, of leaf (terminal) nodes "
    117      res = {}
    118      for smi,child in self.children.iteritems():
    119        if not len(child.children):
    120          res[smi] = child
    121        else:
    122          child._gacRecurse(res,terminalOnly=True)
    123      return res
    124
    125 -  def getUltimateParents(self):
    126      """ returns all the nodes in the hierarchy tree that contain this
    127          node as a child
    128      """
    129      if not self.parents:
    130        res = [self]
    131      else:
    132        res = []
    133        for p in self.parents.values():
    134          for uP in p.getUltimateParents():
    135            if uP not in res:
    136              res.append(uP)
    137      return res
    138
    139 -  def _gacRecurse(self,res,terminalOnly=False):
    140      for smi,child in self.children.iteritems():
    141        if not terminalOnly or not len(child.children):
    142          res[smi] = child
    143        child._gacRecurse(res,terminalOnly=terminalOnly)
    144
    145 -  def __del__(self):
    146      self.children={}
    147      self.parent={}
    148      self.mol=None
    149
    150
    151 -def RecapDecompose(mol,allNodes=None,minFragmentSize=0):
    152    """ returns the recap decomposition for a molecule """
    153    mSmi = Chem.MolToSmiles(mol,1)
    154
    155    if allNodes is None:
    156      allNodes={}
    157    if allNodes.has_key(mSmi):
    158      return allNodes[mSmi]
    159
    160    res = RecapHierarchyNode(mol)
    161    res.smiles =mSmi
    162    activePool={mSmi:res}
    163    allNodes[mSmi]=res
    164    for rxnIdx,reaction in enumerate(reactions):
    165      localRes = {}
    166      #print '>',rxnIdx,len(activePool.keys())
    167      missed = {}
    168      while activePool:
    169        nSmi = activePool.keys()[0]
    170        node = activePool.pop(nSmi)
    171        ps = reaction.RunReactants((node.mol,))
    172        if not ps:
    173          #print '  !',nSmi
    174          localRes[nSmi]=node
    175        else:
    176          rxnApplied=False
    177          #print '  .',nSmi
    178          for prodSeq in ps:
    179            seqOk=True
    180            # we want to disqualify small fragments, so sort the product sequence by size
    181            # and then look for "forbidden" fragments
    182            prodSeq = [(prod.GetNumAtoms(onlyHeavy=True),prod) for prod in prodSeq]
    183            prodSeq.sort()
    184            for nats,prod in prodSeq:
    185              pSmi = Chem.MolToSmiles(prod,1)
    186              if minFragmentSize>0:
    187                nDummies = pSmi.count('*')
    188                if nats-nDummies<minFragmentSize:
    189                  seqOk=False
    190                  break
    191              # don't forget after replacing dummy atoms to remove any empty
    192              # branches:
    193              elif pSmi.replace('[*]','').replace('()','') in ('','C','CC','CCC'):
    194                seqOk=False
    195                break
    196              prod.pSmi = pSmi
    197            if seqOk:
    198              rxnApplied=True
    199              for nats,prod in prodSeq:
    200                pSmi = prod.pSmi
    201                pNode = allNodes.get(pSmi,RecapHierarchyNode(prod))
    202                pNode.smiles=pSmi
    203                pNode.parents[nSmi]=weakref.proxy(node)
    204                node.children[pSmi]=pNode
    205                activePool[pSmi] = pNode
    206                allNodes[pSmi]=pNode
    207          if not rxnApplied:
    208            localRes[nSmi]=node
    209      activePool=localRes
    210    return res
    211
    212
    213  # ------- ------- ------- ------- ------- ------- ------- -------
    214  # Begin testing code
    215  if __name__=='__main__':
    216    import unittest
    217 -  class TestCase(unittest.TestCase):
    218 -    def test1(self):
    219        m = Chem.MolFromSmiles('C1CC1Oc1ccccc1-c1ncc(OC)cc1')
    220        res = RecapDecompose(m)
    221        self.failUnless(res)
    222        self.failUnless(len(res.children.keys())==2)
    223        self.failUnless(len(res.GetAllChildren().keys())==4)
    224        self.failUnless(len(res.GetLeaves().keys())==3)
    225 -    def test2(self):
    226        m = Chem.MolFromSmiles('CCCOCCC')
    227        res = RecapDecompose(m)
    228        self.failUnless(res)
    229        self.failUnless(res.children=={})
    230 -    def test3(self):
    231        allNodes={}
    232        m = Chem.MolFromSmiles('c1ccccc1-c1ncccc1')
    233        res = RecapDecompose(m,allNodes=allNodes)
    234        self.failUnless(res)
    235        self.failUnless(len(res.children.keys())==2)
    236        self.failUnless(len(allNodes.keys())==3)
    237
    238        m = Chem.MolFromSmiles('COc1ccccc1-c1ncccc1')
    239        res = RecapDecompose(m,allNodes=allNodes)
    240        self.failUnless(res)
    241        self.failUnless(len(res.children.keys())==2)
    242        # we get two more nodes from that:
    243        self.failUnless(len(allNodes.keys())==5)
    244        self.failUnless(allNodes.has_key('[*]c1ccccc1OC'))
    245        self.failUnless(allNodes.has_key('[*]c1ccccc1'))
    246
    247        m = Chem.MolFromSmiles('C1CC1Oc1ccccc1-c1ncccc1')
    248        res = RecapDecompose(m,allNodes=allNodes)
    249        self.failUnless(res)
    250        self.failUnless(len(res.children.keys())==2)
    251        self.failUnless(len(allNodes.keys())==9)
    252
    253 -    def testSFNetIssue1801871(self):
    254        m = Chem.MolFromSmiles('c1ccccc1OC(Oc1ccccc1)Oc1ccccc1')
    255        res = RecapDecompose(m)
    256        self.failUnless(res)
    257        self.failUnless(len(res.GetLeaves())==2)
    258        ks = res.GetLeaves().keys()
    259        self.failIf('[*]C([*])[*]' in ks)
    260        self.failUnless('[*]c1ccccc1' in ks)
    261        self.failUnless('[*]C([*])Oc1ccccc1' in ks)
    262
    263 -    def testSFNetIssue1804418(self):
    264        m = Chem.MolFromSmiles('C1CCCCN1CCCC')
    265        res = RecapDecompose(m)
    266        self.failUnless(res)
    267        self.failUnless(len(res.GetLeaves())==2)
    268        ks = res.GetLeaves().keys()
    269        self.failUnless('[*]N1CCCCC1' in ks)
    270        self.failUnless('[*]CCCC' in ks)
    271
    272 -    def testMinFragmentSize(self):
    273        m = Chem.MolFromSmiles('CCCOCCC')
    274        res = RecapDecompose(m)
    275        self.failUnless(res)
    276        self.failUnless(res.children=={})
    277        res = RecapDecompose(m,minFragmentSize=3)
    278        self.failUnless(res)
    279        self.failUnless(len(res.GetLeaves())==1)
    280        ks = res.GetLeaves().keys()
    281        self.failUnless('[*]CCC' in ks)
    282
    283        m = Chem.MolFromSmiles('CCCOCC')
    284        res = RecapDecompose(m,minFragmentSize=3)
    285        self.failUnless(res)
    286        self.failUnless(res.children=={})
    287
    288        m = Chem.MolFromSmiles('CCCOCCOC')
    289        res = RecapDecompose(m,minFragmentSize=2)
    290        self.failUnless(res)
    291        self.failUnless(len(res.GetLeaves())==2)
    292        ks = res.GetLeaves().keys()
    293        self.failUnless('[*]CCC' in ks)
    294        ks = res.GetLeaves().keys()
    295        self.failUnless('[*]CCOC' in ks)
    296
    297 -    def testAmideRxn(self):
    298        m = Chem.MolFromSmiles('C1CC1C(=O)NC1OC1')
    299        res = RecapDecompose(m)
    300        self.failUnless(res)
    301        self.failUnless(len(res.GetLeaves())==2)
    302        ks = res.GetLeaves().keys()
    303        self.failUnless('[*]C(=O)C1CC1' in ks)
    304        self.failUnless('[*]NC1CO1' in ks)
    305
    306        m = Chem.MolFromSmiles('C1CC1C(=O)N(C)C1OC1')
    307        res = RecapDecompose(m)
    308        self.failUnless(res)
    309        self.failUnless(len(res.GetLeaves())==2)
    310        ks = res.GetLeaves().keys()
    311        self.failUnless('[*]C(=O)C1CC1' in ks)
    312        self.failUnless('[*]N(C)C1CO1' in ks)
    313
    314        m = Chem.MolFromSmiles('C1CC1C(=O)n1cccc1')
    315        res = RecapDecompose(m)
    316        self.failUnless(res)
    317        self.failUnless(len(res.GetLeaves())==2)
    318        ks = res.GetLeaves().keys()
    319        self.failUnless('[*]C(=O)C1CC1' in ks)
    320        self.failUnless('[*]n1cccc1' in ks)
    321
    322        m = Chem.MolFromSmiles('C1CC1C(=O)CC1OC1')
    323        res = RecapDecompose(m)
    324        self.failUnless(res)
    325        self.failUnless(len(res.GetLeaves())==0)
    326
    327        m = Chem.MolFromSmiles('C1CCC(=O)NC1')
    328        res = RecapDecompose(m)
    329        self.failUnless(res)
    330        self.failUnless(len(res.GetLeaves())==0)
    331
    332        m = Chem.MolFromSmiles('CC(=O)NC')
    333        res = RecapDecompose(m)
    334        self.failUnless(res)
    335        self.failUnless(len(res.GetLeaves())==2)
    336        ks = res.GetLeaves().keys()
    337
    338        m = Chem.MolFromSmiles('CC(=O)N')
    339        res = RecapDecompose(m)
    340        self.failUnless(res)
    341        self.failUnless(len(res.GetLeaves())==0)
    342
    343        m = Chem.MolFromSmiles('C(=O)NCCNC(=O)CC')
    344        res = RecapDecompose(m)
    345        self.failUnless(res)
    346        self.failUnless(len(res.children)==4)
    347        self.failUnless(len(res.GetLeaves())==3)
    348
    349 -    def testEsterRxn(self):
    350        m = Chem.MolFromSmiles('C1CC1C(=O)OC1OC1')
    351        res = RecapDecompose(m)
    352        self.failUnless(res)
    353        self.failUnless(len(res.GetLeaves())==2)
    354        ks = res.GetLeaves().keys()
    355        self.failUnless('[*]C(=O)C1CC1' in ks)
    356        self.failUnless('[*]OC1CO1' in ks)
    357
    358        m = Chem.MolFromSmiles('C1CC1C(=O)CC1OC1')
    359        res = RecapDecompose(m)
    360        self.failUnless(res)
    361        self.failUnless(len(res.GetLeaves())==0)
    362
    363        m = Chem.MolFromSmiles('C1CCC(=O)OC1')
    364        res = RecapDecompose(m)
    365        self.failUnless(res)
    366        self.failUnless(len(res.GetLeaves())==0)
    367
    368 -    def testUreaRxn(self):
    369        m = Chem.MolFromSmiles('C1CC1NC(=O)NC1OC1')
    370        res = RecapDecompose(m)
    371        self.failUnless(res)
    372        self.failUnless(len(res.GetLeaves())==2)
    373        ks = res.GetLeaves().keys()
    374        self.failUnless('[*]NC1CC1' in ks)
    375        self.failUnless('[*]NC1CO1' in ks)
    376
    377        m = Chem.MolFromSmiles('C1CC1NC(=O)N(C)C1OC1')
    378        res = RecapDecompose(m)
    379        self.failUnless(res)
    380        self.failUnless(len(res.GetLeaves())==2)
    381        ks = res.GetLeaves().keys()
    382        self.failUnless('[*]NC1CC1' in ks)
    383        self.failUnless('[*]N(C)C1CO1' in ks)
    384
    385        m = Chem.MolFromSmiles('C1CCNC(=O)NC1C')
    386        res = RecapDecompose(m)
    387        self.failUnless(res)
    388        self.failUnless(len(res.GetLeaves())==0)
    389
    390        m = Chem.MolFromSmiles('c1cccn1C(=O)NC1OC1')
    391        res = RecapDecompose(m)
    392        self.failUnless(res)
    393        self.failUnless(len(res.GetLeaves())==2)
    394        ks = res.GetLeaves().keys()
    395        self.failUnless('[*]n1cccc1' in ks)
    396        self.failUnless('[*]NC1CO1' in ks)
    397
    398        m = Chem.MolFromSmiles('c1cccn1C(=O)n1c(C)ccc1')
    399        res = RecapDecompose(m)
    400        self.failUnless(res)
    401        self.failUnless(len(res.GetLeaves())==2)
    402        ks = res.GetLeaves().keys()
    403        self.failUnless('[*]n1c(C)ccc1' in ks)
    404
    405 -    def testAmineRxn(self):
    406        m = Chem.MolFromSmiles('C1CC1N(C1NC1)C1OC1')
    407        res = RecapDecompose(m)
    408        self.failUnless(res)
    409        self.failUnless(len(res.GetLeaves())==3)
    410        ks = res.GetLeaves().keys()
    411        self.failUnless('[*]C1CC1' in ks)
    412        self.failUnless('[*]C1CO1' in ks)
    413        self.failUnless('[*]C1CN1' in ks)
    414
    415        m = Chem.MolFromSmiles('c1ccccc1N(C1NC1)C1OC1')
    416        res = RecapDecompose(m)
    417        self.failUnless(res)
    418        self.failUnless(len(res.GetLeaves())==3)
    419        ks = res.GetLeaves().keys()
    420        self.failUnless('[*]c1ccccc1' in ks)
    421        self.failUnless('[*]C1CO1' in ks)
    422        self.failUnless('[*]C1CN1' in ks)
    423
    424        m = Chem.MolFromSmiles('c1ccccc1N(c1ncccc1)C1OC1')
    425        res = RecapDecompose(m)
    426        self.failUnless(res)
    427        self.failUnless(len(res.GetLeaves())==3)
    428        ks = res.GetLeaves().keys()
    429        self.failUnless('[*]c1ccccc1' in ks)
    430        self.failUnless('[*]c1ncccc1' in ks)
    431        self.failUnless('[*]C1CO1' in ks)
    432
    433        m = Chem.MolFromSmiles('c1ccccc1N(c1ncccc1)c1ccco1')
    434        res = RecapDecompose(m)
    435        self.failUnless(res)
    436        self.failUnless(len(res.GetLeaves())==3)
    437        ks = res.GetLeaves().keys()
    438        self.failUnless('[*]c1ccccc1' in ks)
    439        self.failUnless('[*]c1ncccc1' in ks)
    440        self.failUnless('[*]c1occc1' in ks)
    441
    442        m = Chem.MolFromSmiles('C1CCCCN1C1CC1')
    443        res = RecapDecompose(m)
    444        self.failUnless(res)
    445        self.failUnless(len(res.GetLeaves())==2)
    446        ks = res.GetLeaves().keys()
    447        self.failUnless('[*]N1CCCCC1' in ks)
    448        self.failUnless('[*]C1CC1' in ks)
    449
    450        m = Chem.MolFromSmiles('C1CCC2N1CC2')
    451        res = RecapDecompose(m)
    452        self.failUnless(res)
    453        self.failUnless(len(res.GetLeaves())==0)
    454
    455 -    def testEtherRxn(self):
    456        m = Chem.MolFromSmiles('C1CC1OC1OC1')
    457        res = RecapDecompose(m)
    458        self.failUnless(res)
    459        self.failUnless(len(res.GetLeaves())==2)
    460        ks = res.GetLeaves().keys()
    461        self.failUnless('[*]C1CC1' in ks)
    462        self.failUnless('[*]C1CO1' in ks)
    463
    464        m = Chem.MolFromSmiles('C1CCCCO1')
    465        res = RecapDecompose(m)
    466        self.failUnless(res)
    467        self.failUnless(len(res.GetLeaves())==0)
    468
    469        m = Chem.MolFromSmiles('c1ccccc1OC1OC1')
    470        res = RecapDecompose(m)
    471        self.failUnless(res)
    472        self.failUnless(len(res.GetLeaves())==2)
    473        ks = res.GetLeaves().keys()
    474        self.failUnless('[*]c1ccccc1' in ks)
    475        self.failUnless('[*]C1CO1' in ks)
    476
    477        m = Chem.MolFromSmiles('c1ccccc1Oc1ncccc1')
    478        res = RecapDecompose(m)
    479        self.failUnless(res)
    480        self.failUnless(len(res.GetLeaves())==2)
    481        ks = res.GetLeaves().keys()
    482        self.failUnless('[*]c1ccccc1' in ks)
    483        self.failUnless('[*]c1ncccc1' in ks)
    484
    485 -    def testOlefinRxn(self):
    486        m = Chem.MolFromSmiles('ClC=CBr')
    487        res = RecapDecompose(m)
    488        self.failUnless(res)
    489        self.failUnless(len(res.GetLeaves())==2)
    490        ks = res.GetLeaves().keys()
    491        self.failUnless('[*]CCl' in ks)
    492        self.failUnless('[*]CBr' in ks)
    493
    494        m = Chem.MolFromSmiles('C1CC=CC1')
    495        res = RecapDecompose(m)
    496        self.failUnless(res)
    497        self.failUnless(len(res.GetLeaves())==0)
    498
    499 -    def testAromNAliphCRxn(self):
    500        m = Chem.MolFromSmiles('c1cccn1CCCC')
    501        res = RecapDecompose(m)
    502        self.failUnless(res)
    503        self.failUnless(len(res.GetLeaves())==2)
    504        ks = res.GetLeaves().keys()
    505        self.failUnless('[*]n1cccc1' in ks)
    506        self.failUnless('[*]CCCC' in ks)
    507
    508        m = Chem.MolFromSmiles('c1ccc2n1CCCC2')
    509        res = RecapDecompose(m)
    510        self.failUnless(res)
    511        self.failUnless(len(res.GetLeaves())==0)
    512
    513 -    def testLactamNAliphCRxn(self):
    514        m = Chem.MolFromSmiles('C1CC(=O)N1CCCC')
    515        res = RecapDecompose(m)
    516        self.failUnless(res)
    517        self.failUnless(len(res.GetLeaves())==2)
    518        ks = res.GetLeaves().keys()
    519        self.failUnless('[*]N1C(=O)CC1' in ks)
    520        self.failUnless('[*]CCCC' in ks)
    521
    522        m = Chem.MolFromSmiles('O=C1CC2N1CCCC2')
    523        res = RecapDecompose(m)
    524        self.failUnless(res)
    525        self.failUnless(len(res.GetLeaves())==0)
    526
    527 -    def testAromCAromCRxn(self):
    528        m = Chem.MolFromSmiles('c1ccccc1c1ncccc1')
    529        res = RecapDecompose(m)
    530        self.failUnless(res)
    531        self.failUnless(len(res.GetLeaves())==2)
    532        ks = res.GetLeaves().keys()
    533        self.failUnless('[*]c1ccccc1' in ks)
    534        self.failUnless('[*]c1ncccc1' in ks)
    535
    536        m = Chem.MolFromSmiles('c1ccccc1C1CC1')
    537        res = RecapDecompose(m)
    538        self.failUnless(res)
    539        self.failUnless(len(res.GetLeaves())==0)
    540
    541 -    def testAromNAromCRxn(self):
    542        m = Chem.MolFromSmiles('c1cccn1c1ccccc1')
    543        res = RecapDecompose(m)
    544        self.failUnless(res)
    545        self.failUnless(len(res.GetLeaves())==2)
    546        ks = res.GetLeaves().keys()
    547        self.failUnless('[*]n1cccc1' in ks)
    548        self.failUnless('[*]c1ccccc1' in ks)
    549
    550 -    def testSulfonamideRxn(self):
    551        m = Chem.MolFromSmiles('CCCNS(=O)(=O)CC')
    552        res = RecapDecompose(m)
    553        self.failUnless(res)
    554        self.failUnless(len(res.GetLeaves())==2)
    555        ks = res.GetLeaves().keys()
    556        self.failUnless('[*]NCCC' in ks)
    557        self.failUnless('[*]S(=O)(=O)CC' in ks)
    558
    559        m = Chem.MolFromSmiles('c1cccn1S(=O)(=O)CC')
    560        res = RecapDecompose(m)
    561        self.failUnless(res)
    562        self.failUnless(len(res.GetLeaves())==2)
    563        ks = res.GetLeaves().keys()
    564        self.failUnless('[*]n1cccc1' in ks)
    565        self.failUnless('[*]S(=O)(=O)CC' in ks)
    566
    567        m = Chem.MolFromSmiles('C1CNS(=O)(=O)CC1')
    568        res = RecapDecompose(m)
    569        self.failUnless(res)
    570        self.failUnless(len(res.GetLeaves())==0)
    571
    572 -    def testSFNetIssue1881803(self):
    573        m = Chem.MolFromSmiles('c1ccccc1n1cccc1')
    574        res = RecapDecompose(m)
    575        self.failUnless(res)
    576        self.failUnless(len(res.GetLeaves())==2)
    577        m = Chem.MolFromSmiles('c1ccccc1[n+]1ccccc1')
    578        res = RecapDecompose(m)
    579        self.failUnless(res)
    580        self.failUnless(len(res.GetLeaves())==0)
    581
    582        m = Chem.MolFromSmiles('C1CC1NC(=O)CC')
    583        res = RecapDecompose(m)
    584        self.failUnless(res)
    585        self.failUnless(len(res.GetLeaves())==2)
    586        m = Chem.MolFromSmiles('C1CC1[NH+]C(=O)CC')
    587        res = RecapDecompose(m)
    588        self.failUnless(res)
    589        self.failUnless(len(res.GetLeaves())==0)
    590
    591        m = Chem.MolFromSmiles('C1CC1NC(=O)NC1CCC1')
    592        res = RecapDecompose(m)
    593        self.failUnless(res)
    594        self.failUnless(len(res.GetLeaves())==2)
    595        m = Chem.MolFromSmiles('C1CC1[NH+]C(=O)[NH+]C1CCC1')
    596        res = RecapDecompose(m)
    597        self.failUnless(res)
    598        self.failUnless(len(res.GetLeaves())==0)
    599
    600    unittest.main()
    601