drug-discovery-pipeline
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDrug Discovery Pipeline
药物发现流程
Screen drug candidates end-to-end using three BioNeMo NIMs in sequence:
Step 1: GenMol → Step 2: DiffDock → Step 3: Boltz2
(Generate mols) (Dock to target) (Predict affinity)依次使用三个BioNeMo NIMs端到端筛选候选药物:
Step 1: GenMol → Step 2: DiffDock → Step 3: Boltz2
(Generate mols) (Dock to target) (Predict affinity)Overview
概述
This pipeline is used for:
- De novo hit discovery: generate drug-like molecules and screen them against a target
- Lead optimization: start from a known scaffold and generate improved analogs, then dock and score
- Virtual screening: dock a library of candidates and filter by docking confidence + affinity
本流程适用于:
- 全新命中发现:生成类药物分子并针对靶点进行筛选
- 先导化合物优化:从已知骨架出发生成改良类似物,然后进行对接和评分
- 虚拟筛选:对接候选化合物库,并根据对接置信度和亲和力进行筛选
Before you start
开始之前
Confirm with the user:
- Target protein: PDB file or sequence of the binding target
- Starting point: de novo (no scaffold) or scaffold decoration (known core)?
- Scoring: drug-likeness (QED) or lipophilicity (LogP)?
- API mode: hosted or local Docker?
For local Docker, do not assume all NIMs are running on at the
same time. Either run one container at a time and hand files/results between
steps, or start each NIM on a distinct host port and set the per-step URLs.
localhost:8000请与用户确认以下信息:
- 靶点蛋白质:结合靶点的PDB文件或序列
- 起始方式:全新生成(无骨架)还是骨架修饰(已知核心)?
- 评分标准:类药性(QED)还是亲脂性(LogP)?
- API模式:托管式还是本地Docker?
对于本地Docker模式,请勿假设所有NIMs同时运行在。可以每次运行一个容器并在步骤间传递文件/结果,或者在不同的主机端口启动每个NIM并设置各步骤的URL。
localhost:8000Step 1: Generate molecules with GenMol
步骤1:使用GenMol生成分子
GenMol requires SAFE notation input (not raw SMILES). Use the package.
safe-molpython
import requests, json, os
import safe as sf # pip install safe-mol
from pathlib import Path
NGC_API_KEY = os.getenv("NGC_API_KEY")
HOSTED = True
if HOSTED:
genmol_url = "https://health.api.nvidia.com/v1/biology/nvidia/genmol/generate"
headers = {"Content-Type": "application/json",
"Authorization": f"Bearer {NGC_API_KEY}"}
else:
genmol_url = "http://localhost:8000/generate"
headers = {"Content-Type": "application/json"}GenMol需要SAFE符号输入(而非原始SMILES)。请使用包。
safe-molpython
import requests, json, os
import safe as sf # pip install safe-mol
from pathlib import Path
NGC_API_KEY = os.getenv("NGC_API_KEY")
HOSTED = True
if HOSTED:
genmol_url = "https://health.api.nvidia.com/v1/biology/nvidia/genmol/generate"
headers = {"Content-Type": "application/json",
"Authorization": f"Bearer {NGC_API_KEY}"}
else:
genmol_url = "http://localhost:8000/generate"
headers = {"Content-Type": "application/json"}De novo generation (no scaffold):
De novo generation (no scaffold):
safe_input = "[*{20-30}]"
safe_input = "[*{20-30}]"
Scaffold decoration (known core):
Scaffold decoration (known core):
scaffold_smiles = "c1ccccc1"
scaffold_smiles = "c1ccccc1"
safe_input = sf.encode(scaffold_smiles) + ".[*{5-10}]"
safe_input = sf.encode(scaffold_smiles) + ".[*{5-10}]"
payload = {
"smiles": safe_input, # field is named 'smiles' but takes SAFE notation
"num_molecules": 30, # request more to compensate for post-generation filtering
"scoring": "QED", # QED or LogP
"unique": True,
"temperature": "1.0", # NOTE: must be string, not float
"noise": "1.0", # NOTE: must be string, not float
}
r = requests.post(genmol_url, headers=headers, json=payload)
r.raise_for_status()
molecules = r.json()["molecules"]
molecules_sorted = sorted(molecules, key=lambda x: x["score"], reverse=True)
top_20 = molecules_sorted[:20]
print(f"Generated {len(molecules)} valid molecules (requested 30)")
print("Top 5 by QED score:")
for m in top_20[:5]:
print(f" {m['smiles'][:50]} score={m['score']:.4f}")
---payload = {
"smiles": safe_input, # field is named 'smiles' but takes SAFE notation
"num_molecules": 30, # request more to compensate for post-generation filtering
"scoring": "QED", # QED or LogP
"unique": True,
"temperature": "1.0", # NOTE: must be string, not float
"noise": "1.0", # NOTE: must be string, not float
}
r = requests.post(genmol_url, headers=headers, json=payload)
r.raise_for_status()
molecules = r.json()["molecules"]
molecules_sorted = sorted(molecules, key=lambda x: x["score"], reverse=True)
top_20 = molecules_sorted[:20]
print(f"Generated {len(molecules)} valid molecules (requested 30)")
print("Top 5 by QED score:")
for m in top_20[:5]:
print(f" {m['smiles'][:50]} score={m['score']:.4f}")
---Step 2: Dock molecules with DiffDock
步骤2:使用DiffDock进行分子对接
Prepare the protein and dock each candidate:
python
undefined准备蛋白质并对接每个候选分子:
python
undefinedLoad protein (ATOM records only)
Load protein (ATOM records only)
receptor_pdb_raw = Path("target.pdb").read_text()
receptor_pdb = "\n".join(line for line in receptor_pdb_raw.splitlines()
if line.startswith("ATOM"))
if HOSTED:
diffdock_url = "https://health.api.nvidia.com/v1/biology/mit/diffdock"
else:
diffdock_url = "http://localhost:8000/molecular-docking/diffdock/generate"
docking_results = []
for i, mol in enumerate(top_20):
payload = {
"protein": receptor_pdb,
"ligand": mol["smiles"],
"ligand_file_type": "txt", # "txt" for SMILES input
"num_poses": 5,
"time_divisions": 20,
"steps": 18,
"save_trajectory": False,
}
r = requests.post(diffdock_url, headers=headers, json=payload)
r.raise_for_status()
result = r.json()
best_conf = result["position_confidence"][0] # rank 1 pose
best_pose = result["ligand_positions"][0]
docking_results.append({
"smiles": mol["smiles"],
"qed_score": mol["score"],
"docking_confidence": best_conf,
"best_pose_sdf": best_pose,
})
print(f" Mol {i+1:2d}: QED={mol['score']:.3f} docking_conf={best_conf:.4f}")receptor_pdb_raw = Path("target.pdb").read_text()
receptor_pdb = "\n".join(line for line in receptor_pdb_raw.splitlines()
if line.startswith("ATOM"))
if HOSTED:
diffdock_url = "https://health.api.nvidia.com/v1/biology/mit/diffdock"
else:
diffdock_url = "http://localhost:8000/molecular-docking/diffdock/generate"
docking_results = []
for i, mol in enumerate(top_20):
payload = {
"protein": receptor_pdb,
"ligand": mol["smiles"],
"ligand_file_type": "txt", # "txt" for SMILES input
"num_poses": 5,
"time_divisions": 20,
"steps": 18,
"save_trajectory": False,
}
r = requests.post(diffdock_url, headers=headers, json=payload)
r.raise_for_status()
result = r.json()
best_conf = result["position_confidence"][0] # rank 1 pose
best_pose = result["ligand_positions"][0]
docking_results.append({
"smiles": mol["smiles"],
"qed_score": mol["score"],
"docking_confidence": best_conf,
"best_pose_sdf": best_pose,
})
print(f" Mol {i+1:2d}: QED={mol['score']:.3f} docking_conf={best_conf:.4f}")Rank by docking confidence
Rank by docking confidence
docking_results.sort(key=lambda x: x["docking_confidence"], reverse=True)
print(f"\nTop 3 by docking confidence:")
for d in docking_results[:3]:
print(f" {d['smiles'][:50]} conf={d['docking_confidence']:.4f}")
---docking_results.sort(key=lambda x: x["docking_confidence"], reverse=True)
print(f"\nTop 3 by docking confidence:")
for d in docking_results[:3]:
print(f" {d['smiles'][:50]} conf={d['docking_confidence']:.4f}")
---Step 3: Predict binding affinity with Boltz2
步骤3:使用Boltz2预测结合亲和力
For the top docking candidates, predict structure-based binding affinity:
python
if HOSTED:
boltz_url = "https://health.api.nvidia.com/v1/biology/mit/boltz2/predict"
else:
boltz_url = "http://localhost:8000/biology/mit/boltz2/predict"针对排名靠前的对接候选分子,基于结构预测结合亲和力:
python
if HOSTED:
boltz_url = "https://health.api.nvidia.com/v1/biology/mit/boltz2/predict"
else:
boltz_url = "http://localhost:8000/biology/mit/boltz2/predict"Use the target protein sequence (not PDB)
Use the target protein sequence (not PDB)
target_sequence = "<YOUR_TARGET_PROTEIN_SEQUENCE>"
affinity_results = []
for d in docking_results[:5]: # score top 5 docking hits
payload = {
"polymers": [
{"id": "A", "molecule_type": "protein", "sequence": target_sequence}
],
"ligands": [
{"id": "L1", "smiles": d["smiles"], "predict_affinity": True}
],
"recycling_steps": 3,
"sampling_steps": 50,
"diffusion_samples": 1,
"output_format": "mmcif",
}
r = requests.post(boltz_url, headers=headers, json=payload)
r.raise_for_status()
result = r.json()
aff = result["affinities"]["L1"]
pic50 = aff["affinity_pic50"][0]
prob_binding = aff["affinity_probability_binary"][0]
affinity_results.append({
**d,
"pic50": pic50,
"probability_binding": prob_binding,
})
print(f" {d['smiles'][:40]} pIC50={pic50:.2f} P(bind)={prob_binding:.3f}")target_sequence = "<YOUR_TARGET_PROTEIN_SEQUENCE>"
affinity_results = []
for d in docking_results[:5]: # score top 5 docking hits
payload = {
"polymers": [
{"id": "A", "molecule_type": "protein", "sequence": target_sequence}
],
"ligands": [
{"id": "L1", "smiles": d["smiles"], "predict_affinity": True}
],
"recycling_steps": 3,
"sampling_steps": 50,
"diffusion_samples": 1,
"output_format": "mmcif",
}
r = requests.post(boltz_url, headers=headers, json=payload)
r.raise_for_status()
result = r.json()
aff = result["affinities"]["L1"]
pic50 = aff["affinity_pic50"][0]
prob_binding = aff["affinity_probability_binary"][0]
affinity_results.append({
**d,
"pic50": pic50,
"probability_binding": prob_binding,
})
print(f" {d['smiles'][:40]} pIC50={pic50:.2f} P(bind)={prob_binding:.3f}")Final ranking by pIC50
Final ranking by pIC50
affinity_results.sort(key=lambda x: x["pic50"], reverse=True)
---affinity_results.sort(key=lambda x: x["pic50"], reverse=True)
---Interpreting results
结果解读
- GenMol QED score: 0–1; >0.5 is drug-like
- DiffDock confidence: higher = more reliable binding pose prediction
- Boltz2 pIC50: predicted -log10(IC50); >6 = sub-micromolar, >8 = very potent
- P(bind): probability of binary binding; >0.7 = likely binder
- GenMol QED评分:0–1;大于0.5表示具有类药性
- DiffDock置信度:数值越高,结合构象预测越可靠
- Boltz2 pIC50:预测的-log10(IC50);大于6表示亚微摩尔级活性,大于8表示活性极强
- P(bind):二元结合概率;大于0.7表示很可能是结合剂
Quick reference — skill dependencies
快速参考 — 技能依赖
| Step | Skill | Key endpoint |
|---|---|---|
| Molecule generation | | |
| Docking | | |
| Affinity prediction | | |
| 步骤 | 技能 | 关键端点 |
|---|---|---|
| 分子生成 | | |
| 分子对接 | | |
| 亲和力预测 | | |