MayaChemTools

   1 #!/bin/env python
   2 #
   3 # File: RDKitSearchSMARTS.py
   4 # Author: Manish Sud <msud@san.rr.com>
   5 #
   6 # Copyright (C) 2026 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 import os
  32 import sys
  33 import time
  34 import re
  35 import multiprocessing as mp
  36 
  37 # RDKit imports...
  38 try:
  39     from rdkit import rdBase
  40     from rdkit import Chem
  41     from rdkit.Chem import AllChem
  42 except ImportError as ErrMsg:
  43     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
  44     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
  45     sys.exit(1)
  46 
  47 # MayaChemTools imports...
  48 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
  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 
  63 def main():
  64     """Start execution of the script."""
  65 
  66     MiscUtil.PrintInfo(
  67         "\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
  68         % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())
  69     )
  70 
  71     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
  72 
  73     # Retrieve command line arguments and options...
  74     RetrieveOptions()
  75 
  76     # Process and validate command line arguments and options...
  77     ProcessOptions()
  78 
  79     # Perform actions required by the script...
  80     PerformSearch()
  81 
  82     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
  83     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
  84 
  85 
  86 def PerformSearch():
  87     """Perform search using specified SMARTS pattern."""
  88 
  89     # Set up a pattern molecule...
  90     PatternMol = Chem.MolFromSmarts(OptionsInfo["Pattern"])
  91 
  92     # Setup a molecule reader...
  93     MiscUtil.PrintInfo("\nProcessing file %s..." % OptionsInfo["Infile"])
  94     Mols = RDKitUtil.ReadMolecules(OptionsInfo["Infile"], **OptionsInfo["InfileParams"])
  95 
  96     # Set up molecule writers...
  97     Writer, WriterFiltered = SetupMoleculeWriters()
  98 
  99     MolCount, ValidMolCount, RemainingMolCount = ProcessMolecules(Mols, PatternMol, Writer, WriterFiltered)
 100 
 101     if Writer is not None:
 102         Writer.close()
 103     if WriterFiltered is not None:
 104         WriterFiltered.close()
 105 
 106     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
 107     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
 108     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
 109 
 110     MiscUtil.PrintInfo("\nNumber of remaining molecules: %d" % RemainingMolCount)
 111     MiscUtil.PrintInfo("Number of filtered molecules: %d" % (ValidMolCount - RemainingMolCount))
 112 
 113 
 114 def ProcessMolecules(Mols, PatternMol, Writer, WriterFiltered):
 115     """Process and filter molecules."""
 116 
 117     if OptionsInfo["MPMode"]:
 118         return ProcessMoleculesUsingMultipleProcesses(Mols, PatternMol, Writer, WriterFiltered)
 119     else:
 120         return ProcessMoleculesUsingSingleProcess(Mols, PatternMol, Writer, WriterFiltered)
 121 
 122 
 123 def ProcessMoleculesUsingSingleProcess(Mols, PatternMol, Writer, WriterFiltered):
 124     """Process and filter molecules using a single process."""
 125 
 126     NegateMatch = OptionsInfo["NegateMatch"]
 127     OutfileFilteredMode = OptionsInfo["OutfileFilteredMode"]
 128     Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"]
 129     SetSMILESMolProps = OptionsInfo["OutfileParams"]["SetSMILESMolProps"]
 130 
 131     MiscUtil.PrintInfo("\nFiltering molecules...")
 132 
 133     (MolCount, ValidMolCount, RemainingMolCount) = [0] * 3
 134     FirstMol = True
 135     for Mol in Mols:
 136         MolCount += 1
 137 
 138         if Mol is None:
 139             continue
 140 
 141         if RDKitUtil.IsMolEmpty(Mol):
 142             MolName = RDKitUtil.GetMolName(Mol, MolCount)
 143             MiscUtil.PrintWarning("Ignoring empty molecule: %s" % MolName)
 144             continue
 145 
 146         ValidMolCount += 1
 147         if FirstMol:
 148             FirstMol = False
 149             if SetSMILESMolProps:
 150                 if Writer is not None:
 151                     RDKitUtil.SetWriterMolProps(Writer, Mol)
 152                 if WriterFiltered is not None:
 153                     RDKitUtil.SetWriterMolProps(WriterFiltered, Mol)
 154 
 155         MolMatched = DoesMoleculeContainsPattern(Mol, PatternMol)
 156         if MolMatched != NegateMatch:
 157             RemainingMolCount += 1
 158             WriteMolecule(Writer, Mol, Compute2DCoords)
 159         else:
 160             if OutfileFilteredMode:
 161                 WriteMolecule(WriterFiltered, Mol, Compute2DCoords)
 162 
 163     return (MolCount, ValidMolCount, RemainingMolCount)
 164 
 165 
 166 def ProcessMoleculesUsingMultipleProcesses(Mols, PatternMol, Writer, WriterFiltered):
 167     """Process and filter molecules using multiprocessing."""
 168 
 169     MiscUtil.PrintInfo("\nFiltering molecules using multiprocessing...")
 170 
 171     MPParams = OptionsInfo["MPParams"]
 172     NegateMatch = OptionsInfo["NegateMatch"]
 173     OutfileFilteredMode = OptionsInfo["OutfileFilteredMode"]
 174     Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"]
 175     SetSMILESMolProps = OptionsInfo["OutfileParams"]["SetSMILESMolProps"]
 176 
 177     # Setup data for initializing a worker process...
 178     MiscUtil.PrintInfo("Encoding options info and pattern molecule...")
 179     OptionsInfo["EncodedPatternMol"] = RDKitUtil.MolToBase64EncodedMolString(PatternMol)
 180     InitializeWorkerProcessArgs = (
 181         MiscUtil.ObjectToBase64EncodedString(Options),
 182         MiscUtil.ObjectToBase64EncodedString(OptionsInfo),
 183     )
 184 
 185     # Setup a encoded mols data iterable for a worker process...
 186     WorkerProcessDataIterable = RDKitUtil.GenerateBase64EncodedMolStrings(Mols)
 187 
 188     # Setup process pool along with data initialization for each process...
 189     MiscUtil.PrintInfo(
 190         "\nConfiguring multiprocessing using %s method..."
 191         % ("mp.Pool.imap()" if re.match("^Lazy$", MPParams["InputDataMode"], re.I) else "mp.Pool.map()")
 192     )
 193     MiscUtil.PrintInfo(
 194         "NumProcesses: %s; InputDataMode: %s; ChunkSize: %s\n"
 195         % (
 196             MPParams["NumProcesses"],
 197             MPParams["InputDataMode"],
 198             ("automatic" if MPParams["ChunkSize"] is None else MPParams["ChunkSize"]),
 199         )
 200     )
 201 
 202     ProcessPool = mp.Pool(MPParams["NumProcesses"], InitializeWorkerProcess, InitializeWorkerProcessArgs)
 203 
 204     # Start processing...
 205     if re.match("^Lazy$", MPParams["InputDataMode"], re.I):
 206         Results = ProcessPool.imap(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
 207     elif re.match("^InMemory$", MPParams["InputDataMode"], re.I):
 208         Results = ProcessPool.map(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
 209     else:
 210         MiscUtil.PrintError(
 211             'The value, %s, specified for "--inputDataMode" is not supported.' % (MPParams["InputDataMode"])
 212         )
 213 
 214     (MolCount, ValidMolCount, RemainingMolCount) = [0] * 3
 215     FirstMol = True
 216     for Result in Results:
 217         MolCount += 1
 218         MolIndex, EncodedMol, MolMatched = Result
 219 
 220         if EncodedMol is None:
 221             continue
 222         ValidMolCount += 1
 223 
 224         Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
 225 
 226         if FirstMol:
 227             FirstMol = False
 228             if SetSMILESMolProps:
 229                 if Writer is not None:
 230                     RDKitUtil.SetWriterMolProps(Writer, Mol)
 231                 if WriterFiltered is not None:
 232                     RDKitUtil.SetWriterMolProps(WriterFiltered, Mol)
 233 
 234         if MolMatched != NegateMatch:
 235             RemainingMolCount += 1
 236             WriteMolecule(Writer, Mol, Compute2DCoords)
 237         else:
 238             if OutfileFilteredMode:
 239                 WriteMolecule(WriterFiltered, Mol, Compute2DCoords)
 240 
 241     return (MolCount, ValidMolCount, RemainingMolCount)
 242 
 243 
 244 def InitializeWorkerProcess(*EncodedArgs):
 245     """Initialize data for a worker process."""
 246 
 247     global Options, OptionsInfo
 248 
 249     MiscUtil.PrintInfo("Starting process (PID: %s)..." % os.getpid())
 250 
 251     # Decode Options and OptionInfo...
 252     Options = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[0])
 253     OptionsInfo = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[1])
 254 
 255     # Decode PatternMol...
 256     OptionsInfo["PatternMol"] = RDKitUtil.MolFromBase64EncodedMolString(OptionsInfo["EncodedPatternMol"])
 257 
 258 
 259 def WorkerProcess(EncodedMolInfo):
 260     """Process data for a worker process."""
 261 
 262     MolIndex, EncodedMol = EncodedMolInfo
 263 
 264     if EncodedMol is None:
 265         return [MolIndex, None, False]
 266 
 267     Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
 268     if RDKitUtil.IsMolEmpty(Mol):
 269         MolName = RDKitUtil.GetMolName(Mol, (MolIndex + 1))
 270         MiscUtil.PrintWarning("Ignoring empty molecule: %s" % MolName)
 271         return [MolIndex, None, False]
 272 
 273     MolMatched = DoesMoleculeContainsPattern(Mol, OptionsInfo["PatternMol"])
 274 
 275     return [MolIndex, EncodedMol, MolMatched]
 276 
 277 
 278 def WriteMolecule(Writer, Mol, Compute2DCoords):
 279     """Write out molecule."""
 280 
 281     if OptionsInfo["CountMode"]:
 282         return
 283 
 284     if Compute2DCoords:
 285         AllChem.Compute2DCoords(Mol)
 286 
 287     Writer.write(Mol)
 288 
 289 
 290 def SetupMoleculeWriters():
 291     """Setup molecule writers."""
 292 
 293     Writer = None
 294     WriterFiltered = None
 295 
 296     if OptionsInfo["CountMode"]:
 297         return (Writer, WriterFiltered)
 298 
 299     Writer = RDKitUtil.MoleculesWriter(OptionsInfo["Outfile"], **OptionsInfo["OutfileParams"])
 300     if Writer is None:
 301         MiscUtil.PrintError("Failed to setup a writer for output fie %s " % OptionsInfo["Outfile"])
 302     MiscUtil.PrintInfo("Generating file %s..." % OptionsInfo["Outfile"])
 303 
 304     if OptionsInfo["OutfileFilteredMode"]:
 305         WriterFiltered = RDKitUtil.MoleculesWriter(OptionsInfo["OutfileFiltered"], **OptionsInfo["OutfileParams"])
 306         if WriterFiltered is None:
 307             MiscUtil.PrintError("Failed to setup a writer for output fie %s " % OptionsInfo["OutfileFiltered"])
 308         MiscUtil.PrintInfo("Generating file %s..." % OptionsInfo["OutfileFiltered"])
 309 
 310     return (Writer, WriterFiltered)
 311 
 312 
 313 def DoesMoleculeContainsPattern(Mol, PatternMol):
 314     """Check presence of pattern in the molecule."""
 315 
 316     return True if Mol.HasSubstructMatch(PatternMol, useChirality=OptionsInfo["UseChirality"]) else False
 317 
 318 
 319 def ProcessOptions():
 320     """Process and validate command line arguments and options."""
 321 
 322     MiscUtil.PrintInfo("Processing options...")
 323 
 324     # Validate options...
 325     ValidateOptions()
 326 
 327     OptionsInfo["Infile"] = Options["--infile"]
 328     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
 329         "--infileParams", Options["--infileParams"], Options["--infile"]
 330     )
 331 
 332     OptionsInfo["Outfile"] = Options["--outfile"]
 333     OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters(
 334         "--outfileParams", Options["--outfileParams"], Options["--infile"], Options["--outfile"]
 335     )
 336 
 337     OptionsInfo["OutfileFiltered"] = ""
 338     if Options["--outfile"]:
 339         FileDir, FileName, FileExt = MiscUtil.ParseFileName(Options["--outfile"])
 340         OutfileFiltered = "%s_Filtered.%s" % (FileName, FileExt)
 341         OptionsInfo["OutfileFiltered"] = OutfileFiltered
 342     OptionsInfo["OutfileFilteredMode"] = True if re.match("^yes$", Options["--outfileFiltered"], re.I) else False
 343 
 344     OptionsInfo["Overwrite"] = Options["--overwrite"]
 345 
 346     OptionsInfo["CountMode"] = True if re.match("^count$", Options["--mode"], re.I) else False
 347     OptionsInfo["NegateMatch"] = True if re.match("^yes$", Options["--negate"], re.I) else False
 348 
 349     OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
 350     OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
 351 
 352     OptionsInfo["Pattern"] = Options["--pattern"]
 353     OptionsInfo["UseChirality"] = True if re.match("^yes$", Options["--useChirality"], re.I) else False
 354 
 355 
 356 def RetrieveOptions():
 357     """Retrieve command line arguments and options."""
 358 
 359     # Get options...
 360     global Options
 361     Options = docopt(_docoptUsage_)
 362 
 363     # Set current working directory to the specified directory...
 364     WorkingDir = Options["--workingdir"]
 365     if WorkingDir:
 366         os.chdir(WorkingDir)
 367 
 368     # Handle examples option...
 369     if "--examples" in Options and Options["--examples"]:
 370         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
 371         sys.exit(0)
 372 
 373 
 374 def ValidateOptions():
 375     """Validate option values."""
 376 
 377     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
 378     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd smi txt csv tsv")
 379     if Options["--outfile"]:
 380         MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd smi")
 381         MiscUtil.ValidateOptionsOutputFileOverwrite(
 382             "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
 383         )
 384         MiscUtil.ValidateOptionsDistinctFileNames(
 385             "-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]
 386         )
 387 
 388     MiscUtil.ValidateOptionTextValue("--outfileFiltered", Options["--outfileFiltered"], "yes no")
 389 
 390     MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "retrieve count")
 391     if re.match("^retrieve$", Options["--mode"], re.I):
 392         if not Options["--outfile"]:
 393             MiscUtil.PrintError(
 394                 'The outfile must be specified using "-o, --outfile" during "retrieve" value of "-m, --mode" option'
 395             )
 396 
 397     MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
 398     MiscUtil.ValidateOptionTextValue("-n, --negate", Options["--negate"], "yes no")
 399 
 400     PatternMol = Chem.MolFromSmarts(Options["--pattern"])
 401     if PatternMol is None:
 402         MiscUtil.PrintError(
 403             'The value specified, %s, using option "-p, --pattern" is not a valid SMARTS: Failed to create pattern molecule'
 404             % Options["--pattern"]
 405         )
 406 
 407     MiscUtil.ValidateOptionTextValue("--useChirality", Options["--useChirality"], "yes no")
 408 
 409 
 410 # Setup a usage string for docopt...
 411 _docoptUsage_ = """
 412 RDKitSearchSMARTS.py - Perform a substructure search using SMARTS pattern
 413 
 414 Usage:
 415     RDKitSearchSMARTS.py  [--infileParams <Name,Value,...>] [--mode <retrieve or count>]
 416                           [--mp <yes or no>] [--mpParams <Name,Value,...>] [--negate <yes or no>]
 417                           [--outfileFiltered <yes or no>] [--outfileParams <Name,Value,...>] [--overwrite]
 418                           [--useChirality <yes or no>] [-w <dir>] [-o <outfile>] -p <SMARTS> -i <infile>
 419     RDKitSearchSMARTS.py -h | --help | -e | --examples
 420 
 421 Description:
 422     Perform a substructure search in an input file using specified SMARTS pattern and
 423     write out the matched molecules to an output file or simply count the number
 424     of matches.
 425 
 426     The supported input file formats are: SD (.sdf, .sd), SMILES (.smi., csv, .tsv, .txt)
 427 
 428     The supported output file formats are: SD (.sdf, .sd), SMILES (.smi)
 429 
 430 Options:
 431     -e, --examples
 432         Print examples.
 433     -h, --help
 434         Print this help message.
 435     -i, --infile <infile>
 436         Input file name.
 437     --infileParams <Name,Value,...>  [default: auto]
 438         A comma delimited list of parameter name and value pairs for reading
 439         molecules from files. The supported parameter names for different file
 440         formats, along with their default values, are shown below:
 441             
 442             SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
 443             SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
 444                 smilesTitleLine,auto,sanitize,yes
 445             
 446         Possible values for smilesDelimiter: space, comma or tab.
 447     -m, --mode <retrieve or count>  [default: retrieve]
 448         Specify whether to retrieve and write out matched molecules to an output
 449         file or simply count the number of matches.
 450     --mp <yes or no>  [default: no]
 451         Use multiprocessing.
 452          
 453         By default, input data is retrieved in a lazy manner via mp.Pool.imap()
 454         function employing lazy RDKit data iterable. This allows processing of
 455         arbitrary large data sets without any additional requirements memory.
 456         
 457         All input data may be optionally loaded into memory by mp.Pool.map()
 458         before starting worker processes in a process pool by setting the value
 459         of 'inputDataMode' to 'InMemory' in '--mpParams' option.
 460         
 461         A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
 462         data mode may adversely impact the performance. The '--mpParams' section
 463         provides additional information to tune the value of 'chunkSize'.
 464     --mpParams <Name,Value,...>  [default: auto]
 465         A comma delimited list of parameter name and value pairs to configure
 466         multiprocessing.
 467         
 468         The supported parameter names along with their default and possible
 469         values are shown below:
 470         
 471             chunkSize, auto
 472             inputDataMode, Lazy   [ Possible values: InMemory or Lazy ]
 473             numProcesses, auto   [ Default: mp.cpu_count() ]
 474         
 475         These parameters are used by the following functions to configure and
 476         control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
 477         mp.Pool.imap().
 478         
 479         The chunkSize determines chunks of input data passed to each worker
 480         process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
 481         The default value of chunkSize is dependent on the value of 'inputDataMode'.
 482         
 483         The mp.Pool.map() function, invoked during 'InMemory' input data mode,
 484         automatically converts RDKit data iterable into a list, loads all data into
 485         memory, and calculates the default chunkSize using the following method
 486         as shown in its code:
 487         
 488             chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
 489             if extra: chunkSize += 1
 490         
 491         For example, the default chunkSize will be 7 for a pool of 4 worker processes
 492         and 100 data items.
 493         
 494         The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
 495         'lazy' RDKit data iterable to retrieve data as needed, without loading all the
 496         data into memory. Consequently, the size of input data is not known a priori.
 497         It's not possible to estimate an optimal value for the chunkSize. The default 
 498         chunkSize is set to 1.
 499         
 500         The default value for the chunkSize during 'Lazy' data mode may adversely
 501         impact the performance due to the overhead associated with exchanging
 502         small chunks of data. It is generally a good idea to explicitly set chunkSize to
 503         a larger value during 'Lazy' input data mode, based on the size of your input
 504         data and number of processes in the process pool.
 505         
 506         The mp.Pool.map() function waits for all worker processes to process all
 507         the data and return the results. The mp.Pool.imap() function, however,
 508         returns the the results obtained from worker processes as soon as the
 509         results become available for specified chunks of data.
 510         
 511         The order of data in the results returned by both mp.Pool.map() and 
 512         mp.Pool.imap() functions always corresponds to the input data.
 513     -n, --negate <yes or no>  [default: no]
 514         Specify whether to find molecules not matching the specified SMARTS pattern.
 515     -o, --outfile <outfile>
 516         Output file name.
 517     --outfileFiltered <yes or no>  [default: no]
 518         Write out a file containing filtered molecules. Its name is automatically
 519         generated from the specified output file. Default: <OutfileRoot>_
 520         Filtered.<OutfileExt>.
 521     --outfileParams <Name,Value,...>  [default: auto]
 522         A comma delimited list of parameter name and value pairs for writing
 523         molecules to files. The supported parameter names for different file
 524         formats, along with their default values, are shown below:
 525             
 526             SD: compute2DCoords,auto,kekulize,yes,forceV3000,no
 527             SMILES: smilesKekulize,no,smilesDelimiter,space, smilesIsomeric,yes,
 528                 smilesTitleLine,yes,smilesMolName,yes,smilesMolProps,no
 529             
 530         Default value for compute2DCoords: yes for SMILES input file; no for all other
 531         file types.
 532     --overwrite
 533         Overwrite existing files.
 534     -p, --pattern <SMARTS>  [default: none]
 535         SMARTS pattern for performing search.
 536     -u, --useChirality <yes or no>  [default: no]
 537         Use stereochemistry information for SMARTS search.
 538     -w, --workingdir <dir>
 539         Location of working directory which defaults to the current directory.
 540 
 541 Examples:
 542     To retrieve molecules containing the substructure corresponding to a specified
 543     SMARTS pattern and write out a SMILES file, type: 
 544 
 545         % RDKitSearchSMARTS.py -p 'c1ccccc1' -i Sample.smi -o SampleOut.smi
 546 
 547     To retrieve molecules containing the substructure corresponding to a specified
 548     SMARTS pattern,  perform filtering in multiprocessing mode on all available
 549     CPUs without loading all data into memory, and write out a SMILES file, type: 
 550 
 551         % RDKitSearchSMARTS.py --mp yes -p 'c1ccccc1' -i Sample.smi -o SampleOut.smi
 552 
 553     To retrieve molecules containing the substructure corresponding to a specified
 554     SMARTS pattern,  perform filtering in multiprocessing mode on all available
 555     CPUs by loading all data into memory, and write out a SMILES file, type: 
 556 
 557         % RDKitSearchSMARTS.py --mp yes --mpParams "inputDataMode,InMemory"
 558           -p 'c1ccccc1' -i Sample.smi -o SampleOut.smi
 559 
 560     To retrieve molecules containing the substructure corresponding to a specified
 561     SMARTS pattern,  perform filtering in multiprocessing mode on specific number
 562     of CPUs and chunk size without loading all data into memory, and write out
 563     a SMILES file, type: 
 564 
 565         % RDKitSearchSMARTS.py --mp yes --mpParams "inputDataMode,Lazy,
 566           numProcesses,4,chunkSize,8" -p 'c1ccccc1' -i Sample.smi -o SampleOut.smi
 567 
 568     To only count the number of molecules containing the substructure corresponding
 569     to a specified SMARTS pattern without writing out any file, type: 
 570 
 571         % RDKitSearchSMARTS.py -m count -p 'c1ccccc1' -i Sample.smi
 572 
 573     To count the number of molecules in a SD file not containing the substructure
 574     corresponding to a specified SMARTS pattern and write out a SD file, type: 
 575 
 576         % RDKitSearchSMARTS.py -n yes -p 'c1ccccc1' -i Sample.sdf -o SampleOut.sdf
 577 
 578     To retrieve molecules containing the substructure corresponding to a specified
 579     SMARTS pattern from a CSV SMILES file, SMILES strings in column 1, name in
 580     and write out a SD file, type: 
 581 
 582         % RDKitSearchSMARTS.py -p 'c1ccccc1' --infileParams
 583           "smilesDelimiter,comma,smilesTitleLine,yes,smilesColumn,1,
 584           smilesNameColumn,2" --outfileParams "compute2DCoords,yes"
 585           -i SampleSMILES.csv -o SampleOut.sdf
 586 
 587 Author:
 588     Manish Sud(msud@san.rr.com)
 589 
 590 See also:
 591     RDKitConvertFileFormat.py, RDKitFilterPAINS.py, RDKitSearchFunctionalGroups.py 
 592 
 593 Copyright:
 594     Copyright (C) 2026 Manish Sud. All rights reserved.
 595 
 596     The functionality available in this script is implemented using RDKit, an
 597     open source toolkit for cheminformatics developed by Greg Landrum.
 598 
 599     This file is part of MayaChemTools.
 600 
 601     MayaChemTools is free software; you can redistribute it and/or modify it under
 602     the terms of the GNU Lesser General Public License as published by the Free
 603     Software Foundation; either version 3 of the License, or (at your option) any
 604     later version.
 605 
 606 """
 607 
 608 if __name__ == "__main__":
 609     main()