手上有三种对接方法,什么时候该信哪个?答案不是「哪个更准」,而是「在什么场景下哪个更可靠」。这篇给出系统的比较方法与决策框架。
三者的原理差异
| AutoDock Vina | GNINA | DiffDock | |
|---|---|---|---|
| 范式 | 物理打分 + 随机搜索 | 搜索 + CNN 重打分 | 扩散生成 |
| 需要口袋 | 是 | 是 | 否(盲对接) |
| 速度(单分子) | 秒级 | 秒到十几秒 | 十几秒到分钟 |
| 硬件 | CPU 即可 | GPU 更快 | 需 GPU |
| 可复现 | 固定 seed 即可 | 固定 seed 即可 | 采样有随机性 |
| 物理合理性 | 好 | 好 | 需检查 |
| 大规模筛选 | 最适合 | 可行 | 成本高 |
用自家体系做系统对比
公开基准的结论不能直接套用到你的靶点上,必须自己测:
from rdkit import Chem
from rdkit.Chem import rdMolAlign
import pandas as pd, subprocess, os
def redock_rmsd(method, receptor, ref_ligand_sdf, center, box):
"""重对接验证:能否复现共晶姿势"""
ref = Chem.RemoveHs(Chem.SDMolSupplier(ref_ligand_sdf)[0])
if method == "vina":
subprocess.run(["vina", "--receptor", receptor,
"--ligand", "ref.pdbqt",
"--center_x", str(center[0]), "--center_y", str(center[1]),
"--center_z", str(center[2]),
"--size_x", str(box[0]), "--size_y", str(box[1]),
"--size_z", str(box[2]),
"--exhaustiveness", "32", "--seed", "42",
"--out", "vina_out.pdbqt"], check=True)
subprocess.run(["mk_export.py", "vina_out.pdbqt", "-s", "vina_out.sdf"])
pose = Chem.RemoveHs(Chem.SDMolSupplier("vina_out.sdf")[0])
elif method == "gnina":
subprocess.run(["./gnina", "-r", receptor, "-l", ref_ligand_sdf,
"--autobox_ligand", ref_ligand_sdf,
"--exhaustiveness", "32", "--seed", "42",
"--cnn_scoring", "rescore",
"-o", "gnina_out.sdf"], check=True)
pose = Chem.RemoveHs(Chem.SDMolSupplier("gnina_out.sdf")[0])
return rdMolAlign.CalcRMS(pose, ref)
# 对该靶点家族的多个共晶结构做重对接
cases = [("1M17", ...), ("4HJO", ...), ("5UG9", ...)]
results = []
for pdb_id, params in cases:
for method in ["vina", "gnina", "diffdock"]:
rmsd = redock_rmsd(method, *params)
results.append({"pdb": pdb_id, "method": method, "rmsd": rmsd})
df = pd.DataFrame(results)
summary = df.groupby("method").agg(
success_rate=("rmsd", lambda x: (x <= 2.0).mean()),
median_rmsd=("rmsd", "median"),
)
print(summary)
# 在你的靶点家族上,哪个方法的成功率最高?
富集度对比(如果有活性数据)
import numpy as np
def enrichment_factor(scores, labels, frac=0.01, higher_is_better=False):
n_top = max(1, int(len(scores) * frac))
order = np.argsort(-scores if higher_is_better else scores)
hits = labels[order[:n_top]].sum()
return (hits / n_top) / (labels.sum() / len(labels))
# 准备:已知活性分子 + 诱饵(性质匹配)
print("Vina EF@1%:", enrichment_factor(vina_scores, labels))
print("GNINA EF@1%:", enrichment_factor(cnn_affinity, labels, higher_is_better=True))
# DiffDock 的置信度不适合做富集排序,不参与此项比较
# 富集度才是虚拟筛选真正关心的指标,
# 而它与「姿势预测成功率」不是一回事
决策框架
# 场景 → 推荐方法
if 口袋未知 and 无同源结构:
→ DiffDock 做探索,找出候选口袋位置
→ 再用 Vina/GNINA 在该口袋做精细对接
elif 需要筛选百万级以上化合物库:
→ Vina(成本最低)
→ top 5% 用 GNINA 重打分
elif 有共晶结构 and 做类似物对接:
→ Vina 或 GNINA(重对接验证通过后)
→ 关键分子上 MM/GBSA 或 FEP
elif 关注姿势准确度 and 分子数量少:
→ GNINA(--cnn_scoring refinement)
→ 或 Boltz/Chai-1 复合物预测(见 344)
elif 需要亲和力排序:
→ 三者都不可靠
→ 用 FEP(见 201)或直接做实验
一致性检验:最实用的做法
from rdkit.Chem import rdMolAlign
import itertools
def consensus_check(poses_dict, threshold=2.0):
"""检查多个方法给出的姿势是否一致"""
names = list(poses_dict.keys())
agreements = {}
for a, b in itertools.combinations(names, 2):
try:
rmsd = rdMolAlign.CalcRMS(poses_dict[a], poses_dict[b])
agreements[f"{a}-{b}"] = rmsd
except Exception:
agreements[f"{a}-{b}"] = None
consistent = sum(1 for v in agreements.values()
if v is not None and v <= threshold)
total = sum(1 for v in agreements.values() if v is not None)
return agreements, consistent, total
poses = {"vina": vina_pose, "gnina": gnina_pose, "diffdock": diffdock_pose}
agr, ok, total = consensus_check(poses)
for pair, rmsd in agr.items():
print(f"{pair:20s} RMSD = {rmsd:.2f} Å" if rmsd else f"{pair}: N/A")
if ok == total:
print("✓ 三个方法完全一致 —— 结合模式可信度高")
elif ok >= 1:
print("~ 部分一致 —— 需要进一步验证(如 MD 或实验)")
else:
print("✗ 各说各话 —— 该体系的结合模式尚不明确,不应据此做设计决策")
这是三种方法组合使用的最大价值:它们原理完全不同,如果给出一致的姿势,可信度远高于任何单一方法的高分;如果各说各话,那本身就是「不要在此基础上做决策」的重要警示。
共同的根本限制
无论哪种方法,打分与实测亲和力的相关性都很弱。算法差别主要体现在姿势预测上,而不是排序能力。选方法时应该问「它能不能给出正确的结合姿势」,而不是「它的分数准不准」。真正的亲和力预测需要 FEP 或实验。
常见坑与提示
- 公开基准结论不能直接套用,必须在自家靶点家族上做重对接对比;
- 姿势预测成功率与虚拟筛选富集度是两回事,要分开评估;
- 三方法一致性检验是最实用的可信度判据;
- 三者都不能可靠预测亲和力,排序需要 FEP 或实验。