Пример #1
0
    def testRoundTripOps(self):
        """Test IO operation on generated related molecules"""
        try:
            oeIoU = OeIoUtils()
            mU = MarshalUtil()
            mU.mkdir(self.__molfileDirPath)
            ccMolD = self.__getChemCompDefs()
            oemf = OeMoleculeFactory()
            for ccId, ccObj in list(ccMolD.items())[:10]:
                # ----
                tId = oemf.setChemCompDef(ccObj)
                self.assertEqual(tId, ccId)
                relatedIdxD = oemf.buildRelated(limitPerceptions=False)
                logger.info("%s generated %d molecular forms", ccId,
                            len(relatedIdxD))
                for sId, idxD in relatedIdxD.items():
                    logger.info("sId %r smiles %r", sId, idxD["smiles"])
                    mol2Path = os.path.join(self.__molfileDirPath,
                                            sId + ".mol2")
                    oeMol = oeIoU.descriptorToMol(idxD["smiles"],
                                                  "oe-iso-smiles",
                                                  limitPerceptions=False,
                                                  messageTag=None)
                    oeIoU.write(mol2Path,
                                oeMol,
                                constantMol=True,
                                addSdTags=True)
                    tMolL = oeIoU.fileToMols(mol2Path)
                    #
                    nextMol2Path = os.path.join(self.__molfileDirPath,
                                                sId + "-next.mol2")
                    oeIoU.write(nextMol2Path,
                                tMolL[0],
                                constantMol=True,
                                addSdTags=True)

                    sdfPath = os.path.join(self.__molfileDirPath, sId + ".mol")
                    oeMol = oeIoU.descriptorToMol(idxD["smiles"],
                                                  "oe-iso-smiles",
                                                  limitPerceptions=False,
                                                  messageTag=None)
                    oeIoU.write(sdfPath,
                                oeMol,
                                constantMol=True,
                                addSdTags=True)
                    #
                    tMolL = oeIoU.fileToMols(sdfPath)
                    nextSdfPath = os.path.join(self.__molfileDirPath,
                                               sId + "-next.sdf")
                    oeIoU.write(nextSdfPath,
                                tMolL[0],
                                constantMol=True,
                                addSdTags=True)
                # ----
        except Exception as e:
            logger.exception("Failing with %s", str(e))
            self.fail()
    def __fingerPrintSearch(self, numMols, **kwargs):
        maxFpResults = kwargs.get("maxFpResults", 50)
        limitPerceptions = kwargs.get("limitPerceptions", False)
        fpTypeCuttoffList = kwargs.get("fpTypeCuttoffList", [("TREE", 0.6)])
        buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
        #
        oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
        oesU = OeSearchUtils(oesmP,
                             fpTypeList=[tup[0] for tup in fpTypeCuttoffList])
        oeioU = OeIoUtils()
        # This will reload the oe binary cache.
        oeMol = oesmP.getMol("004")
        self.assertGreaterEqual(len(list(oeMol.GetAtoms())), 12)
        missedFpD = {}
        missedBuildD = {}
        numMols = min(len(ccIdxD), numMols) if numMols else len(ccIdxD)
        logger.info("Begin finger print search on %d molecules", numMols)
        # ----
        startTime = time.time()
        for ccId, ccD in list(ccIdxD.items())[:numMols]:
            for buildType in buildTypeList:
                if buildType in ccD:
                    oeMol = oeioU.descriptorToMol(
                        ccD[buildType],
                        buildType,
                        limitPerceptions=limitPerceptions,
                        messageTag=ccId + ":" + buildType)
                    if not oeMol:
                        continue
                    selfHit = False
                    for fpType, minFpScore in fpTypeCuttoffList:
                        retStatus, mL = oesU.searchFingerPrints(
                            oeMol,
                            fpType=fpType,
                            minFpScore=minFpScore,
                            maxFpResults=maxFpResults)
                        self.assertTrue(retStatus)
                        #
                        matchedSelf = self.__resultContains(ccId, mL)
                        selfHit = selfHit or matchedSelf
                        if not matchedSelf:
                            missedFpD.setdefault(ccId, []).append(
                                (buildType, fpType, len(mL)))
                    #
                    if not selfHit:
                        missedBuildD.setdefault(ccId, []).append(buildType)
        # ------
        for ccId, bTL in missedBuildD.items():
            logger.info("%s missed all fptypes:  buildtype list %r", ccId, bTL)

        if ccId in missedFpD:
            logger.info("%s unmatched by fpTypes %r", ccId, missedFpD[ccId])

        # ----
        logger.info("%s fingerprints search on %d in (%.4f seconds)",
                    len(fpTypeCuttoffList), numMols,
                    time.time() - startTime)
        # ----
        return True
    def matchByDescriptor(self,
                          descriptor,
                          descriptorType,
                          matchOpts="graph-relaxed",
                          searchId=None):
        """Return graph match (w/  finger print pre-filtering) and finger print search results for the
           input desriptor.

        Args:
            descriptor (str):  molecular descriptor (SMILES, InChI)
            descriptorType (str): descriptor type (SMILES, InChI
            matchOpts (str, optional): graph match criteria (graph-relaxed, graph-relaxed-stereo, graph-strict,
                                       fingerprint-similarity, Defaults to "graph-relaxed")
            searchId (str, optional): search identifier for logging. Defaults to None.

        Returns:
            (statusCode, list, list): status, graph match and finger match lists of type (MatchResults)
                                      -100 descriptor processing error
                                      -200 search execution error
                                         0 search execution success
        """
        ssL = fpL = []
        retStatus = False
        statusCode = -200
        try:
            fpTypeCuttoffD = self.__configD["oesmpKwargs"][
                "fpTypeCuttoffD"] if "fpTypeCuttoffD" in self.__configD[
                    "oesmpKwargs"] else {}
            maxFpResults = self.__configD["oesmpKwargs"][
                "maxFpResults"] if "maxFpResults" in self.__configD[
                    "oesmpKwargs"] else 50
            limitPerceptions = self.__configD["oesmpKwargs"][
                "limitPerceptions"] if "limitPerceptions" in self.__configD[
                    "oesmpKwargs"] else False
            #
            searchId = searchId if searchId else "query"
            messageTag = searchId + ":" + descriptorType
            oeioU = OeIoUtils()
            oeMol = oeioU.descriptorToMol(descriptor,
                                          descriptorType,
                                          limitPerceptions=limitPerceptions,
                                          messageTag=messageTag)
            oeMol = oeioU.suppressHydrogens(oeMol)
            if not oeMol:
                logger.warning("descriptor type %r molecule build fails: %r",
                               descriptorType, descriptor)
                return self.__statusDescriptorError, ssL, fpL
            #
            retStatus, ssL, fpL = self.__oesU.searchSubStructureAndFingerPrint(
                oeMol,
                list(fpTypeCuttoffD.items())[:2],
                maxFpResults,
                matchOpts=matchOpts)
            statusCode = 0 if retStatus else self.__searchError
        except Exception as e:
            logger.exception("Failing with %s", str(e))
            #
        return statusCode, ssL, fpL
 def __subStructureSearchScreened(self, numMols, **kwargs):
     #
     buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
     screenTypeList = kwargs.get("screenTypeList", ["SMARTS"])
     oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
     for screenType in screenTypeList:
         oesU = OeSearchUtils(oesmP,
                              screenType=screenType,
                              numProc=self.__numProc)
         oeioU = OeIoUtils()
         #
         missL = []
         numMols = min(len(ccIdxD), numMols) if numMols else len(ccIdxD)
         for (
                 ii,
                 ccId,
         ) in enumerate(list(ccIdxD.keys())[:numMols]):
             ccD = ccIdxD[ccId]
             for buildType in buildTypeList:
                 if buildType in ccD:
                     if screenType == "SMARTS":
                         smiles = oeioU.descriptorToSmiles(ccD[buildType],
                                                           buildType,
                                                           messageTag=ccId +
                                                           ":" + buildType)
                         oeQMol = oeioU.descriptorToMol(smiles,
                                                        "SMARTS",
                                                        messageTag=ccId +
                                                        ":" + buildType)
                     else:
                         oeQMol = oeioU.descriptorToQMol(ccD[buildType],
                                                         "SMARTS",
                                                         messageTag=ccId +
                                                         ":" + buildType)
                     if not oeQMol:
                         logger.debug("%s build failed for %s - skipping",
                                      ccId, buildType)
                         continue
                     # ----
                     startTime = time.time()
                     retStatus, mL = oesU.searchSubStructureScreened(
                         oeQMol, maxMatches=100)
                     if retStatus:
                         logger.debug(
                             "%s - %s - %s (status=%r) match length %d in (%.4f seconds)",
                             ccId, buildType, screenType, retStatus,
                             len(mL),
                             time.time() - startTime)
                     if not self.__resultContains(ccId, mL):
                         missL.append((ccId, buildType, screenType))
                     # ----
             if ii % 100 == 0:
                 logger.info("Completed %d of %d missed count %d", ii,
                             numMols, len(missL))
         logger.info("Screen %r missed searches (%d) %r", screenType,
                     len(missL), missL)
     return True
    def subStructSearchByDescriptor(self,
                                    descriptor,
                                    descriptorType,
                                    matchOpts="sub-struct-graph-relaxed",
                                    searchId=None):
        """Return graph match (w/  finger print pre-filtering) and finger print search results for the
           input desriptor.

        Args:
            descriptor (str):  molecular descriptor (SMILES, InChI)
            descriptorType (str): descriptor type (SMILES, InChI)
            matchOpts (str, optional): graph match criteria (sub-struct-graph-relaxed, sub-struct-graph-relaxed-stereo,
                                       sub-struct-graph-strict). Defaults to "sub-struct-graph-relaxed".
            searchId (str, optional): search identifier for logging. Defaults to None.

        Returns:
            (statusCode, list, list): status, substructure search results of type (MatchResults), empty list placeholder
                                      -100 descriptor processing error
                                      -200 search execution error
                                         0 search execution success
        """
        ssL = []
        retStatus = False
        statusCode = -200
        try:
            limitPerceptions = self.__configD["oesmpKwargs"][
                "limitPerceptions"] if "limitPerceptions" in self.__configD[
                    "oesmpKwargs"] else False
            numProc = self.__configD["oesmpKwargs"][
                "numProc"] if "numProc" in self.__configD["oesmpKwargs"] else 4
            #
            searchId = searchId if searchId else "query"
            messageTag = searchId + ":" + descriptorType
            oeioU = OeIoUtils()
            oeMol = oeioU.descriptorToMol(descriptor,
                                          descriptorType,
                                          limitPerceptions=limitPerceptions,
                                          messageTag=messageTag)
            oeMol = oeioU.suppressHydrogens(oeMol)
            if not oeMol:
                logger.warning("descriptor type %r molecule build fails: %r",
                               descriptorType, descriptor)
                return self.__statusDescriptorError, ssL, []
            #
            ccIdL = self.__oesubsU.prefilterIndex(oeMol,
                                                  self.__siIdxP,
                                                  matchOpts=matchOpts)
            retStatus, ssL = self.__oesubsU.searchSubStructure(
                oeMol, ccIdList=ccIdL, matchOpts=matchOpts, numProc=numProc)
            statusCode = 0 if retStatus else self.__searchError
        except Exception as e:
            logger.exception("Failing with %s", str(e))
            #
        return statusCode, ssL, []
 def __exhaustiveSubStructureSearch(self, numMols, **kwargs):
     """Exhaustive substructure search."""
     try:
         limitPerceptions = kwargs.get("limitPerceptions", False)
         buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
         oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
         oesU = OeSearchUtils(oesmP, fpTypeList=[])
         oeioU = OeIoUtils()
         #
         for ccId, ccD in list(ccIdxD.items())[:numMols]:
             matchCount = 0
             mtS = set()
             for buildType in buildTypeList:
                 if buildType in ccD:
                     oeMol = oeioU.descriptorToMol(
                         ccD[buildType],
                         buildType,
                         limitPerceptions=limitPerceptions,
                         messageTag=ccId + ":" + buildType)
                     if not oeMol:
                         logger.error(
                             "%s %s build query molecule build fails (skipping)",
                             ccId, buildType)
                         continue
                     # ----
                     startTime = time.time()
                     retStatus, mL = oesU.searchSubStructure(
                         oeMol, matchOpts="graph-strict")
                     if not retStatus:
                         logger.info("%s match fails for build type %s",
                                     ccId, buildType)
                     elif not self.__resultContains(ccId, mL):
                         logger.info(
                             "%s failed match length %d build type %s in (%.4f seconds)",
                             ccId, len(mL), buildType,
                             time.time() - startTime)
                     elif self.__resultContains(ccId, mL):
                         mtS.update([m.ccId for m in mL])
                         matchCount += 1
                     self.assertTrue(retStatus)
                     self.assertTrue(self.__resultContains(ccId, mL))
             if matchCount:
                 logger.info("%s MATCHES %d: %r", ccId, matchCount, mtS)
             else:
                 logger.info("%s NO MATCHES", ccId)
             # ----
         return True
     except Exception as e:
         logger.exception("Failing with %s", str(e))
         self.fail()
     return False
 def __displayAlignedDescriptorPair(self,
                                    ccId,
                                    descrRef,
                                    buildTypeRef,
                                    descrFit,
                                    buildTypeFit,
                                    title=None,
                                    limitPerceptions=True):
     oeioU = OeIoUtils()
     oeMolRef = oeioU.descriptorToMol(descrRef,
                                      buildTypeRef,
                                      limitPerceptions=limitPerceptions,
                                      messageTag=ccId + ":" + buildTypeRef)
     oeMolFit = oeioU.descriptorToMol(descrFit,
                                      buildTypeFit,
                                      limitPerceptions=limitPerceptions,
                                      messageTag=ccId + ":" + buildTypeFit)
     #
     oed = OeDepictMCSAlignPage()
     oed.setSearchType(sType="graph-relaxed", minAtomMatchFraction=0.50)
     oed.setDisplayOptions(labelAtomName=True,
                           labelAtomCIPStereo=True,
                           labelAtomIndex=False,
                           labelBondIndex=False,
                           highlightStyleFit="ballAndStickInverse",
                           bondDisplayWidth=0.5)
     oed.setRefMol(oeMolRef, ccId)
     oed.setFitMol(oeMolFit, ccId)
     myTitle = title if title else buildTypeRef + "-" + buildTypeFit
     imgPath = os.path.join(self.__workPath, myTitle + "-" + ccId + ".svg")
     logger.info("Using image path %r", imgPath)
     aML = oed.alignPair(imagePath=imgPath)
     if aML:
         logger.info("%s aligned image path %r", ccId, imgPath)
         for (rCC, rAt, tCC, tAt) in aML:
             logger.debug("%5s %-5s %5s %-5s", rCC, rAt, tCC, tAt)
 def __getMol(self,
              query,
              queryType,
              queryId,
              limitPerceptions=False,
              suppressHydrogens=True):
     oeioU = OeIoUtils()
     if queryType == "CC":
         oeMol = self.__oesmP.getMol(query)
     else:
         oeMol = oeioU.descriptorToMol(query,
                                       queryType,
                                       limitPerceptions=limitPerceptions,
                                       messageTag=queryId)
     #
     if suppressHydrogens:
         oeMol = oeioU.suppressHydrogens(oeMol)
     oeMol.SetTitle(queryId)
     return oeMol
 def __exhaustiveSubStructureSearch(self, numMols, **kwargs):
     """Exhaustive substructure search."""
     try:
         limitPerceptions = kwargs.get("limitPerceptions", False)
         buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
         oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
         oesU = OeSearchUtils(oesmP, fpTypeList=[])
         oeioU = OeIoUtils()
         #
         for ccId, ccD in list(ccIdxD.items())[:numMols]:
             for buildType in buildTypeList:
                 if buildType in ccD:
                     oeMol = oeioU.descriptorToMol(
                         ccD[buildType],
                         buildType,
                         limitPerceptions=limitPerceptions,
                         messageTag=ccId + ":" + buildType)
                     if not oeMol:
                         continue
                     # ----
                     startTime = time.time()
                     retStatus, mL = oesU.searchSubStructure(
                         oeMol, matchOpts="graph-strict")
                     if not self.__resultContains(ccId, mL):
                         logger.info(
                             "%s match length %d build type %s in (%.4f seconds)",
                             ccId, len(mL), buildType,
                             time.time() - startTime)
                     self.assertTrue(retStatus)
                     self.assertTrue(self.__resultContains(ccId, mL))
             # ----
         return True
     except Exception as e:
         logger.exception("Failing with %s", str(e))
         self.fail()
     return False
    def __sssWithFingerPrintFromDescriptor(self, numMols, **kwargs):
        maxFpResults = kwargs.get("maxResults", 50)
        limitPerceptions = kwargs.get("limitPerceptions", False)
        fpTypeCuttoffList = kwargs.get("fpTypeCuttoffList", [("TREE", 0.6)])
        buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
        doDisplay = kwargs.get("doDisplay", False)
        #
        oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
        oesU = OeSearchUtils(oesmP,
                             fpTypeList=[tup[0] for tup in fpTypeCuttoffList])
        oeioU = OeIoUtils()
        # This will reload the oe binary cache.
        oeMol = oesmP.getMol("004")
        self.assertGreaterEqual(len(list(oeMol.GetAtoms())), 12)

        # matchOpts = "graph-relaxed"
        matchOpts = "graph-strict"
        missTupL = []
        missedD = {}
        missedFpD = {}
        numMols = min(len(ccIdxD), numMols) if numMols else len(ccIdxD)
        logger.info(
            "Begin substructure search w/ finger print filter on %d molecules",
            numMols)
        # ----
        startTime = time.time()
        for (
                ii,
                ccId,
        ) in enumerate(list(ccIdxD.keys())[:numMols]):
            ccD = ccIdxD[ccId]
            for buildType in buildTypeList:
                if buildType in ccD:
                    startTime1 = time.time()
                    oeMol = oeioU.descriptorToMol(
                        ccD[buildType],
                        buildType,
                        limitPerceptions=limitPerceptions,
                        messageTag=ccId + ":" + buildType)
                    if not oeMol:
                        logger.debug("%s build failed for %s - skipping", ccId,
                                     buildType)
                        continue
                    maxHits = 0
                    minHits = maxFpResults
                    selfHit = False
                    for fpType, minFpScore in fpTypeCuttoffList:
                        retStatus, mL = oesU.searchSubStructureWithFingerPrint(
                            oeMol,
                            fpType,
                            minFpScore,
                            maxFpResults,
                            matchOpts=matchOpts)
                        self.assertTrue(retStatus)
                        logger.debug("%s fpType %r hits %d", ccId, fpType,
                                     len(mL))
                        maxHits = max(maxHits, len(mL))
                        minHits = min(minHits, len(mL))
                        matchedSelf = self.__resultContains(ccId, mL)
                        selfHit = selfHit or matchedSelf
                        if not matchedSelf:
                            missedFpD.setdefault(ccId, []).append(
                                (buildType, fpType, len(mL)))
                    if not selfHit:
                        missedD.setdefault(ccId, []).append(buildType)

                    if maxHits < 1 or not selfHit:
                        logger.info(
                            "%s (%r) buildType %r min hits %d max hits %d (%.4f seconds)",
                            ccId, selfHit, buildType, minHits, maxHits,
                            time.time() - startTime1)
                else:
                    logger.debug("%s missing descriptor %r", ccId, buildType)
            if ii % 100 == 0:
                logger.info("Completed %d of %d missed count %d", ii, numMols,
                            len(missedD))
        #
        for ccId, missL in missedD.items():
            logger.info("%s missed list %r", ccId, missL)
            if ccId in missedFpD:
                logger.info("%s unmatched for fpTypes %r", ccId,
                            missedFpD[ccId])
        # ----
        if doDisplay:
            mD = {}
            for missTup in missTupL:
                mD.setdefault(missTup[0], []).append(missTup[1])

            for ccId, buildTypeL in mD.items():
                idxD = ccIdxD[ccId]
                if "oe-iso-smiles" in idxD:
                    for buildType in buildTypeL:
                        self.__displayAlignedDescriptorPair(
                            ccId,
                            idxD["oe-iso-smiles"],
                            "oe-iso-smiles",
                            idxD[buildType],
                            buildType,
                            title=None,
                            limitPerceptions=True)

        logger.info("%s fingerprints search on %d in (%.4f seconds)",
                    len(fpTypeCuttoffList), numMols,
                    time.time() - startTime)
        return True
    def __fingerPrintScores(self, numMols, **kwargs):
        maxFpResults = kwargs.get("maxResults", 50)
        limitPerceptions = kwargs.get("limitPerceptions", True)
        fpTypeCuttoffList = kwargs.get("fpTypeCuttoffList", [("TREE", 0.6)])
        buildTypeList = kwargs.get("buildTypeList", ["oe-iso-smiles"])
        doDisplay = kwargs.get("doDisplay", False)
        failedIdList = kwargs.get("failedIdList", [])
        #
        oesmP, ccIdxD = self.__getSearchDataProviders(**kwargs)
        oesU = OeSearchUtils(oesmP,
                             fpTypeList=[tup[0] for tup in fpTypeCuttoffList])
        oeioU = OeIoUtils()
        # This will reload the oe binary cache.
        oeMol = oesmP.getMol("004")
        self.assertGreaterEqual(len(list(oeMol.GetAtoms())), 12)
        #
        missedFpD = {}
        missedBuildD = {}
        numMols = min(len(ccIdxD), numMols) if numMols else len(ccIdxD)
        logger.info("Begin finger print score search on %d molecules", numMols)
        # ----
        startTime = time.time()
        # for ccId, ccD in list(ccIdxD.items())[:numMols]:
        for ii, ccId in enumerate(failedIdList[:numMols]):
            ccD = ccIdxD[ccId]
            for buildType in buildTypeList:
                if buildType in ccD:
                    oeMol = oeioU.descriptorToMol(
                        ccD[buildType],
                        buildType,
                        limitPerceptions=limitPerceptions,
                        messageTag=ccId + ":" + buildType)
                    if not oeMol:
                        logger.debug("%s build failed for %s - skipping", ccId,
                                     buildType)
                        continue
                    maxHits = 0
                    minHits = maxFpResults
                    selfHit = False
                    #
                    startTime1 = time.time()
                    for fpType, minFpScore in fpTypeCuttoffList:
                        retStatus, mL = oesU.getFingerPrintScores(
                            oeMol, fpType, minFpScore, maxFpResults)
                        self.assertTrue(retStatus)
                        logger.debug("%s fpType %r hits %d", ccId, fpType,
                                     len(mL))
                        maxHits = max(maxHits, len(mL))
                        minHits = min(minHits, len(mL))
                        matchedSelf = self.__resultContains(ccId, mL)
                        selfHit = selfHit or matchedSelf
                        if not matchedSelf:
                            missedFpD.setdefault(ccId, []).append(
                                (buildType, fpType, len(mL)))
                    #
                    if not selfHit:
                        missedBuildD.setdefault(ccId, []).append(buildType)
                    #
                    if maxHits < 1 or not selfHit:
                        logger.info(
                            "%s MISSED for buildType %r min hits %d max hits %d (%.4f seconds)",
                            ccId, buildType, minHits, maxHits,
                            time.time() - startTime1)
                    else:
                        logger.debug(
                            "%s MATCHED for buildType %r min hits %d max hits %d (%.4f seconds)",
                            ccId, buildType, minHits, maxHits,
                            time.time() - startTime1)
                else:
                    logger.debug("%s missing descriptor %r", ccId, buildType)
            if ii % 100 == 0:
                logger.info(
                    "Completed %d of %d missed count %d in (%.4f seconds)", ii,
                    len(failedIdList), len(missedBuildD),
                    time.time() - startTime)

        # ------
        for ccId, bTL in missedBuildD.items():
            logger.info("%s missed all fptypes:  buildtype list %r", ccId, bTL)

        if ccId in missedFpD:
            logger.info("%s unmatched by fpTypes %r", ccId, missedFpD[ccId])

        #
        if doDisplay:
            for ccId, bTL in missedBuildD.items():
                idxD = ccIdxD[ccId]
                if "oe-iso-smiles" in idxD:
                    for bT in bTL:
                        self.__displayAlignedDescriptorPair(
                            ccId,
                            idxD["oe-iso-smiles"],
                            "oe-iso-smiles",
                            idxD[bT],
                            bT,
                            title=None,
                            limitPerceptions=True)

        logger.info("%s fingerprints search on %d in (%.4f seconds)",
                    len(fpTypeCuttoffList), numMols,
                    time.time() - startTime)