       1 #!/bin/env python
       2 #
       3 # File: RDKitSearchFunctionalGroups.py
       4 # Author: Manish Sud <msud@san.rr.com>
       5 #
       6 # Copyright (C) 2018 Manish Sud. All rights reserved.
       7 #
       8 # The functionality available in this script is implemented using RDKit, an
       9 # open source toolkit for cheminformatics developed by Greg Landrum.
      10 #
      11 # This file is part of MayaChemTools.
      12 #
      13 # MayaChemTools is free software; you can redistribute it and/or modify it under
      14 # the terms of the GNU Lesser General Public License as published by the Free
      15 # Software Foundation; either version 3 of the License, or (at your option) any
      16 # later version.
      17 #
      18 # MayaChemTools is distributed in the hope that it will be useful, but without
      19 # any warranty; without even the implied warranty of merchantability of fitness
      20 # for a particular purpose.  See the GNU Lesser General Public License for more
      21 # details.
      22 #
      23 # You should have received a copy of the GNU Lesser General Public License
      24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
      25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
      26 # Boston, MA, 02111-1307, USA.
      27 #
      28
      29 from __future__ import print_function
      30
      31 # Add local python path to the global path and import standard library modules...
      32 import os
      33 import sys;  sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
      34 import time
      35 import re
      36
      37 # RDKit imports...
      38 try:
      39     from rdkit import rdBase
      40     from rdkit import Chem
      41     from rdkit.Chem import AllChem
      42     from rdkit.Chem import FunctionalGroups
      43 except ImportError as ErrMsg:
      44     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
      45     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
      46     sys.exit(1)
      47
      48 # MayaChemTools imports...
      49 try:
      50     from docopt import docopt
      51     import MiscUtil
      52     import RDKitUtil
      53 except ImportError as ErrMsg:
      54     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
      55     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
      56     sys.exit(1)
      57
      58 ScriptName = os.path.basename(sys.argv[0])
      59 Options = {}
      60 OptionsInfo = {}
      61
      62 FunctionalGroupsMap = {}
      63
      64 def main():
      65     """Start execution of the script"""
      66
      67     MiscUtil.PrintInfo("\n%s (RDK v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, time.asctime()))
      68
      69     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
      70
      71     # Retrieve command line arguments and options...
      72     RetrieveOptions()
      73
      74     # Process and validate command line arguments and options...
      75     ProcessOptions()
      76
      77     # Perform actions required by the script...
      78     PerformFunctionalGroupsSearch()
      79
      80     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
      81     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
      82
      83 def PerformFunctionalGroupsSearch():
      84     """Retrieve functional groups information and perform search."""
      85
      86     ProcessFunctionalGroupsInfo()
      87     PerformSearch()
      88
      89 def PerformSearch():
      90     """Perform search using SMARTS pattern for specified functional groups."""
      91
      92     Infile = OptionsInfo["Infile"]
      93     Outfile = OptionsInfo["Outfile"]
      94
      95     Groups = OptionsInfo["SpecifiedFunctionalGroups"]
      96     GroupsNegateMatch = OptionsInfo["SpecifiedFunctionalGroupsNegateMatch"]
      97     GroupsPatternMols = OptionsInfo["SpecifiedFunctionalGroupsPatternMols"]
      98
      99     GroupsOutfiles = OptionsInfo["SpecifiedFunctionalGroupsOutfiles"]
     100     GroupsCount = len(Groups)
     101
     102     CombineMatchResults = OptionsInfo["CombineMatchResults"]
     103     AndCombineOperatorMode = OptionsInfo["AndCombineOperatorMode"]
     104
     105     CountMode = OptionsInfo["CountMode"]
     106     UseChirality = OptionsInfo["UseChirality"]
     107
     108     # Setup a molecule reader...
     109     MiscUtil.PrintInfo("\nProcessing file %s..." % Infile)
     110     Mols  = RDKitUtil.ReadMolecules(Infile, **OptionsInfo["InfileParams"])
     111
     112     # Set up  molecule writers...
     113     Writer = None
     114     GroupOutfilesWriters = []
     115     if not CountMode:
     116         Writer,  GroupOutfilesWriters = SetupMoleculeWriters(CombineMatchResults, Outfile, GroupsOutfiles)
     117
     118     # Initialize pattern mols match count and status...
     119     GroupsPatternMolsMatchCount = [0] * GroupsCount
     120     GroupsPatternMolsMatchStatus = [False] * GroupsCount
     121
     122     # Process molecules...
     123     MolCount = 0
     124     ValidMolCount = 0
     125     MatchCount = 0
     126
     127     Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"]
     128
     129     for Mol in Mols:
     130         MolCount += 1
     131
     132         if Mol is None:
     133             continue
     134
     135         if RDKitUtil.IsMolEmpty(Mol):
     136             MolName = RDKitUtil.GetMolName(Mol, MolCount)
     137             MiscUtil.PrintWarning("Ignoring empty molecule: %s" % MolName)
     138             continue
     139
     140         ValidMolCount += 1
     141
     142         # Match pattern mols...
     143         for GroupIndex in range(0, GroupsCount):
     144             GroupsPatternMolsMatchStatus[GroupIndex] = DoesPatternMolMatch(GroupsPatternMols[GroupIndex], Mol, UseChirality, GroupsNegateMatch[GroupIndex])
     145             if GroupsPatternMolsMatchStatus[GroupIndex]:
     146                 GroupsPatternMolsMatchCount[GroupIndex] += 1
     147
     148         # Match mol against all specified criteria...
     149         MolMatched = DoesMolMeetSpecifiedMatchCriteria(GroupsPatternMolsMatchStatus, CombineMatchResults, AndCombineOperatorMode)
     150         if MolMatched:
     151             MatchCount += 1
     152
     153         # Nothing to write...
     154         if CountMode or (not MolMatched):
     155             continue
     156
     157         # Write out matched molecules...
     158         if Compute2DCoords:
     159             AllChem.Compute2DCoords(Mol)
     160
     161         if CombineMatchResults:
     162             Writer.write(Mol)
     163         else:
     164             for GroupIndex in range(0, GroupsCount):
     165                 if GroupsPatternMolsMatchStatus[GroupIndex]:
     166                     GroupOutfilesWriters[GroupIndex].write(Mol)
     167
     168     if Writer is not None:
     169         Writer.close()
     170     for GroupOutfileWriter in GroupOutfilesWriters:
     171         GroupOutfileWriter.close()
     172
     173     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
     174     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
     175     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
     176
     177     MiscUtil.PrintInfo("\nTotal number of molecules matched against specified match criteria: %d" % MatchCount)
     178
     179     MiscUtil.PrintInfo("\nNumber of molecuels matched against individual functional groups:")
     180     MiscUtil.PrintInfo("FunctionalGroupName,MatchCount")
     181
     182     for GroupIndex in range(0, GroupsCount):
     183         GroupName = Groups[GroupIndex]
     184         if GroupsNegateMatch[GroupIndex]:
     185             GroupName = '!' + GroupName
     186         GroupMatchCount = GroupsPatternMolsMatchCount[GroupIndex]
     187         MiscUtil.PrintInfo("%s,%d" % (GroupName, GroupMatchCount))
     188
     189 def DoesMolMeetSpecifiedMatchCriteria(GroupsPatternMolsMatchStatus,  CombineMatchResults, AndCombineOperatorMode):
     190     """Match molecule using specified match criteia."""
     191
     192     if CombineMatchResults and AndCombineOperatorMode:
     193         # Must match all specified SMARTS
     194         Status = True
     195         for MatchStatus in GroupsPatternMolsMatchStatus:
     196             if not MatchStatus:
     197                 Status = False
     198                 break
     199     else:
     200         # One match is enough...
     201         Status = False
     202         for MatchStatus in GroupsPatternMolsMatchStatus:
     203             if MatchStatus:
     204                 Status = True
     205                 break
     206
     207     return Status
     208
     209 def SetupMoleculeWriters(CombineMatchResults, Outfile, GroupsOutfiles):
     210     """Set up molecule writers for output files."""
     211
     212     Writer = None
     213     GroupOutfilesWriters = []
     214
     215     if CombineMatchResults:
     216         Writer = RDKitUtil.MoleculesWriter(Outfile, **OptionsInfo["OutfileParams"])
     217         if Writer is None:
     218             MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
     219         MiscUtil.PrintInfo("Generating file %s..." % Outfile)
     220     else:
     221         for GroupOutfile in GroupsOutfiles:
     222             GroupOutfileWriter = RDKitUtil.MoleculesWriter(GroupOutfile, **OptionsInfo["OutfileParams"])
     223             if GroupOutfileWriter is None:
     224                 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Writer)
     225             GroupOutfilesWriters.append(GroupOutfileWriter)
     226
     227         GroupsCount = len(GroupsOutfiles)
     228         if GroupsCount > 4:
     229             MiscUtil.PrintInfo("Generating %d output files with the following file name format: %s<GroupName>.%s" % (GroupsCount, OptionsInfo["OutfileBasename"], OptionsInfo["OutfileExt"]))
     230         else:
     231             Delmiter = ', '
     232             OutfileNames = Delmiter.join(GroupsOutfiles)
     233             MiscUtil.PrintInfo("Generating %d output files: %s..." % (GroupsCount, OutfileNames))
     234
     235     return (Writer, GroupOutfilesWriters)
     236
     237 def DoesPatternMolMatch(PatternMol, Mol, UseChirality, NegateMatch):
     238     """Perform a substructure match for the presence of pattern molecule in a molecule."""
     239
     240     MolMatched = Mol.HasSubstructMatch(PatternMol, useChirality = UseChirality)
     241     if NegateMatch:
     242         if MolMatched:
     243             MolMatched = False
     244         else:
     245             MolMatched = True
     246
     247     return MolMatched
     248
     249 def ProcessFunctionalGroupsInfo():
     250     """Process functional groups information."""
     251
     252     RetrieveFunctionalGroupsInfo()
     253     ProcessSpecifiedFunctionalGroups()
     254
     255     SetupFunctionalGroupsSMARTSPatterns()
     256     SetupFunctionalGroupsOutputFileNames()
     257
     258 def ProcessSpecifiedFunctionalGroups():
     259     """Process and validate specified functional groups"""
     260
     261     OptionsInfo["SpecifiedFunctionalGroups"] = []
     262     OptionsInfo["SpecifiedFunctionalGroupsNegateMatch"] = []
     263
     264     if re.match("^All$", OptionsInfo["FunctionalGroups"], re.I):
     265         OptionsInfo["SpecifiedFunctionalGroups"] = FunctionalGroupsMap['Names']
     266         OptionsInfo["SpecifiedFunctionalGroupsNegateMatch"] = [False] * len(OptionsInfo["SpecifiedFunctionalGroups"])
     267         return
     268
     269     # Set up a map of valid group names for checking specified group names...
     270     CanonicalGroupNameMap = {}
     271     for GroupName in FunctionalGroupsMap['Names']:
     272         CanonicalGroupNameMap[GroupName.lower()] = GroupName
     273
     274     # Parse and validate specified names...
     275     GroupNames = re.sub(" ", "", OptionsInfo["FunctionalGroups"])
     276     if not GroupNames:
     277         MiscUtil.PrintError("No functional group name specified for \"-f, --functionalGroups\" option")
     278
     279     SpecifiedFunctionalGroups = []
     280     SpecifiedNegateMatchStatus = []
     281
     282     for GroupName in GroupNames.split(","):
     283         CanonicalGroupName = GroupName.lower()
     284         NegateMatchStatus = False
     285         if re.match("^!", CanonicalGroupName, re.I):
     286             NegateMatchStatus = True
     287             CanonicalGroupName = re.sub("^!", "", CanonicalGroupName)
     288         if CanonicalGroupName in CanonicalGroupNameMap:
     289             SpecifiedFunctionalGroups.append(CanonicalGroupNameMap[CanonicalGroupName])
     290             SpecifiedNegateMatchStatus.append(NegateMatchStatus)
     291         else:
     292             MiscUtil.PrintWarning("The functional group name, %s, specified using \"-f, --functionalGroups\" option is not a valid name." % (GroupName))
     293
     294     if not len(SpecifiedFunctionalGroups):
     295         MiscUtil.PrintError("No valid functional group names specified for \"-f, --functionalGroups\" option")
     296
     297     OptionsInfo["SpecifiedFunctionalGroups"] = SpecifiedFunctionalGroups
     298     OptionsInfo["SpecifiedFunctionalGroupsNegateMatch"] = SpecifiedNegateMatchStatus
     299
     300 def SetupFunctionalGroupsSMARTSPatterns():
     301     """Setup SMARTS patterns for specified functional groups."""
     302
     303     OptionsInfo["SpecifiedFunctionalGroupsSMARTSPatterns"] = []
     304     OptionsInfo["SpecifiedFunctionalGroupsPatternMols"] = []
     305
     306     for Name in OptionsInfo["SpecifiedFunctionalGroups"]:
     307         SMARTSPattern = FunctionalGroupsMap['SMARTSPattern'][Name]
     308         PatternMol = Chem.MolFromSmarts(SMARTSPattern)
     309         if PatternMol is None:
     310             MiscUtil.PrintError("Failed to parse SMARTS pattern, %s, for function group, %s" % (SMARTSPattern, Name))
     311
     312         OptionsInfo["SpecifiedFunctionalGroupsSMARTSPatterns"].append(SMARTSPattern)
     313         OptionsInfo["SpecifiedFunctionalGroupsPatternMols"].append(PatternMol)
     314
     315 def SetupFunctionalGroupsOutputFileNames():
     316     """Setup output file names for specified functional group names."""
     317
     318     OptionsInfo["SpecifiedFunctionalGroupsOutfiles"] = []
     319
     320     if OptionsInfo["CountMode"]:
     321         # No need of any output file...
     322         return
     323
     324     if OptionsInfo["CombineMatchResults"]:
     325         # No need of output files for specified functional groups...
     326         return
     327
     328     OutfileBasename = OptionsInfo["OutfileBasename"]
     329     OutfileExt = OptionsInfo["OutfileExt"]
     330     SpecifiedFunctionalGroupsOutfiles = []
     331
     332     GroupsCount = len(OptionsInfo["SpecifiedFunctionalGroups"])
     333     for GroupIndex in range(0, GroupsCount):
     334         GroupName = OptionsInfo["SpecifiedFunctionalGroups"][GroupIndex]
     335         if OptionsInfo["SpecifiedFunctionalGroupsNegateMatch"][GroupIndex]:
     336             GroupName = "Not" + GroupName
     337         GroupName = re.sub("\.", "", GroupName)
     338
     339         GroupOutfile = "%s%s.%s" % (OutfileBasename, GroupName, OutfileExt)
     340         SpecifiedFunctionalGroupsOutfiles.append(GroupOutfile)
     341
     342     OptionsInfo["SpecifiedFunctionalGroupsOutfiles"] = SpecifiedFunctionalGroupsOutfiles
     343
     344 def RetrieveFunctionalGroupsInfo():
     345     """Retrieve functional groups information"""
     346
     347     MiscUtil.PrintInfo("\nRetrieving data from default RDKit functional groups hierarchy file Functional_Group_Hierarchy.txt...")
     348
     349     FunctionalGroupNamesFile = OptionsInfo["GroupNamesFile"]
     350     FunctionalGroupsNodes = FunctionalGroups.BuildFuncGroupHierarchy(FunctionalGroupNamesFile)
     351
     352     FunctionalGroupsMap['Names'] = []
     353     FunctionalGroupsMap['SMARTSPattern'] = {}
     354
     355     RetrieveDataFromFunctionalGroupsHierarchy(FunctionalGroupsNodes)
     356
     357     if not len(FunctionalGroupsMap['Names']):
     358         MiscUtil.PrintError("Failed to retrieve any functional group names and SMARTS patterns...")
     359
     360     MiscUtil.PrintInfo("Total number of functional groups present functional group hierarchy: %d" % (len(FunctionalGroupsMap['Names'])))
     361
     362 def RetrieveDataFromFunctionalGroupsHierarchy(FGNodes):
     363     """Retrieve functional groups data by recursively visiting functional group nodes."""
     364
     365     for FGNode in FGNodes:
     366         Name = FGNode.label
     367         SMARTSPattern = FGNode.smarts
     368
     369         if Name in FunctionalGroupsMap['SMARTSPattern']:
     370             MiscUtil.PrintWarning("Ignoring duplicate functional group name: %s..." % Name)
     371         else:
     372             FunctionalGroupsMap['Names'].append(Name)
     373             FunctionalGroupsMap['SMARTSPattern'][Name] = SMARTSPattern
     374
     375         RetrieveDataFromFunctionalGroupsHierarchy(FGNode.children)
     376
     377 def ListFunctionalGroupsInfo():
     378     """List functional groups information"""
     379
     380     MiscUtil.PrintInfo("\nListing available functional groups names and SMARTS patterns...")
     381     MiscUtil.PrintInfo("\nFunctionalGroupName\tSMARTSPattern")
     382
     383     for Name in sorted(FunctionalGroupsMap['Names']):
     384         SMARTSPattern = FunctionalGroupsMap['SMARTSPattern'][Name]
     385         MiscUtil.PrintInfo("%s\t%s" % (Name, SMARTSPattern))
     386
     387     MiscUtil.PrintInfo("")
     388
     389 def ProcessOptions():
     390     """Process and validate command line arguments and options"""
     391
     392     MiscUtil.PrintInfo("Processing options...")
     393
     394     # Validate options...
     395     ValidateOptions()
     396
     397     OptionsInfo["CombineMatches"] = Options["--combineMatches"]
     398
     399     OptionsInfo["CombineMatchResults"] = True
     400     if re.match("^No$", Options["--combineMatches"], re.I):
     401         OptionsInfo["CombineMatchResults"] = False
     402         if Options["--outfile"]:
     403             FileDir, FileName, FileExt = MiscUtil.ParseFileName(Options["--outfile"])
     404             OptionsInfo["OutfileBasename"] = FileName
     405             OptionsInfo["OutfileExt"] = FileExt
     406
     407     OptionsInfo["CombineOperator"] = Options["--combineOperator"]
     408     OptionsInfo["AndCombineOperatorMode"] = True
     409     if re.match("^or$", Options["--combineOperator"], re.I):
     410         OptionsInfo["AndCombineOperatorMode"] = False
     411
     412     OptionsInfo["GroupNamesFile"] = None
     413     if not re.match("^auto$", Options["--groupNamesFile"], re.I):
     414         OptionsInfo["GroupNamesFile"] = Options["--groupNamesFile"]
     415
     416     OptionsInfo["FunctionalGroups"] = Options["--functionalGroups"]
     417
     418     OptionsInfo["Infile"] = Options["--infile"]
     419     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], Options["--infile"])
     420
     421     OptionsInfo["Outfile"] = Options["--outfile"]
     422     OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters("--outfileParams", Options["--outfileParams"], Options["--infile"], Options["--outfile"])
     423
     424     OptionsInfo["Overwrite"] = Options["--overwrite"]
     425
     426     OptionsInfo["CountMode"] = False
     427     if re.match("^count$", Options["--mode"], re.I):
     428         OptionsInfo["CountMode"] = True
     429
     430     OptionsInfo["UseChirality"] = False
     431     if re.match("^yes$", Options["--useChirality"], re.I):
     432         OptionsInfo["UseChirality"] = True
     433
     434 def RetrieveOptions():
     435     """Retrieve command line arguments and options"""
     436
     437     # Get options...
     438     global Options
     439     Options = docopt(_docoptUsage_)
     440
     441     # Set current working directory to the specified directory...
     442     WorkingDir = Options["--workingdir"]
     443     if WorkingDir:
     444         os.chdir(WorkingDir)
     445
     446     # Handle examples option...
     447     if "--examples" in Options and Options["--examples"]:
     448         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
     449         sys.exit(0)
     450
     451     # Handle listing of functional group information...
     452     if  Options and Options["--list"]:
     453         ProcessListFunctionalGroupsOption()
     454         sys.exit(0)
     455
     456 def ProcessListFunctionalGroupsOption():
     457     """Process list functional groups information."""
     458
     459     # Validate and process dataFile option for listing functional groups information...
     460     OptionsInfo["GroupNamesFile"] = None
     461     if not re.match("^auto$", Options["--groupNamesFile"], re.I):
     462         MiscUtil.ValidateOptionFilePath("-g, --groupNamesFile", Options["--groupNamesFile"])
     463         OptionsInfo["GroupNamesFile"] = Options["--groupNamesFile"]
     464
     465     RetrieveFunctionalGroupsInfo()
     466     ListFunctionalGroupsInfo()
     467
     468 def ValidateOptions():
     469     """Validate option values"""
     470
     471     MiscUtil.ValidateOptionTextValue("-c, --combineMatches", Options["--combineMatches"], "yes no")
     472     MiscUtil.ValidateOptionTextValue("--combineOperator", Options["--combineOperator"], "and or")
     473
     474     if not re.match("^auto$", Options["--groupNamesFile"], re.I):
     475         MiscUtil.ValidateOptionFilePath("-g, groupNamesFile", Options["--groupNamesFile"])
     476
     477     if re.match("^none$", Options["--functionalGroups"], re.I):
     478         MiscUtil.PrintError("The name(s) of functional groups must be specified using \"-f, --functionalGroups\" option")
     479
     480     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
     481     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd smi txt csv tsv")
     482     if Options["--outfile"]:
     483         MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd smi")
     484         MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"])
     485         MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"])
     486
     487     MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "retrieve count")
     488     if re.match("^retrieve$", Options["--mode"], re.I):
     489         if not Options["--outfile"]:
     490             MiscUtil.PrintError("The outfile must be specified using \"-o, --outfile\" during \"retrieve\" value of \"-m, --mode\" option")
     491
     492     MiscUtil.ValidateOptionTextValue("--useChirality", Options["--useChirality"], "yes no")
     493
     494 # Setup a usage string for docopt...
     495 _docoptUsage_ = """
     496 RDKitSearchFunctionalGroups.py - Search for functional groups using SMARTS patterns
     497
     498 Usage:
     499     RDKitSearchFunctionalGroups.py  [--combineMatches <yes or no>] [--combineOperator <and or or>]
     500                                            [--groupNamesFile <FileName or auto>] [--infileParams <Name,Value,...>]
     501                                            [--mode <retrieve or count>] [--negate <yes or no>]
     502                                            [--outfileParams <Name,Value,...>] [--overwrite] [--useChirality <yes or no>]
     503                                            [-w <dir>] [-o <outfile>] -i <infile> -f <Name1,Name2,Name3... or All>
     504     RDKitSearchFunctionalGroups.py [--groupNamesFile <FileName or auto>] -l | --list
     505     RDKitSearchFunctionalGroups.py -h | --help | -e | --examples
     506
     507 Description:
     508     Perform a substructure search in an input file using SMARTS patterns for functional
     509     groups and write out the matched molecules to an output file or simply count the
     510     number of matches.
     511
     512     The SMARTS patterns for specified functional group(s) are retrieved from file,
     513     Functional_Group_Hierarchy.txt, available in RDKit data directory.
     514
     515     The names of valid functional groups and hierarchies  are dynamically retrieved from the
     516     functional groups hierarchy file and are shown below:
     517
     518         AcidChloride, AcidChloride.Aromatic, AcidChloride.Aliphatic
     519         Alcohol, Alcohol.Aromatic, Alcohol.Aliphatic
     520         Aldehyde, Aldehyde.Aromatic, Aldehyde.Aliphatic
     521         Amine, Amine.Primary, Amine.Primary.Aromatic, Amine.Primary.Aliphatic,
     522         Amine.Secondary, Amine.Secondary.Aromatic, Amine.Secondary.Aliphatic
     523         Amine.Tertiary, Amine.Tertiary.Aromatic, Amine.Tertiary.Aliphatic
     524         Amine.Aromatic, Amine.Aliphatic, Amine.Cyclic
     525         Azide, Azide.Aromatic, Azide.Aliphatic
     526         BoronicAcid, BoronicAcid.Aromatic, BoronicAcid.Aliphatic
     527         CarboxylicAcid, CarboxylicAcid.Aromatic, CarboxylicAcid.Aliphatic,
     528         CarboxylicAcid.AlphaAmino
     529         Halogen, Halogen.Aromatic, Halogen.Aliphatic
     530         Halogen.NotFluorine, Halogen.NotFluorine.Aliphatic,
     531         Halogen.NotFluorine.Aromatic
     532         Halogen.Bromine, Halogen.Bromine.Aliphatic, Halogen.Bromine.Aromatic,
     533         Halogen.Bromine.BromoKetone
     534         Isocyanate, Isocyanate.Aromatic, Isocyanate.Aliphatic
     535         Nitro, Nitro.Aromatic, Nitro.Aliphatic,
     536         SulfonylChloride, SulfonylChloride.Aromatic, SulfonylChloride.Aliphatic
     537         TerminalAlkyne
     538
     539     The supported input file formats are: SD (.sdf, .sd), SMILES (.smi, .csv, .tsv, .txt)
     540
     541     The supported output file formats are: SD (.sdf, .sd), SMILES (.smi)
     542
     543 Options:
     544     -c, --combineMatches <yes or no>  [default: yes]
     545         Combine search results for matching SMARTS patterns of specified functional groups
     546         against a molecule. Possible values: yes or no.
     547
     548         The matched molecules are written to a single output file for "yes" value. Otherwise,
     549         multiple output files are generated, one for each functional group. The names of
     550         these files correspond to a combination of the basename of the specified output file
     551         and the name of the functional group.
     552
     553         No output files are generated during "count" value of "-m, --mode" option.
     554     --combineOperator <and or or>  [default: and]
     555         Logical operator to use for combining match results corresponding to specified
     556         functional group names before writing out a single file. This option is ignored
     557         during "No" value of  "-c, --combineMatches" option.
     558     -e, --examples
     559         Print examples.
     560     -g, --groupNamesFile <FileName or auto>  [default: auto]
     561         Specify a file name containing data for functional groups hierarchy or use functional
     562         group hierarchy file, Functional_Group_Hierarchy.txt, available in RDKit data directory.
     563
     564         RDKit data format: Name<tab>Smarts<tab>Label<tab>RemovalReaction (optional)
     565
     566         The format of data in local functional group hierarchy must match format of the
     567         data in functional group file available in RDKit data directory.
     568     -f, --functionalGroups <Name1,Name2,Name3... or All>  [default: none]
     569         Functional group names for performing substructure SMARTS search. Possible values:
     570         Comma delimited list of valid functional group names or All. The current set of valid
     571         functional group names are listed in the description section.
     572
     573         The match results for multiple functional group names are combined using 'and'
     574         operator before writing them out to single file. No merging of match results takes
     575         place during generation of individual result files corresponding to fictional group
     576         names.
     577
     578         The functional group name may be started with an exclamation mark to negate
     579         the match result for that fictional group.
     580     -h, --help
     581         Print this help message.
     582     -i, --infile <infile>
     583         Input file name.
     584     --infileParams <Name,Value,...>  [default: auto]
     585         A comma delimited list of parameter name and value pairs for reading
     586         molecules from files. The supported parameter names for different file
     587         formats, along with their default values, are shown below:
     588
     589             SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
     590             SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
     591                 smilesTitleLine,auto,sanitize,yes
     592
     593         Possible values for smilesDelimiter: space, comma or tab.
     594     -l, --list
     595         List functional groups information without performing any search.
     596     -m, --mode <retrieve or count>  [default: retrieve]
     597         Specify whether to retrieve and write out matched molecules to an output
     598         file or simply count the number of matches.
     599     -o, --outfile <outfile>
     600         Output file name.
     601     --outfileParams <Name,Value,...>  [default: auto]
     602         A comma delimited list of parameter name and value pairs for writing
     603         molecules to files. The supported parameter names for different file
     604         formats, along with their default values, are shown below:
     605
     606             SD: compute2DCoords,auto,kekulize,no
     607             SMILES: kekulize,no,smilesDelimiter,space, smilesIsomeric,yes,
     608                 smilesTitleLine,yes
     609
     610         Default value for compute2DCoords: yes for SMILES input file; no for all other
     611         file types.
     612     --overwrite
     613         Overwrite existing files.
     614     -u, --useChirality <yes or no>  [default: no]
     615         Use stereochemistry information for SMARTS search.
     616     -w, --workingdir <dir>
     617         Location of working directory which defaults to the current directory.
     618
     619 Examples:
     620     To list names of all available functional groups along with their SMARTS patterns, type:
     621
     622         % RDKitSearchFunctionalGroups.py -l
     623
     624     To retrieve molecule containing amine functional group and write out a SMILES file, type:
     625
     626         % RDKitSearchFunctionalGroups.py -f Amine -i Sample.smi -o SampleOut.smi
     627
     628     To retrieve molecules containing amine functional group but not halogens and carboxylic
     629     acid functional groups and write out a SMILES file, type:
     630
     631         % RDKitSearchFunctionalGroups.py -f 'Amine,!Halogen,!CarboxylicAcid'
     632           -i Sample.smi -o SampleOut.smi
     633
     634     To retrieve molecules containing amine, halogens or carboxylic  acid functional groups
     635     and write out a SMILES file, type:
     636
     637         % RDKitSearchFunctionalGroups.py -f 'Amine,Halogen,CarboxylicAcid'
     638           --combineOperator or -i Sample.smi -o SampleOut.smi
     639
     640     To retrieve molecules containing amine and carboxylic acid functional groups defined in
     641     a local functional groups hierarchy file and write out individual SD files for each
     642     funcitonal group, type:
     643
     644         % RDKitSearchFunctionalGroups.py -f 'Amine,CarboxylicAcid' -i Sample.sdf
     645           -g Custom_Functional_Group_Hierarchy.txt --combineMatches No -o SampleOut.sdf
     646
     647     To count number of all functional groups in molecules without writing out an output
     648     files, type:
     649
     650         % RDKitSearchFunctionalGroups.py -m count -f All --combineMatches no -i Sample.smi
     651
     652     To retrieve molecule not containing aromatic alcohol and aromatic halogen functional
     653     group along with the use of chirality during substructure search and write out individual
     654     SMILES files for eeah functional group, type:
     655
     656         % RDKitSearchFunctionalGroups.py --combineMatches no -u yes
     657            -f '!Alcohol.Aromatic,!Halogen.Aromatic' -i Sample.smi -o SampleOut.smi
     658
     659     To retrieve molecule containing amine functional group from a CSV SMILES file,
     660     SMILES strings in column 1, name in column 2, and write out a SD file, type:
     661
     662         % RDKitSearchFunctionalGroups.py -f Amine --infileParams
     663           "smilesDelimiter,comma,smilesTitleLine,yes,smilesColumn,1,
     664           smilesNameColumn,2" --outfileParams "compute2DCoords,yes"
     665           -i SampleSMILES.csv -o SampleOut.sdf
     666
     667 Author:
     668     Manish Sud(msud@san.rr.com)
     669
     670 See also:
     671     RDKitConvertFileFormat.py, RDKitFilterPAINS.py, RDKitSearchSMARTS.py
     672
     673 Copyright:
     674     Copyright (C) 2018 Manish Sud. All rights reserved.
     675
     676     The functionality available in this script is implemented using RDKit, an
     677     open source toolkit for cheminformatics developed by Greg Landrum.
     678
     679     This file is part of MayaChemTools.
     680
     681     MayaChemTools is free software; you can redistribute it and/or modify it under
     682     the terms of the GNU Lesser General Public License as published by the Free
     683     Software Foundation; either version 3 of the License, or (at your option) any
     684     later version.
     685
     686 """
     687
     688 if __name__ == "__main__":
     689     main()