| name | rdkit |
| description | Open-source cheminformatics and machine learning toolkit for drug discovery, molecular manipulation, and chemical property calculation. RDKit handles SMILES, molecular fingerprints, substructure searching, 3D conformer generation, pharmacophore modeling, and QSAR. Use when working with chemical structures, drug-like properties, molecular similarity, virtual screening, or computational chemistry workflows. |
| version | 2023.09.4 |
| license | BSD-3-Clause |
RDKit - Cheminformatics and Drug Discovery
RDKit is the industry-standard open-source toolkit for cheminformatics. It provides comprehensive tools for molecular manipulation, descriptor calculation, fingerprinting, substructure searching, and 3D molecular modeling. RDKit is used extensively in pharmaceutical companies for drug discovery and virtual screening.
When to Use
- Reading and writing chemical file formats (SMILES, SDF, MOL2, PDB).
- Calculating molecular descriptors and drug-like properties (Lipinski's Rule of Five).
- Generating molecular fingerprints for similarity searching.
- Substructure searching and chemical pattern matching (SMARTS).
- 3D conformer generation and molecular alignment.
- Virtual screening of compound libraries.
- Pharmacophore modeling and shape similarity.
- QSAR (Quantitative Structure-Activity Relationship) modeling.
- Reaction enumeration and retrosynthesis.
- Visualizing chemical structures in 2D and 3D.
- Building machine learning models for molecular property prediction.
Reference Documentation
Official docs: https://www.rdkit.org/docs/
RDKit Book: https://www.rdkit.org/docs/RDKit_Book.html
GitHub: https://github.com/rdkit/rdkit
Search patterns: rdkit.Chem, rdkit.Chem.Descriptors, rdkit.Chem.AllChem, rdkit.DataStructs
Core Principles
Molecular Representation
RDKit represents molecules as graphs where atoms are nodes and bonds are edges. The core object is Mol, which can be created from SMILES, SDF files, or built programmatically.
SMILES (Simplified Molecular Input Line Entry System)
A text-based notation for chemical structures. Example: CCO is ethanol, c1ccccc1 is benzene. RDKit can parse and generate SMILES strings.
Fingerprints for Similarity
Molecular fingerprints are binary vectors encoding structural features. They enable fast similarity searching and clustering of large compound libraries.
Lazy Evaluation
Many RDKit operations are lazy - properties are computed only when needed. This makes operations on large libraries very efficient.
Quick Reference
Installation
conda install -c conda-forge rdkit
pip install rdkit
pip install rdkit pillow
Standard Imports
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors, Draw, Lipinski
from rdkit.Chem import rdFingerprintGenerator
from rdkit import DataStructs
import numpy as np
import pandas as pd
Basic Pattern - SMILES to Molecule
from rdkit import Chem
smiles = "CC(=O)OC1=CC=CC=C1C(=O)O"
mol = Chem.MolFromSmiles(smiles)
if mol is None:
print("Invalid SMILES")
else:
print(f"Molecular formula: {Chem.rdMolDescriptors.CalcMolFormula(mol)}")
print(f"Molecular weight: {Descriptors.MolWt(mol):.2f}")
Basic Pattern - Calculate Properties
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
mw = Descriptors.MolWt(mol)
logp = Descriptors.MolLogP(mol)
hbd = Lipinski.NumHDonors(mol)
hba = Lipinski.NumHAcceptors(mol)
print(f"MW: {mw:.2f}, LogP: {logp:.2f}, HBD: {hbd}, HBA: {hba}")
lipinski_pass = (mw <= 500 and logp <= 5 and hbd <= 5 and hba <= 10)
print(f"Lipinski compliant: {lipinski_pass}")
Critical Rules
✅ DO
- Always Validate Molecules - Check
mol is not None after parsing SMILES/files to catch invalid structures.
- Use Canonical SMILES - Use
Chem.MolToSmiles(mol) to get canonical (standardized) SMILES for comparison.
- Sanitize Molecules - RDKit auto-sanitizes by default (valence checking, aromaticity). Keep it enabled unless you have a specific reason.
- Generate 3D Coordinates - Use
AllChem.EmbedMolecule() before 3D operations like alignment or docking.
- Use Fingerprints for Large Libraries - For similarity searching in millions of compounds, fingerprints are 1000x faster than direct comparison.
- Specify Random Seeds - For reproducible conformer generation, always set
randomSeed.
- Handle Stereochemistry - Use
Chem.AssignStereochemistry() to properly assign R/S and E/Z labels.
- Batch Processing - Use generators or chunking for processing millions of molecules to avoid memory issues.
❌ DON'T
- Don't Ignore Invalid Molecules - Always handle the case when
MolFromSmiles() returns None.
- Don't Compare SMILES Strings Directly - Two different SMILES can represent the same molecule. Use canonical SMILES or InChI.
- Don't Skip Kekulization - For aromatic systems, ensure proper Kekulé structure assignment.
- Don't Use Descriptors for Similarity - Use fingerprints (much faster and more appropriate).
- Don't Forget Hydrogens - Add explicit hydrogens with
Chem.AddHs() when needed for 3D operations.
- Don't Overuse 3D Minimization - Energy minimization is slow; only use when necessary (docking, visualization).
Anti-Patterns (NEVER)
from rdkit import Chem
from rdkit.Chem import AllChem
smiles = "INVALID_SMILES"
mol = Chem.MolFromSmiles(smiles)
mw = Descriptors.MolWt(mol)
mol = Chem.MolFromSmiles(smiles)
if mol is not None:
mw = Descriptors.MolWt(mol)
else:
print("Invalid SMILES")
smiles1 = "CC(C)C"
smiles2 = "C(C)CC"
if smiles1 == smiles2:
print("Same")
mol1 = Chem.MolFromSmiles(smiles1)
mol2 = Chem.MolFromSmiles(smiles2)
can1 = Chem.MolToSmiles(mol1)
can2 = Chem.MolToSmiles(mol2)
if can1 == can2:
print("Same molecule")
mol = Chem.MolFromSmiles("CCO")
AllChem.AlignMol(mol, ref_mol)
mol = Chem.MolFromSmiles("CCO")
AllChem.EmbedMolecule(mol)
AllChem.AlignMol(mol, ref_mol)
Molecular I/O and Conversion
SMILES Parsing
from rdkit import Chem
mol = Chem.MolFromSmiles("CCO")
mol = Chem.MolFromSmiles("CCO", sanitize=True)
canonical = Chem.MolToSmiles(mol)
iso_smiles = Chem.MolToSmiles(mol, isomericSmiles=True)
non_iso = Chem.MolToSmiles(mol, isomericSmiles=False)
smiles_list = ["CCO", "INVALID", "c1ccccc1"]
mols = []
for smi in smiles_list:
mol = Chem.MolFromSmiles(smi)
if mol is not None:
mols.append(mol)
else:
print(f"Failed to parse: {smi}")
Reading SDF Files
from rdkit import Chem
mol = Chem.MolFromMolFile("molecule.mol")
suppl = Chem.SDMolSupplier("compounds.sdf")
for mol in suppl:
if mol is None:
continue
smiles = Chem.MolToSmiles(mol)
print(f"SMILES: {smiles}")
if mol.HasProp("_Name"):
name = mol.GetProp("_Name")
print(f"Name: {name}")
suppl = Chem.SDMolSupplier("compounds.sdf", removeHs=False)
Writing SDF Files
from rdkit import Chem
mol = Chem.MolFromSmiles("CCO")
writer = Chem.SDWriter("output.sdf")
writer.write(mol)
writer.close()
mols = [Chem.MolFromSmiles(s) for s in ["CCO", "c1ccccc1", "CC(=O)O"]]
writer = Chem.SDWriter("output.sdf")
for mol in mols:
if mol is not None:
writer.write(mol)
writer.close()
mol = Chem.MolFromSmiles("CCO")
mol.SetProp("_Name", "Ethanol")
mol.SetProp("Activity", "10.5")
writer = Chem.SDWriter("output.sdf")
writer.write(mol)
writer.close()
InChI and InChIKey
from rdkit import Chem
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
inchi = Chem.MolToInchi(mol)
print(f"InChI: {inchi}")
inchikey = Chem.MolToInchiKey(mol)
print(f"InChIKey: {inchikey}")
mol_from_inchi = Chem.MolFromInchi(inchi)
Molecular Descriptors
Common Descriptors
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski, Crippen
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
mw = Descriptors.MolWt(mol)
num_atoms = mol.GetNumAtoms()
num_heavy_atoms = Lipinski.HeavyAtomCount(mol)
logp = Descriptors.MolLogP(mol)
hbd = Lipinski.NumHDonors(mol)
hba = Lipinski.NumHAcceptors(mol)
rotatable_bonds = Lipinski.NumRotatableBonds(mol)
tpsa = Descriptors.TPSA(mol)
rings = Lipinski.RingCount(mol)
aromatic_rings = Lipinski.NumAromaticRings(mol)
bertz_ct = Descriptors.BertzCT(mol)
print(f"""
Molecular Weight: {mw:.2f}
LogP: {logp:.2f}
HBD: {hbd}
HBA: {hba}
TPSA: {tpsa:.2f}
Rotatable Bonds: {rotatable_bonds}
Aromatic Rings: {aromatic_rings}
""")
Calculate All Descriptors
from rdkit import Chem
from rdkit.Chem import Descriptors
mol = Chem.MolFromSmiles("CCO")
descriptor_names = [desc[0] for desc in Descriptors.descList]
descriptors = {}
for name in descriptor_names:
calc = getattr(Descriptors, name)
descriptors[name] = calc(mol)
print(f"Total descriptors: {len(descriptors)}")
print(f"First 5: {list(descriptors.items())[:5]}")
Drug-Likeness Filters
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski
def check_lipinski(mol):
"""Check Lipinski's Rule of Five."""
mw = Descriptors.MolWt(mol)
logp = Descriptors.MolLogP(mol)
hbd = Lipinski.NumHDonors(mol)
hba = Lipinski.NumHAcceptors(mol)
rules = {
'MW <= 500': mw <= 500,
'LogP <= 5': logp <= 5,
'HBD <= 5': hbd <= 5,
'HBA <= 10': hba <= 10
}
passed = all(rules.values())
return passed, rules
def check_veber(mol):
"""Check Veber's rules for oral bioavailability."""
rotatable = Lipinski.NumRotatableBonds(mol)
tpsa = Descriptors.TPSA(mol)
rules = {
'Rotatable bonds <= 10': rotatable <= 10,
'TPSA <= 140': tpsa <= 140
}
passed = all(rules.values())
return passed, rules
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
lipinski_pass, lipinski_rules = check_lipinski(mol)
veber_pass, veber_rules = check_veber(mol)
print(f"Lipinski: {lipinski_pass}")
print(f"Veber: {veber_pass}")
Molecular Fingerprints
Morgan Fingerprints (Circular)
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit import DataStructs
mol = Chem.MolFromSmiles("CCO")
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
import numpy as np
arr = np.zeros((1,))
DataStructs.ConvertToNumpyArray(fp, arr)
fp_counts = AllChem.GetMorganFingerprint(mol, radius=2)
info = {}
fp_info = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048, bitInfo=info)
print(f"Number of on-bits: {len(info)}")
Fingerprint Similarity
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit import DataStructs
mol1 = Chem.MolFromSmiles("CCO")
mol2 = Chem.MolFromSmiles("CCCO")
mol3 = Chem.MolFromSmiles("c1ccccc1")
fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, radius=2)
fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, radius=2)
fp3 = AllChem.GetMorganFingerprintAsBitVect(mol3, radius=2)
sim_12 = DataStructs.TanimotoSimilarity(fp1, fp2)
sim_13 = DataStructs.TanimotoSimilarity(fp1, fp3)
print(f"Ethanol vs Propanol: {sim_12:.3f}")
print(f"Ethanol vs Benzene: {sim_13:.3f}")
dice_12 = DataStructs.DiceSimilarity(fp1, fp2)
fps = [fp1, fp2, fp3]
similarities = DataStructs.BulkTanimotoSimilarity(fp1, fps)
print(f"Bulk similarities: {similarities}")
Other Fingerprint Types
from rdkit import Chem
from rdkit.Chem import AllChem, RDKFingerprint
mol = Chem.MolFromSmiles("CCO")
fp_rdkit = Chem.RDKFingerprint(mol)
fp_atompair = AllChem.GetHashedAtomPairFingerprintAsBitVect(mol)
fp_torsion = AllChem.GetHashedTopologicalTorsionFingerprintAsBitVect(mol)
from rdkit.Chem import MACCSkeys
fp_maccs = MACCSkeys.GenMACCSKeys(mol)
Substructure Searching
SMARTS Pattern Matching
from rdkit import Chem
mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")
pattern = Chem.MolFromSmarts("C(=O)O")
has_match = mol.HasSubstructMatch(pattern)
print(f"Contains carboxylic acid: {has_match}")
matches = mol.GetSubstructMatches(pattern)
print(f"Number of matches: {len(matches)}")
print(f"Matching atom indices: {matches}")
patterns = {
'Carboxylic acid': 'C(=O)O',
'Ester': 'C(=O)O[C,c]',
'Amide': 'C(=O)N',
'Alcohol': '[OH][C,c]',
'Primary amine': '[NH2][C,c]',
'Aromatic ring': 'c1ccccc1'
}
for name, smarts in patterns.items():
pattern = Chem.MolFromSmarts(smarts)
if mol.HasSubstructMatch(pattern):
print(f"Contains {name}")
Substructure Filtering
from rdkit import Chem
smiles_list = [
"CC(=O)O",
"CCO",
"c1ccccc1C(=O)O",
"CCCC",
]
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
pattern = Chem.MolFromSmarts("C(=O)O")
filtered = [mol for mol in mols if mol.HasSubstructMatch(pattern)]
print(f"Molecules with carboxylic acid: {len(filtered)}/{len(mols)}")
pattern1 = Chem.MolFromSmarts("c1ccccc1")
pattern2 = Chem.MolFromSmarts("C(=O)O")
aromatic_acids = [
mol for mol in mols
if mol.HasSubstructMatch(pattern1) and mol.HasSubstructMatch(pattern2)
]
Replace Substructures
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("CC(=O)O")
rxn = AllChem.ReactionFromSmarts('[C:1](=O)O>>[C:1](=O)OC')
products = rxn.RunReactants((mol,))
if products:
product = products[0][0]
print(f"Product: {Chem.MolToSmiles(product)}")
3D Conformer Generation
Generate 3D Coordinates
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("CCO")
mol = Chem.AddHs(mol)
result = AllChem.EmbedMolecule(mol, randomSeed=42)
if result == 0:
print("3D coordinates generated")
else:
print("Failed to generate 3D coordinates")
AllChem.MMFFOptimizeMolecule(mol)
conf = mol.GetConformer()
for i in range(mol.GetNumAtoms()):
pos = conf.GetAtomPosition(i)
print(f"Atom {i}: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})")
Multiple Conformers
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("CCCC")
mol = Chem.AddHs(mol)
conf_ids = AllChem.EmbedMultipleConfs(
mol,
numConfs=10,
randomSeed=42,
pruneRmsThresh=0.5
)
print(f"Generated {len(conf_ids)} conformers")
for conf_id in conf_ids:
AllChem.MMFFOptimizeMolecule(mol, confId=conf_id)
props = AllChem.MMFFGetMoleculeProperties(mol)
for conf_id in conf_ids:
ff = AllChem.MMFFGetMoleculeForceField(mol, props, confId=conf_id)
energy = ff.CalcEnergy()
print(f"Conformer {conf_id}: {energy:.2f} kcal/mol")
Molecular Alignment
from rdkit import Chem
from rdkit.Chem import AllChem
ref_mol = Chem.MolFromSmiles("c1ccccc1C")
ref_mol = Chem.AddHs(ref_mol)
AllChem.EmbedMolecule(ref_mol)
probe_mol = Chem.MolFromSmiles("c1ccccc1CC")
probe_mol = Chem.AddHs(probe_mol)
AllChem.EmbedMolecule(probe_mol)
rmsd = AllChem.AlignMol(probe_mol, ref_mol)
print(f"RMSD: {rmsd:.3f} Å")
Molecular Visualization
2D Drawings
from rdkit import Chem
from rdkit.Chem import Draw
import matplotlib.pyplot as plt
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
img = Draw.MolToImage(mol, size=(300, 300))
plt.imshow(img)
plt.axis('off')
plt.show()
mols = [Chem.MolFromSmiles(s) for s in ["CCO", "c1ccccc1", "CC(=O)O"]]
legends = ["Ethanol", "Benzene", "Acetic acid"]
img = Draw.MolsToGridImage(
mols,
molsPerRow=3,
subImgSize=(200, 200),
legends=legends
)
plt.imshow(img)
plt.axis('off')
plt.show()
Highlight Substructures
from rdkit import Chem
from rdkit.Chem import Draw
mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O")
pattern = Chem.MolFromSmarts("C(=O)O")
match = mol.GetSubstructMatch(pattern)
img = Draw.MolToImage(mol, highlightAtoms=match, size=(300, 300))
Save to File
from rdkit import Chem
from rdkit.Chem import Draw
mol = Chem.MolFromSmiles("CCO")
Draw.MolToFile(mol, "molecule.png", size=(300, 300))
from rdkit.Chem.Draw import rdMolDraw2D
drawer = rdMolDraw2D.MolDraw2DSVG(300, 300)
drawer.DrawMolecule(mol)
drawer.FinishDrawing()
svg = drawer.GetDrawingText()
with open("molecule.svg", "w") as f:
f.write(svg)
Practical Workflows
1. Virtual Screening Pipeline
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors, Lipinski
from rdkit import DataStructs
import pandas as pd
def screen_library(library_file, reference_smiles, similarity_threshold=0.7):
"""Screen compound library for similar, drug-like molecules."""
ref_mol = Chem.MolFromSmiles(reference_smiles)
ref_fp = AllChem.GetMorganFingerprintAsBitVect(ref_mol, radius=2)
hits = []
suppl = Chem.SDMolSupplier(library_file)
for i, mol in enumerate(suppl):
if mol is None:
continue
mw = Descriptors.MolWt(mol)
logp = Descriptors.MolLogP(mol)
hbd = Lipinski.NumHDonors(mol)
hba = Lipinski.NumHAcceptors(mol)
if not (mw <= 500 and logp <= 5 and hbd <= 5 and hba <= 10):
continue
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2)
similarity = DataStructs.TanimotoSimilarity(ref_fp, fp)
if similarity < similarity_threshold:
continue
rdkit.Chem FilterCatalog
params = FilterCatalog.FilterCatalogParams()
params.AddCatalog(FilterCatalog.FilterCatalogParams.FilterCatalogs.PAINS)
catalog = FilterCatalog.FilterCatalog(params)
catalog.HasMatch(mol):
hits.append({
: i,
: Chem.MolToSmiles(mol),
: similarity,
: mw,
: logp
})
df_hits = pd.DataFrame(hits)
df_hits = df_hits.sort_values(, ascending=)
df_hits
2. Diversity Selection
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit import DataStructs
import numpy as np
def select_diverse_set(smiles_list, n_select=100):
"""Select diverse subset using MaxMin algorithm."""
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
fps = [AllChem.GetMorganFingerprintAsBitVect(m, radius=2) for m in mols if m]
if len(fps) < n_select:
return list(range(len(fps)))
from rdkit.SimDivFilters import MaxMinPicker
def distance_function(i, j):
return 1 - DataStructs.TanimotoSimilarity(fps[i], fps[j])
picker = MaxMinPicker()
picks = picker.LazyPick(
distance_function,
len(fps),
n_select,
seed=42
)
return list(picks)
smiles_list = ["CCO", "CCCO", "c1ccccc1", "CC(=O)O", "CCCCCCCC"]
diverse_indices = select_diverse_set(smiles_list, n_select=3)
diverse_smiles = [smiles_list[i] i diverse_indices]
()
3. QSAR Model Building
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
import numpy as np
def build_qsar_model(smiles_list, activities):
"""Build QSAR model from SMILES and activities."""
fps = []
valid_activities = []
for smi, act in zip(smiles_list, activities):
mol = Chem.MolFromSmiles(smi)
if mol is not None:
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
arr = np.zeros((1,))
DataStructs.ConvertToNumpyArray(fp, arr)
fps.append(arr)
valid_activities.append(act)
X = np.array(fps)
y = np.array(valid_activities)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
print(f"Train R²: {r2_score(y_train, y_pred_train):.3f}")
()
()
model
4. Scaffold Analysis
from rdkit import Chem
from rdkit.Chem.Scaffolds import MurckoScaffold
from collections import Counter
def analyze_scaffolds(smiles_list):
"""Analyze Murcko scaffolds in compound set."""
scaffolds = []
for smi in smiles_list:
mol = Chem.MolFromSmiles(smi)
if mol is not None:
scaffold = MurckoScaffold.GetScaffoldForMol(mol)
scaffold_smi = Chem.MolToSmiles(scaffold)
scaffolds.append(scaffold_smi)
scaffold_counts = Counter(scaffolds)
print(f"Unique scaffolds: {len(scaffold_counts)}")
print("\nTop 5 scaffolds:")
for scaffold, count in scaffold_counts.most_common(5):
print(f"{scaffold}: {count}")
return scaffold_counts
5. Reaction Enumeration
from rdkit import Chem
from rdkit.Chem import AllChem
def enumerate_amide_coupling(acids, amines):
"""Enumerate all possible amide products."""
rxn = AllChem.ReactionFromSmarts('[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3]')
products = []
for acid_smi in acids:
for amine_smi in amines:
acid = Chem.MolFromSmiles(acid_smi)
amine = Chem.MolFromSmiles(amine_smi)
if acid is None or amine is None:
continue
products_tuple = rxn.RunReactants((acid, amine))
if products_tuple:
product = products_tuple[0][0]
Chem.SanitizeMol(product)
product_smi = Chem.MolToSmiles(product)
products.append({
'acid': acid_smi,
'amine': amine_smi,
'product': product_smi
})
return products
acids = ["CC(=O)O", "c1ccccc1C(=O)O"]
amines = ["CCN", "c1ccccc1N"]
products = enumerate_amide_coupling(acids, amines)
print(f"Generated {len(products)} amides")
Performance Optimization
Bulk Operations
from rdkit import Chem
from rdkit.Chem import AllChem
import pandas as pd
from rdkit.Chem import PandasTools
df = pd.DataFrame({'SMILES': smiles_list})
PandasTools.AddMoleculeColumnToFrame(df, 'SMILES', 'Molecule')
df['MW'] = df['Molecule'].apply(lambda x: Descriptors.MolWt(x) if x else None)
df['LogP'] = df['Molecule'].apply(lambda x: Descriptors.MolLogP(x) if x else None)
Parallel Processing
from rdkit import Chem
from rdkit.Chem import AllChem
from multiprocessing import Pool
import pandas as pd
def process_molecule(smiles):
"""Process single molecule."""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
return {
'smiles': smiles,
'mw': Descriptors.MolWt(mol),
'logp': Descriptors.MolLogP(mol),
'fp': AllChem.GetMorganFingerprintAsBitVect(mol, 2)
}
def process_library_parallel(smiles_list, n_jobs=4):
"""Process library in parallel."""
with Pool(n_jobs) as pool:
results = pool.map(process_molecule, smiles_list)
results = [r for r in results if r is not None]
return pd.DataFrame(results)
Caching Calculations
from functools import lru_cache
from rdkit import Chem
from rdkit.Chem import Descriptors
@lru_cache(maxsize=10000)
def get_mol_properties(smiles):
"""Calculate properties with caching."""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
return {
'mw': Descriptors.MolWt(mol),
'logp': Descriptors.MolLogP(mol),
'tpsa': Descriptors.TPSA(mol)
}
props1 = get_mol_properties("CCO")
props2 = get_mol_properties("CCO")
Common Pitfalls and Solutions
The "Invalid SMILES" Problem
Not all SMILES strings are valid.
smiles_list = ["CCO", "INVALID", "c1ccccc1"]
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
valid_mols = [m for m in mols if m is not None]
results = []
for smi in smiles_list:
mol = Chem.MolFromSmiles(smi)
if mol is not None:
results.append({'smiles': smi, 'mol': mol, 'valid': True})
else:
results.append({'smiles': smi, 'mol': None, 'valid': False})
The "Stereochemistry Loss" Problem
SMILES generation can lose stereochemistry if not careful.
from rdkit import Chem
chiral_smiles = "C[C@H](O)CC"
mol = Chem.MolFromSmiles(chiral_smiles)
non_iso = Chem.MolToSmiles(mol, isomericSmiles=False)
print(non_iso)
iso = Chem.MolToSmiles(mol, isomericSmiles=True)
print(iso)
The "3D Without Hydrogens" Problem
3D operations require explicit hydrogens.
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("CCO")
result = AllChem.EmbedMolecule(mol)
mol_h = Chem.AddHs(mol)
result = AllChem.EmbedMolecule(mol_h)
AllChem.MMFFOptimizeMolecule(mol_h)
The "Fingerprint Type Mismatch" Problem
Comparing different fingerprint types gives meaningless results.
from rdkit import Chem
from rdkit.Chem import AllChem, MACCSkeys
from rdkit import DataStructs
mol1 = Chem.MolFromSmiles("CCO")
mol2 = Chem.MolFromSmiles("CCCO")
fp1_morgan = AllChem.GetMorganFingerprintAsBitVect(mol1, 2)
fp2_maccs = MACCSkeys.GenMACCSKeys(mol2)
fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, 2)
fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, 2)
similarity = DataStructs.TanimotoSimilarity(fp1, fp2)
The "Memory Explosion" Problem
Processing millions of molecules can exhaust memory.
suppl = Chem.SDMolSupplier('huge_library.sdf')
mols = [mol for mol in suppl]
def process_in_batches(sdf_file, batch_size=10000):
suppl = Chem.SDMolSupplier(sdf_file)
batch = []
for mol in suppl:
if mol is not None:
batch.append(mol)
if len(batch) >= batch_size:
yield batch
batch = []
if batch:
yield batch
for batch in process_in_batches('huge_library.sdf'):
pass
RDKit is the cornerstone of computational drug discovery and cheminformatics. Its comprehensive toolkit for molecular manipulation, descriptor calculation, and similarity searching makes it indispensable for pharmaceutical research, virtual screening, and chemical data analysis. Master RDKit, and you'll have the power to computationally explore vast chemical spaces and accelerate drug discovery.