Example #1
0
   def run_reactants(self, rxnSmarts, reactantSmiles,
                     needsExplicitHs=False, needsEnvironments=True):
       """Return a list of unique reaction products.
   
       Function is a wrapper around the RDKit built-in RunReaction() which attempts to
       address its limitations related to handling stereochemistry in reaction cores.
       """
       # By default, rewrite the transform using SMARTS environments.
       logging.debug("processing run_reactants")
       if needsEnvironments:
           logging.debug('environment needed: compacting: '+ rxnSmarts)
           rxnSmarts = compact(rxnSmarts)
           logging.debug('environmental compacting done: '+ rxnSmarts)
       # Generate reaction from input SMARTS.
       try:
           logging.debug('trying rdkit reaction from smarts with ' + rxnSmarts)
           rxn = AllChem.ReactionFromSmarts(rxnSmarts)
           rxn.Initialize()
       except:
           logging.error('invalid SMARTS: {0}'.format(rxnSmarts))
           self.out.append('Error: invalid SMARTS: {0}.\n'.format(rxnSmarts))
           sys.exit(1)
   
       # Convert reactants SMILES to molecules and add explicit H atoms need if
       # requested.
       logging.debug('reaction initialized')
       reactants = [Chem.MolFromSmiles(s) for s in reactantSmiles.split('.')]
       if needsExplicitHs:
           reactants = [self.add_Hs(m) for m in reactants]
   
       if None not in reactants:
           [m.UpdatePropertyCache() for m in reactants]
       else:
           self.out.append('Error: invalid input SMILES.\n')
           sys.exit(1)
   
       # Number atoms in reactants.
       self.number_atoms(reactants)
   
       # Create reactant atom map.
       reactantAtomMap = self.create_reactant_map(reactants)
   
       # Run reaction transform.
       try:
           productList = rxn.RunReactants(tuple(reactants))
       except:
           self.out.append('Error: Applying transform failed.\n')
           sys.exit(1)
   
       # Create template atom maps.
       [rTemplateList, pTemplateList, rTemplateAtomMaps, pTemplateAtomMaps, IsMatchChiral] = self.create_template_map(reactants, rxn)
       if len(IsMatchChiral) != len(productList):
           self.out.append('Error: Template maps does not match products.\n')
           sys.exit(1)
   
       # Remove non-chiral matches AND check for ring-opening reactions.
       newProductList = []
       for idx, products in enumerate(productList):
           if IsMatchChiral[idx]:
               newProducts = list(products)
               possibleOverlaps = True
               while possibleOverlaps:
                   [possibleOverlaps, newProducts] = self.merge(newProducts)
               newProductList.append(tuple(newProducts))
       productList = tuple(newProductList)
   
       # Parse products to correct chirality.
       for matchIdx, products in enumerate(productList):
           for product in products:
               for productAtom in product.GetAtoms():
                   if not productAtom.HasProp('Core'):
                       # Unique atom ID
                       atomIdx = productAtom.GetIsotope()
   
                       # Map Atoms
                       tmp = reactantAtomMap[atomIdx] if atomIdx in reactantAtomMap.keys() else None
                       reactantAtom = reactants[tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None
                       tmp = rTemplateAtomMaps[matchIdx][atomIdx] if atomIdx in rTemplateAtomMaps[matchIdx].keys() else None
                       rTemplateAtom = rTemplateList[matchIdx][tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None
                       tmp = pTemplateAtomMaps[matchIdx][atomIdx] if atomIdx in pTemplateAtomMaps[matchIdx].keys() else None
                       pTemplateAtom = pTemplateList[matchIdx][tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None
   
                       # Correct Chirality of Product Atom
                       self.fix_chirality(reactantAtom, productAtom, rTemplateAtom, pTemplateAtom)
 
       # Remove Hs if necessary
       if needsExplicitHs:
           tempProductList = []
           for products in productList:
               tempProductList.append([self.remove_Hs(p) for p in products])
           productList = tempProductList
       
       # Remove atom mappings.
       for products in productList:
           for product in products:
               for atom in product.GetAtoms():
                   atom.SetIsotope(0)
                   atom.ClearProp('Core')
   
       # Get unique product sets.
       uniqueProductSets = []
       for products in productList:
           uniqueProductSets.append('.'.join(set([Chem.MolToSmiles(p, isomericSmiles=True) for p in products])))
   
       return self.uniquify(uniqueProductSets)
def run_reactants(rxnSmarts, reactantSmiles,
                  needsExplicitHs=False, needsEnvironments=True,
                  needsStereoTunneling=True,
                  error=None):
    """Return a list of unique reaction products.

    Function is a wrapper around the RDKit built-in RunReaction() which attempts to
    address its limitations related to handling stereochemistry in reaction cores.
    """
    if error == None : error=logging.getLogger("stereotest.stereofix").error

    # By default, rewrite the transform using SMARTS environments.
    if needsEnvironments:
        rxnSmarts = compact(rxnSmarts)

    # Generate reaction from input SMARTS.
    try:
        rxn = AllChem.ReactionFromSmarts(rxnSmarts)
        rxn.Initialize()
    except:
        error('Invalid SMARTS: {0}.'.format(rxnSmarts))
        return None

    # Convert reactants SMILES to molecules and add explicit H atoms need if
    # requested.
    reactants = [Chem.MolFromSmiles(s) for s in reactantSmiles.split('.')]
    if needsExplicitHs:
        reactants = [add_Hs(m) for m in reactants]

    if None not in reactants:
        [m.UpdatePropertyCache() for m in reactants]
    else:
        error('Invalid SMILES among reactants.')
        return None

    # Number atoms in reactants.
    number_atoms(reactants)

    # Create reactant and product atom maps.
    reactantAtomMap = create_atom_map(reactants)

    # Temporarily remove chiral centers from the reactants.
    if needsStereoTunneling:
        backup = copy.deepcopy(reactants)
        for reactant in reactants:
            for rAtom in reactant.GetAtoms():
                if rAtom.GetChiralTag() != ChiralType.CHI_UNSPECIFIED:
                    rAtom.SetChiralTag(ChiralType.CHI_UNSPECIFIED)

    # Run reaction transform.
    try:
        productList = rxn.RunReactants(tuple(reactants))
    except:
        error('Applying transform failed.')
        return None

    # Restore chiral centers to reactants and introduce them in relevant atoms
    # of products.
    if needsStereoTunneling:
        reactants = backup

        for products in productList:
            for product in products:
                for pAtom in product.GetAtoms():
                    if pAtom.GetIsotope() not in reactantAtomMap.keys():
                        continue

                    rIdx, rAtomIdx = reactantAtomMap[pAtom.GetIsotope()]
                    rAtom = reactants[rIdx].GetAtomWithIdx(rAtomIdx)
                    if rAtom.GetChiralTag() != pAtom.GetChiralTag():
                        copy_chirality(rAtom, pAtom)

    # Create template atom maps.
    [rTemplateList, pTemplateList, rTemplateAtomMaps, pTemplateAtomMaps, IsMatchChiral] = create_template_map(reactants, rxn)
    if len(IsMatchChiral) != len(productList):
        error('Template maps does not match products.')
        return None

    # Remove non-chiral matches AND check for ring-opening reactions.
    newProductList = []
    rTemplateListIdx = 0
    for idx, products in enumerate(productList):
        if IsMatchChiral[idx]:
            newProducts = list(products)

            while True:
                [mergeIdx, newProducts] = merge(newProducts)
                if mergeIdx < 0:
                    break

            # Fix bonds
            #
            # Note:
            # It is probably an overkill, but I am not sure at the moment
            # how to pass relevant reactant and reactant template in case
            # of multi reactants reaction. Thus, using a brute force
            # approach, I iterate over all of them.
            for newProductIdx in range(len(newProducts)):
                for reactant, rTemplate in zip(reactants, rTemplateList[rTemplateListIdx]):
                    newProducts[newProductIdx] = fix_bonds(reactant, rTemplate, newProducts[newProductIdx])

            newProductList.append(tuple(newProducts))
            rTemplateListIdx += 1
    productList = tuple(newProductList)

    # Parse products to correct chirality.
    for matchIdx, products in enumerate(productList):
        for product in products:
            for productAtom in product.GetAtoms():
                if not productAtom.HasProp('Core'):
                    # Unique atom ID
                    atomIdx = productAtom.GetIsotope()

                    # Map Atoms
                    tmp = reactantAtomMap[atomIdx] if atomIdx in reactantAtomMap.keys() else None
                    reactantAtom = reactants[tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None
                    tmp = rTemplateAtomMaps[matchIdx][atomIdx] if atomIdx in rTemplateAtomMaps[matchIdx].keys() else None
                    rTemplateAtom = rTemplateList[matchIdx][tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None
                    tmp = pTemplateAtomMaps[matchIdx][atomIdx] if atomIdx in pTemplateAtomMaps[matchIdx].keys() else None
                    pTemplateAtom = pTemplateList[matchIdx][tmp[0]].GetAtomWithIdx(tmp[1]) if tmp is not None else None

                    # Correct Chirality and charge of Product Atom
                    fix_chirality(reactantAtom, productAtom, rTemplateAtom, pTemplateAtom)
                    fix_charge(reactantAtom, productAtom, rTemplateAtom, pTemplateAtom)

    # Remove Hs if necessary
    if needsExplicitHs:
        tempProductList = []
        for products in productList:
            mols = [remove_Hs(p) for p in products]
            if None in mols:
                return None
            tempProductList.append(mols)
        productList = tempProductList

    # Remove atom mappings.
    for products in productList:
        for product in products:
            for atom in product.GetAtoms():
                atom.SetIsotope(0)
                atom.ClearProp('Core')

    # Get unique product sets.
    uniqueProductSets = []
    for products in productList:
        uniqueProductSets.append('.'.join([Chem.MolToSmiles(p, isomericSmiles=True) for p in products]))
    uniqueProductsList = uniquify(uniqueProductSets)

    # Convert to molecules.
    productList = []
    for s in uniqueProductsList:
        products = [Chem.MolFromSmiles(smi) for smi in s.split('.')]
        productList.append(products)

    # Get transpose matrix of the productList.
    newProductList = map(list, zip(*productList))

    # Merge product list if any isomers are present.
    finalProducts = []
    needsMerging = False
    for products in newProductList:
        if are_isomers(products):
            needsMerging = True
            break

    needsMerging = False
    if needsMerging:
        for products in newProductList:
            mi = products[0]
            for mj in products:
                mi = combine_stereoisomers(mi, mj)
                if mi is None:
                    return None
            finalProducts.append(Chem.MolToSmiles(mi, isomericSmiles=True))
        finalProductSets = ['.'.join(finalProducts)]

    else:
        finalProductSets = uniqueProductsList

    return finalProductSets