4. 分子表示与指纹计算

虚拟筛选库的构建,本质上是在“分子结构”与“可计算数值”之间建立一条稳定的转换链。同一批化合物,用不同的分子表示和指纹方式编码,会得到不同的相似性排序、不同的聚类结果,甚至不同的命中集合。因此,理解 SMILES、InChI、分子图、Morgan 指纹、MACCS 键、药效团指纹以及描述符计算与并行化,是后续相似性搜索、聚类去重和机器学习建模的基础。

一、分子表示:从一维字符串到二维图

SMILES 是最常用的线性表示,用原子符号、键符号和括号表达连接关系。例如阿司匹林可写为 CC(=O)Oc1ccccc1C(=O)O。SMILES 紧凑、易存储,但同一分子可以有多种合法写法,因此做去重或键值匹配前通常需要先做规范化(canonical SMILES)。InChI 是 IUPAC 推出的标准表示,带有分层结构(分子式、连接层、氢层、电荷层、立体层等),适合跨数据库比对;InChIKey 则是固定长度的哈希,常用于快速精确匹配。分子图是更底层的表示:节点是原子,边是化学键,节点和边可携带元素、电荷、芳香性、手性、键级等属性。几乎所有指纹和描述符计算,最终都建立在分子图之上。

实际工程中,推荐的处理顺序是:读入原始结构(SDF/SMILES/InChI)→ 标准化(去盐、中和、互变异构体处理)→ 生成 canonical SMILES 与 InChIKey → 构建分子图 → 计算指纹与描述符。标准化必须在指纹计算之前完成,否则同一化合物的不同盐型或互变异构体会被当成不同分子,导致库内重复和筛选偏差。

二、指纹类型与适用场景

Morgan 指纹(又称圆形指纹,ECFP)以每个原子为中心,按半径逐层扩展,把邻域环境哈希成整数集合。半径 2、2048 位是最常见的配置,对应 ECFP4。它擅长捕捉局部化学环境,适合相似性搜索、聚类和 QSAR 特征。MACCS 键是 166 位的结构键指纹,每一位对应一条预定义子结构规则(如“含苯环”“含羧基”),可解释性强,适合快速粗筛和规则型过滤。药效团指纹不直接编码拓扑,而是把氢键供体、受体、芳香中心、疏水中心及其间距抽象为特征,适合基于三维药效团假设的筛选。三者并非互斥,常见做法是先用 MACCS 做快速去重和粗聚类,再用 Morgan 做精细相似性排序,最后用药效团指纹对接到靶点假设。

分子表示 → 指纹/描述符 → 并行计算流水线 原始结构输入 SDF / SMILES / InChI 标准化 去盐·中和·互变异构 规范表示 canonical SMILES / InChIKey 分子图构建 原子节点·键边·属性 Morgan 指纹 半径2 · 2048位 · ECFP4 局部环境·相似性搜索 MACCS 键 166位子结构规则 可解释·快速粗筛 药效团指纹 供体·受体·芳香·疏水 三维特征·靶点假设 描述符计算 MW·LogP·TPSA·HBD/HBA 可旋转键·环数·Fsp3 并行化执行 分块 → 多进程/多线程 Worker 1 Worker 2 Worker 3 Worker N chunk_size 可调 n_jobs = 核数-1 输出矩阵 指纹位向量 描述符数值表 InChIKey 索引 相似性矩阵 聚类标签 供筛选/建模使用 关键约束: ① 标准化必须在指纹计算前完成,否则盐型/互变异构体导致重复;② 指纹半径与位数需固定并记录,保证可复现; ③ 并行化时每个 Worker 独立处理分子块,避免共享状态;④ 输出需保留原始 ID 与规范表示的映射关系。

三、描述符计算与并行化

描述符是连续或离散的分子性质数值,常见包括分子量(MW)、脂水分配系数(LogP)、拓扑极性表面积(TPSA)、氢键供体/受体数(HBD/HBA)、可旋转键数、环数、芳香环数、Fsp3 等。它们既可用于类药性过滤(如 Lipinski 五规则、Veber 规则),也可作为机器学习特征。计算描述符时要注意:不同工具包(RDKit、Open Babel、CDK)对同一描述符的定义可能略有差异,因此整条流水线应固定使用同一工具包和版本。

当库规模达到十万到百万级时,串行计算会成为瓶颈。以 RDKit 为例,可用 Python 的 multiprocessing 或 joblib 做数据并行:把分子列表切成若干块,每块交给一个 Worker 独立计算指纹和描述符,最后合并结果。关键参数包括 n_jobs(通常设为 CPU 核数减一,留出系统余量)和 chunk_size(每块分子数,过小则进程调度开销大,过大则负载不均)。下面是一个可参考的并行计算骨架:

from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
from joblib import Parallel, delayed

def calc_one(smi):
    mol = Chem.MolFromSmiles(smi)
    if mol is None:
        return None
    fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
    desc = {
        "MW": Descriptors.MolWt(mol),
        "LogP": Descriptors.MolLogP(mol),
        "TPSA": Descriptors.TPSA(mol),
        "HBD": Descriptors.NumHDonors(mol),
        "HBA": Descriptors.NumHAcceptors(mol),
        "RotB": Descriptors.NumRotatableBonds(mol),
    }
    return smi, fp.ToBitString(), desc

results = Parallel(n_jobs=7, chunk_size=500)(
    delayed(calc_one)(s) for s in smiles_list
)

运行后应检查三类结果:一是解析失败率(None 的比例),若超过 1%~2% 需回查输入结构;二是指纹位向量的长度是否一致(应为 2048);三是描述符的分布是否合理,例如 MW 是否出现异常大值、LogP 是否超出常见范围。若发现某批分子全部解析失败,通常是 SMILES 中含有未处理的盐离子或立体标记错误。

建议在流水线中同时保存 canonical SMILES、InChIKey、Morgan 指纹位串和描述符表,并用 InChIKey 作为主键去重。这样后续无论做相似性搜索、聚类还是建模,都不需要重新解析原始结构。
并行化时不要在多进程间共享 RDKit 的 Mol 对象或指纹对象,应在每个 Worker 内部独立解析和计算。共享状态容易导致结果错乱或进程崩溃,且难以排查。

总结来说,分子表示决定了“能不能算”,指纹类型决定了“算出来像不像”,描述符决定了“算出来合不合规”,而并行化决定了“算得快不快”。四者共同构成虚拟筛选库构建的计算底座。把标准化、指纹配置、描述符定义和并行参数固定下来并记录版本,才能保证不同批次、不同人员构建的库具有可比性和可复现性。