RDKit 化学信息学教程

使用环境:Windows + Miniconda,Python 3.14.4,RDKit 2024.09 / 2025.x 稳定版,VS Code,纯 .py 脚本

本教程所有示例均用 SMILES 字符串构造分子,不依赖任何外部文件,可直接复制运行。


目录

  1. 简介与定位
  2. 安装与版本检查
  3. 分子表示:SMILES / InChI / Mol 构建、读写 SDF / MOL
  4. 分子处理:加氢去氢、2D 坐标、3D 构象、标准化
  5. 子结构匹配:SMARTS 找官能团
  6. 描述符计算:2D / 3D
  7. 分子指纹:Morgan / MACCS / Tanimoto
  8. 相似性搜索与聚类
  9. 可视化:2D 结构图与网格
  10. 反应处理与化学变换
  11. 科研实战:特征表、相似配体筛选、官能团匹配
  12. 常见坑
  13. 延伸资源

1. 简介与定位

RDKit 是一个开源的化学信息学工具包(C++ 内核 + Python 绑定,BSD 协议)。在你的催化 / 材料研究里,它主要扮演「分子层面数据预处理与表征」的角色:

  • 配体 / 底物 / 产物结构处理:加氢去氢、标准化(canonical SMILES)、生成 3D 构象(配体对接、力场优化前处理)。
  • 描述符提取供机器学习:从一大批催化底物 / 配体 / MOF 连接体批量计算物理化学描述符(分子量、logP、TPSA、可旋转键等),拼成特征表喂给 sklearn / PyTorch。
  • 分子筛选:用指纹相似度从库里挑「像」某个已知活性配体的分子;用 SMARTS 快速统计官能团 / 骨架。
  • 反应处理:用反应 SMARTS 定义化学变换,做虚拟产物枚举。

它和你的主力工具(DFT / VASP、力场、机器学习势)互补:RDKit 做「粗、快、结构层」的处理,DFT 做「精、慢、电子层」的计算。一个典型工作流是:RDKit 从 SMILES 建构象 → 交给 xTB / 力场做预优化 → 再交给 DFT。

注意:RDKit 不做量子化学计算、不做分子动力学。3D 构象用内置的 ETKDG(基于距离几何的经验方法)+ MMFF/UFF 力场,速度快但精度是「经验级」,适合初猜结构。


2. 安装与版本检查

2.1 安装(推荐 conda-forge)

你已经装了 Miniconda,直接:

# 推荐:conda-forge 频道(Windows 下最省事,二进制预编译)
conda install -c conda-forge rdkit

# 或者单独建一个环境(推荐,避免污染 base)
conda create -n chem python=3.12
conda activate chem
conda install -c conda-forge rdkit

pip install rdkit 在 Windows 上有时缺二进制 wheel,优先 conda-forge。

Python 3.14 提示:RDKit 的 conda-forge 构建对最新 Python 版本的支持通常有几周到几个月的滞后。如果你的 conda install 报「找不到匹配的 rdkit 构建」,就建一个 python=3.12 的环境装 RDKit,脚本语法与 API 完全一致。

2.2 版本检查与自检

# 版本检查
from rdkit import rdBase
print("RDKit 版本:", rdBase.rdkitVersion)

# 环境自检:能建分子、能算指纹,说明装好了
from rdkit import Chem
from rdkit.Chem import Descriptors
mol = Chem.MolFromSmiles("O=C=O")          # CO2
print("CO2 分子量:", Descriptors.MolWt(mol))  # 应为 44.009(约)

3. 分子表示:SMILES / InChI / Mol 构建、读写 SDF / MOL

3.1 从 SMILES 构建分子

from rdkit import Chem

# 从 SMILES 构建 Mol 对象
smiles = "O=Cc1ccccc1"                     # 苯甲醛
mol = Chem.MolFromSmiles(smiles)
print(mol)                                  # <rdkit.Chem.rdchem.Mol object at ...>
print("原子数:", mol.GetNumAtoms())          # 重原子数(不含 H)

# SMILES 与其它表示互相转换
print("Canonical SMILES:", Chem.MolToSmiles(mol))
print("InChI:", Chem.MolToInchi(mol))

3.2 SMILES 一览(催化 / 材料常用分子)

分子SMILES用途
二氧化碳O=C=OCO2 还原底物
甲醇CO还原产物
甲酸O=CO还原产物
甲醛C=O中间体
乙烯C=C加氢底物
苯甲醛O=Cc1ccccc1选择性加氢底物
苯甲醇OCc1ccccc1产物
苯乙烯C=Cc1ccccc1底物
硝基苯O=[N+]([O-])c1ccccc1加氢底物
苯胺Nc1ccccc1产物
对苯二甲酸OC(=O)c1ccc(cc1)C(=O)OMOF 连接体(MIL / UiO)
2-甲基咪唑Cc1ncc[nH]1ZIF-8 连接体
2,2'-联吡啶c1ccc(nc1)-c1ccccn1双齿配体
三苯基膦c1ccc(cc1)P(c1ccccc1)c1ccccc1膦配体
丙酮CC(=O)C溶剂 / 底物

3.3 从 InChI 构建、往返转换

from rdkit import Chem

mol = Chem.MolFromSmiles("O=Cc1ccccc1")

# 输出 InChI(IUPAC 国际化学标识符,独立于 SMILES 的规范化字符串)
inchi = Chem.MolToInchi(mol)
print("InChI:", inchi)

# 从 InChI 重建分子
mol2 = Chem.MolFromInchi(inchi)
print("往返后 SMILES:", Chem.MolToSmiles(mol2))   # 应与原 SMILES 一致(规范化后)

# InChIKey(27 字符哈希,适合做唯一键 / 数据库去重)
from rdkit.Chem.inchi import MolToInchiKey
print("InChIKey:", MolToInchiKey(mol))

InChI / InChIKey 适合做「结构唯一标识」:InChIKey 固定 27 位,可用作 pandas 索引或数据库主键去重。

3.4 读写 SDF / MOL 文件

from rdkit import Chem

mol = Chem.MolFromSmiles("O=Cc1ccccc1")
mol.SetProp("_Name", "benzaldehyde")        # 设分子名(写入 SDF 时保留)

# 写单个 MOL 文件(V2000)
Chem.MolToMolFile(mol, "benzaldehyde.mol")

# 得到 MOL 块字符串(适合直接塞进 pandas 单元格或写文本)
block = Chem.MolToMolBlock(mol)
print(block[:60], "...")

# 从 MOL 块 / 文件读回
m = Chem.MolFromMolBlock(block)
m2 = Chem.MolFromMolFile("benzaldehyde.mol")

批量读 SDF(化合物库常见格式):

# 写:SDWriter
w = Chem.SDWriter("library.sdf")
for smi, name in [("O=Cc1ccccc1", "benzaldehyde"), ("Nc1ccccc1", "aniline")]:
    m = Chem.MolFromSmiles(smi)
    m.SetProp("_Name", name)
    w.write(m)
w.close()

# 读:SDMolSupplier(迭代器,解析失败的条目返回 None,务必过滤)
suppl = Chem.SDMolSupplier("library.sdf")
mols = [m for m in suppl if m is not None]
for m in mols:
    print(m.GetProp("_Name"), Chem.MolToSmiles(m))

4. 分子处理:加氢去氢、2D 坐标、3D 构象、标准化

4.1 加氢 / 去氢

from rdkit import Chem

mol = Chem.MolFromSmiles("O=Cc1ccccc1")
print("重原子数:", mol.GetNumAtoms())

# AddHs / RemoveHs 都返回【新分子】,不改动原对象
mol_h = Chem.AddHs(mol)                      # 加显式 H
print("加氢后原子数:", mol_h.GetNumAtoms())

mol_noH = Chem.RemoveHs(mol_h)               # 去 H
print("去氢后原子数:", mol_noH.GetNumAtoms())

什么时候加氢:

  • 生成 3D 构象、力场优化、对接前,通常要 AddHs(否则价层不完整,几何不合理)。
  • 计算某些描述符(如 3D 描述符、氢键)时,显式 H 会影响结果。

4.2 2D 坐标

from rdkit import Chem
from rdkit.Chem import AllChem

mol = Chem.MolFromSmiles("c1ccc(nc1)-c1ccccn1")   # 2,2'-联吡啶
AllChem.Compute2DCoords(mol)                       # 生成 / 刷新 2D 坐标(画图用)

2D 坐标只用于画图,不参与任何化学计算。

4.3 3D 构象生成(ETKDG + MMFF 优化)

这是你配体 / 底物初猜结构最常用的流程:

from rdkit import Chem
from rdkit.Chem import AllChem

mol = Chem.MolFromSmiles("OC(=O)c1ccc(cc1)C(=O)O")  # 对苯二甲酸
mol = Chem.AddHs(mol)                                # 先加 H

# ETKDGv3:基于距离几何的经验构象生成器,随机种子保证可复现
params = AllChem.ETKDGv3()
params.randomSeed = 42
res = AllChem.EmbedMolecule(mol, params)
print("Embed 结果(0=成功,-1=失败):", res)

if res == 0:
    # MMFF94 力场优化(返回 (状态码, 能量),状态码 0=收敛)
    status, energy = AllChem.MMFFOptimizeMolecule(mol)
    print("MMFF 优化状态:", status, "能量:", energy)

生成多个构象并取能量最低的:

mol = Chem.MolFromSmiles("Cc1ncc[nH]1")          # 2-甲基咪唑
mol = Chem.AddHs(mol)

params = AllChem.ETKDGv3()
params.randomSeed = 0
cids = AllChem.EmbedMultipleConfs(mol, numConfs=10, params=params)
print("成功生成的构象数:", len(cids))

# 批量 MMFF 优化所有构象,返回 [(状态, 能量), ...]
results = AllChem.MMFFOptimizeMoleculeConfs(mol)
print("各构象能量:", [round(e, 3) for _, e in results])
print("构象总数:", mol.GetNumConformers())

金属配合物特别注意:MMFF94 没有过渡金属参数。处理单原子催化 / 金属配合物时,用 AllChem.MMFFHasAllMoleculeParams(mol) 检查;若返回 False,改用 AllChem.UFFOptimizeMolecule(mol)(UFF 覆盖更多元素,但精度一般)。ETKDG 本身面向有机物,金属中心配位几何的初猜需要谨慎,必要时用 DFT / xTB 重优化。

4.4 分子标准化(canonical SMILES)

标准化是「同一分子不同 SMILES 写法 → 同一字符串」,用于去重和建表:

from rdkit import Chem

def canonical_smiles(smi):
    """把任意合法 SMILES 转成规范形式;解析失败返回 None"""
    m = Chem.MolFromSmiles(smi)
    if m is None:
        return None
    # canonical=True 生成规范 SMILES;isomericSmiles=True 保留立体信息
    return Chem.MolToSmiles(m, canonical=True, isomericSmiles=True)

# 不同写法 -> 同一规范结果
print(canonical_smiles("O=Cc1ccccc1"))
print(canonical_smiles("c1ccccc1C=O"))          # 与上一条相同分子
print(canonical_smiles("not_a_smiles"))          # None

进阶:去盐(去除反离子,如盐酸盐的 Cl-)、去电荷等。Chem.SaltRemover 可以剥离常见无机盐:

remover = Chem.SaltRemover.SaltRemover()
stripped = remover.StripMol(Chem.MolFromSmiles("Nc1ccccc1.Cl"))   # 苯胺盐酸盐 -> 苯胺
print(Chem.MolToSmiles(stripped))

5. 子结构匹配:SMARTS 找官能团

SMARTS 是「带查询语义的 SMILES」,用来描述模式(如「任何羰基」「任何伯胺」)。RDKit 用它做子结构匹配。

from rdkit import Chem

mol = Chem.MolFromSmiles("O=Cc1ccccc1")          # 苯甲醛

# 定义一个 SMARTS 模式:羰基 C(=O)
carbonyl = Chem.MolFromSmarts("[CX3]=[OX1]")

# 是否存在该子结构
print("含羰基?", mol.HasSubstructMatch(carbonyl))       # True

# 返回所有匹配的原子索引(每个匹配是一个原子索引元组)
print("匹配原子索引:", mol.GetSubstructMatches(carbonyl))  # ((1, 7),)

5.1 常用官能团 SMARTS

patterns = {
    "羰基 C=O":       "[CX3]=[OX1]",
    "羧酸 COOH":      "[CX3](=O)[OX2H1]",
    "伯胺 NH2":       "[NX3;H2]",
    "硝基 NO2":       "[N+](=O)[O-]",
    "碳碳双键":       "[CX3]=[CX3]",
    "碳碳三键":       "[CX2]#[CX2]",
    "卤素":           "[F,Cl,Br,I]",
    "芳香环":         "a1aaaaa1",                 # 任意六元芳环
    "腈基 CN":        "[CX2]#[NX1]",
    "酰胺键":         "[NX3][CX3](=[OX1])",
}

mol = Chem.MolFromSmiles("O=[N+]([O-])c1ccccc1")   # 硝基苯
for name, sma in patterns.items():
    q = Chem.MolFromSmarts(sma)
    print(f"{name:12s} 匹配数: {len(mol.GetSubstructMatches(q))}")

5.2 批量统计官能团

def count_frag(mol, smarts):
    """统计某个官能团出现的次数"""
    q = Chem.MolFromSmarts(smarts)
    return len(mol.GetSubstructMatches(q))

substrates = ["O=Cc1ccccc1", "O=[N+]([O-])c1ccccc1", "Nc1ccccc1", "C=Cc1ccccc1"]
for smi in substrates:
    m = Chem.MolFromSmiles(smi)
    print(smi, "| 羰基:", count_frag(m, "[CX3]=[OX1]"),
          "| 硝基:", count_frag(m, "[N+](=O)[O-]"),
          "| 胺:", count_frag(m, "[NX3;H2]"))

GetSubstructMatches 返回的是原子索引;对同一模式可能出现多个重叠匹配,默认 uniquify=True 会去重。需要「键匹配」可用 GetSubstructMatches(query, uniquify=False) 查看完整返回。


6. 描述符计算

6.1 常用 2D 描述符(Descriptors 模块)

from rdkit import Chem
from rdkit.Chem import Descriptors

mol = Chem.MolFromSmiles("O=Cc1ccccc1")          # 苯甲醛

print("分子量 MW:", Descriptors.MolWt(mol))               # 平均分子量 g/mol
print("精确分子量:", Descriptors.ExactMolWt(mol))          # 单同位素精确质量
print("logP (Crippen):", Descriptors.MolLogP(mol))        # 脂水分配系数(无量纲)
print("TPSA (Ų):", Descriptors.TPSA(mol))                # 拓扑极性表面积
print("可旋转键数:", Descriptors.NumRotatableBonds(mol))
print("氢键供体数:", Descriptors.NumHDonors(mol))
print("氢键受体数:", Descriptors.NumHAcceptors(mol))
print("杂原子数:", Descriptors.NumHeteroatoms(mol))
print("环数:", Descriptors.RingCount(mol))
print("芳香环数:", Descriptors.NumAromaticRings(mol))
print("sp3 碳比例 (Fsp3):", Descriptors.FractionCSP3(mol))
print("摩尔折射率 MR:", Descriptors.MolMR(mol))
print("QED 类药性:", Descriptors.qed(mol))

6.2 一次性计算全部描述符

# CalcMolDescriptors 返回一个 dict,含 200+ 描述符
d = Descriptors.CalcMolDescriptors(mol)
print("描述符总数:", len(d))
for k in ["MolWt", "MolLogP", "TPSA", "NumRotatableBonds", "FractionCSP3", "qed"]:
    print(f"{k:20s} = {d[k]}")

把 CalcMolDescriptors 的结果直接转成 pandas 的一行,就是 ML 特征表的一行(见第 11 节)。

6.3 3D 描述符(需要先 embed)

3D 描述符基于原子 3D 坐标,必须先 EmbedMolecule 生成构象,否则会抛错。

from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors3D

mol = Chem.MolFromSmiles("OC(=O)c1ccc(cc1)C(=O)O")
mol = Chem.AddHs(mol)
params = AllChem.ETKDGv3(); params.randomSeed = 42
if AllChem.EmbedMolecule(mol, params) == 0:
    AllChem.MMFFOptimizeMolecule(mol)

    # 回转半径(Å)
    print("回转半径:", Descriptors3D.RadiusOfGyration(mol))
    # 主惯性矩 PMI1 <= PMI2 <= PMI3(衡量分子「扁/长/球形」程度)
    print("PMI1/2/3:", Descriptors3D.PMI1(mol), Descriptors3D.PMI2(mol), Descriptors3D.PMI3(mol))
    # 归一化主惯性矩比(0~1,1=球形)
    print("NPR1/2:", Descriptors3D.NPR1(mol), Descriptors3D.NPR2(mol))
    # 惯性形状因子
    print("惯性形状因子:", Descriptors3D.InertialShapeFactor(mol))
    # 偏心度 / 非球性
    print("偏心度:", Descriptors3D.Eccentricity(mol))
    print("非球性:", Descriptors3D.Asphericity(mol))

3D 描述符依赖构象,数值会随 randomSeed / 优化状态变化。若用于 ML,务必固定 randomSeed 并统一「加氢—embed—优化」流程,保证可复现。


7. 分子指纹:Morgan / MACCS / Tanimoto

指纹把分子结构压缩成固定长度的比特向量,用于相似度比较和机器学习输入。

7.1 Morgan / ECFP 指纹(推荐新 API)

from rdkit import Chem
from rdkit.Chem import rdFingerprintGenerator

# 新 API(2020.09+,推荐):先建生成器,再对分子生成指纹
morgan_gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)

mol1 = Chem.MolFromSmiles("O=Cc1ccccc1")   # 苯甲醛
mol2 = Chem.MolFromSmiles("OCc1ccccc1")    # 苯甲醇

fp1 = morgan_gen.GetFingerprint(mol1)       # 位向量(ExplicitBitVect)
fp2 = morgan_gen.GetFingerprint(mol2)
print("fp1 长度:", fp1.GetNumBits(), "| 置 1 位数:", fp1.GetNumOnBits())

# 计数指纹(保留每个特征出现次数,有时信息更丰富)
fp1_count = morgan_gen.GetCountFingerprint(mol1)
print("计数指纹:", fp1_count)

参数说明:

  • radius:Morgan 半径(半径 2 即「ECFP4」,考虑每个原子周围 2 键以内的环境)。
  • fpSize:指纹长度(比特数),通常 1024 / 2048。
  • includeChirality=True:是否纳入手性信息(研究立体选择性时建议开启)。
# 含手性信息的 Morgan 指纹
gen_chiral = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048, includeChirality=True)

旧 API AllChem.GetMorganFingerprintAsBitVet(mol, 2, nBits=2048) 已弃用,建议统一换到 rdFingerprintGenerator 以兼容未来版本。

7.2 其它指纹

from rdkit import Chem
from rdkit.Chem import MACCSkeys, rdMolDescriptors

mol = Chem.MolFromSmiles("O=Cc1ccccc1")

# MACCS 键指纹(167 位,基于预定义的 166 个结构键,解释性强)
maccs = MACCSkeys.GenMACCSKeys(mol)
print("MACCS 长度:", maccs.GetNumBits())

# RDKit 拓扑指纹(类 Daylight,2048 位)
rdk = Chem.RDKFingerprint(mol)

# 原子对 / 拓扑扭转指纹(补充 2D 拓扑信息)
ap = rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect(mol, nBits=2048)
tt = rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect(mol, nBits=2048)

7.3 Tanimoto 相似度

from rdkit import Chem
from rdkit.Chem import DataStructs, rdFingerprintGenerator

gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
fp1 = gen.GetFingerprint(Chem.MolFromSmiles("O=Cc1ccccc1"))   # 苯甲醛
fp2 = gen.GetFingerprint(Chem.MolFromSmiles("OCc1ccccc1"))    # 苯甲醇
fp3 = gen.GetFingerprint(Chem.MolFromSmiles("C=Cc1ccccc1"))   # 苯乙烯

print("苯甲醛 vs 苯甲醇:", DataStructs.TanimotoSimilarity(fp1, fp2))
print("苯甲醛 vs 苯乙烯:", DataStructs.TanimotoSimilarity(fp1, fp3))

# 其它相似度
print("Dice:", DataStructs.DiceSimilarity(fp1, fp2))
print("Cosine:", DataStructs.CosineSimilarity(fp1, fp2))

Tanimoto(Jaccard)系数 = 交集 / 并集,取值 0~1,1 表示完全相同。


8. 相似性搜索与聚类

8.1 批量相似性搜索

from rdkit import Chem
from rdkit.Chem import DataStructs, rdFingerprintGenerator

gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)

# 一个小型「配体库」
ligands = [
    "c1ccc(cc1)P(c1ccccc1)c1ccccc1",   # 三苯基膦
    "CP(C)C",                          # 三甲基膦
    "c1ccc(nc1)-c1ccccn1",             # 2,2'-联吡啶
    "c1ccncc1",                        # 吡啶
    "Cc1ncc[nH]1",                     # 2-甲基咪唑
]
fps = [gen.GetFingerprint(Chem.MolFromSmiles(s)) for s in ligands]

query = gen.GetFingerprint(Chem.MolFromSmiles("c1ccc(cc1)P(c1ccccc1)c1ccccc1"))

# 一次算查询分子与所有库分子的相似度
sims = DataStructs.BulkTanimotoSimilarity(query, fps)
for smi, s in zip(ligands, sims):
    print(f"{smi:40s} Tanimoto = {s:.3f}")

8.2 聚类(Butina,简要)

Butina 是一种快速的球心聚类,常用于把大库拆成若干「骨架簇」:

from rdkit import Chem
from rdkit.Chem import DataStructs, rdFingerprintGenerator
from rdkit.ML.Cluster import Butina

gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
smis = ["O=Cc1ccccc1", "OCc1ccccc1", "C=Cc1ccccc1", "CCc1ccccc1",
        "O=CO", "CO", "O=C=O", "CC(=O)C"]
fps = [gen.GetFingerprint(Chem.MolFromSmiles(s)) for s in smis]

# 1. 计算两两距离(1 - Tanimoto)
dists = []
nfps = len(fps)
for i in range(1, nfps):
    sims = DataStructs.BulkTanimotoSimilarity(fps[i], fps[:i])
    dists.extend([1 - x for x in sims])

# 2. Butina 聚类(阈值 0.3 表示簇内两两距离 <= 0.3,即相似度 >= 0.7)
clusters = Butina.ClusterData(dists, nfps, 0.3, isDistData=True, reordering=False)
for cid, cluster in enumerate(clusters):
    print(f"簇 {cid}:", [smis[i] for i in cluster])

9. 可视化:2D 结构图与网格

RDKit 的画图函数返回 PIL 图像。在纯 .py 脚本(非 Jupyter)里,通常用 .save() 存 PNG 再看。

from rdkit import Chem
from rdkit.Chem import Draw

mol = Chem.MolFromSmiles("OC(=O)c1ccc(cc1)C(=O)O")   # 对苯二甲酸

# 单个分子:返回 PIL Image
img = Draw.MolToImage(mol, size=(300, 300))
img.save("terephthalic_acid.png")

# 或者直接写文件
Draw.MolToFile(mol, "terephthalic_acid.png", size=(300, 300))

多个分子网格(带图注,很适合批量查看底物 / 配体):

smis = ["O=C=O", "CO", "O=CO", "C=O",
        "O=Cc1ccccc1", "OCc1ccccc1", "C=Cc1ccccc1", "CCc1ccccc1"]
names = ["CO2", "甲醇", "甲酸", "甲醛",
         "苯甲醛", "苯甲醇", "苯乙烯", "乙苯"]
mols = [Chem.MolFromSmiles(s) for s in smis]

img = Draw.MolsToGridImage(mols, molsPerRow=4, subImgSize=(300, 220), legends=names)
img.save("substrates_grid.png")

Draw 默认给每个结构自动计算 2D 坐标;如果之前已经 Compute2DCoords,画图会复用已有坐标。


10. 反应处理与化学变换

用「反应 SMARTS」定义化学变换(原子映射用 :1、:2 标记),可对反应物做虚拟产物枚举。

from rdkit import Chem
from rdkit.Chem import AllChem

# SN2 胺化:卤代烷 + 胺 -> 胺(原子映射标记了连接点)
rxn = AllChem.ReactionFromSmarts("[C:1][Br].[N:2]>>[C:1][N:2]")
print("反应物模板数:", rxn.GetNumReactantTemplates())
print("产物模板数:", rxn.GetNumProductTemplates())

# 运行反应:RunReactants 输入 (反应物1, 反应物2)
reactants = (Chem.MolFromSmiles("CCBr"), Chem.MolFromSmiles("NC"))
products = rxn.RunReactants(reactants)

# products 是「每套产物的元组」的元组;一套可能含多个产物
for prod_set in products:
    for p in prod_set:
        print("产物:", Chem.MolToSmiles(p))

典型用途:

  • 虚拟组合化学:一批卤代物 × 一批胺 → 枚举全部产物,再算描述符筛选。
  • 定义某类反应的「骨架变换」,检查底物是否匹配反应模板(rxn.IsMoleculeReactantOf / 手动 HasSubstructMatch)。

反应 SMARTS 只做拓扑层的原子重排,不涉及机理 / 能量;得到的是「可能的产物」,是否热力学 / 动力学可行要靠实验或 DFT 判断。


11. 科研实战示例

11.1 从一组催化底物批量算描述符,构建 ML 特征表

from rdkit import Chem
from rdkit.Chem import Descriptors
import pandas as pd

# 一组 CO2 还原 / 加氢相关底物与产物
substrates = {
    "CO2":     "O=C=O",
    "甲醇":    "CO",
    "甲酸":    "O=CO",
    "甲醛":    "C=O",
    "苯甲醛":  "O=Cc1ccccc1",
    "苯甲醇":  "OCc1ccccc1",
    "苯乙烯":  "C=Cc1ccccc1",
    "乙苯":    "CCc1ccccc1",
    "硝基苯":  "O=[N+]([O-])c1ccccc1",
    "苯胺":    "Nc1ccccc1",
}

rows = []
for name, smi in substrates.items():
    mol = Chem.MolFromSmiles(smi)
    if mol is None:
        print(f"跳过无法解析的: {name} ({smi})")
        continue
    # 手工挑选的关键描述符 + 全量描述符二选一
    row = {
        "name": name,
        "SMILES": Chem.MolToSmiles(mol),
        "MW": Descriptors.MolWt(mol),
        "logP": Descriptors.MolLogP(mol),
        "TPSA": Descriptors.TPSA(mol),
        "RotBond": Descriptors.NumRotatableBonds(mol),
        "HDonors": Descriptors.NumHDonors(mol),
        "HAcceptors": Descriptors.NumHAcceptors(mol),
        "Fsp3": Descriptors.FractionCSP3(mol),
    }
    rows.append(row)

df = pd.DataFrame(rows)
print(df.to_string(index=False))
# df.to_csv("substrate_features.csv", index=False)   # 存成 ML 特征表

用全量描述符建表(200+ 列,适合「先全量再特征选择」的 ML 流程):

all_rows = []
for name, smi in substrates.items():
    mol = Chem.MolFromSmiles(smi)
    if mol is None:
        continue
    d = Descriptors.CalcMolDescriptors(mol)   # dict
    d["name"] = name
    all_rows.append(d)

df_all = pd.DataFrame(all_rows).set_index("name")
print("特征表维度:", df_all.shape)

11.2 基于指纹的相似配体筛选

从配体库里找「像三苯基膦」的候选(比如做膦配体虚拟筛选的第一步):

from rdkit import Chem
from rdkit.Chem import DataStructs, rdFingerprintGenerator

gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)

ligands = [
    "c1ccc(cc1)P(c1ccccc1)c1ccccc1",   # 三苯基膦(query)
    "CP(C)C",                          # 三甲基膦
    "CCP(CC)CC",                       # 三乙基膦
    "c1ccc(cc1)N(c1ccccc1)c1ccccc1",   # 三苯胺
    "c1ccc(nc1)-c1ccccn1",             # 联吡啶
    "c1ccncc1",                        # 吡啶
    "Cc1ncc[nH]1",                     # 2-甲基咪唑
]

query = gen.GetFingerprint(Chem.MolFromSmiles(ligands[0]))
results = []
for smi in ligands:
    fp = gen.GetFingerprint(Chem.MolFromSmiles(smi))
    results.append((smi, DataStructs.TanimotoSimilarity(query, fp)))

# 按相似度降序,给出阈值筛选
results.sort(key=lambda x: -x[1])
for smi, s in results:
    flag = "  <-- 命中" if s >= 0.5 else ""
    print(f"Tanimoto={s:.3f}  {smi}{flag}")

11.3 官能团 / 骨架匹配(分子分类器)

from rdkit import Chem

# 关键官能团 SMARTS
SMARTS = {
    "羰基":  "[CX3]=[OX1]",
    "羧酸":  "[CX3](=O)[OX2H1]",
    "硝基":  "[N+](=O)[O-]",
    "胺基":  "[NX3;H2]",
    "双键":  "[CX3]=[CX3]",
    "芳环":  "a1aaaaa1",
}

def fingerprint_by_frag(smi):
    """把分子转成「官能团 one-hot」向量"""
    mol = Chem.MolFromSmiles(smi)
    if mol is None:
        return None
    vec = {}
    for name, sma in SMARTS.items():
        q = Chem.MolFromSmarts(sma)
        vec[name] = 1 if mol.HasSubstructMatch(q) else 0
    return vec

for smi in ["O=Cc1ccccc1", "O=[N+]([O-])c1ccccc1", "Nc1ccccc1", "OC(=O)c1ccc(cc1)C(=O)O"]:
    print(smi, "->", fingerprint_by_frag(smi))

这个「官能团 one-hot 向量」本身也能作为 ML 特征(可解释性强,比指纹更直观)。


12. 常见坑

  1. MolFromSmiles 返回 None:SMILES 解析失败不会抛异常,而是返回 None。任何批量处理的第一步都要判空,否则后续 .GetNumAtoms() 之类会 AttributeError。

  2. 默认 sanitize 可能「纠正」你的分子:RDKit 默认对输入做规范化(价态、芳香性、电荷),有时会把不标准结构改掉。想拿到原始解析结果再手动处理:

    mol = Chem.MolFromSmiles(smi, sanitize=False)
    try:
        Chem.SanitizeMol(mol)
    except Exception as e:
        print("sanitize 失败:", e)
    
  3. AddHs / RemoveHs 不是原地操作:它们返回新对象,原 mol 不变。常见的 bug 是写了 Chem.AddHs(mol) 却忘了接返回值。

  4. 描述符单位与含义:

    • MolWt 是平均分子量(同位素加权),ExactMolWt 是单同位素精确质量(谱图对应用 Exact)。
    • TPSA 单位是 Ų。
    • NumHDonors / NumHAcceptors 基于隐式 H 即可,不必先 AddHs;但加氢后数值语义不变、不要重复加。
    • 加氢会改变 NumHeteroatoms、NumAtoms 等计数类描述符——描述符表要统一「加不加 H」的约定。
  5. canonical SMILES 不是万能唯一:它能统一「写法」差异,但互变异构、质子化状态、立体异构会被当作不同分子。去重时若关心这些,先做标准化(去盐、中和、选规范互变异构)。

  6. 立体化学:

    • 用 Chem.MolToSmiles(mol, isomericSmiles=True) 保留立体信息(@/@@ 手性、//\ 双键)。
    • 解析后某些手性中心可能是「未指定」,可用 Chem.AssignStereochemistry(mol, cleanIt=True, force=True) 让 RDKit 依据 CIP 规则标注。
  7. 3D 描述符必须先 embed:Descriptors3D.* 在无 3D 坐标时直接抛 ValueError。而且 3D 描述符依赖构象,务必固定 randomSeed。

  8. MMFF 不覆盖过渡金属:处理金属配合物 / 单原子催化时,AllChem.MMFFHasAllMoleculeParams(mol) 会返回 False,改用 UFFOptimizeMolecule。ETKDG 对金属中心配位几何的初猜也不可靠。

  9. 指纹 API 新旧混用:旧 AllChem.GetMorganFingerprintAsBitVect 已弃用,新代码统一用 rdFingerprintGenerator.GetMorganGenerator,避免版本升级后报错。


13. 延伸资源

推荐学习顺序:先跑通本教程第 3–7 节(构建、处理、描述符、指纹),再用第 11 节模板套到自己的底物 / 配体库上;遇到 API 报错,优先查官方 Getting Started 和 Cookbook,别信过时博客。


本教程基于 RDKit 2024.09 / 2025.x 稳定版 API 编写,全部示例可直接运行。