1 #!/bin/env python 2 # 3 # File: RDKitEnumerateCompoundLibrary.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 36 # RDKit imports... 37 try: 38 from rdkit import rdBase 39 from rdkit import Chem 40 from rdkit.Chem import AllChem 41 except ImportError as ErrMsg: 42 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg) 43 sys.stderr.write("Check/update your RDKit environment and try again.\n\n") 44 sys.exit(1) 45 46 # MayaChemTools imports... 47 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python")) 48 try: 49 from docopt import docopt 50 import MiscUtil 51 import RDKitUtil 52 except ImportError as ErrMsg: 53 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg) 54 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n") 55 sys.exit(1) 56 57 ScriptName = os.path.basename(sys.argv[0]) 58 Options = {} 59 OptionsInfo = {} 60 61 RxnNamesMap = {} 62 63 64 def main(): 65 """Start execution of the script.""" 66 67 MiscUtil.PrintInfo( 68 "\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n" 69 % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime()) 70 ) 71 72 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime() 73 74 # Retrieve command line arguments and options... 75 RetrieveOptions() 76 77 # Process and validate command line arguments and options... 78 ProcessOptions() 79 80 # Perform actions required by the script... 81 PerformChemicalLibraryEnumeration() 82 83 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName) 84 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime)) 85 86 87 def PerformChemicalLibraryEnumeration(): 88 """Retrieve functional groups information and perform search.""" 89 90 ProcessReactionNamesInfo() 91 PerformEnumeration() 92 93 94 def PerformEnumeration(): 95 """Enumerate virutal compound library.""" 96 97 ReactantFilesList = OptionsInfo["ReactantFilesList"] 98 Outfile = OptionsInfo["Outfile"] 99 100 RxnByNameMode = OptionsInfo["RxnByNameMode"] 101 if RxnByNameMode: 102 RxnSMARTSPattern = OptionsInfo["RxnNameSMARTS"] 103 else: 104 RxnSMARTSPattern = OptionsInfo["SpecifiedSMARTS"] 105 106 # Set up a reaction and match number of reactants in rxn SMARTS against number of 107 # reactant files... 108 MiscUtil.PrintInfo("\nValidating reaction SMARTS...") 109 try: 110 Rxn = AllChem.ReactionFromSmarts(RxnSMARTSPattern) 111 except Exception as ErrMsg: 112 MiscUtil.PrintError("Failed to validate reaction SMARTS %s\n%s\n" % (RxnSMARTSPattern, ErrMsg)) 113 114 RxnReactantsCount = Rxn.GetNumReactantTemplates() 115 116 ReactantFilesList = OptionsInfo["ReactantFilesList"] 117 ReactantFilesCount = len(ReactantFilesList) 118 if ReactantFilesCount != RxnReactantsCount: 119 MiscUtil.PrintError( 120 "The number of specified reactant files, %d, must match number of reactants, %d, in reaction SMARTS" 121 % (ReactantFilesCount, RxnReactantsCount) 122 ) 123 124 # Retrieve reactant molecules... 125 ReactantsMolsList = RetrieveReactantsMolecules() 126 127 # Set up a molecule writer... 128 Writer = None 129 Writer = RDKitUtil.MoleculesWriter(Outfile, **OptionsInfo["OutfileParams"]) 130 if Writer is None: 131 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile) 132 133 MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile) 134 135 # Set up reaction... 136 ReturnReactants = False 137 if OptionsInfo["UseReactantNames"]: 138 ReturnReactants = True 139 RxnProducts = AllChem.EnumerateLibraryFromReaction(Rxn, ReactantsMolsList, ReturnReactants) 140 141 # Generate product molecules and write them out... 142 143 Compute2DCoords = OptionsInfo["Compute2DCoords"] 144 Sanitize = OptionsInfo["Sanitize"] 145 146 ProdMolCount = 0 147 ValidProdMolCount = 0 148 149 if ReturnReactants: 150 for Products, Reactants in list(RxnProducts): 151 for ProdMol in Products: 152 ProdMolCount += 1 153 154 # Set product name... 155 ReactantMolNames = [ReactantMol.GetProp("_Name") for ReactantMol in Reactants] 156 Delimiter = "_" 157 ProdMolName = Delimiter.join(ReactantMolNames) + "_Prod%d" % ProdMolCount 158 ProdMol.SetProp("_Name", ProdMolName) 159 160 Status = WriteProductMolecule(Writer, ProdMol, Sanitize, Compute2DCoords) 161 if Status: 162 ValidProdMolCount += 1 163 else: 164 for Products in list(RxnProducts): 165 for ProdMol in Products: 166 ProdMolCount += 1 167 168 # Set product name... 169 ProdMolName = "Prod%d" % ProdMolCount 170 ProdMol.SetProp("_Name", ProdMolName) 171 172 Status = WriteProductMolecule(Writer, ProdMol, Sanitize, Compute2DCoords) 173 if Status: 174 ValidProdMolCount += 1 175 176 if Writer is not None: 177 Writer.close() 178 179 if ValidProdMolCount: 180 MiscUtil.PrintInfo("\nTotal number of product molecules: %d" % ProdMolCount) 181 MiscUtil.PrintInfo("Number of valid product molecules: %d" % ValidProdMolCount) 182 MiscUtil.PrintInfo("Number of ignored product molecules: %d" % (ProdMolCount - ValidProdMolCount)) 183 else: 184 MiscUtil.PrintInfo( 185 "\nThe compound library enumeration failed to generate any product molecules.\nCheck to make sure the reactants specified in input files match their corresponding specifications in reaction SMARTS and try again." 186 ) 187 188 189 def WriteProductMolecule(Writer, ProdMol, Sanitize, Compute2DCoords): 190 """Prepare and write out product molecule.""" 191 192 try: 193 if Sanitize: 194 Chem.SanitizeMol(ProdMol) 195 except (RuntimeError, ValueError): 196 MiscUtil.PrintWarning("Ignoring product molecule: Failed to sanitize...\n") 197 return False 198 199 try: 200 if Compute2DCoords: 201 AllChem.Compute2DCoords(ProdMol) 202 except (RuntimeError, ValueError): 203 MiscUtil.PrintWarning("Ignoring product molecule: Failed to compute 2D coordinates...\n") 204 return False 205 206 Writer.write(ProdMol) 207 208 return True 209 210 211 def RetrieveReactantsMolecules(): 212 """Retrieve reactant molecules from each reactant file and return a list containing lists of molecules 213 for each reactant file.""" 214 215 MiscUtil.PrintInfo("\nProcessing reactant file(s)...") 216 217 ReactantsMolsList = [] 218 ReactantFilesList = OptionsInfo["ReactantFilesList"] 219 UseReactantNames = OptionsInfo["UseReactantNames"] 220 ReactantCount = 0 221 222 for FileIndex in range(0, len(ReactantFilesList)): 223 ReactantCount += 1 224 ReactantFile = ReactantFilesList[FileIndex] 225 226 MiscUtil.PrintInfo("\nProcessing reactant file: %s..." % ReactantFile) 227 228 Mols = RDKitUtil.ReadMolecules(ReactantFile, **OptionsInfo["InfileParams"]) 229 230 ValidMols = [] 231 MolCount = 0 232 ValidMolCount = 0 233 234 for Mol in Mols: 235 MolCount += 1 236 if Mol is None: 237 continue 238 239 if RDKitUtil.IsMolEmpty(Mol): 240 MolName = RDKitUtil.GetMolName(Mol, MolCount) 241 MiscUtil.PrintWarning("Ignoring empty molecule: %s" % MolName) 242 continue 243 244 ValidMolCount += 1 245 246 # Check and set mol name... 247 if UseReactantNames: 248 MolName = RDKitUtil.GetMolName(Mol) 249 if not len(MolName): 250 MolName = "React%dMol%d" % (ReactantCount, MolCount) 251 Mol.SetProp("_Name", MolName) 252 253 ValidMols.append(Mol) 254 255 ReactantsMolsList.append(ValidMols) 256 257 MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount) 258 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount) 259 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount)) 260 261 return ReactantsMolsList 262 263 264 def ProcessReactionNamesInfo(): 265 """Process reaction names information.""" 266 267 if not OptionsInfo["RxnByNameMode"]: 268 return 269 270 RetrieveReactionNamesInfo() 271 ProcessSpecifiedReactionName() 272 273 274 def ProcessSpecifiedReactionName(): 275 """Process and validate specified reaction name.""" 276 277 OptionsInfo["RxnNameSMARTS"] = None 278 279 # Set up a map of valid group rxn names for checking specified rxn names... 280 CanonicalRxnNameMap = {} 281 for Name in RxnNamesMap["Names"]: 282 CanonicalRxnNameMap[Name.lower()] = Name 283 284 CanonicalRxnName = OptionsInfo["RxnName"].lower() 285 if CanonicalRxnName in CanonicalRxnNameMap: 286 Name = CanonicalRxnNameMap[CanonicalRxnName] 287 OptionsInfo["RxnNameSMARTS"] = RxnNamesMap["SMARTSPattern"][Name] 288 else: 289 MiscUtil.PrintError( 290 'The rxn name name, %s, specified using "-r, --rxnName" option is not a valid name.' 291 % (OptionsInfo["RxnName"]) 292 ) 293 294 295 def ProcessListReactionNamesOption(): 296 """Process list reaction names information.""" 297 298 ProcessReactionNamesFileOption() 299 ProcessColumnOptions() 300 301 RetrieveReactionNamesInfo() 302 ListAndValidateReactionNamesInfo() 303 304 305 def RetrieveReactionNamesInfo(): 306 """Retrieve reaction names information.""" 307 308 RxnNamesFile = OptionsInfo["RxnNamesFile"] 309 310 MiscUtil.PrintInfo("\nRetrieving reaction names and SMARTS patterns from file %s" % (RxnNamesFile)) 311 312 if not os.path.exists(RxnNamesFile): 313 MiscUtil.PrintError("The reaction names file, %s, doesn't exist.\n" % (RxnNamesFile)) 314 315 IgnoreHeaderLine = True 316 RxnLinesWords = MiscUtil.GetTextLinesWords( 317 RxnNamesFile, OptionsInfo["RxnNamesFileDelimiter"], OptionsInfo["RxnNamesFileQuote"], IgnoreHeaderLine 318 ) 319 320 RxnNamesMap["Names"] = [] 321 RxnNamesMap["SMARTSPattern"] = {} 322 323 RxnNameColindex = OptionsInfo["RxnNameColnum"] - 1 324 RxnSMARTSColindex = OptionsInfo["RxnSMARTSColnum"] - 1 325 326 for LineWords in RxnLinesWords: 327 Name = LineWords[RxnNameColindex] 328 SMARTSPattern = LineWords[RxnSMARTSColindex] 329 330 if Name in RxnNamesMap["SMARTSPattern"]: 331 MiscUtil.PrintWarning("Ignoring duplicate reaction name: %s..." % Name) 332 else: 333 RxnNamesMap["Names"].append(Name) 334 RxnNamesMap["SMARTSPattern"][Name] = SMARTSPattern 335 336 if not len(RxnNamesMap["Names"]): 337 MiscUtil.PrintError("Failed to retrieve any reaction names and SMARTS patterns...") 338 339 MiscUtil.PrintInfo( 340 "Total number of reactions present in reaction names and SMARTS file: %d" % (len(RxnNamesMap["Names"])) 341 ) 342 343 344 def ListAndValidateReactionNamesInfo(): 345 """List and validate reaction names information.""" 346 347 ListReactionNamesInfo() 348 ValidateReactionNamesInfo() 349 350 351 def ListReactionNamesInfo(): 352 """List reaction names information.""" 353 354 MiscUtil.PrintInfo("\nListing available reaction names and SMARTS patterns...") 355 MiscUtil.PrintInfo("\nReactionName\tSMARTSPattern") 356 357 RxnCount = 0 358 for Name in sorted(RxnNamesMap["Names"]): 359 RxnCount += 1 360 SMARTSPattern = RxnNamesMap["SMARTSPattern"][Name] 361 MiscUtil.PrintInfo("%s\t%s" % (Name, SMARTSPattern)) 362 363 MiscUtil.PrintInfo("\nTotal number of reactions: %s" % RxnCount) 364 365 366 def ValidateReactionNamesInfo(): 367 """Validate reaction names information.""" 368 369 MiscUtil.PrintInfo("\nValidating reaction SMARTS patterns...") 370 371 RxnCount = 0 372 ValidRxnCount = 0 373 for Name in sorted(RxnNamesMap["Names"]): 374 RxnCount += 1 375 SMARTSPattern = RxnNamesMap["SMARTSPattern"][Name] 376 try: 377 AllChem.ReactionFromSmarts(SMARTSPattern) 378 ValidRxnCount += 1 379 except Exception as ErrMsg: 380 MiscUtil.PrintInfo( 381 "\nFailed to validate reaction SMARTS. ReactionName: %s; SMARTSPattern: %s\n%s\n" 382 % (Name, SMARTSPattern, ErrMsg) 383 ) 384 385 InvalidRxnCount = RxnCount - ValidRxnCount 386 MiscUtil.PrintInfo( 387 "\nTotal number of reactions: %s\nNumber of valid reactions: %s\nNumber of invalid reactions: %s" 388 % (RxnCount, ValidRxnCount, InvalidRxnCount) 389 ) 390 391 MiscUtil.PrintInfo("") 392 393 394 def ProcessReactionNamesFileOption(): 395 """Process reaction names file option.""" 396 397 RxnNamesFile = None 398 if not re.match("^auto$", Options["--rxnNamesFile"], re.I): 399 MiscUtil.ValidateOptionFilePath("--rxnNamesFile", Options["--rxnNamesFile"]) 400 RxnNamesFile = Options["--rxnNamesFile"] 401 402 if RxnNamesFile is None: 403 MayaChemToolsDataDir = MiscUtil.GetMayaChemToolsLibDataPath() 404 RxnNamesFile = os.path.join(MayaChemToolsDataDir, "ReactionNamesAndSMARTS.csv") 405 406 OptionsInfo["RxnNamesFile"] = RxnNamesFile 407 OptionsInfo["RxnNamesFileDelimiter"] = "," 408 OptionsInfo["RxnNamesFileQuote"] = '"' 409 410 411 def ProcessColumnOptions(): 412 """Process column options.""" 413 414 ProcessColumnModeOption() 415 RetrieveColumnNames() 416 417 ProcessReactionNameColOption() 418 ProcessReactionSMARTSColOption() 419 420 421 def ProcessColumnModeOption(): 422 """Process column mode option.""" 423 424 CollabelMode, ColnumMode = [False, False] 425 Colmode = Options["--colmode"] 426 if re.match("^collabel$", Colmode, re.I): 427 CollabelMode = True 428 elif re.match("^colnum$", Colmode, re.I): 429 ColnumMode = True 430 else: 431 MiscUtil.PrintError( 432 'The value, %s, specified for option "-c, --colmode" is not valid. Supported values: collabel or colnum\n' 433 % (Colmode) 434 ) 435 436 OptionsInfo["Colmode"] = Colmode 437 OptionsInfo["CollabelMode"] = CollabelMode 438 OptionsInfo["ColnumMode"] = ColnumMode 439 440 441 def RetrieveColumnNames(): 442 """Retrieve column names.""" 443 444 RxnNamesFile = OptionsInfo["RxnNamesFile"] 445 IgnoreHeaderLine = False 446 RxnLinesWords = MiscUtil.GetTextLinesWords( 447 RxnNamesFile, OptionsInfo["RxnNamesFileDelimiter"], OptionsInfo["RxnNamesFileQuote"], IgnoreHeaderLine 448 ) 449 Colnames = RxnLinesWords[0] 450 451 if len(Colnames) == 0: 452 MiscUtil.PrintError( 453 "The first line in reaction names, %s, is empty. It must contain column names.\n" 454 % OptionsInfo["RxnNamesFile"] 455 ) 456 457 ColnameToColnumMap = {} 458 ColnumToColnameMap = {} 459 for ColIndex, Colname in enumerate(Colnames): 460 Colnum = ColIndex + 1 461 ColnameToColnumMap[Colname] = Colnum 462 ColnumToColnameMap[Colnum] = Colname 463 464 OptionsInfo["Colnames"] = Colnames 465 OptionsInfo["ColCount"] = len(Colnames) 466 OptionsInfo["ColnameToColnumMap"] = ColnameToColnumMap 467 OptionsInfo["ColnumToColnameMap"] = ColnumToColnameMap 468 469 470 def ProcessReactionNameColOption(): 471 """Process reaction name column option.""" 472 473 RxnNameCol = Options["--colRxnName"] 474 if re.match("^auto$", RxnNameCol, re.I): 475 Colname = "RxnName" 476 if Colname not in OptionsInfo["ColnameToColnumMap"]: 477 MiscUtil.PrintError( 478 'The reaction name column name, %s, doen\'t exist in reaction names file. You must specify a valid reaction name column name or number using "--colRxnName" option.\n' 479 % Colname 480 ) 481 482 Colnum = OptionsInfo["ColnameToColnumMap"][Colname] 483 RxnNameColSpec = Colnum if OptionsInfo["ColnumMode"] else Colname 484 else: 485 RxnNameColSpec = RxnNameCol 486 487 RxnNameColname, RxnNameColnum = ProcessColumnSpecification("--colRxnName", RxnNameColSpec) 488 489 OptionsInfo["RxnNameCol"] = RxnNameCol 490 OptionsInfo["RxnNameColname"] = RxnNameColname 491 OptionsInfo["RxnNameColnum"] = RxnNameColnum 492 493 494 def ProcessReactionSMARTSColOption(): 495 """Process reaction SMARTS column option.""" 496 497 RxnSMARTSCol = Options["--colRxnSMARTS"] 498 if re.match("^auto$", RxnSMARTSCol, re.I): 499 Colname = "RxnSMARTS" 500 if Colname not in OptionsInfo["ColnameToColnumMap"]: 501 MiscUtil.PrintError( 502 'The reaction SMARTS column name, %s, doen\'t exist in reaction names file. You must specify a valid reaction name column name or number using "--colRxnSMARTS" option.\n' 503 % Colname 504 ) 505 506 Colnum = OptionsInfo["ColnameToColnumMap"][Colname] 507 RxnSMARTSColSpec = Colnum if OptionsInfo["ColnumMode"] else Colname 508 else: 509 RxnSMARTSColSpec = RxnSMARTSCol 510 511 RxnSMARTSColname, RxnSMARTSColnum = ProcessColumnSpecification("--colRxnSMARTS", RxnSMARTSColSpec) 512 513 OptionsInfo["RxnSMARTSCol"] = RxnSMARTSCol 514 OptionsInfo["RxnSMARTSColname"] = RxnSMARTSColname 515 OptionsInfo["RxnSMARTSColnum"] = RxnSMARTSColnum 516 517 518 def ProcessColumnSpecification(OptionName, Colspec): 519 """Process column specification corresponding to a column name or number.""" 520 521 Colname, Colnum = [None, None] 522 if OptionsInfo["ColnumMode"]: 523 Colnum = int(Colspec) 524 if Colnum not in OptionsInfo["ColnumToColnameMap"]: 525 MiscUtil.PrintError( 526 'The column number, %s, specified using "%s" option doesn\'t exist in reaction names file. You must specify a valid column number. Valid values: >= 1 and <= %s\n' 527 % (Colnum, OptionName, OptionsInfo["ColCount"]) 528 ) 529 Colname = OptionsInfo["ColnumToColnameMap"][Colnum] 530 else: 531 Colname = Colspec 532 if Colname not in OptionsInfo["ColnameToColnumMap"]: 533 MiscUtil.PrintError( 534 'The column name, %s, specified using "%s" option doesn\'t exist in input file. You must specify a valid column name. Valid values: %s\n' 535 % (Colname, OptionName, " ".join(OptionsInfo["Colnames"])) 536 ) 537 Colnum = OptionsInfo["ColnameToColnumMap"][Colname] 538 539 return (Colname, Colnum) 540 541 542 def ProcessOptions(): 543 """Process and validate command line arguments and options.""" 544 545 MiscUtil.PrintInfo("Processing options...") 546 547 # Validate options... 548 ValidateOptions() 549 550 Compute2DCoords = True 551 if not re.match("^yes$", Options["--compute2DCoords"], re.I): 552 Compute2DCoords = False 553 OptionsInfo["Compute2DCoords"] = Compute2DCoords 554 555 OptionsInfo["Mode"] = Options["--mode"] 556 RxnByNameMode = True 557 if not re.match("^RxnByName$", Options["--mode"], re.I): 558 RxnByNameMode = False 559 OptionsInfo["RxnByNameMode"] = RxnByNameMode 560 561 OptionsInfo["ProdMolNamesMode"] = Options["--prodMolNames"] 562 UseReactantNames = False 563 if re.match("^UseReactants$", Options["--prodMolNames"], re.I): 564 UseReactantNames = True 565 OptionsInfo["UseReactantNames"] = UseReactantNames 566 567 OptionsInfo["RxnName"] = Options["--rxnName"] 568 OptionsInfo["RxnNameSMARTS"] = None 569 if OptionsInfo["RxnByNameMode"]: 570 if not Options["--rxnName"]: 571 MiscUtil.PrintError( 572 'No rxn name specified using "-r, --rxnName" option during "RxnByName" value of "-m, --mode" option' 573 ) 574 575 ProcessReactionNamesFileOption() 576 ProcessColumnOptions() 577 578 ReactantFiles = re.sub(" ", "", Options["--infiles"]) 579 ReactantFilesList = [] 580 ReactantFilesList = ReactantFiles.split(",") 581 OptionsInfo["ReactantFiles"] = ReactantFiles 582 OptionsInfo["ReactantFilesList"] = ReactantFilesList 583 584 OptionsInfo["SpecifiedSMARTS"] = Options["--smartsRxn"] 585 if not OptionsInfo["RxnByNameMode"]: 586 if not Options["--smartsRxn"]: 587 MiscUtil.PrintError( 588 'No rxn SMARTS pattern specified using "-r, --rxnName" option during "RxnByName" value of "-m, --mode" option' 589 ) 590 591 OptionsInfo["Outfile"] = Options["--outfile"] 592 OptionsInfo["Overwrite"] = Options["--overwrite"] 593 594 # Use first reactant file as input file as all input files have the same format... 595 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters( 596 "--infileParams", Options["--infileParams"], ReactantFilesList[0] 597 ) 598 599 # No need to pass any input or output file name due to absence of any auto parameter... 600 OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters( 601 "--outfileParams", Options["--outfileParams"] 602 ) 603 604 Sanitize = True 605 if not re.match("^yes$", Options["--sanitize"], re.I): 606 Sanitize = False 607 OptionsInfo["Sanitize"] = Sanitize 608 609 610 def RetrieveOptions(): 611 """Retrieve command line arguments and options.""" 612 613 # Get options... 614 global Options 615 Options = docopt(_docoptUsage_) 616 617 # Set current working directory to the specified directory... 618 WorkingDir = Options["--workingdir"] 619 if WorkingDir: 620 os.chdir(WorkingDir) 621 622 # Handle examples option... 623 if "--examples" in Options and Options["--examples"]: 624 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_)) 625 sys.exit(0) 626 627 # Handle listing of functional group information... 628 if Options and Options["--list"]: 629 ProcessListReactionNamesOption() 630 sys.exit(0) 631 632 633 def ValidateOptions(): 634 """Validate option values.""" 635 636 MiscUtil.ValidateOptionTextValue("-c, --colmode", Options["--colmode"], "collabel colnum") 637 638 MiscUtil.ValidateOptionTextValue("--compute2DCoords", Options["--compute2DCoords"], "yes no") 639 640 MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "RxnByName RxnBySMARTS") 641 MiscUtil.ValidateOptionTextValue("-p, --prodMolNames", Options["--prodMolNames"], "UseReactants Sequential") 642 643 if not re.match("^auto$", Options["--rxnNamesFile"], re.I): 644 MiscUtil.ValidateOptionFilePath("--rxnNamesFile", Options["--rxnNamesFile"]) 645 646 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd smi") 647 MiscUtil.ValidateOptionsOutputFileOverwrite( 648 "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"] 649 ) 650 651 ReactantFiles = re.sub(" ", "", Options["--infiles"]) 652 if not ReactantFiles: 653 MiscUtil.PrintError('No reactant files specified for "-i, --infiles" option') 654 655 # Validate file extensions... 656 for ReactantFile in ReactantFiles.split(","): 657 MiscUtil.ValidateOptionFilePath("-i, --infiles", ReactantFile) 658 MiscUtil.ValidateOptionFileExt("-i, --infiles", ReactantFile, "sdf sd smi csv tsv txt") 659 MiscUtil.ValidateOptionsDistinctFileNames("-i, --infiles", ReactantFile, "-o, --outfile", Options["--outfile"]) 660 661 # Match file formats... 662 FirstFile = True 663 FirstFileFormat = "" 664 for ReactantFile in ReactantFiles.split(","): 665 FileFormat = "" 666 if MiscUtil.CheckFileExt(ReactantFile, "sdf sd"): 667 FileFormat = "SD" 668 elif MiscUtil.CheckFileExt(ReactantFile, "smi csv tsv txt"): 669 FileFormat = "SMILES" 670 else: 671 MiscUtil.PrintError( 672 'The file name specified , %s, for option "-i, --infiles" is not valid. Supported file formats: sdf sd smi csv tsv txt\n' 673 % ReactantFile 674 ) 675 676 if FirstFile: 677 FirstFile = False 678 FirstFileFormat = FileFormat 679 continue 680 681 if not re.match("^%s$" % FirstFileFormat, FileFormat, re.IGNORECASE): 682 MiscUtil.PrintError( 683 'All reactant file names - %s - specified using option "-i, --infiles" must have the same file format.\n' 684 % ReactantFiles 685 ) 686 687 MiscUtil.ValidateOptionTextValue("--sanitize", Options["--sanitize"], "yes no") 688 689 690 # Setup a usage string for docopt... 691 _docoptUsage_ = """ 692 RDKitEnumerateCompoundLibrary.py - Enumerate a virtual compound library 693 694 Usage: 695 RDKitEnumerateCompoundLibrary.py [--colmode <collabel or colnum>] [--colRxnName <text or number>] 696 [--colRxnSMARTS <text or number>] [--compute2DCoords <yes or no>] [--infileParams <Name,Value,...>] 697 [--mode <RxnByName or RxnBySMARTS>] [--outfileParams <Name,Value,...>] [--overwrite] 698 [--prodMolNames <UseReactants or Sequential>] [--rxnName <text>] 699 [--rxnNamesFile <FileName or auto>] [--smartsRxn <text>] [--sanitize <yes or no>] 700 [-w <dir>] -i <ReactantFile1,...> -o <outfile> 701 RDKitEnumerateCompoundLibrary.py [--colmode <collabel or colnum>] [--colRxnName <text or number>] [--colRxnSMARTS <text or number>] 702 [--rxnNamesFile <FileName or auto>] -l | --list 703 RDKitEnumerateCompoundLibrary.py -h | --help | -e | --examples 704 705 Description: 706 Perform a combinatorial enumeration of a virtual library of molecules for a reaction specified 707 using a reaction name or SMARTS pattern and reactant input files. 708 709 The SMARTS patterns for supported reactions names [ Ref 134 ] are retrieved from file, 710 ReactionNamesAndSMARTS.csv, available in MayaChemTools data directory. The current 711 list of supported reaction names is shown below: 712 713 '1,2,4_triazole_acetohydrazide', '1,2,4_triazole_carboxylic_acid_ester', 3_nitrile_pyridine, 714 Benzimidazole_derivatives_aldehyde, Benzimidazole_derivatives_carboxylic_acid_ester, 715 Benzofuran, Benzothiazole, Benzothiophene, Benzoxazole_aromatic_aldehyde, 716 Benzoxazole_carboxylic_acid, Buchwald_Hartwig, Decarboxylative_coupling, Fischer_indole, 717 Friedlaender_chinoline, Grignard_alcohol, Grignard_carbonyl, Heck_non_terminal_vinyl, 718 Heck_terminal_vinyl, Heteroaromatic_nuc_sub, Huisgen_Cu_catalyzed_1,4_subst, 719 Huisgen_disubst_alkyne, Huisgen_Ru_catalyzed_1,5_subst, Imidazole, Indole, Mitsunobu_imide, 720 Mitsunobu_phenole, Mitsunobu_sulfonamide, Mitsunobu_tetrazole_1, Mitsunobu_tetrazole_2, 721 Mitsunobu_tetrazole_3, Mitsunobu_tetrazole_4, N_arylation_heterocycles, Negishi, 722 Niementowski_quinazoline, Nucl_sub_aromatic_ortho_nitro, Nucl_sub_aromatic_para_nitro, 723 Oxadiazole, Paal_Knorr_pyrrole, Phthalazinone, Pictet_Spengler, Piperidine_indole, 724 Pyrazole, Reductive_amination, Schotten_Baumann_amide, Sonogashira, Spiro_chromanone, 725 Stille, Sulfon_amide, Suzuki, Tetrazole_connect_regioisomer_1, Tetrazole_connect_regioisomer_2, 726 Tetrazole_terminal, Thiazole, Thiourea, Triaryl_imidazole, Urea, Williamson_ether, Wittig 727 728 The supported input file formats are: SD (.sdf, .sd), SMILES (.smi, .csv, .tsv, .txt) 729 730 The supported output file formats are: SD (.sdf, .sd), SMILES (.smi) 731 732 Options: 733 -c, --colmode <collabel or colnum> [default: collabel] 734 Use column number or name for the specification of columns in a CSV 735 file containing reaction names along with reaction SMARTS. You may 736 specify a reaction names file using '--rxnNamesFile' option. 737 --colRxnName <text or number> [default: auto] 738 Column name or number corresponding to reaction names. The default value 739 is automatically set based on the value of '-c, --colmode': 'RxnName' for 740 'collabel'; Reaction name column number for 'colnum'. 741 --colRxnSMARTS <text or number> [default: auto] 742 Column name or number corresponding to reaction SMARTS strings. The default 743 value is automatically set based on the value of '-c, --colmode': 'RxnSMARTS' 744 for 'collabel'; Reacton SMARTS column number for 'colnum'. 745 --compute2DCoords <yes or no> [default: yes] 746 Compute 2D coordinates of product molecules before writing them out. 747 -i, --infiles <ReactantFile1, ReactantFile2...> 748 Comma delimited list of reactant file names for enumerating a compound library 749 using reaction SMARTS. The number of reactant files must match number of 750 reaction components in reaction SMARTS. All reactant input files must have 751 the same format. 752 --infileParams <Name,Value,...> [default: auto] 753 A comma delimited list of parameter name and value pairs for reading 754 molecules from files. The supported parameter names for different file 755 formats, along with their default values, are shown below: 756 757 SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes 758 SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space, 759 smilesTitleLine,auto,sanitize,yes 760 761 Possible values for smilesDelimiter: space, comma or tab. These parameters apply 762 to all reactant input files, which must have the same file format. 763 -e, --examples 764 Print examples. 765 -h, --help 766 Print this help message. 767 -l, --list 768 List available reaction names along with corresponding SMARTS patterns without 769 performing any enumeration. In addition, reaction SMARTS patterns are validated. 770 -m, --mode <RxnByName or RxnBySMARTS> [default: RxnByName] 771 Indicate whether a reaction is specified by a reaction name or a SMARTS pattern. 772 Possible values: RxnByName or RxnBySMARTS. 773 -o, --outfile <outfile> 774 Output file name. 775 --outfileParams <Name,Value,...> [default: auto] 776 A comma delimited list of parameter name and value pairs for writing 777 molecules to files. The supported parameter names for different file 778 formats, along with their default values, are shown below: 779 780 SD: kekulize,yes,forceV3000,no 781 SMILES: smilesKekulize,no,smilesDelimiter,space, smilesIsomeric,yes, 782 smilesTitleLine,yes 783 784 -p, --prodMolNames <UseReactants or Sequential> [default: UseReactants] 785 Generate names of product molecules using reactant names or assign names in 786 a sequential order. Possible values: UseReactants or Sequential. Format of 787 molecule names: UseReactants - <ReactName1>_<ReactName2>..._Prod<Num>; 788 Sequential - Prod<Num> 789 --overwrite 790 Overwrite existing files. 791 -r, --rxnName <text> 792 Name of a reaction to use for enumerating a compound library. This option 793 is only used during 'RxnByName' value of '-m, --mode' option. 794 --rxnNamesFile <FileName or auto> [default: auto] 795 Specify a file name containing data for names of reactions and SMARTS patterns or 796 use default file, ReactionNamesAndSMARTS.csv, available in MayaChemTools data 797 directory. 798 799 Default reactions SMARTS file format: RxnName,RxnSMARTS. 800 801 The local file format is assumed to be same as the default file format. You may 802 explicitly specify column names or numbers for reaction name and reaction 803 SMARTS using '--colRxnName' and '--colRxnSMARTS' options. 804 -s, --smartsRxn <text> 805 SMARTS pattern of a reaction to use for enumerating a compound library. This 806 option is only used during 'RxnBySMARTS' value of '-m, --mode' option. 807 --sanitize <yes or no> [default: yes] 808 Sanitize product molecules before writing them out. 809 -w, --workingdir <dir> 810 Location of working directory which defaults to the current directory. 811 812 Examples: 813 To list all available reaction names along with their SMARTS pattern, type: 814 815 % RDKitEnumerateCompoundLibrary.py -l 816 817 To perform a combinatorial enumeration of a virtual compound library corresponding 818 to named amide reaction, Schotten_Baumann_amide, and write out a SMILES file 819 type: 820 821 % RDKitEnumerateCompoundLibrary.py -r Schotten_Baumann_amide 822 -i 'SampleAcids.smi,SampleAmines.smi' -o SampleOutCmpdLibrary.smi 823 824 To run the previous command using a local reaction names file with explicit 825 specification of column names containing reaction names and SMARTS, and write 826 out a SMILES file type: 827 828 % RDKitEnumerateCompoundLibrary.py -r Schotten_Baumann_amide 829 --rxnNamesFile ReactionNamesAndSMARTS.csv 830 --colmode collabel --colRxnName RxnName --colRxnSMARTS RxnSMARTS 831 -i 'SampleAcids.smi,SampleAmines.smi' -o SampleOutCmpdLibrary.smi 832 833 To perform a combinatorial enumeration of a virtual compound library corresponding 834 to an amide reaction specified using a SMARTS pattern and write out a SD file containing 835 sanitized molecules, computed 2D coordinates, and generation of molecule names from 836 reactant names, type: 837 838 % RDKitEnumerateCompoundLibrary.py -m RxnBySMARTS 839 -s '[O:2]=[C:1][OH].[N:3]>>[O:2]=[C:1][N:3]' 840 -i 'SampleAcids.smi,SampleAmines.smi' -o SampleOutCmpdLibrary.sdf 841 842 To perform a combinatorial enumeration of a virtual compound library corresponding 843 to an amide reaction specified using a SMARTS pattern and write out a SD file containing 844 unsanitized molecules, without generating 2D coordinates, and a sequential generation 845 of molecule names, type: 846 847 % RDKitEnumerateCompoundLibrary.py -m RxnBySMARTS -c no --sanitize no 848 -p Sequential -s '[O:2]=[C:1][OH].[N:3]>>[O:2]=[C:1][N:3]' 849 -i 'SampleAcids.smi,SampleAmines.smi' -o SampleOutCmpdLibrary.sdf 850 851 Author: 852 Manish Sud(msud@san.rr.com) 853 854 See also: 855 RDKitConvertFileFormat.py, RDKitFilterPAINS.py, RDKitSearchFunctionalGroups.py, 856 RDKitSearchSMARTS.py 857 858 Copyright: 859 Copyright (C) 2026 Manish Sud. All rights reserved. 860 861 The functionality available in this script is implemented using RDKit, an 862 open source toolkit for cheminformatics developed by Greg Landrum. 863 864 This file is part of MayaChemTools. 865 866 MayaChemTools is free software; you can redistribute it and/or modify it under 867 the terms of the GNU Lesser General Public License as published by the Free 868 Software Foundation; either version 3 of the License, or (at your option) any 869 later version. 870 871 """ 872 873 if __name__ == "__main__": 874 main()