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