1 #!/bin/env python
2 #
3 # File: RDKitClusterMolecules.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 from rdkit import DataStructs
42 from rdkit.Chem.Fingerprints import FingerprintMols
43 from rdkit.Chem import rdMolDescriptors
44 from rdkit.ML.Cluster import Butina
45 from rdkit.SimDivFilters import rdSimDivPickers
46 from rdkit.SimDivFilters.rdSimDivPickers import HierarchicalClusterPicker
47 except ImportError as ErrMsg:
48 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
49 sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
50 sys.exit(1)
51
52 # MayaChemTools imports...
53 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
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
68 def main():
69 """Start execution of the script."""
70
71 MiscUtil.PrintInfo(
72 "\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
73 % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())
74 )
75
76 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
77
78 # Retrieve command line arguments and options...
79 RetrieveOptions()
80
81 # Process and validate command line arguments and options...
82 ProcessOptions()
83
84 # Perform actions required by the script...
85 ClusterMolecules()
86
87 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
88 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
89
90
91 def ClusterMolecules():
92 """Cluster molecules."""
93
94 Mols = RetrieveMolecules()
95 MolsFingerprints = GenerateFingerprints(Mols)
96 MolsClusters = PerformClustering(Mols, MolsFingerprints)
97
98 WriteMolecules(MolsClusters)
99
100
101 def PerformClustering(Mols, MolsFingerprints):
102 """Perform clustering."""
103
104 ClusteredMols = []
105 if re.match("^Butina$", OptionsInfo["ClusteringMethod"], re.I):
106 return PerformButinaClustering(Mols, MolsFingerprints)
107 else:
108 return PerformHierarchicalClustering(Mols, MolsFingerprints)
109
110 return ClusteredMols
111
112
113 def PerformButinaClustering(Mols, MolsFingerprints):
114 """Perform clustering using Butina methodology."""
115
116 MiscUtil.PrintInfo(
117 "\nClustering molecules using Butina methodology and %s similarity metric..." % OptionsInfo["SimilarityMetric"]
118 )
119
120 FingerprintsCount = len(MolsFingerprints)
121 DistanceCutoff = 1 - OptionsInfo["ButinaSimilarityCutoff"]
122 Reordering = OptionsInfo["ButinaReordering"]
123
124 DistanceMatrix = GenerateLowerTriangularDistanceMatrix(MolsFingerprints)
125
126 ClusteredMolIndices = Butina.ClusterData(
127 DistanceMatrix, FingerprintsCount, DistanceCutoff, reordering=Reordering, isDistData=True
128 )
129
130 MolsClusters = []
131 for Cluster in ClusteredMolIndices:
132 MolsCluster = [Mols[MolIndex] for MolIndex in Cluster]
133 MolsClusters.append(MolsCluster)
134
135 return MolsClusters
136
137
138 def PerformHierarchicalClustering(Mols, MolsFingerprints):
139 """Perform hierarchical clustering."""
140
141 try:
142 import numpy
143 except ImportError:
144 MiscUtil.PrintError(
145 "Failed to import numpy python module. This is required to cluster molecules using hierarchical clustering methodology."
146 )
147
148 if OptionsInfo["NumClusters"] > len(Mols):
149 MiscUtil.PrintError(
150 'The number of clusters, %d, specified using "-n, --numClusters" must be less than total number of valid molecules, %d'
151 % (OptionsInfo["NumClusters"], len(Mols))
152 )
153
154 MiscUtil.PrintInfo(
155 "\nCluster molecules using %s hierarchical clustering methodology and %s similarity metric..."
156 % (OptionsInfo["SpecifiedHierarchicalClusteringMethod"], OptionsInfo["SimilarityMetric"])
157 )
158
159 NumFingerprints = len(MolsFingerprints)
160 NumClusters = OptionsInfo["NumClusters"]
161 DistanceMatrix = GenerateLowerTriangularDistanceMatrix(MolsFingerprints)
162
163 ClusterPicker = HierarchicalClusterPicker(OptionsInfo["SpecifiedHierarchicalClusteringMethodID"])
164 ClusteredMolIndices = ClusterPicker.Cluster(numpy.asarray(DistanceMatrix), NumFingerprints, NumClusters)
165
166 MolsClusters = []
167 for Cluster in ClusteredMolIndices:
168 MolsCluster = [Mols[MolIndex] for MolIndex in Cluster]
169 MolsClusters.append(MolsCluster)
170
171 return MolsClusters
172
173
174 def WriteMolecules(MolsClusters):
175 """Write out molecules for each cluster along with cluster numbers."""
176
177 ClustersCount = len(MolsClusters)
178
179 SingleOutFileMode = OptionsInfo["SingleOutFileMode"]
180 TextOutFileMode = OptionsInfo["TextOutFileMode"]
181 TextOutFileDelim = OptionsInfo["TextOutFileDelim"]
182
183 Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"]
184
185 SMILESIsomeric = OptionsInfo["OutfileParams"]["SMILESIsomeric"]
186 SMILESKekulize = OptionsInfo["OutfileParams"]["SMILESKekulize"]
187
188 # Setup outfile names and writers...
189 SetupClustersOutFilesNames(len(MolsClusters))
190 SingleClusterWriter, ClustersOutfilesWriters = SetupMoleculeWriters(ClustersCount)
191
192 MolCount = 0
193 SingleMolClustersCount = 0
194
195 if SingleOutFileMode:
196 Writer = SingleClusterWriter
197
198 for ClusterIndex in range(0, ClustersCount):
199 MolsCluster = MolsClusters[ClusterIndex]
200 ClusterNum = ClusterIndex + 1
201
202 if len(MolsCluster) == 1:
203 SingleMolClustersCount += 1
204
205 if not SingleOutFileMode:
206 Writer = ClustersOutfilesWriters[ClusterIndex]
207
208 for Mol in MolsCluster:
209 MolCount += 1
210
211 if TextOutFileMode:
212 # Write out text file including SMILES file...
213 SMILES = Chem.MolToSmiles(Mol, isomericSmiles=SMILESIsomeric, kekuleSmiles=SMILESKekulize)
214 MolName = RDKitUtil.GetMolName(Mol, MolCount)
215 Line = TextOutFileDelim.join([SMILES, MolName, "%d" % ClusterNum])
216 Writer.write("%s\n" % Line)
217 else:
218 # Write out SD file...
219 Mol.SetProp("ClusterNumber", "%s" % ClusterNum)
220 if Compute2DCoords:
221 AllChem.Compute2DCoords(Mol)
222 Writer.write(Mol)
223
224 if SingleClusterWriter is not None:
225 SingleClusterWriter.close()
226 for ClusterOutfileWriter in ClustersOutfilesWriters:
227 ClusterOutfileWriter.close()
228
229 MiscUtil.PrintInfo("\nTotal number of clusters: %d" % ClustersCount)
230
231 if ClustersCount > 0:
232 MiscUtil.PrintInfo("\nNumber of clusters containing only a single molecule: %d" % SingleMolClustersCount)
233 MiscUtil.PrintInfo("Average number of molecules per cluster: %.1f" % (MolCount / ClustersCount))
234
235 MiscUtil.PrintInfo("\nNumber of molecules in each cluster:")
236 MiscUtil.PrintInfo("ClusterNumber,MolCount")
237 ClusterNum = 0
238 for MolsCluster in MolsClusters:
239 ClusterNum += 1
240 MiscUtil.PrintInfo("%d,%d" % (ClusterNum, len(MolsCluster)))
241
242
243 def RetrieveMolecules():
244 """Retrieve molecules."""
245
246 Infile = OptionsInfo["Infile"]
247
248 # Read molecules...
249 MiscUtil.PrintInfo("\nReading file %s..." % Infile)
250 OptionsInfo["InfileParams"]["AllowEmptyMols"] = False
251 ValidMols, MolCount, ValidMolCount = RDKitUtil.ReadAndValidateMolecules(Infile, **OptionsInfo["InfileParams"])
252
253 MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount)
254 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
255 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
256
257 return ValidMols
258
259
260 def GenerateFingerprints(Mols):
261 """Generate fingerprints."""
262
263 FingerprintsName = OptionsInfo["SpecifiedFingerprints"]
264
265 MolsFingerprints = []
266 if re.match("^AtomPairs$", FingerprintsName, re.I):
267 return GenerateAtomPairsFingerprints(Mols)
268 elif re.match("^MACCS166Keys$", FingerprintsName, re.I):
269 return GenerateMACCS166KeysFingerprints(Mols)
270 elif re.match("^Morgan$", FingerprintsName, re.I):
271 return GenerateMorganFingerprints(Mols)
272 elif re.match("^MorganFeatures$", FingerprintsName, re.I):
273 return GenerateMorganFeaturesFingerprints(Mols)
274 elif re.match("^PathLength$", FingerprintsName, re.I):
275 return GeneratePathLengthFingerprints(Mols)
276 elif re.match("^TopologicalTorsions$", FingerprintsName, re.I):
277 return GenerateTopologicalTorsionsFingerprints(Mols)
278 else:
279 MiscUtil.PrintError("Fingerprints name, %s, is not a valid name" % FingerprintsName)
280
281 return MolsFingerprints
282
283
284 def GenerateAtomPairsFingerprints(Mols):
285 """Generate AtomPairs fingerprints."""
286
287 MiscUtil.PrintInfo("\nGenerating AtomPairs %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
288
289 MinLength = OptionsInfo["FingerprintsParams"]["AtomPairs"]["MinLength"]
290 MaxLength = OptionsInfo["FingerprintsParams"]["AtomPairs"]["MaxLength"]
291 UseChirality = OptionsInfo["FingerprintsParams"]["AtomPairs"]["UseChirality"]
292 FPSize = OptionsInfo["FingerprintsParams"]["AtomPairs"]["FPSize"]
293 BitsPerHash = OptionsInfo["FingerprintsParams"]["AtomPairs"]["BitsPerHash"]
294
295 if re.match("^BitVect$", OptionsInfo["SpecifiedFingerprintsType"], re.I):
296 # Generate ExplicitBitVect fingerprints...
297 MiscUtil.PrintInfo("FPSize: %s; BitsPerHash: %s" % (FPSize, BitsPerHash))
298 MolsFingerprints = [
299 rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect(
300 Mol,
301 minLength=MinLength,
302 maxLength=MaxLength,
303 includeChirality=UseChirality,
304 nBits=FPSize,
305 nBitsPerEntry=BitsPerHash,
306 )
307 for Mol in Mols
308 ]
309 else:
310 # Generate IntSparseIntVect fingerprints...
311 MolsFingerprints = [
312 rdMolDescriptors.GetAtomPairFingerprint(
313 Mol, minLength=MinLength, maxLength=MaxLength, includeChirality=UseChirality
314 )
315 for Mol in Mols
316 ]
317
318 return MolsFingerprints
319
320
321 def GenerateMACCS166KeysFingerprints(Mols):
322 """Generate MACCS166Keys fingerprints."""
323
324 MiscUtil.PrintInfo("\nGenerating MACCS166Keys %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
325
326 # Generate ExplicitBitVect fingerprints...
327 MolsFingerprints = [rdMolDescriptors.GetMACCSKeysFingerprint(Mol) for Mol in Mols]
328
329 return MolsFingerprints
330
331
332 def GenerateMorganFingerprints(Mols):
333 """Generate Morgan fingerprints."""
334
335 MiscUtil.PrintInfo("\nGenerating Morgan %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
336
337 Radius = OptionsInfo["FingerprintsParams"]["Morgan"]["Radius"]
338 UseChirality = OptionsInfo["FingerprintsParams"]["Morgan"]["UseChirality"]
339 FPSize = OptionsInfo["FingerprintsParams"]["Morgan"]["FPSize"]
340 UseFeatures = False
341
342 if re.match("^BitVect$", OptionsInfo["SpecifiedFingerprintsType"], re.I):
343 # Generate ExplicitBitVect fingerprints...
344 MiscUtil.PrintInfo("FPSize: %s" % (FPSize))
345 MolsFingerprints = [
346 rdMolDescriptors.GetMorganFingerprintAsBitVect(
347 Mol, Radius, useFeatures=UseFeatures, useChirality=UseChirality, nBits=FPSize
348 )
349 for Mol in Mols
350 ]
351 else:
352 # Generate UIntSparseIntVect fingerprints...
353 MolsFingerprints = [
354 rdMolDescriptors.GetMorganFingerprint(Mol, Radius, useFeatures=UseFeatures, useChirality=UseChirality)
355 for Mol in Mols
356 ]
357
358 return MolsFingerprints
359
360
361 def GenerateMorganFeaturesFingerprints(Mols):
362 """Generate MorganFeatures fingerprints."""
363
364 MiscUtil.PrintInfo("\nGenerating MorganFeatures %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
365
366 # Setup fingerprints parameters...
367 Radius = OptionsInfo["FingerprintsParams"]["MorganFeatures"]["Radius"]
368 UseChirality = OptionsInfo["FingerprintsParams"]["MorganFeatures"]["UseChirality"]
369 FPSize = OptionsInfo["FingerprintsParams"]["MorganFeatures"]["FPSize"]
370 UseFeatures = True
371
372 if re.match("^BitVect$", OptionsInfo["SpecifiedFingerprintsType"], re.I):
373 # Generate ExplicitBitVect fingerprints...
374 MiscUtil.PrintInfo("FPSize: %s" % (FPSize))
375 MolsFingerprints = [
376 rdMolDescriptors.GetMorganFingerprintAsBitVect(
377 Mol, Radius, useFeatures=UseFeatures, useChirality=UseChirality, nBits=FPSize
378 )
379 for Mol in Mols
380 ]
381 else:
382 # Generate UIntSparseIntVect fingerprints...
383 MolsFingerprints = [
384 rdMolDescriptors.GetMorganFingerprint(Mol, Radius, useFeatures=UseFeatures, useChirality=UseChirality)
385 for Mol in Mols
386 ]
387
388 return MolsFingerprints
389
390
391 def GeneratePathLengthFingerprints(Mols):
392 """Generate PathLength fingerprints."""
393
394 MiscUtil.PrintInfo("\nGenerating PathLength %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
395
396 MinPath = OptionsInfo["FingerprintsParams"]["PathLength"]["MinPath"]
397 MaxPath = OptionsInfo["FingerprintsParams"]["PathLength"]["MaxPath"]
398 FPSize = OptionsInfo["FingerprintsParams"]["PathLength"]["FPSize"]
399 BitsPerHash = OptionsInfo["FingerprintsParams"]["PathLength"]["BitsPerHash"]
400 UseHs = False
401 TargetDensity = 0.3
402 MinSize = 54
403
404 # Generate ExplicitBitVect fingerprints...
405 MiscUtil.PrintInfo("FPSize: %s; BitsPerHash: %s" % (FPSize, BitsPerHash))
406 MolsFingerprints = [
407 FingerprintMols.FingerprintMol(
408 Mol,
409 minPath=MinPath,
410 maxPath=MaxPath,
411 fpSize=FPSize,
412 bitsPerHash=BitsPerHash,
413 useHs=UseHs,
414 tgtDensity=TargetDensity,
415 minSize=MinSize,
416 )
417 for Mol in Mols
418 ]
419
420 return MolsFingerprints
421
422
423 def GenerateTopologicalTorsionsFingerprints(Mols):
424 """Generate TopologicalTorsions fingerprints."""
425
426 MiscUtil.PrintInfo("\nGenerating TopologicalTorsions %s fingerprints..." % OptionsInfo["SpecifiedFingerprintsType"])
427
428 UseChirality = OptionsInfo["FingerprintsParams"]["TopologicalTorsions"]["UseChirality"]
429 FPSize = OptionsInfo["FingerprintsParams"]["TopologicalTorsions"]["FPSize"]
430 BitsPerHash = OptionsInfo["FingerprintsParams"]["TopologicalTorsions"]["BitsPerHash"]
431
432 if re.match("^BitVect$", OptionsInfo["SpecifiedFingerprintsType"], re.I):
433 # Generate ExplicitBitVect fingerprints...
434 MiscUtil.PrintInfo("FPSize: %s; BitsPerHash: %s" % (FPSize, BitsPerHash))
435 MolsFingerprints = [
436 rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect(
437 Mol, includeChirality=UseChirality, nBits=FPSize, nBitsPerEntry=BitsPerHash
438 )
439 for Mol in Mols
440 ]
441 else:
442 # Generate LongSparseIntVect fingerprint...
443 MolsFingerprints = [
444 rdMolDescriptors.GetTopologicalTorsionFingerprint(Mol, includeChirality=UseChirality) for Mol in Mols
445 ]
446
447 return MolsFingerprints
448
449
450 def GenerateLowerTriangularDistanceMatrix(MolsFingerprints):
451 """Generate a lower triangular distance matrix without the diagonal."""
452
453 SimilarityFunction = OptionsInfo["SimilarityFunction"]
454
455 DistanceMatrix = []
456 NumFPs = len(MolsFingerprints)
457 for Index1 in range(0, NumFPs):
458 for Index2 in range(0, Index1):
459 Distance = 1 - SimilarityFunction(
460 MolsFingerprints[Index1],
461 MolsFingerprints[Index2],
462 )
463 DistanceMatrix.append(Distance)
464
465 return DistanceMatrix
466
467
468 def SetupMoleculeWriters(ClustersCount):
469 """Set up molecule writers for SD and text files."""
470
471 Writer = None
472 ClustersOutfilesWriters = []
473
474 TextOutFileMode = OptionsInfo["TextOutFileMode"]
475 TextOutFileDelim = OptionsInfo["TextOutFileDelim"]
476 TextOutFileTitleLine = OptionsInfo["TextOutFileTitleLine"]
477
478 if OptionsInfo["SingleOutFileMode"]:
479 Outfile = OptionsInfo["Outfile"]
480 if TextOutFileMode:
481 Writer = open(Outfile, "w")
482 else:
483 Writer = RDKitUtil.MoleculesWriter(Outfile, **OptionsInfo["OutfileParams"])
484 if Writer is None:
485 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
486
487 if TextOutFileMode:
488 if TextOutFileTitleLine:
489 WriteTextFileHeaderLine(Writer, TextOutFileDelim)
490
491 MiscUtil.PrintInfo("Generating file %s..." % Outfile)
492 else:
493 for ClusterIndex in range(0, ClustersCount):
494 Outfile = OptionsInfo["ClustersOutfiles"][ClusterIndex]
495 if TextOutFileMode:
496 ClusterWriter = open(Outfile, "w")
497 else:
498 ClusterWriter = RDKitUtil.MoleculesWriter(Outfile, **OptionsInfo["OutfileParams"])
499 if ClusterWriter is None:
500 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
501
502 if TextOutFileMode:
503 if TextOutFileTitleLine:
504 WriteTextFileHeaderLine(ClusterWriter, TextOutFileDelim)
505
506 ClustersOutfilesWriters.append(ClusterWriter)
507
508 if ClustersCount > 4:
509 MiscUtil.PrintInfo(
510 "Generating %d output files with the following file name format: %s_Cluster<Num>.%s"
511 % (ClustersCount, OptionsInfo["OutfileBasename"], OptionsInfo["OutfileExt"])
512 )
513 else:
514 Delmiter = ","
515 OutfileNames = Delmiter.join(OptionsInfo["ClustersOutfiles"])
516 MiscUtil.PrintInfo("Generating %d output files: %s..." % (ClustersCount, OutfileNames))
517
518 return (Writer, ClustersOutfilesWriters)
519
520
521 def WriteTextFileHeaderLine(Writer, TextOutFileDelim):
522 """Write out a header line for text files including SMILES file."""
523
524 Line = TextOutFileDelim.join(["SMILES", "Name", "ClusterNumber"])
525 Writer.write("%s\n" % Line)
526
527
528 def SetupClustersOutFilesNames(ClustersCount):
529 """Set up out file names for clusters."""
530
531 OptionsInfo["ClustersOutfiles"] = []
532 if OptionsInfo["SingleOutFileMode"] or ClustersCount == 0:
533 # Nothing to do...
534 return
535
536 OutfileBasename = OptionsInfo["OutfileBasename"]
537 OutfileExt = OptionsInfo["OutfileExt"]
538
539 ClusterOutfiles = []
540 for ClusterIndex in range(0, ClustersCount):
541 ClusterNum = ClusterIndex + 1
542 ClusterOutfile = "%s_Cluster%d.%s" % (OutfileBasename, ClusterNum, OutfileExt)
543 ClusterOutfiles.append(ClusterOutfile)
544
545 OptionsInfo["ClustersOutfiles"] = ClusterOutfiles
546
547
548 def ProcessFingerprintsParameters():
549 """Set up and process fingerprints parameters."""
550
551 SetupFingerprintsNamesAndParameters()
552
553 ProcessSpecifiedFingerprintsName()
554 ProcessSpecifiedFingerprintsType()
555
556 ProcessSpecifiedFingerprintsParameters()
557
558
559 def SetupFingerprintsNamesAndParameters():
560 """Set up fingerprints parameters."""
561
562 OptionsInfo["FingerprintsNames"] = [
563 "AtomPairs",
564 "MACCS166Keys",
565 "Morgan",
566 "MorganFeatures",
567 "PathLength",
568 "TopologicalTorsions",
569 ]
570
571 OptionsInfo["FingerprintsParams"] = {}
572 OptionsInfo["FingerprintsParams"]["AtomPairs"] = {
573 "MinLength": 1,
574 "MaxLength": 30,
575 "UseChirality": False,
576 "FPSize": 2048,
577 "BitsPerHash": 2,
578 }
579 OptionsInfo["FingerprintsParams"]["MACCS166Keys"] = {}
580 OptionsInfo["FingerprintsParams"]["Morgan"] = {"Radius": 2, "UseChirality": False, "FPSize": 2048}
581 OptionsInfo["FingerprintsParams"]["MorganFeatures"] = {"Radius": 2, "UseChirality": False, "FPSize": 2048}
582 OptionsInfo["FingerprintsParams"]["TopologicalTorsions"] = {"UseChirality": False, "FPSize": 2048, "BitsPerHash": 4}
583 OptionsInfo["FingerprintsParams"]["PathLength"] = {"MinPath": 1, "MaxPath": 7, "FPSize": 2048, "BitsPerHash": 2}
584
585
586 def ProcessSpecifiedFingerprintsName():
587 """Process specified fingerprints name."""
588
589 # Set up a canonical fingerprints name map...
590 CanonicalFingerprintsNamesMap = {}
591 for Name in OptionsInfo["FingerprintsNames"]:
592 CanonicalName = Name.lower()
593 CanonicalFingerprintsNamesMap[CanonicalName] = Name
594
595 # Validate specified fingerprints name...
596 CanonicalFingerprintsName = OptionsInfo["Fingerprints"].lower()
597 if CanonicalFingerprintsName not in CanonicalFingerprintsNamesMap:
598 MiscUtil.PrintError(
599 'The fingerprints name, %s, specified using "-f, --fingerprints" option is not a valid name.'
600 % (OptionsInfo["Fingerprints"])
601 )
602
603 OptionsInfo["SpecifiedFingerprints"] = CanonicalFingerprintsNamesMap[CanonicalFingerprintsName]
604
605
606 def ProcessSpecifiedFingerprintsType():
607 """Process specified fingerprints type."""
608
609 FingerprintsName = OptionsInfo["SpecifiedFingerprints"]
610 FingerprintsType = OptionsInfo["FingerprintsType"]
611 SimilarityName = OptionsInfo["SimilarityMetric"]
612
613 if re.match("^auto$", FingerprintsType, re.I):
614 if re.match("^(MACCS166Keys|PathLength)$", FingerprintsName, re.I):
615 SpecifiedFingerprintsType = "BitVect"
616 else:
617 if re.match("^(Tanimoto|Dice)$", SimilarityName, re.I):
618 SpecifiedFingerprintsType = "IntVect"
619 else:
620 SpecifiedFingerprintsType = "BitVect"
621 elif re.match("^IntVect$", FingerprintsType, re.I):
622 SpecifiedFingerprintsType = "IntVect"
623
624 if re.match("^(MACCS166Keys|PathLength)$", FingerprintsName, re.I):
625 MiscUtil.PrintError(
626 'The fingerprints Type, %s, specified using "--fingerprintsType" is not allowed for fingerprints %s.'
627 % (FingerprintsType, FingerprintsName)
628 )
629
630 # RDKit similarity functions, besides Dice and Tanimoto, are not able to handle int bit vectors...
631 if not re.match("^(Tanimoto|Dice)$", SimilarityName, re.I):
632 MiscUtil.PrintError(
633 'The fingerprints Type, %s, specified using "--fingerprintsType" is not allowed for similarity metric %s.\nSupported similarity metrics: Tanimoto or Dice'
634 % (FingerprintsType, SimilarityName)
635 )
636 elif re.match("^BitVect$", FingerprintsType, re.I):
637 SpecifiedFingerprintsType = "BitVect"
638 else:
639 MiscUtil.PrintError("The fingerprints Type, %s, is not supported." % (FingerprintsType))
640
641 OptionsInfo["SpecifiedFingerprintsType"] = SpecifiedFingerprintsType
642
643
644 def ProcessSpecifiedFingerprintsParameters():
645 """Process specified fingerprints parameters."""
646
647 if re.match("^auto$", OptionsInfo["ParamsFingerprints"], re.I):
648 # Nothing to process...
649 return
650
651 SpecifiedFingerprintsName = OptionsInfo["SpecifiedFingerprints"]
652
653 # Parse specified fingerprints parameters...
654 ParamsFingerprints = re.sub(" ", "", OptionsInfo["ParamsFingerprints"])
655 if not ParamsFingerprints:
656 MiscUtil.PrintError(
657 'No valid parameter name and value pairs specified using "-p, --paramsFingerprints" option corrresponding to fingerprints %s.'
658 % (SpecifiedFingerprintsName)
659 )
660
661 ParamsFingerprintsWords = ParamsFingerprints.split(",")
662 if len(ParamsFingerprintsWords) % 2:
663 MiscUtil.PrintError(
664 'The number of comma delimited paramater names and values, %d, specified using "-p, --paramsFingerprints" option must be an even number.'
665 % (len(ParamsFingerprintsWords))
666 )
667
668 # Setup canonical parameter names for specified fingerprints...
669 ValidParamNames = []
670 CanonicalParamNamesMap = {}
671 for ParamName in sorted(OptionsInfo["FingerprintsParams"][SpecifiedFingerprintsName]):
672 ValidParamNames.append(ParamName)
673 CanonicalParamNamesMap[ParamName.lower()] = ParamName
674
675 # Validate and set paramater names and value...
676 for Index in range(0, len(ParamsFingerprintsWords), 2):
677 Name = ParamsFingerprintsWords[Index]
678 Value = ParamsFingerprintsWords[Index + 1]
679
680 CanonicalName = Name.lower()
681 if CanonicalName not in CanonicalParamNamesMap:
682 MiscUtil.PrintError(
683 'The parameter name, %s, specified using "-p, --paramsFingerprints" option for fingerprints, %s, is not a valid name. Supported parameter names: %s'
684 % (Name, SpecifiedFingerprintsName, " ".join(ValidParamNames))
685 )
686
687 ParamName = CanonicalParamNamesMap[CanonicalName]
688 if re.match("^UseChirality$", ParamName, re.I):
689 if not re.match("^(Yes|No|True|False)$", Value, re.I):
690 MiscUtil.PrintError(
691 'The parameter value, %s, specified using "-p, --paramsFingerprints" option for fingerprints, %s, is not a valid value. Supported values: Yes No True False'
692 % (Value, SpecifiedFingerprintsName)
693 )
694 ParamValue = False
695 if re.match("^(Yes|True)$", Value, re.I):
696 ParamValue = True
697 else:
698 ParamValue = int(Value)
699 if ParamValue <= 0:
700 MiscUtil.PrintError(
701 'The parameter value, %s, specified using "-p, --paramsFingerprints" option for fingerprints, %s, is not a valid value. Supported values: > 0'
702 % (Value, SpecifiedFingerprintsName)
703 )
704
705 # Set value...
706 OptionsInfo["FingerprintsParams"][SpecifiedFingerprintsName][ParamName] = ParamValue
707
708
709 def ProcessSimilarityMetricParameter():
710 """Process specified similarity metric value."""
711
712 SimilarityInfoMap = {}
713 CanonicalNameMap = {}
714
715 for SimilarityFunctionInfo in DataStructs.similarityFunctions:
716 Name = SimilarityFunctionInfo[0]
717 Function = SimilarityFunctionInfo[1]
718
719 SimilarityInfoMap[Name] = Function
720 CanonicalName = Name.lower()
721 CanonicalNameMap[CanonicalName] = Name
722
723 SpecifiedCanonicalName = OptionsInfo["SimilarityMetric"].lower()
724 SimilarityFunction = None
725 if SpecifiedCanonicalName in CanonicalNameMap:
726 SimilarityName = CanonicalNameMap[SpecifiedCanonicalName]
727 SimilarityFunction = SimilarityInfoMap[SimilarityName]
728 else:
729 MiscUtil.PrintError("Similarity metric name, %s, is not a valid name. " % OptionsInfo["SimilarityMetric"])
730
731 OptionsInfo["SimilarityMetric"] = SimilarityName
732 OptionsInfo["SimilarityFunction"] = SimilarityFunction
733
734
735 def ProcessClusteringMethodParameter():
736 """Process specified clustering method parameter."""
737
738 OptionsInfo["SpecifiedHierarchicalClusteringMethod"] = ""
739 OptionsInfo["SpecifiedHierarchicalClusteringMethodID"] = ""
740
741 if re.match("^Butina$", OptionsInfo["ClusteringMethod"], re.I):
742 # Nothing to process...
743 return
744
745 # Setup a canonical cluster method name map..
746 ClusteringMethodInfoMap = {}
747 CanonicalClusteringMethodNameMap = {}
748 for Name in sorted(rdSimDivPickers.ClusterMethod.names):
749 NameID = rdSimDivPickers.ClusterMethod.names[Name]
750 ClusteringMethodInfoMap[Name] = NameID
751
752 CanonicalName = Name.lower()
753 CanonicalClusteringMethodNameMap[CanonicalName] = Name
754
755 CanonicalName = OptionsInfo["ClusteringMethod"].lower()
756 if CanonicalName not in CanonicalClusteringMethodNameMap:
757 MiscUtil.PrintError(
758 'The clustering method, %s, specified using "-c, --clusteringMethod" option is not a valid name.'
759 % (OptionsInfo["ClusteringMethod"])
760 )
761
762 SpecifiedHierarchicalClusteringMethodName = CanonicalClusteringMethodNameMap[CanonicalName]
763 OptionsInfo["SpecifiedHierarchicalClusteringMethod"] = SpecifiedHierarchicalClusteringMethodName
764 OptionsInfo["SpecifiedHierarchicalClusteringMethodID"] = ClusteringMethodInfoMap[
765 SpecifiedHierarchicalClusteringMethodName
766 ]
767
768
769 def ProcessOptions():
770 """Process and validate command line arguments and options."""
771
772 MiscUtil.PrintInfo("Processing options...")
773
774 # Validate options...
775 ValidateOptions()
776
777 OptionsInfo["ButinaSimilarityCutoff"] = float(Options["--butinaSimilarityCutoff"])
778 OptionsInfo["ButinaReordering"] = False
779 if re.match("^Yes$", Options["--butinaReordering"], re.I):
780 OptionsInfo["ButinaReordering"] = True
781
782 OptionsInfo["Fingerprints"] = Options["--fingerprints"]
783 OptionsInfo["FingerprintsType"] = Options["--fingerprintsType"]
784
785 OptionsInfo["ClusteringMethod"] = Options["--clusteringMethod"]
786 ProcessClusteringMethodParameter()
787
788 OptionsInfo["NumClusters"] = int(Options["--numClusters"])
789
790 OptionsInfo["Infile"] = Options["--infile"]
791 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
792 "--infileParams", Options["--infileParams"], Options["--infile"]
793 )
794
795 OptionsInfo["Outfile"] = Options["--outfile"]
796 OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters(
797 "--outfileParams", Options["--outfileParams"], Options["--infile"], Options["--outfile"]
798 )
799
800 OptionsInfo["Overwrite"] = Options["--overwrite"]
801
802 OptionsInfo["OutFileMode"] = Options["--outfileMode"]
803 SingleOutFileMode = True
804 if not re.match("^SingleFile$", Options["--outfileMode"], re.I):
805 SingleOutFileMode = False
806 OptionsInfo["SingleOutFileMode"] = SingleOutFileMode
807
808 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Options["--outfile"])
809 OptionsInfo["OutfileBasename"] = FileName
810 OptionsInfo["OutfileExt"] = FileExt
811
812 TextOutFileMode = False
813 TextOutFileDelim = ""
814 TextOutFileTitleLine = True
815
816 if MiscUtil.CheckFileExt(Options["--outfile"], "csv"):
817 TextOutFileMode = True
818 TextOutFileDelim = ","
819 elif MiscUtil.CheckFileExt(Options["--outfile"], "tsv txt"):
820 TextOutFileMode = True
821 TextOutFileDelim = "\t"
822 elif MiscUtil.CheckFileExt(Options["--outfile"], "smi"):
823 TextOutFileMode = True
824 TextOutFileDelim = OptionsInfo["OutfileParams"]["SMILESDelimiter"]
825 TextOutFileTitleLine = OptionsInfo["OutfileParams"]["SMILESTitleLine"]
826
827 OptionsInfo["TextOutFileMode"] = TextOutFileMode
828 OptionsInfo["TextOutFileDelim"] = TextOutFileDelim
829 OptionsInfo["TextOutFileTitleLine"] = TextOutFileTitleLine
830
831 OptionsInfo["SimilarityMetric"] = Options["--similarityMetric"]
832 ProcessSimilarityMetricParameter()
833
834 OptionsInfo["ParamsFingerprints"] = Options["--paramsFingerprints"]
835 ProcessFingerprintsParameters()
836
837
838 def RetrieveOptions():
839 """Retrieve command line arguments and options."""
840
841 # Get options...
842 global Options
843 Options = docopt(_docoptUsage_)
844
845 # Set current working directory to the specified directory...
846 WorkingDir = Options["--workingdir"]
847 if WorkingDir:
848 os.chdir(WorkingDir)
849
850 # Handle examples option...
851 if "--examples" in Options and Options["--examples"]:
852 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
853 sys.exit(0)
854
855
856 def ValidateOptions():
857 """Validate option values."""
858
859 MiscUtil.ValidateOptionFloatValue(
860 "-b, --butinaSimilarityCutoff", Options["--butinaSimilarityCutoff"], {">": 0.0, "<=": 1.0}
861 )
862 MiscUtil.ValidateOptionTextValue("--butinaReordering", Options["--butinaReordering"], "yes no")
863
864 MiscUtil.ValidateOptionTextValue(
865 "-c, --clusteringMethod", Options["--clusteringMethod"], "Butina Centroid CLink Gower McQuitty SLink UPGMA Ward"
866 )
867 MiscUtil.ValidateOptionTextValue(
868 "-f, --fingerprints",
869 Options["--fingerprints"],
870 "AtomPairs MACCS166Keys Morgan MorganFeatures PathLength TopologicalTorsions",
871 )
872 MiscUtil.ValidateOptionTextValue("--fingerprintsType", Options["--fingerprintsType"], "IntVect BitVect auto")
873
874 MiscUtil.ValidateOptionIntegerValue("-n, --numClusters", Options["--numClusters"], {">": 0})
875
876 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
877 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol smi csv tsv txt")
878
879 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd smi csv tsv txt")
880 MiscUtil.ValidateOptionsOutputFileOverwrite(
881 "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
882 )
883 MiscUtil.ValidateOptionsDistinctFileNames(
884 "-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]
885 )
886
887 MiscUtil.ValidateOptionTextValue("--outfileMode", Options["--outfileMode"], "SingleFile MultipleFiles")
888
889 MiscUtil.ValidateOptionTextValue(
890 "-s, --similarityMetric",
891 Options["--similarityMetric"],
892 "BraunBlanquet Cosine Dice Kulczynski RogotGoldberg Russel Sokal Tanimoto",
893 )
894
895
896 # Setup a usage string for docopt...
897 _docoptUsage_ = """
898 RDKitClusterMolecules.py - Cluster molecules using 2D fingerprints
899
900 Usage:
901 RDKitClusterMolecules.py [--butinaSimilarityCutoff <number>] [--butinaReordering <yes or no>]
902 [--clusteringMethod <Butina, Centroid, CLink...>] [--fingerprints <MACCS166Keys, Morgan, PathLength...> ]
903 [--fingerprintsType <IntVect, BitVect, or Auto>] [--infileParams <Name,Value,...>]
904 [--numClusters <number>] [--outfileMode <SingleFile or MultipleFiles>]
905 [ --outfileParams <Name,Value,...> ] [--overwrite] [--paramsFingerprints <Name,Value,...>]
906 [--similarityMetric <Dice, Tanimoto...>] [-w <dir>] -i <infile> -o <outfile>
907 RDKitClusterMolecules.py -h | --help | -e | --examples
908
909 Description:
910 Cluster molecules based on a variety of 2D fingerprints using Butina [ Ref 136 ] or any
911 other available hierarchical clustering methodology and write them to output file(s).
912
913 The supported input file formats are: Mol (.mol), SD (.sdf, .sd), SMILES (.smi,
914 .txt, .csv, .tsv)
915
916 The supported output file formats are: SD (.sdf, .sd), SMILES (.smi), CSV/TSV
917 (.csv, .tsv, .txt)
918
919 Options:
920 -b, --butinaSimilarityCutoff <number> [default: 0.55]
921 Similarity cutoff to use during Butina clustering. The molecule pairs with
922 similarity value greater than specified value or distance less than '1 - specified
923 value' are considered neighbors. This value is only used during 'Butina' value
924 of '-c, --clusteringMethod' option and determines the number of clusters
925 during the clustering of molecules. It is ignored for all other clustering methods.
926 --butinaReordering <yes or no> [default: no]
927 Update number of neighbors for unassigned molecules after creating a new
928 cluster in order to insure that the molecule with the largest number of
929 unassigned neighbors is selected as the next cluster center.
930 -c, --clusteringMethod <Butina, Centroid, CLink...> [default: Butina]
931 Clustering method to use for clustering molecules. Supported values:
932 Butina, Centroid, CLink, Gower, McQuitty, SLink, UPGMA, Ward.
933 Butina is an unsupervised database clustering method to automatically
934 cluster small and large data sets. All other clustering methods correspond
935 to hierarchical clustering and require a priori specification of number of
936 clusters to be generated.
937 -f, --fingerprints <MACCS166Keys, Morgan, PathLength...> [default: Morgan]
938 Fingerprints to use for calculating similarity/distance between molecules.
939 Supported values: AtomPairs, MACCS166Keys, Morgan, MorganFeatures, PathLength,
940 TopologicalTorsions. The PathLength fingerprints are Daylight like fingerprints.
941 The Morgan and MorganFeature fingerprints are circular fingerprints, corresponding
942 Scitegic's Extended Connectivity Fingerprints (ECFP) and Features Connectivity
943 Fingerprints (FCFP). The values of default parameters for generating fingerprints
944 can be modified using '-p, --paramsFingerprints' option.
945 --fingerprintsType <IntVect, BitVect, or auto> [default: auto]
946 Fingerprints type to generate for calculating similarity. Supported values:
947 IntVect, BitVect, Auto.
948
949 The following default fingerprints type are automatically generated for
950 available fingerprints, based on the value of similarty metric:
951
952 AtomPairs Tanimoto|Dice: IntVect All Others: BitVect
953 MACCS166Keys All: BitVect
954 Morgan Tanimoto|Dice: IntVect All Others: BitVect
955 MorganFeatures Tanimoto|Dice: IntVect All Others: BitVect
956 PathLength All: BitVect
957 TopologicalTorsions Tanimoto|Dice: IntVect All Others: BitVect
958
959 The Dice and Tanimoto similarity functions available in RDKit are able to
960 handle fingerprints corresponding to both IntVect and BitVect. All other
961 similarity functions, however, expect BitVect fingerprints to calculate
962 pairwise similarity. Consequently, BitVect fingerprints, instead of
963 default IntVect fingerprints, are generated for AtomPairs, Morgan,
964 MorganFeatures, and TopologicalTorsions during the calculation
965 of similarity using all other similarity functions.
966
967 The IntVect fingerprints type is not available for MACCS166Keys and
968 Pathlength fingerprints. In addition, IntVect fingerprints type is only
969 valid for Tanimoto or Dice value of ' -s, --similarityMetric' option. The
970 BitVect fingerprints type is valid for all values of '' -s, --similarityMetric'
971 option.
972 -e, --examples
973 Print examples.
974 -h, --help
975 Print this help message.
976 -i, --infile <infile>
977 Input file name.
978 --infileParams <Name,Value,...> [default: auto]
979 A comma delimited list of parameter name and value pairs for reading
980 molecules from files. The supported parameter names for different file
981 formats, along with their default values, are shown below:
982
983 SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
984 SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
985 smilesTitleLine,auto,sanitize,yes
986
987 Possible values for smilesDelimiter: space, comma or tab.
988 -n, --numClusters <number> [default: 10]
989 Number of clusters to generate during hierarchical clustering. This option is
990 ignored for 'Butina' value of '-c, --clusteringMethod' option.
991 -o, --outfile <outfile>
992 Output file name.
993 --outfileMode <SingleFile or MultipleFiles> [default: SingleFile]
994 Write out a single file containing molecule clusters or generate an individual file
995 for each cluster. Possible values: SingleFile or MultipleFiles. The molecules are
996 grouped for each cluster before they are written to output file(s) along with
997 appropriate cluster numbers. The cluster number is also appended to output
998 file names during generation of multiple output files.
999 --outfileParams <Name,Value,...> [default: auto]
1000 A comma delimited list of parameter name and value pairs for writing
1001 molecules to files. The supported parameter names for different file
1002 formats, along with their default values, are shown below:
1003
1004 SD: compute2DCoords,auto,kekulize,yes,forceV3000,no
1005 SMILES: smilesKekulize,no,smilesDelimiter,space, smilesIsomeric,yes,
1006 smilesTitleLine,yes
1007
1008 Default value for compute2DCoords: yes for SMILES input file; no for all other
1009 file types. The kekulize and smilesIsomeric parameters are also used during
1010 generation of SMILES strings for CSV/TSV files.
1011 --overwrite
1012 Overwrite existing files.
1013 -p, --paramsFingerprints <Name,Value,...> [default: auto]
1014 Parameter values to use for generating fingerprints. The default values
1015 are dependent on the value of '-f, --fingerprints' option. In general, it is a
1016 comma delimited list of parameter name and value pairs for the name of
1017 fingerprints specified using '-f, --fingerprints' option. The supported
1018 parameter names along with their default values for valid fingerprints
1019 names are shown below:
1020
1021 AtomPairs: minLength,1 ,maxLength,30, useChirality,No,
1022 fpSize, 2048, bitsPerHash,4
1023 Morgan: radius,2, useChirality,No, fpSize, 2048
1024 MorganFeatures: radius,2, useChirality,No, fpSize, 2048
1025 PathLength: minPath,1, maxPath,7, fpSize, 2048, bitsPerHash,2
1026 TopologicalTorsions: useChirality,No, fpSize, 2048, bitsPerHash,4
1027
1028 The fpSize and bitsPerHash are only used for BitVect fingerprints type
1029 specified using '--fingerprintsType' option.
1030 -s, --similarityMetric <Dice, Tanimoto...> [default: Tanimoto]
1031 Similarity metric to use for calculating similarity/distance between molecules.
1032 Possible values: BraunBlanquet, Cosine, Dice, Kulczynski, RogotGoldberg,
1033 Russel, Sokal, Tanimoto.
1034 -w, --workingdir <dir>
1035 Location of working directory which defaults to the current directory.
1036
1037 Examples:
1038 To cluster molecules using Butina methodology at a similarity cutoff of 0.55
1039 with automatic determination of number of clusters, Tanimoto similarity
1040 metric corresponding to Morgan fingerprints with radius of 2, and write out
1041 a single SMILES file containing clustered molecules along with cluster number
1042 for each molecule, type:
1043
1044 % RDKitClusterMolecules.py -i Sample.smi -o SampleOut.smi
1045
1046 To cluster molecules using Butina methodology at a similarity cutoff of 0.55
1047 with automatic determination of number of clusters, Tanimoto similarity
1048 metric corresponding to Morgan fingerprints with radius of 2 and type
1049 BitVect, fingerprint BitVect size of 4096, and write out a single SMILES file
1050 containing clustered molecules along with cluster number for each molecule,
1051 type:
1052
1053 % RDKitClusterMolecules.py -f Morgan --fingerprintsType BitVect
1054 -p "fpSize,4096" -s Tanimoto -i Sample.smi -o SampleOut.smi
1055
1056 To cluster molecules using Butina methodology at similarity cutoff of 0.45
1057 with automatic determination of number of clusters, Dice similarity metric
1058 corresponding to Morgan fingerprints with radius of 2, and write out multiple
1059 SD files containing clustered molecules for each cluster, type:
1060
1061 % RDKitClusterMolecules.py -b 0.45 -s Dice --outfileMode MultipleFiles
1062 -i Sample.smi -o SampleOut.sdf
1063
1064 To cluster molecules using Ward hierarchical methodology to generate 15
1065 clusters, Dice similarity metric corresponding to Pathlength fingerprints with
1066 path length between 1 and 7, and write out a single TSV file for clustered
1067 molecules along with cluster numner for each molecule, type:
1068
1069 % RDKitClusterMolecules.py -c Ward -f PathLength -n 15
1070 -p 'minPath,1, maxPath,7' -i Sample.sdf -o SampleOut.tsv
1071
1072 To cluster molecules using Centroid hierarchical methodology to generate 5
1073 clusters, Dice similarity metric corresponding to MACCS166Keys fingerprints
1074 for molecules in a SMILES CSV file, SMILES strings in column 1, name in
1075 column 2, and write out a single SD file for clustered molecules along with
1076 cluster numner for each molecule, type:
1077
1078 % RDKitClusterMolecules.py -c Centroid -f MACCS166Keys --infileParams
1079 "smilesDelimiter,comma,smilesTitleLine,yes,smilesColumn,1,
1080 smilesNameColumn,2" --outfileParams "compute2DCoords,yes"
1081 -i SampleSMILES.csv -o SampleOut.sdf
1082
1083 Author:
1084 Manish Sud(msud@san.rr.com)
1085
1086 See also:
1087 RDKitConvertFileFormat.py, RDKitPickDiverseMolecules.py, RDKitSearchFunctionalGroups.py,
1088 RDKitSearchSMARTS.py
1089
1090 Copyright:
1091 Copyright (C) 2026 Manish Sud. All rights reserved.
1092
1093 The functionality available in this script is implemented using RDKit, an
1094 open source toolkit for cheminformatics developed by Greg Landrum.
1095
1096 This file is part of MayaChemTools.
1097
1098 MayaChemTools is free software; you can redistribute it and/or modify it under
1099 the terms of the GNU Lesser General Public License as published by the Free
1100 Software Foundation; either version 3 of the License, or (at your option) any
1101 later version.
1102
1103 """
1104
1105 if __name__ == "__main__":
1106 main()