☰
RDKit rdkit.Chem.Lipinski 模块详解:从 Rule of Five 到 SMARTS 子结构匹配的完整实现
2026/10/13 11:39:28 网站建设 项目流程
  • 科学计算
  • 科研
  • 机器学习

【免费下载链接】rdkit

The official sources for the RDKit library

项目地址:https://gitcode.com/gh_mirrors/rd/rdkit
点击查看免费下载

本文围绕 RDKit 官方文档 rdkit.Chem.Lipinski.rst 所描述的rdkit.Chem.Lipinski模块展开,完整覆盖该模块导出的全部描述符 API、Lipinski 氢键给体/受体的 SMARTS 判定规则,以及可旋转键的多种严格度定义;结合 Lipinski.py、Lipinski.cpp、Lipinski.h 与测试 UnitTestLipinski.py 的源码,帮助读者理解每个参数背后的计数原理,并能直接落地分子筛选中的"类药性"(drug-likeness)计算。

模块定位:Lipinski 参数在 RDKit 中的角色

原始文档 rdkit.Chem.Lipinski.rst 是一个 Sphinxautomodule指令页面:

rdkit.Chem.Lipinski module ========================== .. automodule:: rdkit.Chem.Lipinski :members: :undoc-members: :show-inheritance:

它声明了模块的一句话定位(Lipinski.py 第 10 行的模块 docstring):"Calculation of Lipinski parameters for molecules"(计算分子的 Lipinski 参数)。模块被收录在 rdkit.Chem.rst 的rdkit.Chem子模块列表中(第 51 行)。Lipinski 参数即业界常说的"Rule of Five"(五规则)所依赖的四个量:分子量、LogP、氢键给体数(HBD)、氢键受体数(HBA)。其中分子量与 LogP 可由其他描述符(如Crippen.MolLogP)计算,本模块聚焦在氢键给体/受体、杂原子数与可旋转键数这几项图论/SMARTS 可直接计算的指标上,并额外提供环数、SP3 碳分数、立体中心等衍生描述符。

从源码结构看,整个模块的分工是:Python 层 Lipinski.py 负责在导入时一次性预编译 SMARTS 模式(注释写明 "on import build the SMARTS patterns so we only have to do it once"),并把公共 API 委托给 C++ 绑定的rdMolDescriptors;C++ 层 Lipinski.cpp 实现了真正的计数逻辑(SMARTS 子结构匹配 + 原子遍历)。

API 一览:Lipinski 模块导出的全部函数

结合 Lipinski.py 的源码,模块导出函数可分为两组:

函数文档字符串version底层实现
NumHDonors(mol)Number of Hydrogen Bond Donors1.0.0rdMolDescriptors.CalcNumHBD
NumHAcceptors(mol)Number of Hydrogen Bond Acceptors2.0.0rdMolDescriptors.CalcNumHBA
NumHeteroatoms(mol)Number of Heteroatoms1.0.0rdMolDescriptors.CalcNumHeteroatoms
NumRotatableBonds(mol)Number of Rotatable Bonds1.0.0rdMolDescriptors.CalcNumRotatableBonds
NOCount(mol)Number of Nitrogens and Oxygens1.0.0rdMolDescriptors.CalcNumLipinskiHBA
NHOHCount(mol)Number of NHs or OHs2.0.0rdMolDescriptors.CalcNumLipinskiHBD
RingCount(mol)环数1.0.0rdMolDescriptors.CalcNumRings
HeavyAtomCount(mol)Number of heavy atoms1.0.1mol.GetNumHeavyAtoms()
FractionCSP3、NumAromaticRings、NumSaturatedRings、NumAromaticHeterocycles、NumAromaticCarbocycles、NumSaturatedHeterocycles、NumSaturatedCarbocycles、NumAliphaticRings、NumAliphaticHeterocycles、NumAliphaticCarbocycles、NumHeterocycles、NumBridgeheadAtoms、NumAmideBonds、NumAtomStereoCenters、NumHeterocycles、NumUnspecifiedAtomStereoCenters、NumSpiroAtoms、Phi同 C++ 端 docstring批量自动生成

最后一组函数值得注意其实现手法:Lipinski.py 第 83-99 行维护了一个_bulkConvert元组,通过getattr(rdMolDescriptors, txt)反射取出 C++ 端的CalcXXX函数,包装成 lambda、拷贝__doc__、从_XXX_version属性读取版本号,最后把Calc前缀去掉后写入模块局部命名空间(例如CalcFractionCSP3→FractionCSP3)。这套机制避免了为每个描述符手写重复代码,也从 C++ 端统一继承了文档与版本信息。

两套"传统/严格"定义的区分是使用时最容易踩坑的一点。Lipinski.py 第 25-27 行的注释明确指出:

NOTE: if you want traditional Lipinski numbers, you should useNOCounts(below) instead ofHAcceptor

即:

  • 传统 Lipinski 计数:HBA = 分子中 N 和 O 原子的总数(NOCount),HBD = NH 或 OH 的总数(NHOHCount);
  • 化学语义更精细的计数:NumHAcceptors/NumHDonors使用 SMARTS 模式识别真正具备氢键能力的原子(例如酰胺 N 不算受体,卤素不算受体,带正电的 N 不算受体等)。

C++ 端 Lipinski.cpp 中对应的实现非常直接——calcLipinskiHBA(第 77-85 行)就是遍历原子统计原子序数为 7 或 8 的个数;calcLipinskiHBD(第 87-95 行)统计 N/O 上的氢原子总数(getTotalNumHs(true),包含隐性氢)。而 Lipinski.h 中lipinskiHBAVersion = "1.0.0"、lipinskiHBDVersion = "2.0.0"表明这两套定义各自独立版本化。

氢键给体与受体的 SMARTS 定义

Lipinski.py 第 19-43 行内置了五组预编译 SMARTS。其中注释部分保留了 Daylight 风格的原始定义供追溯:

HDonor '[!#6;!H0;-0]' HAcceptor '[$([!#6;+0]);!$([F,Cl,Br,I]); !$([o,s,nX3]);!$([Nv5,Pv5,Sv4,Sv6])]' Heteroatom '[B,N,O,P,S,F,Cl,Br,I]'

真正被采用的定义(文件第 29-43 行)为:

HDonorSmarts = Chem.MolFromSmarts('[$([N;!H0;v3]),$([N;!H0;+1;v4]),$([O,S;H1;+0]),$([n;H1;+0])]') HAcceptorSmarts = Chem.MolFromSmarts( '[$([O,S;H1;v2]-[!$(*=[O,N,P,S])]),' '$([O,S;H0;v2]),$([O,S;-]),$([N;v3;!$(N-*=!@[O,N,P,S])]),' '$([nH0,o,s;+0])]') HeteroatomSmarts = Chem.MolFromSmarts('[!#6;!#1]') RotatableBondSmarts = Chem.MolFromSmarts('[!$(*#*)&!D1]-&!@[!$(*#*)&!D1]') NHOHSmarts = Chem.MolFromSmarts('[#8H1,#7H1,#7H2,#7H3]') NOCountSmarts = Chem.MolFromSmarts('[#7,#8]')

逐条解读这些模式的语义:

  • HDonor:三价且带氢的 N(胺)、四价带正电且带氢的 N(铵盐)、带一个氢的中性 O/S(羟基/巯基)、芳香环上带氢的 n(吡咯型 N-H)。核心思想是"与 N/O/S 直接相连且可电离出 H 的位置"。
  • HAcceptor:分五支——非羰基化的羟基氧/硫([O,S;H1;v2]且不双键连 O/N/P/S)、双键型 O/S(羰基、亚砜等)、带负电荷的 O/S(烷氧负离子)、三价 N 且不形成酰胺(!$(N-*=!@[O,N,P,S])排除了 N 连 C=O/C=N/C=S 的酰胺型 N,其中!@表示"非芳香",因此排除了芳香环中形式上连双键杂原子的 N 的情形)、芳香 n/o/s 中性原子。注释第 31 行记录了修订历史:"v2, 1-Nov-2008, GL : fix amide-N exclusion; remove Fs from definition"——即 2008 年修正了酰胺 N 被误判为受体的缺陷,并把 F 从受体定义中移除。
  • RotatableBond:连接两个"非三键原子、度数不为 1"的原子间的非芳香单键。注释(第 37-40 行)特别提示了一个已知细节:该 SMARTS 不区分氘与氢——由于Chem.MolFromSmiles默认去掉 H 但保留 D(D 留在图中并计入碳的度数),[2H]C([2H])([2H])C([2H])([2H])[2H]中的中心 C-C 键会被判为可旋转,与常规直觉可能不一致。
  • NHOH / NOCount:分别对应传统五规则的 HBD/HBA 计数,与 C++ 端calcLipinskiHBD/calcLipinskiHBA的原子遍历逻辑等价。

C++ 端 Lipinski.cpp 第 195-202 行用SMARTSCOUNTFUNC宏以几乎相同的模式实现了NumHBD(v2.0.1)与NumHBA(v2.0.2):

SMARTSCOUNTFUNC(NumHBD, "[N&!H0&v3,N&!H0&+1&v4,O&H1&+0,S&H1&+0,n&H1&+0]", "2.0.1"); SMARTSCOUNTFUNC(NumHBA, "[$([O,S;H1;v2]-[!$(*=[O,N,P,S])]),$([O,S;H0;v2]),$([O,S;-]),$(" "[N;v3;!$(N-*=!@[O,N,P,S])]),$([nH0X2,o,s;+0])]", "2.0.2"); SMARTSCOUNTFUNC(NumHeteroatoms, "[!#6;!#1]", "1.0.1"); SMARTSCOUNTFUNC(NumAmideBonds, "C(=[O;!R])N", "1.0.0");

注意 C++ 端的 HBA 模式尾部是[nH0X2,o,s;+0](要求二价),而 Python 端写的是[nH0,o,s;+0],两者在绝大多数分子上结果一致,但版本号(2.0.2 vs 2.0.0)提示底层定义经历过演进——跨语言端对数值有严格一致性要求时,应以实际测试为准。

该宏(第 66-72 行)同时展示了实现的工程细节:用boost::flyweight缓存 SMARTS 模式,避免每次调用都重新SmartsToMol;且当模式含$(递归查询)时,ss_matcher::countMatches会先拷贝一次 matcher(第 41-46 行注释 "Recursive queries aren't thread safe"),以换取线程安全。

可旋转键:NonStrict / Strict / StrictLinkages 三种定义

可旋转键(rotatable bonds)是构象搜索、柔性分子建模和类药性评估中的关键量,RDKit 为它提供了三档定义,声明于 Lipinski.h 第 35-40 行:

enum NumRotatableBondsOptions { Default = -1, NonStrict = 0, Strict = 1, StrictLinkages = 2, };

各档行为在 Lipinski.cpp 第 106-187 行实现:

  • NonStrict(0):使用基础模式[!$(*#*)&!D1]-,:;!@[!$(*#*)&!D1],即排除三键连接、端基原子(度数 1)与芳香键,其余均计入。
  • Strict(1):在 NonStrict 基础上进一步排除端基卤素(CF3、CCl3、CBr3)、叔丁基(C(CH3)3)、甲基酯/酰胺(CH3=O型,双向排除)、CH3型端基等,模式见第 117-122 行。头文件注释概括为:"a stricter definition of rotatable bonds is used, this excludes amides, esters, etc."
  • StrictLinkages(2):最严格的一档,针对环间连接键做了专门处理(第 126-140 行注释列出相对原始 GPS 计算器的主要变更):脂肪环碳间的单键一律可旋转(如CC1CCCC(C)C1-C1C(C)CCCC1C中心键);连接环上的杂原子不再影响判定;Cc1cccc(C)c1-c1c(C)cccc1这类芳环间连接键不可旋转。实现上采用"基础匹配 − 对称环匹配 − 末端三键 − 非环酰胺"的减法策略,并对酰胺匹配用boost::dynamic_bitset做去重(第 166-180 行)。

一个值得注意的全局开关:第 98-102 行

#ifdef RDK_USE_STRICT_ROTOR_DEFINITION const NumRotatableBondsOptions DefaultStrictDefinition = Strict; #else const NumRotatableBondsOptions DefaultStrictDefinition = NonStrict; #endif

也就是说,Default = -1时的实际行为取决于编译期选项RDK_USE_STRICT_ROTOR_DEFINITION是否开启。这也正是测试代码要做"探测器"式判断的原因(见下节)。

Python 端入口rdMolDescriptors.CalcNumRotatableBonds(mol, options)支持显式传入NumRotatableBondsOptions.NonStrict / Strict / StrictLinkages,而 Lipinski.py 中的NumRotatableBonds走的是 C++ 默认值路径。

实践示例:计算一组分子的 Lipinski 指标

基于上述 API,一段典型的"五规则"筛查代码(基于仓库测试同款数据first_200.props.sdf,其路径由RDConfig.RDDataDir定位)如下:

from rdkit import Chem, RDConfig from rdkit.Chem import Lipinski, Crippen, rdMolDescriptors import os path = os.path.join(RDConfig.RDDataDir, 'NCI', 'first_200.props.sdf') suppl = Chem.SDMolSupplier(path) def rule_of_five(m): logP = Crippen.MolLogP(m) nHBD = Lipinski.NumHDonors(m) nHBA = Lipinski.NumHAcceptors(m) return logP, nHBD, nHBA for m in suppl: if m is None: continue logP, hbd, hba = rule_of_five(m) # 传统 Lipinski 计数可与精细化计数对照 trad_hba = Lipinski.NOCount(m) trad_hbd = Lipinski.NHOHCount(m) rotors = Lipinski.NumRotatableBonds(m) heavy = Lipinski.HeavyAtomCount(m)

要点说明:

  1. 仓库内的参考数据 first_200.props.sdf(NCI 前 200 个分子)带有NUM_HDONORS、NUM_HACCEPTORS、NUM_HETEROATOMS、NUM_ROTATABLEBONDS、NUM_ROTATABLEBONDS_O、NUM_LIPINSKIHDONORS、NUM_LIPINSKIHACCEPTORS等属性列,可直接用于回归对照。
  2. 显式切换可旋转键档位时调用:
    n_ns = rdMolDescriptors.CalcNumRotatableBonds(m, rdMolDescriptors.NumRotatableBondsOptions.NonStrict) n_st = rdMolDescriptors.CalcNumRotatableBonds(m, rdMolDescriptors.NumRotatableBondsOptions.Strict)
  3. 若只想快速判断分子是否"通过"经典五规则(MW ≤ 500、LogP ≤ 5、HBD ≤ 5、HBA ≤ 10),可直接组合Lipinski.HeavyAtomCount与Chem.Descriptors.MolWt、Crippen.MolLogP。

回归测试如何验证这些函数

UnitTestLipinski.py 是理解模块行为边界的最佳材料,其结构为:

  1. test1(NCI 200 分子对照):先用一个探测器分子确定当前构建的默认可旋转键档位——
    m = Chem.MolFromSmiles("CC(C)(C)c1cc(O)c(cc1O)C(C)(C)C") if Lipinski.NumRotatableBonds(m) == 2: rot_prop = NonStrict # "NUM_ROTATABLEBONDS_O" else: rot_prop = Strict # "NUM_ROTATABLEBONDS"

    然后逐分子断言NHOHCountvsNUM_LIPINSKIHDONORS、NOCountvsNUM_LIPINSKIHACCEPTORS、NumHDonorsvsNUM_HDONORS、NumHAcceptorsvsNUM_HACCEPTORS、NumHeteroatomsvsNUM_HETEROATOMS、NumRotatableBondsvs 探测出的rot_prop,并额外显式验证CalcNumRotatableBonds的 NonStrict 与 Strict 两档分别与 SDF 中两列属性相等。Java 端存在完全同构的测试 LipinskiTests.java,说明 C++/Python/Java 三层对 Lipinski 数值有一致性要求。

  2. testIssue2183420(历史 bug 回归):一组胺/酰胺断言,验证 HBA 定义在甲基胺(NC、CNC、CN(C)C,期望 1)、甲酰胺类(NC(=O)、NC(=O)C、CNC(=O)、CNC(=O)C,期望 1,即酰胺 N 不算受体)、尿烷型(O=CNC(=O)C,期望 2)等边界情形下的行为,正是上述 HAcceptor 模式"amide-N exclusion"修订的直接回归保障。
  3. testLipinskiLong(长测试,默认跳过):需-l命令行开关启用,使用 rdkit/Chem/test_data 下的PP_descrs_regress.csv(列 30/31/32 分别为 NumHDonors、NumHeteroatoms、NumRotatableBonds)与Block_regress.Lip.csv(列 1-4 为 NumHAcceptors、NumHDonors、NumHeteroatoms、NumRotatableBonds)做逐行数值回归,缺失数据点以 666.0 占位。

运行方式(在仓库 Python 环境下):

python -m unittest rdkit.Chem.UnitTestLipinski # 默认快速测试 python rdkit/Chem/UnitTestLipinski.py -l # 启用长回归数据集

小结:使用本模块的三个关键点

  1. 选对"传统"还是"精细"计数:做与外部工具历史数值对齐(如 NCI 属性列、文献中原始五规则)时使用NOCount/NHOHCount;做现代 ADME 特征工程时使用NumHAcceptors/NumHDonors(v2 系列定义,含酰胺 N 排除、卤素/氟不计数等修正)。源码注释(Lipinski.py 第 25-27 行)对此有明确的官方提示。
  2. 可旋转键的默认值依赖编译选项:Default在RDK_USE_STRICT_ROTOR_DEFINITION宏开启时为 Strict、否则为 NonStrict;跨环境比较数值时应显式传入rdMolDescriptors.NumRotatableBondsOptions.Strict或.NonStrict,并留意 StrictLinkages 档对环间连接键的专门规则(Lipinski.cpp 第 126-140 行注释)。
  3. 版本可追溯:每个 Python 端函数都带version属性(如NumHDonors.version == "1.0.0"、NHOHCount.version == "2.0.0"),C++ 端则以XXXVersion常量(如NumRotatableBondsVersion = "3.2.0",Lipinski.cpp 第 105 行)独立版本化,便于在描述符结果中记录计算定义、复现实验时核对口径。
  • 科学计算
  • 科研
  • 机器学习

【免费下载链接】rdkit

The official sources for the RDKit library

项目地址:https://gitcode.com/gh_mirrors/rd/rdkit
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询