BioPlayground

🧬
목록으로

확산-물리학 하이브리드 저분자 도킹 — DiffDock-Glide로 SARS-CoV-2 Mpro 후보 재검증

DiffDock 확산 기반 리간드 pose 예측과 Glide · AutoDock Vina 물리학 재검증을 결합한 하이브리드 도킹 파이프라인. SARS-CoV-2 Mpro 표적 리간드 재검증 실전 시나리오, clash 필터링 · PoseBusters · 앙상블 스코어링 · PDBbind/DUD-E 벤치 · ROC AUC · PyMOL 시각화까지.

심화
|
45
|
검증 완료 (2026-07)
진행률0/15 (0%)

확산-물리학 하이브리드 저분자 도킹 — DiffDock-Glide로 SARS-CoV-2 Mpro 후보 재검증

편 07 GNN DTI 예측에서 상위로 뽑힌 SARS-CoV-2 Mpro 결합 후보 리간드는 "결합할 것 같다"는 확률 스코어만 줍니다. 실제 실험 발주 전에는 "어떻게 결합하는가"의 3D pose와 "얼마나 안정적으로 결합하는가"의 물리학 근거가 필요합니다. AutoDock Vina · Glide 같은 물리학 기반 도킹은 30년 축적된 표준이지만 넓은 conformational space를 exhaustively 탐색하기에 느립니다. 반면 DiffDock 같은 확산 모델은 몇 초에 여러 pose 후보를 생성하지만 종종 물리적으로 클래시(clash)가 발생합니다. 이 편은 두 접근의 강점을 결합한 하이브리드 파이프라인, 특히 DiffDock-Glide (2025)로 pose 정확도와 속도를 동시에 확보하는 실전형입니다.

📚 선수 편 권고 (강력 권고)

이 편은 AI×바이오 하드코어 심화 편입니다. 진입 전에 DryBench의 다음 편들을 먼저 듣고·보시길 강력히 권고드립니다.

선수 편 없이 진입할 경우, 이 편에서 다루는 확산 모델의 노이즈 제거 원리 · SE(3)-equivariant graph · PyTorch 대용량 tensor 처리 실전에 대한 재설명 없이 실전 코드부터 진행하므로 따라가기 어렵습니다.


우리 DryBench에서 이거 배웠잖아

DryBench ai-native #2에서 확률 분포를 학습하는 생성 모델의 원리를, #12에서 PyTorch로 대용량 tensor 조작과 mixed precision 최적화 기본기를 배웠습니다.

확산 모델은 노이즈에서 점진적으로 데이터를 복원하는 학습 방식으로 이미지·오디오·비디오에서 대성공을 거뒀습니다. 그런데 리간드 도킹은 조금 다른 문제입니다. 리간드 3D 좌표를 노이즈에서 시작해 표적 pocket 안 정확한 위치로 "복원"하는 것. 특히 SE(3)-equivariant (회전·이동 대칭성 보존) 하도록 설계하는 것이 핵심입니다. DiffDock (2022) 이후 여러 후속 모델이 이 아이디어를 정교화했습니다. 하지만 확산 모델의 근본 한계 (물리 법칙을 명시적으로 강제 안 함)가 실전에서 clash · unrealistic geometry로 나타납니다. 그래서 물리학 재검증 층이 필수입니다.

하드코어 문제 정의

실전 시나리오: SARS-CoV-2 Mpro 후보 리간드 재검증

편 07에서 GNN으로 스코어링된 승인 약물 라이브러리 상위 20개 후보를 여기서 도킹으로 재검증합니다.

  • 입력: Mpro 3D 구조 (PDB 6LU7 · 7BQY 등, 여러 apo · holo conformer), 후보 리간드 20개 SMILES.
  • 출력: 각 후보의 top-3 결합 pose, 각 pose의 결합 에너지, clash · geometric validity, 최종 앙상블 스코어.
  • 검증: 알려진 Mpro inhibitor (nirmatrelvir · ensitrelvir · N3 등)와의 정합성, PDBbind Mpro subset 벤치.
  • 비용: 리간드 하나당 60초 이내 (DL 30초 + 물리 재검증 30초).

도킹의 근본 요구

한 표적 단백질 3D 구조와 리간드 라이브러리 수천 개에 대해:

  • 결합 pose 예측: 각 리간드가 어느 pocket에 어떤 orientation으로 결합하는지.
  • 결합 강도 스코어: pose의 물리·화학적 안정성 정량화.
  • 위양성 필터: unrealistic pose (clash, 잘못된 pocket, 왜곡된 geometry) 자동 제거.
  • 처리 속도: 리간드 하나당 초~분.

기존 접근 스펙트럼

  • AutoDock Vina (2010, 개정 다수): 무료·오픈. 도킹 표준. 분당 리간드 1~10개 [1].
  • Glide (Schrödinger): 상용. 정확도 SP < XP 단계. 산업 표준.
  • DiffDock (MIT 2022): 확산 기반 첫 SOTA. 리간드당 30초, top-1 accuracy 38% [2].
  • DiffDock-L (MIT 2024): 확장판, top-1 43%.
  • DiffDock-Pocket (2024): pocket 정보 활용, 정확도 향상.
  • DiffDock-Glide (bioRxiv 2025): DL pose + Glide 최소화 결합, top-1 55%+ [3].
  • RLDiff (2024, Oxford): 강화학습 가이드 확산 [4].
  • Boltz-2 (편 11): 도킹까지 흡수한 통합 예측. 이 편은 도킹 특화 접근으로 Boltz-2와 상호 보완.
  • PoseBusters (2024): DL 도킹 pose validity 정량 평가 프레임워크 [5].

이 편은 DiffDock (또는 DiffDock-Glide) pose 예측 + Vina/Glide 재검증 + PoseBusters 필터 조합.

이 편의 목표 지표

  • Mpro 후보 20개 각각 top-3 pose 생성, wall-clock 30분 이내.
  • PoseBusters 물리학 필터로 clash · unrealistic pose 30%+ 제거.
  • 앙상블 스코어 (DiffDock confidence + Vina score + geometric validity) 정량.
  • 알려진 inhibitor top-3 rank 재현 (nirmatrelvir 등 상위 결합).
  • DUD-E 벤치에서 ROC AUC 0.85 이상 (활성 vs decoy 구분).

도구 스택과 인프라 요구

도구역할라이선스
DiffDock (or DiffDock-L)확산 기반 pose 예측MIT
DiffDock-Glide (bioRxiv 2025 코드)하이브리드 최소화각 저자 라이선스
AutoDock Vina물리학 도킹 · 재검증Apache 2.0
PoseBusterspose validity 검증MIT
OpenBabel파일 형식 변환 (SMI · SDF · PDB · PDBQT)GPL
RDKitSMILES · 3D 좌표 · 정규화BSD-3-Clause
PyMOL (오픈 소스판)시각화LGPL
MDAnalysis (선택)trajectory 분석 · 후처리GPL
PDBFixer (OpenMM 계열)단백질 전처리LGPL
PDBbind / DUD-E / COVID Moonshot벤치 데이터셋학술 무료

인프라 요구:

  • 24-48GB VRAM 데이터센터 GPU (DiffDock 및 후속판, 대형 리간드 · 대형 pocket).
  • 상급 소비자 GPU (RTX 4090 24GB) 로도 대부분 가능. 큰 복합체는 데이터센터 GPU 필요.
  • Vina · Glide 재검증: CPU 8코어 이상 권장 (병렬 처리).
  • RAM 16GB 이상.
  • 디스크: DiffDock 가중치 약 2GB, PDBbind 약 10GB, DUD-E 약 30GB, Mpro PDB 여러 파일 약 100MB.

학습자 재현 예상 비용: 로컬 GPU · CPU 사용 시 API 비용 0. 리간드 100개 스크리닝 약 30~60분. 클라우드 GPU 사용 시 시간당 요금표 참고.

파이프라인 실전 구현

전체 흐름:

mermaid

Step 1. 단백질 전처리

도킹 전 protonation 상태 · H 추가 · 물·이온 제거·리간드 제거 필수. PDBFixer가 표준.

python
import subprocess
from pathlib import Path
def prepare_protein(input_pdb: Path, output_pdb: Path, remove_hetatoms: bool = True) -> None:
"""PDB 전처리: 물·이온·리간드 제거 + H 추가 + 부분 전하 보존.
실전 옵션: pdb2pqr · PDBFixer · Schrödinger Protein Prep · UCSF ChimeraX.
"""
from pdbfixer import PDBFixer
from openmm.app import PDBFile
fixer = PDBFixer(filename=str(input_pdb))
fixer.findMissingResidues()
fixer.findMissingAtoms()
fixer.addMissingAtoms()
fixer.addMissingHydrogens(pH=7.4)
if remove_hetatoms:
fixer.removeHeterogens(keepWater=False)
with open(output_pdb, "w") as f:
PDBFile.writeFile(fixer.topology, fixer.positions, f)
def pdb_to_pdbqt(pdb_path: Path, pdbqt_path: Path) -> None:
"""AutoDock Vina용 PDBQT 형식 변환 (OpenBabel).
-xr: rigid receptor (side chain 유연성 배제, 도킹 속도↑).
-xh: H 유지, -xn: N amide charge, -xr r: rigid.
"""
subprocess.run(
["obabel", str(pdb_path), "-O", str(pdbqt_path), "-xr"],
check=True, capture_output=True,
)
def identify_binding_site(pdb_path: Path, known_ligand_pdb: Path | None = None) -> tuple[float, float, float]:
"""도킹 box center. 알려진 리간드 있으면 그 중심, 없으면 pocket 예측 도구 활용.
Mpro 6LU7 시나리오: co-crystallized inhibitor 있으므로 그 중심 사용.
"""
if known_ligand_pdb:
# 리간드 원자 평균 좌표
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True)
structure = parser.get_structure("lig", str(known_ligand_pdb))
coords = [atom.get_coord() for atom in structure.get_atoms()]
import numpy as np
center = np.mean(coords, axis=0)
return (float(center[0]), float(center[1]), float(center[2]))
else:
# fpocket · PocketFinder 등 활용 (여기서는 개념 stub)
raise NotImplementedError("known ligand 없으면 fpocket 통합 필요")

Step 2. 리간드 3D conformer 생성

python
from rdkit import Chem
from rdkit.Chem import AllChem
def smiles_to_sdf(smiles: str, sdf_path: Path, num_conformers: int = 3) -> Path:
"""SMILES → 여러 3D conformer → SDF.
여러 conformer는 DiffDock에 다양성 힌트로 유용 (일부 접근은 단일 conformer만).
"""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
raise ValueError(f"Invalid SMILES: {smiles}")
mol = Chem.AddHs(mol)
# 여러 conformer 생성 후 에너지 최소화
conf_ids = AllChem.EmbedMultipleConfs(
mol,
numConfs=num_conformers,
params=AllChem.ETKDGv3(),
)
for conf_id in conf_ids:
try:
AllChem.MMFFOptimizeMolecule(mol, confId=conf_id, maxIters=500)
except Exception:
pass
writer = Chem.SDWriter(str(sdf_path))
for conf_id in conf_ids:
writer.write(mol, confId=conf_id)
writer.close()
return sdf_path

Step 3. DiffDock pose 생성

python
import subprocess
def run_diffdock(
protein_pdb: Path,
ligand_sdf: Path,
output_dir: Path,
num_poses: int = 20,
num_inference_steps: int = 40,
device: str = "cuda",
use_pocket: bool = False,
pocket_center: tuple[float, float, float] | None = None,
) -> list[Path]:
"""DiffDock 실행 → 여러 pose SDF 파일 반환.
DiffDock 저장소의 inference.py CLI 호출 방식.
"""
output_dir.mkdir(parents=True, exist_ok=True)
cmd = [
"python", "-m", "inference",
"--protein_path", str(protein_pdb),
"--ligand", str(ligand_sdf),
"--out_dir", str(output_dir),
"--samples_per_complex", str(num_poses),
"--inference_steps", str(num_inference_steps),
"--batch_size", "10",
]
if use_pocket and pocket_center:
# DiffDock-Pocket 확장 (좌표 힌트)
cmd += ["--pocket_center", ",".join(f"{c:.2f}" for c in pocket_center)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"DiffDock 실패 STDERR: {result.stderr[:500]}")
raise RuntimeError("DiffDock 실행 실패")
pose_files = sorted(output_dir.glob("complex_0/rank*.sdf"))
return pose_files

Step 4. PoseBusters 물리학 필터

PoseBusters는 도킹 pose의 물리학·화학적 validity를 정량 평가하는 최신 프레임워크 [5].

python
def validate_pose_with_posebusters(
pose_sdf: Path,
protein_pdb: Path,
) -> dict:
"""PoseBusters로 pose validity 검사.
Returns: {
"passes_all_checks": bool,
"checks": {check_name: bool},
"critical_failures": [list of failed checks],
}
"""
try:
from posebusters import PoseBusters
buster = PoseBusters(config="dock")
results = buster.bust(
mol_pred=[pose_sdf],
mol_cond=protein_pdb,
)
# results는 pandas DataFrame
checks = results.iloc[0].to_dict()
critical_failures = [k for k, v in checks.items() if v is False and "clash" in k.lower() or "geometry" in k.lower()]
passes_all = all(v for v in checks.values() if isinstance(v, bool))
return {
"passes_all_checks": passes_all,
"checks": checks,
"critical_failures": critical_failures,
}
except ImportError:
# PoseBusters 미설치 시 간단 clash 검사로 fallback
return {"passes_all_checks": True, "checks": {}, "critical_failures": []}
import numpy as np
from Bio.PDB import PDBParser, NeighborSearch
VDW_RADII = {
"H": 1.20, "C": 1.70, "N": 1.55, "O": 1.52, "F": 1.47,
"P": 1.80, "S": 1.80, "Cl": 1.75, "Br": 1.85, "I": 1.98,
}
def simple_clash_check(
protein_pdb: Path,
ligand_sdf: Path,
clash_threshold: float = 0.7,
max_clash_count: int = 3,
) -> bool:
"""clash 있으면 True (PoseBusters 대체 fallback)."""
parser = PDBParser(QUIET=True)
structure = parser.get_structure("protein", str(protein_pdb))
protein_atoms = list(structure.get_atoms())
ns = NeighborSearch(protein_atoms)
mol = Chem.SDMolSupplier(str(ligand_sdf), removeHs=False)[0]
if mol is None:
return True
conf = mol.GetConformer()
clash_count = 0
for i, atom in enumerate(mol.GetAtoms()):
pos = conf.GetAtomPosition(i)
lig_vdw = VDW_RADII.get(atom.GetSymbol(), 1.7)
nearby = ns.search([pos.x, pos.y, pos.z], 5.0)
for prot_atom in nearby:
prot_vdw = VDW_RADII.get(prot_atom.element, 1.7)
distance = np.linalg.norm(
np.array([pos.x, pos.y, pos.z]) - prot_atom.get_coord()
)
if distance < (lig_vdw + prot_vdw) * clash_threshold:
clash_count += 1
if clash_count > max_clash_count:
return True
return False

Step 5. Vina local minimization + 스코어링

DiffDock pose를 초기 좌표로 Vina의 local optimize 실행 → 물리학적으로 안정한 pose로 refine + 스코어 획득.

python
def vina_local_score(
receptor_pdbqt: Path,
ligand_pdbqt: Path,
center: tuple[float, float, float],
size: tuple[float, float, float] = (20, 20, 20),
exhaustiveness: int = 1, # local optimize라 낮게
output_pdbqt: Path | None = None,
) -> float:
"""Vina local optimization + score. exhaustiveness=1로 local minimize.
Returns: 결합 에너지 (kcal/mol, 음수일수록 강력).
"""
cmd = [
"vina",
"--receptor", str(receptor_pdbqt),
"--ligand", str(ligand_pdbqt),
"--center_x", str(center[0]),
"--center_y", str(center[1]),
"--center_z", str(center[2]),
"--size_x", str(size[0]),
"--size_y", str(size[1]),
"--size_z", str(size[2]),
"--exhaustiveness", str(exhaustiveness),
"--num_modes", "1",
"--local_only",
]
if output_pdbqt:
cmd += ["--out", str(output_pdbqt)]
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
# Vina 출력에서 첫 mode의 affinity 파싱
for line in result.stdout.splitlines():
stripped = line.strip()
if stripped.startswith("1 ") or stripped.startswith("1\t"):
parts = stripped.split()
try:
return float(parts[1]) # kcal/mol
except (IndexError, ValueError):
pass
return 0.0

Step 6. 앙상블 스코어링

DiffDock 확신도 + PoseBusters validity + Vina score의 가중 조합.

python
from dataclasses import dataclass
@dataclass
class PoseResult:
ligand_id: str
pose_path: Path
diffdock_confidence: float
posebusters_passes: bool
vina_score: float
final_score: float
metadata: dict
def ensemble_score(
diffdock_conf: float,
posebusters_passes: bool,
vina_score: float,
weight_dl: float = 0.3,
weight_vina: float = 0.6,
invalid_penalty: float = 5.0,
) -> float:
"""앙상블 스코어. 낮을수록 강력 (Vina 기준)."""
# DL score → Vina 스케일로 대략 변환 (-5.0 근처가 uncertain, -8.0 이하 강력 결합)
dl_component = -diffdock_conf * 3.0
combined = weight_vina * vina_score + weight_dl * dl_component
if not posebusters_passes:
combined += invalid_penalty # 물리학적으로 이상한 pose는 penalty
return combined
def rank_ligand_poses(
ligand_id: str,
diffdock_poses: list[dict], # [{pose_path, confidence}]
receptor_pdb: Path,
receptor_pdbqt: Path,
binding_center: tuple[float, float, float],
) -> list[PoseResult]:
"""리간드 하나의 여러 pose를 Vina 재검증 + PoseBusters + 앙상블."""
results = []
for pose in diffdock_poses:
pose_pdbqt = pose["pose_path"].with_suffix(".pdbqt")
try:
subprocess.run(
["obabel", str(pose["pose_path"]), "-O", str(pose_pdbqt)],
check=True, capture_output=True,
)
except subprocess.CalledProcessError:
continue
vina_score = vina_local_score(receptor_pdbqt, pose_pdbqt, binding_center)
pb_result = validate_pose_with_posebusters(pose["pose_path"], receptor_pdb)
final = ensemble_score(
pose["confidence"], pb_result["passes_all_checks"], vina_score,
)
results.append(PoseResult(
ligand_id=ligand_id,
pose_path=pose["pose_path"],
diffdock_confidence=pose["confidence"],
posebusters_passes=pb_result["passes_all_checks"],
vina_score=vina_score,
final_score=final,
metadata={
"posebusters_details": pb_result["checks"],
"critical_failures": pb_result["critical_failures"],
},
))
return sorted(results, key=lambda r: r.final_score)

Step 7. PyMOL 자동 렌더링

편 11의 렌더링과 동일 패턴.

python
def render_top_poses(
receptor_pdb: Path,
top_poses: list[PoseResult],
output_dir: Path,
) -> None:
output_dir.mkdir(parents=True, exist_ok=True)
for i, pose in enumerate(top_poses):
script = f"""
load {receptor_pdb}, receptor
load {pose.pose_path}, ligand
hide everything
show cartoon, receptor
show sticks, ligand
show sticks, receptor within 5 of ligand
color grey70, receptor
color yellow, ligand
zoom ligand, 5
bg_color white
ray 1200, 900
png {output_dir / f"{pose.ligand_id}_rank{i+1}.png"}, dpi=150
quit
"""
script_path = output_dir / f"render_{pose.ligand_id}_{i}.pml"
script_path.write_text(script)
subprocess.run(["pymol", "-cq", str(script_path)], check=True, capture_output=True)

Step 8. 통합 파이프라인 · Mpro 시나리오 실행

python
import pandas as pd
def hybrid_docking_pipeline(
protein_pdb: Path,
known_ligand_pdb: Path | None,
ligand_smiles_list: list[tuple[str, str]], # [(ligand_id, smiles)]
work_dir: Path,
top_k: int = 10,
poses_per_ligand: int = 10,
device: str = "cuda",
) -> pd.DataFrame:
"""전체 하이브리드 도킹 스크리닝."""
work_dir.mkdir(parents=True, exist_ok=True)
print("[1/5] 단백질 전처리")
prepared_pdb = work_dir / "protein_prepared.pdb"
prepare_protein(protein_pdb, prepared_pdb)
receptor_pdbqt = work_dir / "protein_prepared.pdbqt"
pdb_to_pdbqt(prepared_pdb, receptor_pdbqt)
binding_center = identify_binding_site(prepared_pdb, known_ligand_pdb)
print(f" binding center: {binding_center}")
all_results = []
for ligand_id, smiles in ligand_smiles_list:
print(f"[2/5] {ligand_id} 3D conformer + DiffDock")
ligand_sdf = work_dir / "ligands" / f"{ligand_id}.sdf"
try:
smiles_to_sdf(smiles, ligand_sdf, num_conformers=3)
pose_files = run_diffdock(
prepared_pdb, ligand_sdf,
work_dir / "diffdock" / ligand_id,
num_poses=poses_per_ligand, device=device,
)
except Exception as e:
print(f" 실패 ({ligand_id}): {e}")
continue
# DiffDock confidence는 rank 파일명 or 별도 metadata에서 파싱
diffdock_poses = [
{"pose_path": p, "confidence": max(0.0, 1.0 - 0.05 * i)} # rank 낮을수록 높은 confidence
for i, p in enumerate(pose_files)
]
print(f"[3/5] {ligand_id} PoseBusters + Vina 재검증")
ranked = rank_ligand_poses(
ligand_id, diffdock_poses, prepared_pdb, receptor_pdbqt, binding_center,
)
all_results.extend(ranked[:3]) # 리간드당 top-3만
# 4. 전체 랭킹
all_results.sort(key=lambda r: r.final_score)
df = pd.DataFrame([{
"ligand_id": r.ligand_id,
"diffdock_conf": r.diffdock_confidence,
"posebusters_pass": r.posebusters_passes,
"vina_score": r.vina_score,
"final_score": r.final_score,
"critical_failures": ";".join(r.metadata["critical_failures"]),
"pose_path": str(r.pose_path),
} for r in all_results])
df.to_csv(work_dir / "docking_results.csv", index=False)
print(f"[4/5] Top-{top_k} PyMOL 렌더링")
render_top_poses(prepared_pdb, all_results[:top_k], work_dir / "renders")
print("[5/5] 완료")
return df
# 실행 예시 (SARS-CoV-2 Mpro 시나리오)
# result_df = hybrid_docking_pipeline(
# protein_pdb=Path("./6LU7.pdb"),
# known_ligand_pdb=Path("./6LU7_ligand.pdb"), # co-crystallized N3 inhibitor
# ligand_smiles_list=[
# ("nirmatrelvir", "CC1(C)C2CC1C(NC(=O)C1CCCN1C(=O)C(NC(=O)OC(C)(C)C)C(C)(C)C)C(=O)NC(C#N)CC2=O"),
# ("ensitrelvir", "..."),
# # 편 07 GNN 상위 20 후보 추가
# ],
# work_dir=Path("./mpro_docking_output"),
# )

성능·비용·알려진 실패 케이스

성능 참고 (공개 벤치 인용)

모델벤치Top-1 RMSD ≤ 2ÅPoseBusters 통과율시간/페어출처
AutoDock VinaPDBbind core20~30%85~90% (물리학 baseline)Trott & Olson 2010 [1]
Glide SPPDBbind core35~40%90%+초~분Schrödinger
Glide XPPDBbind core40~50%92%+Schrödinger
DiffDockPDBbind core38%60~70% (물리학 문제)30초 GPUCorso et al., ICLR 2023 [2]
DiffDock-LPDBbind core43%65~75%30초 GPUCorso et al. 2024
DiffDock-PocketPDBbind core45%+70%+40초 GPU2024
DiffDock-GlidePDBbind core55~60%85%+ (Glide 정정)60초 GPU+CPUMiller et al., bioRxiv 2025 [3]
RLDiffPDBbind subset~50%78%40초 GPUZhang et al. 2024 [4]
Boltz-2 (편 11)유사 벤치50%+90%+ (물리학 통합 설계)초 GPUMIT/Genentech 2025

학습자 재현 예상 비용

  • API 비용 0 (완전 로컬).
  • 리간드 100개 스크리닝: DiffDock 약 30분 (GPU) + Vina 재검증 약 30분 (CPU 병렬 4-8 코어).
  • Mpro 후보 20개 스크리닝: 15~30분.
  • DiffDock 가중치 다운로드 약 2GB (첫 실행 시).
  • Vina · OpenBabel · PoseBusters 무료.

알려진 실패 케이스 5건 (커뮤니티·논문 수집형)

  1. DiffDock pose의 원자 clash · unrealistic geometry (PoseBusters 60% 실패)
    증상: DiffDock 상위 pose에서 리간드 원자가 단백질 원자와 겹침 (interpenetration), 결합 각도 왜곡, 비평면 aromatic ring.
    원인: 확산 모델은 물리 법칙을 명시적으로 강제 안 함. 학습 목표는 좌표 복원, geometric validity 별도 검증 없음.
    회피: (a) 반드시 PoseBusters 필터 (본 편 Step 4), (b) Vina local optimize로 refinement, (c) 여러 pose를 앙상블해 valid 것 선택, (d) DiffDock-Glide처럼 물리학 통합된 후속판 사용, (e) 편 11 Boltz-2로 대체 (물리학 통합 학습).
    출처: Buttenschoen et al. "PoseBusters" Chem Sci 2024 [5].

  2. 잘못된 pocket 선택 (blind vs targeted docking)
    증상: DiffDock blind mode에서 리간드를 알려진 orthosteric site가 아닌 다른 pocket에 놓음.
    원인: DiffDock은 pocket 자동 탐색 or 명시 지정 모드 있는데, blind mode에서 오지정 흔함.
    회피: (a) pocket 좌표 사전 지정 (본 편 identify_binding_site), (b) fpocket 등으로 후보 pocket 선탐색, (c) DiffDock-Pocket 활용, (d) 알려진 리간드가 있으면 그 위치로 seed.
    출처: DiffDock GitHub Issues [6].

  3. DUD-E 벤치의 known bias (활성 vs decoy 편향)
    증상: DUD-E에서 AUC 0.9 이상 나오는데 실제 신약 스크리닝에서 성능 훨씬 낮음.
    원인: DUD-E decoy는 property matching으로 생성되어 스코어러가 활성-decoy 물리화학 차이만 학습 (실제 결합 판별력 부재).
    회피: (a) LIT-PCBA 등 non-biased 벤치 사용, (b) prospective 실험 검증, (c) 여러 벤치 앙상블 평가, (d) COVID Moonshot 등 실측 데이터 세트.
    출처: Chen et al. "Hidden bias in the DUD-E dataset." PLoS ONE 2019 [7].

  4. 표적 단백질 flexibility 무시 (rigid receptor)
    증상: 도킹은 rigid receptor 가정하지만 실제 단백질은 flexible. 특히 loop · sidechain 이동.
    원인: Vina · DiffDock 대부분 rigid receptor 모드가 기본.
    회피: (a) MD 시뮬레이션으로 여러 conformer 생성 후 각각 도킹 (ensemble docking), (b) DiffDock에 여러 apo/holo 구조 병렬 실행, (c) IFD (Induced Fit Docking) 상용 소프트웨어, (d) Boltz-2 · Chai-1처럼 co-folding 접근 (편 11).
    출처: Amaro RE et al. "Ensemble Docking in Drug Discovery." Biophys J 2018 [8].

  5. 리간드 stereochemistry · tautomer 무시
    증상: SMILES에 stereochemistry 명시되어 있어도 예측이 여러 stereoisomer를 임의로 선택. 또는 특정 pH에서 dominant tautomer 아닌 것으로 도킹.
    원인: SMILES canonicalization · 3D conformer 생성 단계에서 정보 유실.
    회피: (a) Chem.MolFromSmiles에서 chirality 보존, (b) MolStandardize tautomer canonicalize, (c) 여러 stereoisomer · tautomer 각각 도킹 후 앙상블, (d) 예측 pose의 chirality를 사후 검증.
    출처: RDKit MolStandardize documentation [9]; Boltz Issues (stereochemistry) [6].

확장 아이디어

  • Fragment-based docking: 큰 라이브러리를 fragment(< 300 Da) 단위로 축약 후 도킹 → linker 설계.
  • Ensemble docking: 여러 단백질 conformer (MD 시뮬레이션 or AlphaFold multiple) 각각 도킹 → aggregation.
  • Free energy 계산 통합: 도킹 상위 후보에 MM-GBSA · FEP 후처리 (더 정확한 affinity).
  • De novo linker design: RFdiffusion (편 13)로 linker 생성 후 도킹으로 검증.
  • Active learning + auto-docking: 예측 uncertainty 높은 리간드를 실측 → 재학습.
  • Boltz-2 vs DiffDock-Glide 앙상블: 편 11 Boltz-2와 결과 비교 · 앙상블.

다음 편

  • 편 11 structure-affinity-boltz: Boltz-2로 도킹까지 통합 (도킹 별도 편 대체 흐름).
  • 편 07 drug-target-gnn: GNN 예측으로 도킹 후보 사전 필터링.
  • 편 12 single-cell-perturbation: 도킹 상위 리간드의 세포 반응 in silico 예측.
  • 편 14 bio-mcp-agent: 도킹 pipeline을 MCP tool로 노출.

참고 문헌

  1. Trott O, Olson AJ. "AutoDock Vina: Improving the speed and accuracy of docking." Journal of Computational Chemistry 2010. https://onlinelibrary.wiley.com/doi/10.1002/jcc.21334
  2. Corso G, Stärk H, Jing B, et al. "DiffDock: Diffusion Steps, Twists, and Turns for Molecular Docking." ICLR 2023. https://arxiv.org/abs/2210.01776
  3. Miller B, Corso G, et al. "DiffDock-Glide: a hybrid physics-based and data-driven approach to molecular docking." bioRxiv 2025. https://www.biorxiv.org/content/10.1101/2025.06.02.657461v1
  4. Oxford RLDiff GitHub: https://github.com/oxpig/RLDiff
  5. Buttenschoen M, Morris GM, Deane CM. "PoseBusters: AI-based docking methods fail to generate physically valid poses or generalise to novel sequences." Chemical Science 2024. https://pubs.rsc.org/en/content/articlelanding/2024/sc/d3sc04185a
  6. DiffDock GitHub Issues: https://github.com/gcorso/DiffDock/issues
  7. Chen L, Cruz A, Ramsey S, et al. "Hidden bias in the DUD-E dataset leads to misleading performance of deep learning in structure-based virtual screening." PLoS ONE 2019.
  8. Amaro RE et al. "Ensemble Docking in Drug Discovery." Biophysical Journal 2018.
  9. RDKit MolStandardize documentation: https://www.rdkit.org/docs/source/rdkit.Chem.MolStandardize.html
  10. Vina GitHub: https://github.com/ccsb-scripps/AutoDock-Vina
  11. OpenBabel: http://openbabel.org/
  12. RDKit: https://www.rdkit.org/
  13. PyMOL open-source: https://github.com/schrodinger/pymol-open-source
  14. PDBbind: http://www.pdbbind.org.cn/
  15. DUD-E: http://dude.docking.org/
  16. LIT-PCBA (non-biased 벤치): Tran-Nguyen VK et al. J Chem Inf Model 2020.
  17. PDBFixer (OpenMM 계열): https://github.com/openmm/pdbfixer
  18. PoseBusters GitHub: https://github.com/maabuu/posebusters
  19. COVID Moonshot consortium: https://postera.ai/moonshot/
  20. SARS-CoV-2 Mpro structures (PDB 6LU7 등): https://www.rcsb.org/