- 科学计算
- 科研
- 机器学习
【免费下载链接】rdkit
The official sources for the RDKit library
导读
本文围绕 RDKit 官方文档页 rdkit.Chem.AtomPairs.Utils.rst 所记载的rdkit.Chem.AtomPairs.Utils模块展开,系统讲解原子对(Atom Pairs)与拓扑扭转(Topological Torsions)指纹体系中最底层的两个能力:把原子编码成紧凑整数的位域方案(GetAtomCode/ExplainAtomCode)与作用于排序 ID 序列的相似度度量(BitsInCommon、DiceSimilarity、Dot、CosineSimilarity)。读完本文,你将掌握该模块每个函数的完整签名、参数语义、位运算原理,以及它们如何被Pairs.py、Torsions.py、Sheridan.py调用形成可用的分子指纹,并了解仓库中的回归测试如何保证结果稳定。
一、模块定位:AtomPairs 指纹家族的公共工具层
RDKit 的 AtomPairs 目录下包含四个 Python 模块(见 rdkit/Chem/AtomPairs):
| 模块 | 职责 |
|---|---|
| Pairs.py | 原子对指纹(Carhart 1985 论文实现) |
| Torsions.py | 拓扑扭转指纹(Nilakantan 1987 论文实现) |
| Sheridan.py | 理化性质指纹 BP/BT(Kearsley 1996 论文实现) |
| Utils.py | 公共工具:原子编码与相似度度量 |
Utils.py本身不直接产出一份"指纹",它是上面三种指纹共同的"积木":指纹的每个非零项都建立在原子哈希值之上,而两两比较指纹时又会复用其相似度函数。官方文档页Docs/Book/source/rdkit.Chem.AtomPairs.Utils.rst采用 Sphinxautomodule指令(members、undoc-members、show-inheritance)自动收集 Utils.py 中的 docstring 生成 API 参考,因此本文将以该模块源码中的完整 docstring 与 doctest 为第一手素材展开。
模块导出的核心 API 一览:
GetAtomCode(atom, branchSubtract=0, includeChirality=False)—— 原子 → 整数编码;ExplainAtomCode(code, branchSubtract=0, includeChirality=False)—— 整数编码 → 可读元组;BitsInCommon(v1, v2)—— 共同位 ID 计数;DiceSimilarity(v1, v2, bounds=None)—— DICE 相似度;Dot(v1, v2)—— 带重数加权的内积;CosineSimilarity(v1, v2)—— 余弦相似度。
二、GetAtomCode:原子编码的位域布局
GetAtomCode = rdMolDescriptors.GetAtomPairAtomCodeUtils.GetAtomCode实际上是 C++ 函数GetAtomPairAtomCode的 Python 别名,真实实现在 Code/GraphMol/Descriptors/Wrap/rdMolDescriptors.cpp 的封装与 Code/GraphMol/Fingerprints/AtomPairs.cpp 中。其把原子信息压缩进一个std::uint32_t,位域布局在 Code/GraphMol/Fingerprints/FingerprintUtil.h 中定义:
const unsigned int numTypeBits = 4; // 元素类型位宽 const unsigned int numPiBits = 2; // π 电子数位宽 const unsigned int numBranchBits = 3; // 邻接重数(分支度)位宽 const unsigned int numChiralBits = 2; // 手性标记位宽 const unsigned int codeSize = numTypeBits + numPiBits + numBranchBits; // = 9 const unsigned int numPathBits = 5; // 原子对距离位宽 const unsigned int maxPathLen = (1 << numPathBits) - 1; // = 31- 类型(4 bit):查表 atomNumberTypes,共 15 个可区分元素,对应原子序数
{5(B), 6(C), 7(N), 8(O), 9(F), 14(Si), 15(P), 16(S), 17(Cl), 33(As), 34(Se), 35(Br), 51(Sb), 52(Te), 53(I)}。Python 侧通过AtomPairsParameters.atomTypes暴露该表(见 rdMolDescriptors.cpp)。 - π 电子数(2 bit):取值范围 0~3,超出按 3 截断(
maxNumPi)。 - 分支度(3 bit):即原子度数,取值 0~7(
maxNumBranches)。拓扑扭转指纹会利用branchSubtract参数调整它。 - 手性(2 bit,可选):
0表示无手性、1表示 R、2表示 S。
参数语义
branchSubtract:在编码前从邻接数中减去的常量,用于拓扑扭转代码——路径两端原子减 1、中间原子减 2(详见本文第五节)。includeChirality:是否把 R/S 标记编入哈希。注意:即使分子无手性,开启该开关也会改变指纹结果,因为编码位数变了;AtomPairs.h 的头部注释明确说明了这一点(原子对指纹整体尺寸变化,拓扑扭转则改变最大允许路径长度)。
使用示例(来自 docstring)
>>> from rdkit import Chem >>> from rdkit.Chem.AtomPairs import Utils >>> m = Chem.MolFromSmiles('C=CC(=O)O') >>> Utils.GetAtomCode(m.GetAtomWithIdx(0)) # 终端 C,1 个重原子邻接,1 个 π 电子 261三、ExplainAtomCode:把编码还原为可读信息
ExplainAtomCode(code, branchSubtract=0, includeChirality=False)是GetAtomCode的逆操作,按位域顺序依次用掩码取出分支数、π 电子数、类型索引(必要时再取手性标记),返回形如(元素符号, 分支度, π电子数)或(元素符号, 分支度, π电子数, 手性)的元组。实现见 Utils.py:
typeMask = (1 << rdMolDescriptors.AtomPairsParameters.numTypeBits) - 1 branchMask = (1 << rdMolDescriptors.AtomPairsParameters.numBranchBits) - 1 piMask = (1 << rdMolDescriptors.AtomPairsParameters.numPiBits) - 1 chiMask = (1 << rdMolDescriptors.AtomPairsParameters.numChiralBits) - 1 nBranch = int(code & branchMask) # 低 3 位:分支度 code = code >> numBranchBits nPi = int(code & piMask) # 次 2 位:π 电子数 code = code >> numPiBits typeIdx = int(code & typeMask) # 再 4 位:类型索引类型索引超出表长(15)时返回'X';手性用字典{0: '', 1: 'R', 2: 'S'}映射。一个值得注意的特性:即使编码时带了手性,只要includeChirality=False,解码仍能得到正确的前三个字段——因为手性位位于类型位之后的高位,不影响低 9 位。
官方 docstring 演示
>>> m = Chem.MolFromSmiles('C=CC(=O)O') >>> ExplainAtomCode(GetAtomCode(m.GetAtomWithIdx(0))) ('C', 1, 1) # 原子 0:C,1 个邻接,1 个 π 电子 >>> ExplainAtomCode(GetAtomCode(m.GetAtomWithIdx(1))) ('C', 2, 1) >>> ExplainAtomCode(GetAtomCode(m.GetAtomWithIdx(2))) ('C', 3, 1) >>> ExplainAtomCode(GetAtomCode(m.GetAtomWithIdx(3))) ('O', 1, 1) # 羰基 O >>> ExplainAtomCode(GetAtomCode(m.GetAtomWithIdx(4))) ('O', 1, 0) # 羟基 O,无 π 电子手性示例(CC@HCl):
>>> code = GetAtomCode(m.GetAtomWithIdx(1), includeChirality=True) >>> ExplainAtomCode(code, includeChirality=True) ('C', 3, 0, 'R') >>> ExplainAtomCode(code) # 不解手性也能正确解码 ('C', 3, 0) >>> m = Chem.MolFromSmiles('CC@@HCl') # 反转构型 >>> code = GetAtomCode(m.GetAtomWithIdx(1), includeChirality=True) >>> ExplainAtomCode(code, includeChirality=True) ('C', 3, 0, 'S')非手性原子在第 4 个字段返回空串''。testAtomPairTypesChirality(testMolDescriptors.py)也验证了:未开启手性时两种对映体编码相同,开启后互相不同。
四、相似度度量:作用于排序 ID 序列的四个函数
这四个函数接受的输入是"位 ID 序列"(例如SparseIntVect.GetNonzeroElements()排序后的键列表),而不是SparseBitVect对象本身。共同约束:输入向量必须已排序;重复位 ID 会计多次。
4.1 BitsInCommon —— 公共位计数
>>> BitsInCommon( (1,2,3,4,10), (2,4,6) ) 2 >>> BitsInCommon( (1,2,2,3,4), (2,2,4,5,6) ) # v1 有两个 2,v2 也有两个 2,各计一次 3实现为对两个有序序列的线性归并扫描(Utils.py),每个共同 ID 匹配一次计数一次。
4.2 DiceSimilarity —— 论文推荐的 DICE 指标
DiceSimilarity(v1, v2, bounds=None)实现 DICE 相似度。模块 docstring 明确指出:这是 Atom Pairs 论文与 Topological Torsions 论文共同推荐的度量。公式为:
DICE = 2 * BitsInCommon(v1, v2) / (len(v1) + len(v2))>>> DiceSimilarity( (1,2,3), (1,2,3) ) 1.0 >>> DiceSimilarity( (1,2,3), (5,6) ) 0.0 >>> DiceSimilarity( (1,2,3,4), (1,3,5,7) ) 0.5 >>> DiceSimilarity( (), () ) # 空向量边界情形 0.0重复位 ID 的处理规则是"只有两边都重复才多计":
>>> DiceSimilarity( (1,1,3,4,5,6), (1,1) ) 0.5 >>> DiceSimilarity( (1,1,3,4,5,6), (1,) ) == 2./7 Truebounds参数用于过滤"长度差异过大"的比较:当min(len(v1), len(v2)) / (len(v1) + len(v2))小于bounds时,分子强制为 0,结果直接返回 0.0(Utils.py)。这在虚拟筛选场景中可快速剔除骨架差异过大的分子对:
>>> DiceSimilarity( (1,1,3,4), (1,1), bounds=0.3 ) # 2/6≈0.333 ≥ 0.3,正常计算 0.666... >>> DiceSimilarity( (1,1,3,4,5,6), (1,1), bounds=0.34 ) # 2/8=0.25 < 0.34,直接 0 0.04.3 Dot —— 带重数的内积
Dot(v1, v2)返回整数内积,但并非简单按位点乘:每个共同位 ID 按min(两边出现次数)计数,再以min 值的平方累加(Utils.py)。这种"计数平方"的加权方式使重复出现的模式获得超线性权重:
>>> Dot( (1,2,3,4,10), (2,4,6) ) # 两个共同位,各出现 1 次:1²+1² 2 >>> Dot( (1,2,2,3,4), (2,2,4,5,6) ) # 2 出现 2 次与 2 次:2²;4 出现 1 次:1² 5 >>> Dot( (1,2,2,3,4), (2,4,5,6) ) # 2 一边 2 次一边 1 次:min=1 → 1² 2 >>> Dot( (), (5,6) ) 04.4 CosineSimilarity —— LaSSI 论文推荐的余弦指标
CosineSimilarity(v1, v2)返回浮点余弦相似度,分子为Dot(v1, v2),分母为sqrt(Dot(v1,v1) * Dot(v2,v2))(Utils.py)。模块 docstring 注明这是 LaSSI 论文推荐的度量:
>>> print('%.3f' % CosineSimilarity( (1,2,3,4,10), (2,4,6) )) 0.516 >>> print('%.3f' % CosineSimilarity( (1,2,2,3,4), (2,2,4,5,6) )) 0.714 >>> print('%.3f' % CosineSimilarity( (1,2,2,3,4), (1,2,2,3,4) )) 1.000 >>> print('%.3f' % CosineSimilarity( (1,2,2,3,4), (5,6,7) )) 0.000任一向量为空时denom = 0,返回 0.0。
五、在原子对与拓扑扭转指纹中的调用链
Utils的原子编码与解码函数被 Pairs.py 和 Torsions.py 直接复用,形成完整的"编码 → 组装 → 解码"闭环。
5.1 原子对(Atom Pairs)
原子对指纹把"两个原子的编码 + 拓扑距离"压入一个整数。pyScorePair(Pairs.py)演示了位组装规则:
accum = int(dist) % _maxPathLen # 低 5 位:距离(最多 31 键) accum |= min(code1, code2) << numPathBits # 中段:较小原子编码 accum |= max(code1, code2) << (codeSize + numPathBits) # 高段:较大原子编码把两个编码按大小排序再拼接,保证"原子 0-1 对"与"原子 1-0 对"产生相同哈希。ExplainPairScore则按相反顺序用Utils.ExplainAtomCode还原为(原子A描述, 距离, 原子B描述):
>>> m = Chem.MolFromSmiles('C=CC') >>> ExplainPairScore(pyScorePair(m.GetAtomWithIdx(0), m.GetAtomWithIdx(1), 1)) (('C', 1, 1), 1, ('C', 2, 1))开启手性后codeSize增加numChiralBits位,解码同样传includeChirality=True:
>>> m = Chem.MolFromSmiles('FC@@HC@HCl') >>> score = pyScorePair(m.GetAtomWithIdx(1), m.GetAtomWithIdx(3), 1, includeChirality=True) >>> ExplainPairScore(score, includeChirality=True) (('C', 3, 0, 'R'), 1, ('C', 3, 0, 'S'))生产环境通常直接调用 C++ 封装rdMolDescriptors.GetAtomPairFingerprint(含计数)或GetHashedAtomPairFingerprint(默认 2048 位)。GetAtomPairFingerprintAsBitVect(Pairs.py)则把计数指纹的非零键转成SparseBitVect,其长度fpLen = 1 << (numPathBits + 2*codeSize),即 2²³ = 8,388,608 位(不含手性)。
C++ 侧 AtomPairs.h 还暴露了更细的参数:minLength/maxLength(默认为 1 键与maxPathLen-1=30 键)、fromAtoms/ignoreAtoms(限定/排除某些原子参与)、atomInvariants(自定义原子不变量,仅取每个值的前codeSize位)、use2D/confId(默认用 2D 拓扑距离,也可改用 3D 构象距离)。
5.2 拓扑扭转(Topological Torsions)
拓扑扭转指纹把一条路径上连续 4 个原子的编码按顺序拼成一个 64 位整数。pyScorePath(Torsions.py)的关键点在于branchSubtract的应用:
for i in range(size): if i == 0 or i == size - 1: sub = 1 # 路径两端原子:度数减 1 else: sub = 2 # 中间原子:度数减 2 codes[i] = Utils.GetAtomCode(mol.GetAtomWithIdx(path[i]), sub)之后对编码向量做"首尾比较"的规范化:从两端向中间比对,若首元素更大则整体反转,从而让路径 (0,1,2,3) 与 (3,2,1,0) 得到相同哈希。ExplainPathScore解码时把减去的sub加回去(Torsions.py):
>>> m = Chem.MolFromSmiles('C=CC(=O)O') >>> score = pyScorePath(m, (0, 1, 2, 4), 4) >>> ExplainPathScore(score, 4) (('C', 1, 1), ('C', 2, 1), ('C', 3, 1), ('O', 1, 0))GetTopologicalTorsionFingerprintAsIds(Torsions.py)把计数指纹展开为排序后的 ID 列表(每个 ID 重复其计数次),这个输出天然满足Utils相似度函数"必须有序"的前提,可直接喂给BitsInCommon/DiceSimilarity:
>>> m = Chem.MolFromSmiles('C1CCCCN1') >>> tt = Torsions.GetTopologicalTorsionFingerprint(m) >>> tt.GetNonzeroElements() {4437590049: 2, 8732557345: 2, 4445978657: 2} >>> Torsions.GetTopologicalTorsionFingerprintAsIds(m) [4437590049, 4437590049, 4445978657, 4445978657, 8732557345, 8732557345]受 64 位整数上限约束,targetSize * codeSize不得超过 64(见 rdMolDescriptors.cpp):不含手性时codeSize=9,最多 7 个原子;开启手性后codeSize=11,最多 5 个原子。
5.3 理化性质指纹(Sheridan)中的间接复用
Sheridan.py 实现 Kearsley 的 BP(Atom Pairs 变体)/ BT(Torsions 变体)指纹:先用 Data/SmartsLib/patty_rules.txt 把每个原子归类为CAT/ANI/POL/DON/ACC/HYD/OTH七类(AssignPattyTypes),再用rdFingerprintGenerator.GetAtomPairGenerator()/GetTopologicalTorsionGenerator()以类别码作为自定义原子不变量生成计数指纹。其依赖的codeSize位域常量与Utils同源,体现了整个 AtomPairs 家族共用的编码骨架。
六、测试与回归验证:docstring 即测试
Utils.py、Pairs.py、Torsions.py、Sheridan.py都内置了_runDoctests()入口(模块被python直接执行时运行 doctest),而 UnitTestDescriptors.py 通过doctest.DocTestSuite把四个模块的 docstring 全部纳入unittest测试套件:
tests.addTests(doctest.DocTestSuite(Pairs, optionflags=doctest.ELLIPSIS)) tests.addTests(doctest.DocTestSuite(Sheridan, optionflags=doctest.ELLIPSIS)) tests.addTests(doctest.DocTestSuite(Torsions, optionflags=doctest.ELLIPSIS)) tests.addTests(doctest.DocTestSuite(Utils, optionflags=doctest.ELLIPSIS))此外还有针对 1000 个分子的回归测试(UnitTestDescriptors.py),指纹结果与预生成的 gzip 打包 pickle 逐分子比对:
- mols1000.aps.pkl.gz —— 原子对指纹期望值;
- mols1000.tts.pkl.gz —— 拓扑扭转指纹期望值;
- mols1000.pkl.gz —— 分子本体。
testGithub334(UnitTestDescriptors.py)则单独回归了 π 电子计数规则(如N#C两个原子各 2 个 π 电子、C=C各 1 个),因为 π 电子数是原子编码位域的关键组成部分,直接影响指纹哈希。
七、小结与使用建议
- 理解编码位域:
GetAtomCode的 9 位核心编码(4 位类型 + 2 位 π + 3 位分支)来自 FingerprintUtil.h,解析分子指纹的调试场景用ExplainAtomCode还原,注意类型表之外的索引返回'X'。 - 相似度函数的使用前提:
BitsInCommon/DiceSimilarity/Dot/CosineSimilarity只接受有序ID 序列,重复位按重数计;DICE 是原子对与拓扑扭转论文的推荐指标,余弦是 LaSSI 论文的推荐指标,可按筛选场景选择。 - 手性开关的代价:
includeChirality=True会改变指纹尺寸(原子对)或最大路径长度(扭转),即使分子本身无手性,AtomPairs.h 对此有明确说明,比较指纹时须保证两侧使用一致的生成参数。 - 性能与召回平衡:需要定长指纹时用哈希版本(默认 2048 位、
nBitsPerEntry=4);需要完整信息时用稀疏计数版本;fromAtoms/ignoreAtoms可用于限定子结构参与比较。 - 以测试为规范:所有 docstring 示例均被 doctest 套件执行验证,回归数据存放在 rdkit/Chem/AtomPairs/test_data,修改相关实现时应保持这些测试通过。
- 科学计算
- 科研
- 机器学习
【免费下载链接】rdkit
The official sources for the RDKit library
相关推荐
如何给游戏更换 DLSS 版本?DLSS Swapper 从零上手完整指南
如何给游戏更换 DLSS 版本?DLSS Swapper 从零上手完整指南 DLSS Swapper 是一款 Windows 上的图形增强库管理器,专门负责帮你
科学计算科研机器学习RDKit 中 Wildman-Crippen 原子型 LogP 与摩尔折射率(MR)计算指南:rdkit.Chem.Crippen 模块深度解析
RDKit 中 Wildman Crippen 原子型 LogP 与摩尔折射率(MR)计算指南:rdkit.Chem.Crippen 模块深度解析 rdkit.
科学计算科研机器学习CANN ops-math 余弦相似度算子 CosineSimilarity 深度解析:原理、参数、约束与图模式调用
CANN ops math 余弦相似度算子 CosineSimilarity 深度解析:原理、参数、约束与图模式调用 本文围绕 CANN ops math 数学
算子库人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考