확산-물리학 하이브리드 저분자 도킹 — 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 |
| PoseBusters | pose validity 검증 | MIT |
| OpenBabel | 파일 형식 변환 (SMI · SDF · PDB · PDBQT) | GPL |
| RDKit | SMILES · 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 사용 시 시간당 요금표 참고.
파이프라인 실전 구현
전체 흐름:
Step 1. 단백질 전처리
도킹 전 protonation 상태 · H 추가 · 물·이온 제거·리간드 제거 필수. PDBFixer가 표준.
import subprocessfrom 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 생성
from rdkit import Chemfrom 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_pathStep 3. DiffDock pose 생성
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_filesStep 4. PoseBusters 물리학 필터
PoseBusters는 도킹 pose의 물리학·화학적 validity를 정량 평가하는 최신 프레임워크 [5].
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 npfrom 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 FalseStep 5. Vina local minimization + 스코어링
DiffDock pose를 초기 좌표로 Vina의 local optimize 실행 → 물리학적으로 안정한 pose로 refine + 스코어 획득.
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.0Step 6. 앙상블 스코어링
DiffDock 확신도 + PoseBusters validity + Vina score의 가중 조합.
from dataclasses import dataclass
@dataclassclass 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의 렌더링과 동일 패턴.
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}, receptorload {pose.pose_path}, ligandhide everythingshow cartoon, receptorshow sticks, ligandshow sticks, receptor within 5 of ligandcolor grey70, receptorcolor yellow, ligandzoom ligand, 5bg_color whiteray 1200, 900png {output_dir / f"{pose.ligand_id}_rank{i+1}.png"}, dpi=150quit""" 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 시나리오 실행
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 Vina | PDBbind core | 20~30% | 85~90% (물리학 baseline) | 분 | Trott & Olson 2010 [1] |
| Glide SP | PDBbind core | 35~40% | 90%+ | 초~분 | Schrödinger |
| Glide XP | PDBbind core | 40~50% | 92%+ | 분 | Schrödinger |
| DiffDock | PDBbind core | 38% | 60~70% (물리학 문제) | 30초 GPU | Corso et al., ICLR 2023 [2] |
| DiffDock-L | PDBbind core | 43% | 65~75% | 30초 GPU | Corso et al. 2024 |
| DiffDock-Pocket | PDBbind core | 45%+ | 70%+ | 40초 GPU | 2024 |
| DiffDock-Glide | PDBbind core | 55~60% | 85%+ (Glide 정정) | 60초 GPU+CPU | Miller et al., bioRxiv 2025 [3] |
| RLDiff | PDBbind subset | ~50% | 78% | 40초 GPU | Zhang et al. 2024 [4] |
| Boltz-2 (편 11) | 유사 벤치 | 50%+ | 90%+ (물리학 통합 설계) | 초 GPU | MIT/Genentech 2025 |
학습자 재현 예상 비용
- API 비용 0 (완전 로컬).
- 리간드 100개 스크리닝: DiffDock 약 30분 (GPU) + Vina 재검증 약 30분 (CPU 병렬 4-8 코어).
- Mpro 후보 20개 스크리닝: 15~30분.
- DiffDock 가중치 다운로드 약 2GB (첫 실행 시).
- Vina · OpenBabel · PoseBusters 무료.
알려진 실패 케이스 5건 (커뮤니티·논문 수집형)
-
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]. -
잘못된 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]. -
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]. -
표적 단백질 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]. -
리간드 stereochemistry · tautomer 무시
증상: SMILES에 stereochemistry 명시되어 있어도 예측이 여러 stereoisomer를 임의로 선택. 또는 특정 pH에서 dominant tautomer 아닌 것으로 도킹.
원인: SMILES canonicalization · 3D conformer 생성 단계에서 정보 유실.
회피: (a)Chem.MolFromSmiles에서 chirality 보존, (b)MolStandardizetautomer 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로 노출.
참고 문헌
- 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 - 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 - 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 - Oxford RLDiff GitHub:
https://github.com/oxpig/RLDiff - 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 - DiffDock GitHub Issues:
https://github.com/gcorso/DiffDock/issues - 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.
- Amaro RE et al. "Ensemble Docking in Drug Discovery." Biophysical Journal 2018.
- RDKit MolStandardize documentation:
https://www.rdkit.org/docs/source/rdkit.Chem.MolStandardize.html - Vina GitHub:
https://github.com/ccsb-scripps/AutoDock-Vina - OpenBabel:
http://openbabel.org/ - RDKit:
https://www.rdkit.org/ - PyMOL open-source:
https://github.com/schrodinger/pymol-open-source - PDBbind:
http://www.pdbbind.org.cn/ - DUD-E:
http://dude.docking.org/ - LIT-PCBA (non-biased 벤치): Tran-Nguyen VK et al. J Chem Inf Model 2020.
- PDBFixer (OpenMM 계열):
https://github.com/openmm/pdbfixer - PoseBusters GitHub:
https://github.com/maabuu/posebusters - COVID Moonshot consortium:
https://postera.ai/moonshot/ - SARS-CoV-2 Mpro structures (PDB 6LU7 등):
https://www.rcsb.org/