- 科学计算
- 科研
- 机器学习
【免费下载链接】rdkit
The official sources for the RDKit library
导读
本文围绕 RDKit 的rdkit.SimDivFilters.rdSimDivPickers模块展开,它是 RDKit 官方提供的多样性/相似性挑选(diversity & similarity picking)C++ 扩展模块,适用于虚拟筛选、化合物库去冗余、代表性化合物子集选取等典型场景。读完本文,你将掌握MaxMinPicker、LeaderPicker、HierarchicalClusterPicker三大核心类的完整 API、参数语义与算法原理,并能在自己的化合物指纹数据上直接构造距离矩阵、运行挑选并校验结果。
模块定位:一个面向多样性挑选的 C++ 扩展
rdSimDivPickers是rdkit.SimDivFilters包下的核心扩展模块。在 Python 侧,rdkit/SimDivFilters/__init__.py通过from rdkit.SimDivFilters import rdSimDivPickers导入该扩展,并将其中全部成员重新导出(from rdkit.SimDivFilters.rdSimDivPickers import *);如果扩展不可用(例如编译时未启用 Python 包装),则置为None以保证包可以正常导入。
该模块的 Python 绑定入口定义在 Code/SimDivPickers/Wrap/rdSimDivPickers.cpp,其模块级 docstring 为 "Module containing the diversity and similarity pickers",内部依次包装三个类:
MaxMinPicker:基于 MaxMin 算法的多样性挑选器;LeaderPicker:基于 Roger Sayle 提出的 Leader 算法(球排除法的类似物)的挑选器;HierarchicalClusterPicker:基于层次聚类的多样性挑选器。
C++ 侧的实现位于 Code/SimDivPickers 目录:DistPicker.h定义了抽象基类与下三角距离矩阵的索引函数,MaxMinPicker.h、LeaderPicker.h、HierarchicalClusterPicker.h分别实现三种挑选策略,该目录的 CMakeLists.txt 将三个源文件编译为SimDivPickers库,并在启用包装时递归构建Wrap(Boost.Python)与nbWrap(nanobind)两套绑定。
三种挑选器都共享同一个抽象基类DistPicker(见 Code/SimDivPickers/DistPicker.h),其虚函数pick(distMat, poolSize, pickSize)约定:
distMat:一维下三角距离矩阵(double 数组),元素个数应为poolSize*(poolSize-1)/2;poolSize:候选池大小;pickSize:需要挑选的个数(不得超过poolSize);- 返回值为被选中的元素下标(
RDKit::INT_VECT)。
矩阵下标通过getDistFromLTM换算:i==j返回 0;i>j时取distMat[i*(i-1)/2 + j]。
MaxMinPicker:最大化最小距离的贪心挑选
MaxMinPicker是使用频率最高的多样性挑选器,对应算法文献为 Ashton 等发表于Quant. Struct.-Act. Relat., 21 (2002), 598-604的 MaxMin 方法。其核心思想记录在 Code/SimDivPickers/MaxMinPicker.h 的类注释中:
- 用合适的种子化合物初始化子集,置
x = 1; - 对数据集中剩余的每个化合物,计算它与子集中每个化合物之间的距离(不相似度),并保留每个化合物对应的最小距离;
- 选取"最小距离最大"的那个化合物移入子集;
x = x + 1,若x < k则回到第 2 步。
最终结果是"两两距离尽可能大"的k个化合物,这正是多样性子集选取的核心目标。
Pick:基于完整距离矩阵
Python 绑定定义在 Code/SimDivPickers/Wrap/MaxMinPicker.cpp,签名与参数如下:
MaxMinPicker().Pick(distMat, poolSize, pickSize, firstPicks=(), seed=-1)| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
distMat | numpy 一维数组(float64) | 必填 | 下三角距离矩阵 |
poolSize | int | 必填 | 候选池大小 |
pickSize | int | 必填 | 挑选个数,必须小于poolSize(否则抛出ValueError) |
firstPicks | 序列 | () | 预先指定的首批种子下标 |
seed | int | -1 | 随机数种子;小于 0 时使用系统随机源播种 |
包装层要求distMat必须是 numpy 数组(PyArray_Check检查失败即抛ValueError),并通过PyArray_ContiguousFromObject将其转换为连续的一维 double 数组后才进入 C++ 的pick()。C++ 侧会校验:池为空、pickSize > poolSize或firstPicks中下标超出poolSize时均抛出ValueErrorException。未提供firstPicks时,第一个种子由boost::mt19937随机数生成器在[0, poolSize-1]内随机选取(Code/SimDivPickers/MaxMinPicker.h 中lazyPick的实现)。
LazyPick 与 LazyBitVectorPick:按需计算距离
对于距离矩阵过大、无法一次性驻留内存的场景,MaxMinPicker 提供"惰性"接口——由用户提供一个距离函数,算法按需调用:
LazyPick(distFunc, poolSize, pickSize, firstPicks=(), seed=-1, useCache=None):distFunc接收两个下标(i, j)并返回两点间距离。绑定 docstring 特别注明:实现内部会缓存距离值,客户端无需也不应自行缓存;useCache参数已被废弃并忽略(调用时会打印 warning)。LazyBitVectorPick(objects, poolSize, pickSize, firstPicks=(), seed=-1, useCache=None):直接传入一个位向量(指纹)序列。包装层把序列抽取为std::vector<const ExplicitBitVect *>,使用 Code/SimDivPickers/Wrap/PickerHelpers.h 中的pyBVFunctor以 Tanimoto 距离(1 - TanimotoSimilarity)为距离函数执行同样的 MaxMin 算法。LazyPickWithThreshold(distFunc, poolSize, pickSize, threshold, firstPicks=(), seed=-1)与LazyBitVectorPickWithThreshold(objects, poolSize, pickSize, threshold, firstPicks=(), seed=-1):额外接收一个threshold距离阈值,当算法算出的候选最小距离<= threshold时提前终止,返回(picks, threshold)元组——threshold会更新为实际达到的距离值。C++ 实现中threshold < 0表示"不提前终止"(见MaxMinPicker::lazyPick中if (maxOFmin <= threshold && threshold >= 0.0) break;)。
测试验证与使用要点
测试文件 Code/SimDivPickers/Wrap/testPickers.py 提供了大量可复现的实证:
test0MaxMin:对 1000 个二维随机点调用Pick(self.dMat, 1000, 80, (886, 112)),前两个选中的正是种子886、112;同时验证了Pick与LazyPick结果一致,以及非法的firstPicks(如(1012,)、(-1,))会抛出ValueError;testBitVectorMaxMin:同一批位向量分别用LazyPick(自写 Tanimoto 距离函数)与LazyBitVectorPick在相同seed=42下得到完全一致的结果;testBitVectorMaxMin3:用chembl_cyps.head.fps(位于 Code/SimDivPickers/Wrap/test_data)挑选 20 个指纹,固定seed=42可复现确定的下标序列;传入firstPicks=[374, 720, 690, 339, 875]后结果保持不变,说明种子序列被正确采纳;testBitVectorMaxMin4:验证阈值接口——threshold=-1.0时挑选 20 个并返回实际阈值约0.8977;threshold=0.91时提前终止只返回 13 个,且返回阈值>= 0.91。
要点:seed决定随机种子的选取,固定seed可复现结果;不同seed通常得到不同子集(见testNonUniqueCrash中的断言)。LazyBitVectorPick与等价的自定义距离函数LazyPick在相同种子下结果一致,因此当数据是指纹序列时优先使用前者,代码更简洁且无需手写距离函数。
LeaderPicker:球排除式的快速多样性挑选
LeaderPicker实现 Roger Sayle 提出的 Leader 算法(与球排除法 sphere exclusion 类似),包装代码见 Code/SimDivPickers/Wrap/LeaderPicker.cpp,算法实现位于 Code/SimDivPickers/LeaderPicker.h。
核心 API
LeaderPicker().LazyBitVectorPick(objects, poolSize, threshold, pickSize=0, firstPicks=(), numThreads=1) LeaderPicker().LazyPick(distFunc, poolSize, threshold, pickSize=0, firstPicks=(), numThreads=1)与 MaxMin 最大的不同在于threshold是必填位置参数,且其含义是距离(即1 - similarity),而不是相似度。例如 Tanimoto 相似度阈值 0.8 对应 distance 阈值 0.2;LeaderPicker 文档字符串中给出的测试均使用threshold=0.8、0.9这类距离值。pickSize默认为 0,此时算法会一直挑选直到池子耗尽(等价于pickSize = poolSize,见LeaderPicker::lazyPick中if (!pickSize) pickSize = poolSize;)。numThreads参数当前被忽略(绑定 docstring 明确注明)。
算法流程(单线程实现):维护一个未选列表,每轮取出列表首个元素作为新 leader(种子),用距离函数将其与列表中所有剩余元素逐一比较,凡是距离<= threshold的元素全部从列表中剔除("排斥"进该 leader 的球内),直到列表为空或达到pickSize。由于最终任意两个被选元素之间的距离都严格大于threshold,结果天然满足多样性约束。
测试验证
Code/SimDivPickers/Wrap/testPickers.py 中testBitVectorLeader1提供了清晰的验证逻辑:
- 对同一批
chembl_cyps.head.fps指纹,threshold=0.8(距离)时选出 146 个;threshold=0.9时仅选出 14 个——阈值越大,每个 leader 的排斥半径越大,子集越小; - 测试随后遍历所有配对,断言任意两两之间的 Tanimoto 距离
>= threshold,直接验证了算法的核心不变量; pickSize=10时恰好返回 10 个,且同样满足两两距离约束。
testLazyLeader用简单函数i - j作为距离,验证LazyPick(func, 100, 20)返回[0, 21, 42, 63, 84]——即每个新 leader 会把距离 20 以内的元素全部剔除,间隔 21 的等差序列正是预期结果。
HierarchicalClusterPicker:先聚类、再取代表元
HierarchicalClusterPicker基于层次聚类做多样性挑选,头文件见 Code/SimDivPickers/HierarchicalClusterPicker.h,实现沿用Code/ML/Cluster下的 Murtagh 聚类代码。
聚类方法与构造参数
构造函数接收clusterMethod参数,Python 侧通过HierarchicalClusterPicker.ClusterMethod枚举暴露(绑定见 Code/SimDivPickers/Wrap/HierarchicalClusterPicker.cpp):
| 枚举值 | C++ 常量 | 说明 |
|---|---|---|
ClusterMethod.WARD | WARD = 1 | Ward 法(最小方差) |
ClusterMethod.SLINK | SLINK = 2 | 单连接(最近邻) |
ClusterMethod.CLINK | CLINK = 3 | 完全连接(最远邻) |
ClusterMethod.UPGMA | UPGMA = 4 | 非加权平均连接 |
ClusterMethod.MCQUITTY | MCQUITTY = 5 | McQuitty 法 |
ClusterMethod.GOWER | GOWER = 6 | Gower 法 |
ClusterMethod.CENTROID | CENTROID = 7 | 质心法 |
两个方法:Pick 与 Cluster
pkr = rdSimDivPickers.HierarchicalClusterPicker(rdSimDivPickers.ClusterMethod.WARD) pkr.Pick(distMat, poolSize, pickSize) # 返回代表元下标列表 pkr.Cluster(distMat, poolSize, pickSize) # 返回 clusters:每个簇为一个下标列表Pick:先将整个池子按指定方法做层次聚类,当簇的数目合并到pickSize时停止;然后在每个簇内计算各元素到簇内其余元素距离的平方和,选取平方和最小(最接近质心)的元素作为该簇代表元。Cluster:直接返回聚类结果,每个元素是一个下标列表,可用作后续分析。
注意两点(源码与测试均有印证):
- 距离矩阵会被算法修改:头文件注释明确指出 "this matrix WILL BE ALTERED during the picking"。Python 包装层因此在进入 C++ 前用
PyArray_CopyFromObject拷贝一份,避免破坏用户传入的矩阵(见HierarchicalPicks与HierarchicalClusters中的注释与实现)。 Pick要求pickSize < poolSize:包装层在HierarchicalPicks中显式检查并抛ValueError。
测试验证
Code/SimDivPickers/Wrap/testPickers.py 中的test1HierarchPick使用测试数据 Code/SimDivPickers/Wrap/test_data/points.csv(带标签的二维点集):Cluster(self.dMat, n, 2)划分出的两个簇内标签完全一致(聚类正确),而Pick返回(1, 30)作为两个簇的代表元。testIssue208与testInts则验证了对相同输入重复调用结果稳定,且整数距离矩阵也能正确处理。
纯 Python 层的补充接口:TopNOverallPicker 与 SpreadPicker
除 C++ 扩展外,rdkit.SimDivFilters包还提供纯 Python 实现的相似性挑选类,定义在 rdkit/SimDivFilters/SimilarityPickers.py,适用于"给定探针指纹、从库中挑选最相似化合物"的场景:
TopNOverallPicker(numToPick=10, probeFps=None, dataSet=None, simMetric=DataStructs.TanimotoSimilarity):对库中每个指纹,计算其与所有探针相似度的最大值作为该指纹得分,用 rdkit/DataStructs/TopNContainer.py 维护 Top-N 容器,最终返回得分最高的numToPick个(fp, score)对,结果按得分降序排列。SpreadPicker(numToPick=10, probeFps=None, dataSet=None, simMetric=DataStructs.TanimotoSimilarity, expectPickles=True, onlyNames=False):为每个探针维护一个独立 Top-N 列表,再以"轮转取每行当前最优、跳过已被取走的元素"的方式挑选,使结果在多个探针间分散分布(这正是其名称含义)。expectPickles=False时允许dataSet为可转换为位向量的字符串;onlyNames=True时结果只保留_fieldsFromDb[0]名称。
两类 picker 均为惰性求值:构造时_picks为None,首次调用len(picker)或picker[i]才触发MakePicks()(见GenericPicker基类与 rdkit/SimDivFilters/UnitTestSimilarityPickers.py 的test_TopNOverallPicker、test_SpreadPicker对惰性行为的断言)。其 docstring 自带完整的 doctest 示例(连接RDTestDatabase的simple_mols1表、生成 RDKFingerprint、用COC与CC(=O)O作探针),可直接作为上手模板。
端到端实战:从指纹到多样性子集
综合以上 API,下面给出一个可直接运行的完整流程,涵盖"构建池子 → 计算距离 → 多样性与相似性挑选 → 结果校验":
import numpy from rdkit import Chem, DataStructs from rdkit.SimDivFilters import rdSimDivPickers as rdsimdiv from rdkit.SimDivFilters.SimilarityPickers import TopNOverallPicker # 1. 构建化合物池与指纹 smis = ["CCO", "CCOC", "CC(=O)O", "CCN", "C1CCCCC1", "c1ccccc1", "C#N", "CC(C)C"] mols = [Chem.MolFromSmiles(s) for s in smis] fps = [Chem.RDKFingerprint(m) for m in mols] # 2. 位向量版本:MaxMin 多样性挑选(固定种子可复现) picker = rdsimdiv.MaxMinPicker() picks = picker.LazyBitVectorPick(fps, len(fps), 4, seed=42) print("MaxMin picks:", picks) # 3. 阈值版本:同时返回终止时达到的距离 picks, threshold = picker.LazyBitVectorPickWithThreshold(fps, len(fps), 4, -1.0, seed=42) print("threshold reached:", threshold) # 4. Leader 算法:按距离阈值排斥,结果两两距离必大于阈值 lp = rdsimdiv.LeaderPicker() ids = lp.LazyBitVectorPick(fps, len(fps), 0.5) # 注意:0.5 是距离(1-相似度) for i in range(len(ids)): for j in range(i): d = 1.0 - DataStructs.TanimotoSimilarity(fps[ids[i]], fps[ids[j]]) assert d >= 0.5 print("Leader picks:", ids) # 5. 层次聚类挑选(构造欧氏距离矩阵可改用 rdMetricMatrixCalc) n = len(fps) dist_mat = numpy.zeros(n * (n - 1) // 2, "d") for i in range(n - 1): for j in range(i + 1, n): d = 1.0 - DataStructs.TanimotoSimilarity(fps[i], fps[j]) dist_mat[i * (i - 1) // 2 + j] = d # 注意仅填充下三角 hp = rdsimdiv.HierarchicalClusterPicker(rdsimdiv.ClusterMethod.WARD) print("Hierarchical representative picks:", hp.Pick(dist_mat, n, 2)) print("Hierarchical clusters:", hp.Cluster(dist_mat, n, 2)) # 6. 探针驱动的相似性挑选(纯 Python 层) probe = Chem.RDKFingerprint(Chem.MolFromSmiles("CCO")) top = TopNOverallPicker(numToPick=3, probeFps=[probe], dataSet=fps) for fp, score in top: print("top match, score =", score)运行前提:需要已安装并正确编译的 RDKit(rdSimDivPickers为 C++ 扩展,需在构建时启用 Python 包装,参见 Code/SimDivPickers/CMakeLists.txt 中的RDK_BUILD_BOOST_PYTHON_WRAPPERS/RDK_BUILD_NANOBIND_WRAPPERS选项)。欧氏距离矩阵可借助rdkit.DataManip.Metric.rdMetricMatrixCalc的GetEuclideanDistMat直接生成(测试testPickers.py即采用此方式);若使用Pick系列方法,务必遵守下三角一维布局约定。
参数速查与常见陷阱
| 场景 | 关键参数 | 注意事项 |
|---|---|---|
MaxMinPick | distMat为下三角一维数组;pickSize < poolSize | firstPicks下标越界或为负会抛ValueError |
MaxMinLazyPick | distFunc(i, j)返回距离 | 内部缓存距离,客户端不要重复计算;useCache已废弃忽略 |
| MaxMin 位向量接口 | objects为ExplicitBitVect序列 | 与等价LazyPick结果一致;固定seed复现 |
| MaxMin 阈值接口 | threshold为距离 | 达到阈值提前终止,返回元组(picks, actual_threshold) |
| Leader | threshold是距离不是相似度 | 结果任意两两距离> threshold;pickSize=0表示选完 |
| Hierarchical | clusterMethod枚举 7 选 1 | Pick会修改矩阵(包装层自动拷贝);Pick要求pickSize < poolSize |
| TopNOverall / Spread | probeFps、dataSet、simMetric | 惰性求值;SpreadPicker在探针间分散结果 |
最常见的两类错误:其一,把 Leader 的threshold当作相似度传入,导致排斥半径错误;其二,distMat布局不按下三角一维填充,或pickSize不小于poolSize,均会触发ValueError。对照上述速查表即可快速定位。
小结
rdSimDivPickers模块以统一的"下三角距离矩阵 + 距离函数"抽象承载了三种互补的挑选策略:MaxMin 以贪心方式最大化子集内部最小距离,适合精确的多样性优化;Leader 以距离阈值排斥邻近点,速度快、结果天然满足距离下限,适合大规模库快速去冗余;层次聚类则把挑选问题转化为"聚类 + 取代表元",并能顺带产出簇划分。配合纯 Python 层的TopNOverallPicker/SpreadPicker做探针驱动的相似性挑选,RDKit 的SimDivFilters家族可以覆盖化合物库设计的绝大多数挑选需求。想深入了解实现细节的读者,可继续阅读 Code/SimDivPickers/MaxMinPicker.h、Code/SimDivPickers/LeaderPicker.h、Code/SimDivPickers/HierarchicalClusterPicker.h 及对应的包装与测试文件。
- 科学计算
- 科研
- 机器学习
【免费下载链接】rdkit
The official sources for the RDKit library
相关推荐
RDKit 层次聚类核心模块解析:rdkit.ML.Cluster.Clustering 与 Murtagh 算法实战指南
RDKit 层次聚类核心模块解析:rdkit.ML.Cluster.Clustering 与 Murtagh 算法实战指南 导读 rdkit.ML.Cluste
科学计算科研机器学习RDKit Murtagh 层次聚类模块解析:从 Fortran 算法到 Python 聚类树
RDKit Murtagh 层次聚类模块解析:从 Fortran 算法到 Python 聚类树 导读 RDKit 的 rdkit.ML.Cluster.Murt
科学计算科研机器学习RDKit 分子指纹聚类指南:ClusterMols 模块 API 与 Murtagh 层次聚类实战
RDKit 分子指纹聚类指南:ClusterMols 模块 API 与 Murtagh 层次聚类实战 导读 本文围绕 RDKit 官方文档 rdkit.Chem
科学计算科研机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考