1 #!/bin/env python 2 # 3 # File: RDKitPickDiverseMolecules.py 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2024 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 import DataStructs 43 from rdkit.Chem.Fingerprints import FingerprintMols 44 from rdkit.Chem import rdMolDescriptors 45 from rdkit.SimDivFilters import rdSimDivPickers 46 from rdkit.SimDivFilters.rdSimDivPickers import MaxMinPicker 47 from rdkit.SimDivFilters.rdSimDivPickers import HierarchicalClusterPicker 48 except ImportError as ErrMsg: 49 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg) 50 sys.stderr.write("Check/update your RDKit environment and try again.\n\n") 51 sys.exit(1) 52 53 # MayaChemTools imports... 54 try: 55 from docopt import docopt 56 import MiscUtil 57 import RDKitUtil 58 except ImportError as ErrMsg: 59 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg) 60 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n") 61 sys.exit(1) 62 63 ScriptName = os.path.basename(sys.argv[0]) 64 Options = {} 65 OptionsInfo = {} 66 67 def main(): 68 """Start execution of the script.""" 69 70 MiscUtil.PrintInfo("\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())) 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 PickDiverseMolecules() 82 83 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName) 84 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime)) 85 86 def PickDiverseMolecules(): 87 """Pick diverse molecules.""" 88 89 Mols = RetrieveMolecules() 90 MolsFingerprints = GenerateFingerprints(Mols) 91 DiverseMols = SelectMolecules(Mols, MolsFingerprints) 92 93 WriteMolecules(DiverseMols) 94 95 def SelectMolecules(Mols, MolsFingerprints): 96 """Select diverse molecules.""" 97 98 if OptionsInfo["NumMols"] > len(Mols): 99 MiscUtil.PrintError("The number of diverse molecules to pick, %d, specified using \"-n, --numMols\" must be less than total number of valid molecules, %d" % (OptionsInfo["NumMols"], len(Mols))) 100 101 DiverseMols = [] 102 if re.match("^MaxMin$", OptionsInfo["Mode"], re.I): 103 return SelectMoleculesUsingMaxMin(Mols, MolsFingerprints) 104 elif re.match("^HierarchicalClustering$", OptionsInfo["Mode"], re.I): 105 return SelectMoleculesUsingHierarchicalClustering(Mols, MolsFingerprints) 106 else: 107 MiscUtil.PrintError("The mode vaue, %s, is not a valid mode." % OptionsInfo["Mode"]) 108 109 return DiverseMols 110 111 def SelectMoleculesUsingMaxMin(Mols, MolsFingerprints): 112 """Select diverse molecules using MaxMin methodology.""" 113 114 MiscUtil.PrintInfo("\nSelecting diverse molecules using MaxMin methodology and %s similarity metric..." % OptionsInfo["SimilarityMetric"]) 115 116 DiverseMols = [] 117 118 PoolSize = len(MolsFingerprints) 119 PickSize = OptionsInfo["NumMols"] 120 SimilarityFunction = OptionsInfo["SimilarityFunction"] 121 122 Picker = MaxMinPicker() 123 PairwiseDistance = lambda i, j: 1 - SimilarityFunction(MolsFingerprints[i], MolsFingerprints[j]) 124 125 MolIndices = Picker.LazyPick(PairwiseDistance, PoolSize, PickSize) 126 127 for Index in list(MolIndices): 128 DiverseMols.append(Mols[Index]) 129 130 return DiverseMols 131 132 def SelectMoleculesUsingHierarchicalClustering(Mols, MolsFingerprints): 133 """Select diverse molecules using hierarchical clustering methodology.""" 134 135 try: 136 import numpy 137 except ImportError: 138 MiscUtil.PrintError("Failed to import numpy python module. This is required for picking diverse molecules using hierarchical for clustering.") 139 140 MiscUtil.PrintInfo("\nSelecting diverse molecules using %s hierarchical clustering methodology..." % OptionsInfo["SpecifiedClusteringMethod"]) 141 142 DiverseMols = [] 143 144 PoolSize = len(MolsFingerprints) 145 PickSize = OptionsInfo["NumMols"] 146 DistanceMatrix = GenerateLowerTriangularDistanceMatrix(MolsFingerprints) 147 148 ClusterPicker = HierarchicalClusterPicker(OptionsInfo["SpecifiedClusteringMethodID"]) 149 MolIndices = ClusterPicker.Pick(numpy.asarray(DistanceMatrix), PoolSize, PickSize) 150 151 for Index in MolIndices: 152 DiverseMols.append(Mols[Index]) 153 154 return DiverseMols 155 156 def RetrieveMolecules(): 157 """Retrieve molecules.""" 158 159 Infile = OptionsInfo["Infile"] 160 161 # Read molecules... 162 MiscUtil.PrintInfo("\nReading file %s..." % Infile) 163 164 OptionsInfo["InfileParams"]["AllowEmptyMols"] = False 165 ValidMols, MolCount, ValidMolCount = RDKitUtil.ReadAndValidateMolecules(Infile, **OptionsInfo["InfileParams"]) 166 167 MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount) 168 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount) 169 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount)) 170 171 return ValidMols 172 173 def GenerateFingerprints(Mols): 174 """Generate fingerprints.""" 175 176 FingerprintsName = OptionsInfo["SpecifiedFingerprints"] 177 178 MolsFingerprints = [] 179 if re.match("^AtomPairs$", FingerprintsName, re.I): 180 return GenerateAtomPairsFingerprints(Mols) 181 elif re.match("^MACCS166Keys$", FingerprintsName, re.I): 182 return GenerateMACCS166KeysFingerprints(Mols) 183 elif re.match("^Morgan$", FingerprintsName, re.I): 184 return GenerateMorganFingerprints(Mols) 185 elif re.match("^MorganFeatures$", FingerprintsName, re.I): 186 return GenerateMorganFeaturesFingerprints(Mols) 187 elif re.match("^PathLength$", FingerprintsName, re.I): 188 return GeneratePathLengthFingerprints(Mols) 189 elif re.match("^TopologicalTorsions$", FingerprintsName, re.I): 190 return GenerateTopologicalTorsionsFingerprints(Mols) 191 else: 192 MiscUtil.PrintError("Fingerprints name, %s, is not a valid name" % FingerprintsName) 193 194 return MolsFingerprints 195 196 def GenerateAtomPairsFingerprints(Mols): 197 """Generate AtomPairs fingerprints.""" 198 199 MiscUtil.PrintInfo("\nGenerating AtomPairs fingerprints...") 200 201 MinLength = OptionsInfo["FingerprintsParams"]["AtomPairs"]["MinLength"] 202 MaxLength = OptionsInfo["FingerprintsParams"]["AtomPairs"]["MaxLength"] 203 UseChirality = OptionsInfo["FingerprintsParams"]["AtomPairs"]["UseChirality"] 204 205 if OptionsInfo["GenerateBitVectFingerints"]: 206 # Generate ExplicitBitVect fingerprints... 207 FPSize = 2048 208 BitsPerHash = 4 209 MolsFingerprints = [rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect(Mol, minLength = MinLength, maxLength = MaxLength, includeChirality = UseChirality, nBits = FPSize, nBitsPerEntry = BitsPerHash) for Mol in Mols] 210 else: 211 # Generate IntSparseIntVect fingerprints... 212 MolsFingerprints = [rdMolDescriptors.GetAtomPairFingerprint(Mol, minLength = MinLength, maxLength = MaxLength, includeChirality = UseChirality) for Mol in Mols] 213 214 return MolsFingerprints 215 216 def GenerateMACCS166KeysFingerprints(Mols): 217 """Generate MACCS166Keys fingerprints.""" 218 219 MiscUtil.PrintInfo("\nGenerating MACCS166Keys fingerprints...") 220 221 # Generate ExplicitBitVect fingerprints... 222 MolsFingerprints = [rdMolDescriptors.GetMACCSKeysFingerprint(Mol) for Mol in Mols] 223 224 return MolsFingerprints 225 226 def GenerateMorganFingerprints(Mols): 227 """Generate Morgan fingerprints.""" 228 229 MiscUtil.PrintInfo("\nGenerating Morgan fingerprints...") 230 231 Radius = OptionsInfo["FingerprintsParams"]["Morgan"]["Radius"] 232 UseChirality = OptionsInfo["FingerprintsParams"]["Morgan"]["UseChirality"] 233 UseFeatures = False 234 235 if OptionsInfo["GenerateBitVectFingerints"]: 236 # Generate ExplicitBitVect fingerprints... 237 FPSize = 2048 238 MolsFingerprints = [rdMolDescriptors.GetMorganFingerprintAsBitVect(Mol, Radius, useFeatures = UseFeatures, useChirality = UseChirality, nBits = FPSize) for Mol in Mols] 239 else: 240 # Generate UIntSparseIntVect fingerprints... 241 MolsFingerprints = [rdMolDescriptors.GetMorganFingerprint(Mol, Radius, useFeatures = UseFeatures, useChirality = UseChirality) for Mol in Mols] 242 243 return MolsFingerprints 244 245 def GenerateMorganFeaturesFingerprints(Mols): 246 """Generate MorganFeatures fingerprints.""" 247 248 MiscUtil.PrintInfo("\nGenerating MorganFeatures fingerprints...") 249 250 # Setup fingerprints parameters... 251 Radius = OptionsInfo["FingerprintsParams"]["MorganFeatures"]["Radius"] 252 UseChirality = OptionsInfo["FingerprintsParams"]["MorganFeatures"]["UseChirality"] 253 UseFeatures = True 254 255 if OptionsInfo["GenerateBitVectFingerints"]: 256 # Generate ExplicitBitVect fingerprints... 257 FPSize = 2048 258 MolsFingerprints = [rdMolDescriptors.GetMorganFingerprintAsBitVect(Mol, Radius, useFeatures = UseFeatures, useChirality = UseChirality, nBits = FPSize) for Mol in Mols] 259 else: 260 # Generate UIntSparseIntVect fingerprints... 261 MolsFingerprints = [rdMolDescriptors.GetMorganFingerprint(Mol, Radius, useFeatures = UseFeatures, useChirality = UseChirality) for Mol in Mols] 262 263 return MolsFingerprints 264 265 def GeneratePathLengthFingerprints(Mols): 266 """Generate PathLength fingerprints.""" 267 268 MiscUtil.PrintInfo("\nGenerating PathLength fingerprints ...") 269 270 MinPath = OptionsInfo["FingerprintsParams"]["PathLength"]["MinPath"] 271 MaxPath = OptionsInfo["FingerprintsParams"]["PathLength"]["MaxPath"] 272 FPSize = OptionsInfo["FingerprintsParams"]["PathLength"]["FPSize"] 273 BitsPerHash = OptionsInfo["FingerprintsParams"]["PathLength"]["BitsPerHash"] 274 UseHs = False 275 TargetDensity = 0.3 276 MinSize = 54 277 278 # Generate ExplicitBitVect fingerprints... 279 MolsFingerprints = [FingerprintMols.FingerprintMol(Mol, minPath = MinPath, maxPath = MaxPath, fpSize = FPSize, bitsPerHash = BitsPerHash, useHs = UseHs, tgtDensity = TargetDensity, minSize = MinSize) for Mol in Mols] 280 281 return MolsFingerprints 282 283 def GenerateTopologicalTorsionsFingerprints(Mols): 284 """Generate TopologicalTorsions fingerprints.""" 285 286 MiscUtil.PrintInfo("\nGenerating TopologicalTorsions fingerprints...") 287 288 UseChirality = OptionsInfo["FingerprintsParams"]["TopologicalTorsions"]["UseChirality"] 289 290 if OptionsInfo["GenerateBitVectFingerints"]: 291 FPSize = 2048 292 BitsPerHash = 4 293 MolsFingerprints = [rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect(Mol, includeChirality = UseChirality, nBits = FPSize, nBitsPerEntry = BitsPerHash) for Mol in Mols] 294 else: 295 # Generate LongSparseIntVect fingerprint... 296 MolsFingerprints = [rdMolDescriptors.GetTopologicalTorsionFingerprint(Mol, includeChirality = UseChirality) for Mol in Mols] 297 298 return MolsFingerprints 299 300 def GenerateLowerTriangularDistanceMatrix(MolsFingerprints): 301 """Generate a lower triangular distance matrix without the diagonal.""" 302 303 SimilarityFunction = OptionsInfo["SimilarityFunction"] 304 305 DistanceMatrix = [] 306 NumFPs = len(MolsFingerprints) 307 for Index1 in range(0, NumFPs): 308 for Index2 in range(0, Index1): 309 Distance = 1 - SimilarityFunction(MolsFingerprints[Index1], MolsFingerprints[Index2],) 310 DistanceMatrix.append(Distance) 311 312 return DistanceMatrix 313 314 def WriteMolecules(Mols): 315 """Write out molecules.""" 316 317 Outfile = OptionsInfo["Outfile"] 318 319 # Set up a molecule writer... 320 Writer = None 321 Writer = RDKitUtil.MoleculesWriter(Outfile, **OptionsInfo["OutfileParams"]) 322 if Writer is None: 323 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile) 324 MiscUtil.PrintInfo("\nGenerating file %s...\n" % Outfile) 325 326 Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"] 327 SetSMILESMolProps = OptionsInfo["OutfileParams"]["SetSMILESMolProps"] 328 329 # Write out molecules... 330 FirstMol = True 331 for Mol in Mols: 332 if FirstMol: 333 FirstMol = False 334 if SetSMILESMolProps: 335 RDKitUtil.SetWriterMolProps(Writer, Mol) 336 RDKitUtil.SetWriterMolProps(Writer, Mol) 337 338 if Compute2DCoords: 339 AllChem.Compute2DCoords(Mol) 340 Writer.write(Mol) 341 342 if Writer is not None: 343 Writer.close() 344 345 MiscUtil.PrintInfo("Total number of diverse molecules selected: %d" % (len(Mols))) 346 347 def ProcessFingerprintsParameters(): 348 """Set up and process fingerprints parameters.""" 349 350 SetupFingerprintsNamesAndParameters() 351 ProcessSpecifiedFingerprintsName() 352 ProcessSpecifiedFingerprintsParameters() 353 354 def SetupFingerprintsNamesAndParameters(): 355 """Set up fingerprints parameters.""" 356 357 OptionsInfo["FingerprintsNames"] = ["AtomPairs", "MACCS166Keys", "Morgan", "MorganFeatures", "PathLength", "TopologicalTorsions"] 358 359 OptionsInfo["FingerprintsParams"] = {} 360 OptionsInfo["FingerprintsParams"]["AtomPairs"] = {"MinLength": 1, "MaxLength": 30, "UseChirality": False} 361 OptionsInfo["FingerprintsParams"]["MACCS166Keys"] = {} 362 OptionsInfo["FingerprintsParams"]["Morgan"] = {"Radius": 2, "UseChirality": False} 363 OptionsInfo["FingerprintsParams"]["MorganFeatures"] = {"Radius": 2, "UseChirality": False} 364 OptionsInfo["FingerprintsParams"]["TopologicalTorsions"] = {"UseChirality": False} 365 OptionsInfo["FingerprintsParams"]["PathLength"] = {"MinPath": 1, "MaxPath": 7, "FPSize": 2048, "BitsPerHash": 2} 366 367 def ProcessSpecifiedFingerprintsName(): 368 """Process specified fingerprints name.""" 369 370 # Set up a canonical fingerprints name map... 371 CanonicalFingerprintsNamesMap = {} 372 for Name in OptionsInfo["FingerprintsNames"]: 373 CanonicalName = Name.lower() 374 CanonicalFingerprintsNamesMap[CanonicalName] = Name 375 376 # Validate specified fingerprints name... 377 CanonicalFingerprintsName = OptionsInfo["Fingerprints"].lower() 378 if CanonicalFingerprintsName not in CanonicalFingerprintsNamesMap: 379 MiscUtil.PrintError("The fingerprints name, %s, specified using \"-f, --fingerprints\" option is not a valid name." % (OptionsInfo["Fingerprints"])) 380 381 OptionsInfo["SpecifiedFingerprints"] = CanonicalFingerprintsNamesMap[CanonicalFingerprintsName] 382 383 def ProcessSpecifiedFingerprintsParameters(): 384 """Process specified fingerprints parameters.""" 385 386 if re.match("^auto$", OptionsInfo["ParamsFingerprints"], re.I): 387 # Nothing to process... 388 return 389 390 SpecifiedFingerprintsName = OptionsInfo["SpecifiedFingerprints"] 391 392 # Parse specified fingerprints parameters... 393 ParamsFingerprints = re.sub(" ", "", OptionsInfo["ParamsFingerprints"]) 394 if not ParamsFingerprints: 395 MiscUtil.PrintError("No valid parameter name and value pairs specified using \"-p, --paramsFingerprints\" option corrresponding to fingerprints %s." % (SpecifiedFingerprintsName)) 396 397 ParamsFingerprintsWords = ParamsFingerprints.split(",") 398 if len(ParamsFingerprintsWords) % 2: 399 MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"-p, --paramsFingerprints\" option must be an even number." % (len(ParamsFingerprintsWords))) 400 401 # Setup a canonical parameter names for specified fingerprints... 402 ValidParamNames = [] 403 CanonicalParamNamesMap = {} 404 for ParamName in sorted(OptionsInfo["FingerprintsParams"][SpecifiedFingerprintsName]): 405 ValidParamNames.append(ParamName) 406 CanonicalParamNamesMap[ParamName.lower()] = ParamName 407 408 # Validate and set paramater names and value... 409 for Index in range(0, len(ParamsFingerprintsWords), 2): 410 Name = ParamsFingerprintsWords[Index] 411 Value = ParamsFingerprintsWords[Index + 1] 412 413 CanonicalName = Name.lower() 414 if not CanonicalName in CanonicalParamNamesMap: 415 MiscUtil.PrintError("The parameter name, %s, specified using \"-p, --paramsFingerprints\" option for fingerprints, %s, is not a valid name. Supported parameter names: %s" % (Name, SpecifiedFingerprintsName, " ".join(ValidParamNames))) 416 417 ParamName = CanonicalParamNamesMap[CanonicalName] 418 if re.match("^UseChirality$", ParamName, re.I): 419 if not re.match("^(Yes|No|True|False)$", Value, re.I): 420 MiscUtil.PrintError("The parameter value, %s, specified using \"-p, --paramsFingerprints\" option for fingerprints, %s, is not a valid value. Supported values: Yes No True False" % (Value, SpecifiedFingerprintsName)) 421 ParamValue = False 422 if re.match("^(Yes|True)$", Value, re.I): 423 ParamValue = True 424 else: 425 ParamValue = int(Value) 426 if ParamValue <= 0: 427 MiscUtil.PrintError("The parameter value, %s, specified using \"-p, --paramsFingerprints\" option for fingerprints, %s, is not a valid value. Supported values: > 0" % (Value, SpecifiedFingerprintsName)) 428 429 # Set value... 430 OptionsInfo["FingerprintsParams"][SpecifiedFingerprintsName][ParamName] = ParamValue 431 432 def ProcessSimilarityMetricParameter(): 433 """Process specified similarity metric value.""" 434 435 SimilarityInfoMap = {} 436 CanonicalNameMap = {} 437 438 for SimilarityFunctionInfo in DataStructs.similarityFunctions: 439 Name = SimilarityFunctionInfo[0] 440 Function = SimilarityFunctionInfo[1] 441 442 SimilarityInfoMap[Name] = Function 443 CanonicalName = Name.lower() 444 CanonicalNameMap[CanonicalName] = Name 445 446 SpecifiedCanonicalName = OptionsInfo["SimilarityMetric"].lower() 447 SimilarityFunction = None 448 if SpecifiedCanonicalName in CanonicalNameMap: 449 SimilarityName = CanonicalNameMap[SpecifiedCanonicalName] 450 SimilarityFunction = SimilarityInfoMap[SimilarityName] 451 else: 452 MiscUtil.PrintError("Similarity metric name, %s, is not a valid name. " % OptionsInfo["SimilarityMetric"]) 453 454 OptionsInfo["SimilarityMetric"] = SimilarityName 455 OptionsInfo["SimilarityFunction"] = SimilarityFunction 456 457 # RDKit similarity functions, besides Dice and Tanimoto, are not able to handle int bit vectors... 458 GenerateBitVectFingerints = False 459 if not re.match("^(Tanimoto|Dice)$", SimilarityName, re.I): 460 GenerateBitVectFingerints = True 461 OptionsInfo["GenerateBitVectFingerints"] = GenerateBitVectFingerints 462 463 def ProcessClusteringMethodParameter(): 464 """Process specified clustering method parameter.""" 465 466 OptionsInfo["SpecifiedClusteringMethod"] = "" 467 OptionsInfo["SpecifiedClusteringMethodID"] = "" 468 469 if not re.match("^HierarchicalClustering$", OptionsInfo["Mode"], re.I): 470 # Nothing to process... 471 return 472 473 # Setup a canonical cluster method name map.. 474 ClusteringMethodInfoMap = {} 475 CanonicalClusteringMethodNameMap = {} 476 for Name in sorted(rdSimDivPickers.ClusterMethod.names): 477 NameID = rdSimDivPickers.ClusterMethod.names[Name] 478 ClusteringMethodInfoMap[Name] = NameID 479 480 CanonicalName = Name.lower() 481 CanonicalClusteringMethodNameMap[CanonicalName] = Name 482 483 CanonicalName = OptionsInfo["ClusteringMethod"].lower() 484 if not CanonicalName in CanonicalClusteringMethodNameMap: 485 MiscUtil.PrintError("The cluster method, %s, specified using \"-c, --clusteringMethod\" option is not a valid name." % (OptionsInfo["ClusteringMethod"])) 486 487 SpecifiedClusteringMethodName = CanonicalClusteringMethodNameMap[CanonicalName] 488 OptionsInfo["SpecifiedClusteringMethod"] = SpecifiedClusteringMethodName 489 OptionsInfo["SpecifiedClusteringMethodID"] = ClusteringMethodInfoMap[SpecifiedClusteringMethodName] 490 491 def ProcessOptions(): 492 """Process and validate command line arguments and options.""" 493 494 MiscUtil.PrintInfo("Processing options...") 495 496 # Validate options... 497 ValidateOptions() 498 499 OptionsInfo["Mode"] = Options["--mode"] 500 OptionsInfo["Fingerprints"] = Options["--fingerprints"] 501 502 OptionsInfo["ClusteringMethod"] = Options["--clusteringMethod"] 503 ProcessClusteringMethodParameter() 504 505 OptionsInfo["NumMols"] = int(Options["--numMols"]) 506 507 OptionsInfo["Infile"] = Options["--infile"] 508 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], Options["--infile"]) 509 510 OptionsInfo["Outfile"] = Options["--outfile"] 511 OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters("--outfileParams", Options["--outfileParams"], Options["--infile"], Options["--outfile"]) 512 513 OptionsInfo["Overwrite"] = Options["--overwrite"] 514 515 OptionsInfo["SimilarityMetric"] = Options["--similarityMetric"] 516 ProcessSimilarityMetricParameter() 517 518 OptionsInfo["ParamsFingerprints"] = Options["--paramsFingerprints"] 519 ProcessFingerprintsParameters() 520 521 def RetrieveOptions(): 522 """Retrieve command line arguments and options.""" 523 524 # Get options... 525 global Options 526 Options = docopt(_docoptUsage_) 527 528 # Set current working directory to the specified directory... 529 WorkingDir = Options["--workingdir"] 530 if WorkingDir: 531 os.chdir(WorkingDir) 532 533 # Handle examples option... 534 if "--examples" in Options and Options["--examples"]: 535 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_)) 536 sys.exit(0) 537 538 def ValidateOptions(): 539 """Validate option values.""" 540 541 MiscUtil.ValidateOptionTextValue("-c, --clusteringMethod", Options["--clusteringMethod"], "Centroid CLink Gower McQuitty SLink UPGMA Ward") 542 MiscUtil.ValidateOptionTextValue("-f, --fingerprints", Options["--fingerprints"], "AtomPairs MACCS166Keys Morgan MorganFeatures PathLength TopologicalTorsions") 543 544 MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "MaxMin HierarchicalClustering") 545 MiscUtil.ValidateOptionIntegerValue("-n, --numMols", Options["--numMols"], {">": 0}) 546 547 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"]) 548 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd smi txt csv tsv") 549 550 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd smi") 551 MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]) 552 MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]) 553 554 MiscUtil.ValidateOptionTextValue("-s, --similarityMetric", Options["--similarityMetric"], "BraunBlanquet Cosine Dice Kulczynski RogotGoldberg Russel Sokal Tanimoto") 555 556 # Setup a usage string for docopt... 557 _docoptUsage_ = """ 558 RDKitPickDiverseMolecules.py - Pick a diverse subset of molecules 559 560 Usage: 561 RDKitPickDiverseMolecules.py [--clusteringMethod <Centroid, CLink...>] 562 [--fingerprints <MACCS166Keys, Morgan, PathLength...>] 563 [--infileParams <Name,Value,...>] [--mode <MaxMin or HierarchicalClustering>] 564 [--numMols <number>] [--outfileParams <Name,Value,...>] 565 [--overwrite] [--paramsFingerprints <Name,Value,...>] 566 [--similarityMetric <Dice, Tanimoto...>] [-w <dir>] -i <infile> -o <outfile> 567 RDKitPickDiverseMolecules.py -h | --help | -e | --examples 568 569 Description: 570 Pick a subset of diverse molecules based on a variety of 2D fingerprints using 571 MaxMin [ Ref 135 ] or an available hierarchical clustering methodology and write 572 them to a file. 573 574 The default fingerprints types for various fingerprints are shown below: 575 576 AtomPairs IntSparseIntVect 577 MACCS166Keys ExplicitBitVect 578 Morgan UIntSparseIntVect 579 MorganFeatures UIntSparseIntVect 580 PathLength ExplicitBitVect 581 TopologicalTorsions LongSparseIntVect 582 583 The Dice and Tanimoto similarity functions available in RDKit are able to 584 handle fingerprints corresponding to both IntVect and BitVect. All other 585 similarity functions, however, expect BitVect fingerprints to calculate 586 pairwise similarity. Consequently, ExplicitBitVect fingerprints are generated 587 for AtomPairs, Morgan, MorganFeatures, and TopologicalTorsions for 588 similarity calculations instead of default IntVect fingerprints. 589 590 The supported input file formats are: SD (.sdf, .sd), SMILES (.smi, .csv, .tsv, .txt) 591 592 The supported output file formats are: SD (.sdf, .sd), SMILES (.smi) 593 594 Options: 595 -c, --clusteringMethod <Centroid, CLink...> [default: Centroid] 596 Clustering method to use for picking a subset of diverse molecules during 597 hierarchical clustering. Supported values: Centroid, CLink, Gower, 598 McQuitty, SLink, UPGMA, Ward. This option is ignored for 'MaxMin' value 599 of '-m, --mode' option. The Clink and SLink corresponding to CompleteLink 600 and SingleLink cluster method. 601 -f, --fingerprints <MACCS166Keys, Morgan, PathLength...> [default: Morgan] 602 Fingerprints to use for calculating similarity/distance between molecules. 603 Supported values: AtomPairs, MACCS166Keys, Morgan, MorganFeatures, PathLength, 604 TopologicalTorsions. The PathLength fingerprints are Daylight like fingerprints. 605 The Morgan and MorganFeature fingerprints are circular fingerprints, corresponding 606 Scitegic's Extended Connectivity Fingerprints (ECFP) and Features Connectivity 607 Fingerprints (FCFP). The values of default parameters for generating fingerprints 608 can be modified using '-p, --paramsFingerprints' option. 609 -e, --examples 610 Print examples. 611 -h, --help 612 Print this help message. 613 -i, --infile <infile> 614 Input file name. 615 --infileParams <Name,Value,...> [default: auto] 616 A comma delimited list of parameter name and value pairs for reading 617 molecules from files. The supported parameter names for different file 618 formats, along with their default values, are shown below: 619 620 SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes 621 SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space, 622 smilesTitleLine,auto,sanitize,yes 623 624 Possible values for smilesDelimiter: space, comma or tab. 625 -m, --mode <MaxMin or HierarchicalClustering> [default: MaxMin] 626 Pick a diverse subset of molecules using MaxMin or hierarchical clustering 627 methodology. 628 -n, --numMols <number> [default: 25] 629 Number of diverse molecules to pick. 630 -o, --outfile <outfile> 631 Output file name. 632 --outfileParams <Name,Value,...> [default: auto] 633 A comma delimited list of parameter name and value pairs for writing 634 molecules to files. The supported parameter names for different file 635 formats, along with their default values, are shown below: 636 637 SD: compute2DCoords,auto,kekulize,yes,forceV3000,no 638 SMILES: smilesKekulize,no,smilesDelimiter,space, smilesIsomeric,yes, 639 smilesTitleLine,yes,smilesMolName,yes,smilesMolProps,no 640 641 Default value for compute2DCoords: yes for SMILES input file; no for all other 642 file types. 643 --overwrite 644 Overwrite existing files. 645 -p, --paramsFingerprints <Name,Value,...> [default: auto] 646 Parameter values to use for generating fingerprints. The default values 647 are dependent on the value of '-f, --fingerprints' option. In general, it is a 648 comma delimited list of parameter name and value pairs for the name of 649 the fingerprints specified using '-f, --fingerprints' option. The supported 650 parameter names along with their default values for valid fingerprints 651 names are shown below: 652 653 AtomPairs: minLength,1 ,maxLength,30, useChirality,No 654 Morgan: radius,2, useChirality,No 655 MorganFeatures: radius,2, useChirality,No 656 PathLength: minPath,1, maxPath,7, fpSize, 2048, bitsPerHash,2 657 TopologicalTorsions: useChirality,No 658 659 -s, --similarityMetric <Dice, Tanimoto...> [default: Tanimoto] 660 Similarity metric to use for calculating similarity/distance between molecules. 661 Possible values: BraunBlanquet, Cosine, Dice, Kulczynski, RogotGoldberg, 662 Russel, Sokal, Tanimoto. 663 -w, --workingdir <dir> 664 Location of working directory which defaults to the current directory. 665 666 Examples: 667 To pick 25 diverse molecules using MaxMin methodology, Tanimoto similarity 668 metric corresponding to Morgan fingerprints with radius of 2, and write 669 out a SMILES file, type: 670 671 % RDKitPickDiverseMolecules.py -i Sample.smi -o SampleOut.smi 672 673 To pick 50 diverse molecules using MaxMin methodology, Dice similarity metric 674 corresponding to PathLength fingerprints with max path length of 6, and write 675 out a SD file, type: 676 677 % RDKitPickDiverseMolecules.py -m MaxMin -f PathLength -s Dice -n 50 678 -p 'maxPath,6' -i Sample.sdf -o SampleOut.sdf 679 680 To pick 25 diverse molecules using Centroid hierarchical clustering methodology, 681 Tanimoto similarity metric corresponding to Morgan fingerprints with radius of 2, 682 and write out a SMILES file, type: 683 684 % RDKitPickDiverseMolecules.py -m HierarchicalClustering -i Sample.smi 685 -o SampleOut.smi 686 687 To pick 50 diverse molecules using Ward hierarchical methodology methodology, 688 Dice similarity metric corresponding to MorganFeatures fingerprints with radius 689 of 2 along with deploying chirality, and write out a SD file, type: 690 691 % RDKitPickDiverseMolecules.py -m HierarchicalClustering -c Ward -n 50 692 -f MorganFeatures -p 'radius,2,useChirality,No' -i Sample.sdf -o 693 SampleOut.sdf 694 695 To pick 25 diverse molecules using MaxMin methodology, Tanimoto similarity 696 metric corresponding to Morgan fingerprints with radius of 2 from a CSV SMIKES 697 file , SMILES strings in column 1, name in olumn 2, and write out a SD file, type: 698 699 % RDKitPickDiverseMolecules.py --infileParams 700 "smilesDelimiter,comma,smilesTitleLine,yes,smilesColumn,1, 701 smilesNameColumn,2" --outfileParams "compute2DCoords,yes" 702 -i SampleSMILES.csv -o SampleOut.sdf 703 704 Author: 705 Manish Sud(msud@san.rr.com) 706 707 See also: 708 RDKitClusterMolecules.py, RDKitConvertFileFormat.py, RDKitSearchFunctionalGroups.py, 709 RDKitSearchSMARTS.py 710 711 Copyright: 712 Copyright (C) 2024 Manish Sud. All rights reserved. 713 714 The functionality available in this script is implemented using RDKit, an 715 open source toolkit for cheminformatics developed by Greg Landrum. 716 717 This file is part of MayaChemTools. 718 719 MayaChemTools is free software; you can redistribute it and/or modify it under 720 the terms of the GNU Lesser General Public License as published by the Free 721 Software Foundation; either version 3 of the License, or (at your option) any 722 later version. 723 724 """ 725 726 if __name__ == "__main__": 727 main()