BioPlayground

🧬
목록으로

오픈 파운데이션 통합 구조·친화도 예측 — Boltz-2와 Chai-1로 AlphaFold3의 벽을 넘기

MIT 라이선스 오픈 파운데이션 Boltz-2(2025)와 Chai-1로 단백질 복합체 3D 구조와 결합 친화도(log10 IC50)를 밀리초 단위로 통합 예측. AlphaFold3의 라이선스·인프라 부담 없이 FEP 대비 1000x 빠른 정확도. FASTA + SMILES → YAML → 결합 포즈 + 친화도 완결 파이프라인, 배치 스크리닝, PDBbind 벤치, PyMOL 시각화까지.

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

오픈 파운데이션 통합 구조·친화도 예측 — Boltz-2와 Chai-1로 AlphaFold3의 벽을 넘기

2024년 5월 AlphaFold3의 발표는 구조생물학의 흐름을 다시 한번 바꿨습니다. 그런데 실제 실험실이나 신약 개발 팀이 AlphaFold3를 로컬로 돌리려면 수 테라바이트 유전체 데이터베이스와 학술 전용 라이선스라는 두 개의 벽을 넘어야 했습니다. 그리고 2024년 11월 MIT/Genentech이 Boltz-1을 완전 오픈소스(MIT 라이선스)로 공개했고, 2025년 Boltz-2가 여기에 결합 친화도(binding affinity) 예측까지 통합해 자유에너지 섭동(FEP+) 대비 1000배 빠른 속도로 신약 스크리닝에 실전 투입 가능한 도구가 됐습니다. 이 편은 이 오픈 파운데이션 도구 조합으로 하드코어 신약 파이프라인을 구축합니다.

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

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

선수 편 없이 진입할 경우, 이 편에서 다루는 트랜스포머 아키텍처의 3D 좌표 처리, PyTorch에서 대용량 모델 로딩·GPU 관리, Claude Code로 파이프라인 자동화, Bio-AI의 큰 그림에 대한 재설명 없이 실전 코드부터 진행하므로 따라가기 어렵습니다.


우리 DryBench에서 이거 배웠잖아

DryBench ai-native #3에서 트랜스포머가 시퀀스뿐 아니라 3D 좌표·그래프 같은 임의의 구조화된 데이터에 확장 가능하다는 것을, #12에서 PyTorch가 대용량 모델의 파라미터를 GPU 메모리에 로드하고 mixed precision·flash attention 같은 최적화를 어떻게 활용하는지를, #14에서 Claude Code가 반복적인 파이프라인 스크립팅을 압축해주는 실전 도구라는 것을, #15에서 Bio-AI 융합의 큰 그림 안에서 구조 예측이 어디에 위치하는지를 배웠습니다.

그런데 실제 신약 개발 팀 관점에서 "AlphaFold3가 있으니 됐다"는 자족이 왜 부족한지 살펴봐야 합니다. 구조 예측 하나만으로는 리간드가 얼마나 강하게 붙는지(affinity) 모릅니다. 전통적으로 이 답은 FEP(Free Energy Perturbation) 계산으로 얻었는데 리간드 하나 당 며칠~몇 주가 걸립니다. Boltz-2는 이 두 축(구조 + 친화도)을 하나의 신경망 forward pass로 통합해 밀리초 단위로 산출합니다. 신약 스크리닝의 판이 바뀌는 지점이고, 이 편은 그 실전형입니다.

하드코어 문제 정의

신약 스크리닝의 실전 요구

한 표적 단백질(예: 특정 kinase)에 대해 1000개 이상의 후보 저분자 라이브러리에서 유효 hit을 골라야 합니다. 전통 접근:

  • Docking (AutoDock Vina, Glide): 리간드 하나당 초분. 하지만 pose는 대충 맞아도 affinity 정확도는 낮음 (correlation r ~ 0.40.5).
  • FEP+ (Schrödinger): 리간드 하나당 며칠. 정확도 높음 (r ~ 0.8+). 대신 상용, 라이선스 매우 비쌈.
  • AlphaFold3: 복합체 구조는 뛰어나지만 affinity는 직접 출력 안 함. 별도 scoring 필요.
  • Boltz-2: 구조 + affinity(log10 IC50 근사) 통합 출력. FEP+ 근접 정확도. 리간드 하나당 밀리초~초.

이 편에서 다루는 파이프라인 목표:

  • 표적 단백질 서열 + 후보 리간드 SMILES 1000개 → 24시간 이내 스크리닝 완료.
  • 친화도 상위 20개 후보 자동 랭킹 + PyMOL 시각화 자동 생성.
  • 다중 conformation 처리: 리간드 하나에 대해 여러 pose를 샘플링 후 앙상블 스코어링.
  • 재현성 강제: 모든 실행이 YAML config 하나로 정의되어 tag 추적.

왜 AlphaFold3가 아니라 Boltz-2 · Chai-1인가

  • 라이선스: AlphaFold3는 학술 비상업 승인 필요, 상업 이용 별도 협의. Boltz-1/2 · Chai-1은 오픈 (Boltz는 MIT [1], Chai는 연구 무료 + 상업 별도 [2]).
  • 인프라 부담: AlphaFold3는 UniRef·MGnify·PDB 등 수 TB 데이터베이스 로컬 동기화 필요. Boltz-2는 MMseqs2 원격 API로 대체 가능 [1].
  • 친화도 통합: AF3는 구조만. Boltz-2는 구조 + affinity 통합 예측 [1].
  • 속도: Boltz-2는 FEP+ 대비 1000x 빠름 (논문 근거 [3]).
  • 상업 자유: MIT 라이선스는 상업 이용에 제약 없음. Chai-1은 상업 유료 API 제공.

도구 스택과 인프라 요구

도구역할라이선스
Boltz (v2, pip install boltz)구조 + 친화도 통합 예측MIT
Chai-1 (chai_lab)대안·앙상블 파트너연구 무료, 상업 별도
MMseqs2 원격 API (BioLM 무료 tier)MSA 구축 (로컬 DB 불필요)GPL v3 (MMseqs2 자체), API는 BioLM 정책
RDKitSMILES 파싱·리간드 처리BSD-3-Clause
PyMOL (오픈 소스판)3D 구조 시각화LGPL
BiopythonPDB 파일 후처리Biopython License
PyTorch, CUDA신경망 백엔드BSD
PDBbind (벤치)결합 친화도 실측 데이터학술 무료

인프라 요구:

  • 최소 실전: 24GB VRAM 데이터센터 GPU 또는 상급 소비자 GPU (RTX 4090 24GB) 1대. Boltz-2는 fp16 기준 약 15GB VRAM 소모, 큰 복합체는 20GB+.
  • CPU 폴백: 매우 느림 (단일 예측 30분 이상), 사실상 실전 불가.
  • RAM: 32GB 이상.
  • 디스크: Boltz weights 약 2~5GB, Chai-1 weights 약 5GB, MSA 캐시 수 GB.
  • 네트워크: MMseqs2 원격 API 호출 시 표적당 수 MB 통신.

학습자 재현 예상 비용: 로컬 GPU 없다면 클라우드 온디맨드 GPU 인스턴스(예: 24GB VRAM 급) 시간당 요금표 참고. 리간드 1000개 스크리닝 약 2~4시간 GPU (Boltz-2 논문 벤치 기준 [3]).

파이프라인 실전 구현

전체 흐름:

mermaid

Step 1. 표적 단백질 MSA 원격 구축

MMseqs2를 로컬에 설치하고 UniRef 다운로드하는 대신 원격 API를 사용합니다. Boltz는 여러 원격 MSA 서버를 지원합니다 [4].

python
from pathlib import Path
from dataclasses import dataclass
import requests
@dataclass
class MSAResult:
"""Multiple Sequence Alignment 결과."""
query_id: str
a3m_content: str # a3m 포맷 MSA
depth: int # MSA 깊이 (동원된 서열 수)
def build_msa_remote(
query_sequence: str,
query_id: str = "target",
api_base: str = "https://api.colabfold.com",
max_depth: int = 5000,
) -> MSAResult:
"""ColabFold MSA 서버(MMseqs2 백엔드)로 원격 MSA 구축.
Boltz 공식 문서는 ColabFold 또는 BioLM API를 권장 [4].
"""
# 실제 API 호출 (예시: ColabFold MMseqs2 API)
payload = {"query": f">{query_id}\n{query_sequence}", "mode": "env"}
resp = requests.post(f"{api_base}/ticket/msa", data=payload, timeout=600)
resp.raise_for_status()
ticket = resp.json()["id"]
# 폴링해서 완료 대기
import time
while True:
status = requests.get(f"{api_base}/ticket/{ticket}", timeout=30).json()
if status["status"] == "COMPLETE":
break
elif status["status"] == "ERROR":
raise RuntimeError(f"MSA 구축 실패: {status}")
time.sleep(5)
# a3m 결과 다운로드
a3m_resp = requests.get(f"{api_base}/result/download/{ticket}", timeout=60)
a3m_resp.raise_for_status()
a3m_content = a3m_resp.text
depth = a3m_content.count("\n>")
return MSAResult(query_id=query_id, a3m_content=a3m_content, depth=depth)
def save_a3m(msa: MSAResult, output_dir: Path) -> Path:
"""a3m 파일 저장."""
output_dir.mkdir(parents=True, exist_ok=True)
path = output_dir / f"{msa.query_id}.a3m"
path.write_text(msa.a3m_content)
return path

Step 2. 리간드 라이브러리 정규화

SMILES는 같은 분자를 여러 표기로 쓸 수 있어 canonicalization이 필요합니다. RDKit이 표준입니다.

python
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors, Lipinski
import pandas as pd
def canonicalize_smiles(smiles: str) -> str | None:
"""SMILES canonical form 반환. 파싱 실패 시 None."""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
return Chem.MolToSmiles(mol, canonical=True)
def lipinski_filter(smiles: str) -> dict:
"""Lipinski's Rule of Five 통과 여부 + 물리화학 특성."""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return {"valid": False}
props = {
"MW": Descriptors.MolWt(mol),
"LogP": Descriptors.MolLogP(mol),
"HBA": Lipinski.NumHAcceptors(mol),
"HBD": Lipinski.NumHDonors(mol),
"RotB": Lipinski.NumRotatableBonds(mol),
}
violations = sum([
props["MW"] > 500,
props["LogP"] > 5,
props["HBA"] > 10,
props["HBD"] > 5,
])
props["valid"] = True
props["lipinski_violations"] = violations
props["lipinski_pass"] = violations <= 1
return props
def prepare_ligand_library(smiles_list: list[str]) -> pd.DataFrame:
"""리간드 라이브러리 정규화 + 물리화학 필터."""
records = []
for i, raw in enumerate(smiles_list):
canon = canonicalize_smiles(raw)
if canon is None:
continue
props = lipinski_filter(canon)
records.append({
"ligand_id": f"L{i:04d}",
"smiles_raw": raw,
"smiles_canonical": canon,
**props,
})
df = pd.DataFrame(records)
return df[df["lipinski_pass"]].reset_index(drop=True)

Step 3. Boltz-2 YAML Config 생성

Boltz는 YAML 하나에 protein + ligand + MSA를 지정합니다 [4].

python
import yaml
def build_boltz_config(
protein_sequence: str,
protein_msa_path: Path,
ligand_smiles: str,
ligand_id: str,
output_dir: Path,
) -> Path:
"""Boltz YAML config 생성. 하나의 protein-ligand 페어 당 하나."""
config = {
"version": 1,
"sequences": [
{
"protein": {
"id": "A",
"sequence": protein_sequence,
"msa": str(protein_msa_path),
}
},
{
"ligand": {
"id": "L",
"smiles": ligand_smiles,
}
},
],
"constraints": [],
"properties": [
{"affinity": {"binder": "L"}} # 결합 친화도 예측 켜기 (Boltz-2 신규)
],
}
output_dir.mkdir(parents=True, exist_ok=True)
yaml_path = output_dir / f"{ligand_id}.yaml"
with open(yaml_path, "w") as f:
yaml.safe_dump(config, f, sort_keys=False)
return yaml_path

Step 4. Boltz-2 배치 추론

여러 리간드를 순회하며 예측. GPU 메모리를 감안한 세션 관리.

python
import subprocess
import json
@dataclass
class BoltzResult:
"""Boltz-2 예측 결과."""
ligand_id: str
pdb_path: Path # 예측된 3D 복합체 구조
plddt_mean: float # 구조 신뢰도 (0~100)
iptm: float # 인터페이스 pTM (0~1)
affinity_log_ic50: float # log10(IC50 mol/L) 예측치 (음수일수록 강력)
inference_time_sec: float
def run_boltz_prediction(
yaml_path: Path,
output_dir: Path,
device: str = "cuda",
use_msa_server: bool = False,
) -> BoltzResult:
"""Boltz-2 CLI 실행 (실전에서는 Python API도 가능)."""
import time
start = time.time()
cmd = [
"boltz", "predict",
str(yaml_path),
"--out_dir", str(output_dir),
"--devices", "1",
"--accelerator", "gpu" if device == "cuda" else "cpu",
]
if use_msa_server:
cmd.append("--use_msa_server")
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
elapsed = time.time() - start
# Boltz는 output_dir/{name}/{name}_model_0.pdb + confidence.json 생성
name = yaml_path.stem
pdb_path = output_dir / name / f"{name}_model_0.pdb"
conf_path = output_dir / name / f"confidence_{name}_model_0.json"
with open(conf_path) as f:
conf = json.load(f)
# affinity는 별도 JSON에서 (Boltz-2 신규 필드)
affinity_path = output_dir / name / f"affinity_{name}.json"
affinity_data = json.loads(affinity_path.read_text()) if affinity_path.exists() else {}
return BoltzResult(
ligand_id=name,
pdb_path=pdb_path,
plddt_mean=float(conf.get("complex_plddt", 0.0)),
iptm=float(conf.get("iptm", 0.0)),
affinity_log_ic50=float(affinity_data.get("affinity_pred_value", 0.0)),
inference_time_sec=elapsed,
)

Step 5. 배치 스크리닝 오케스트레이션

리간드 라이브러리 전체를 순회 + 실패 처리 + 진행률 표시.

python
from tqdm import tqdm
def batch_screen(
protein_sequence: str,
protein_msa: MSAResult,
ligand_library: pd.DataFrame,
work_dir: Path,
device: str = "cuda",
) -> pd.DataFrame:
"""리간드 라이브러리 전체 스크리닝."""
msa_path = save_a3m(protein_msa, work_dir / "msa")
results = []
failures = []
for _, row in tqdm(ligand_library.iterrows(), total=len(ligand_library), desc="Screening"):
try:
yaml_path = build_boltz_config(
protein_sequence=protein_sequence,
protein_msa_path=msa_path,
ligand_smiles=row["smiles_canonical"],
ligand_id=row["ligand_id"],
output_dir=work_dir / "configs",
)
boltz_result = run_boltz_prediction(
yaml_path=yaml_path,
output_dir=work_dir / "predictions",
device=device,
use_msa_server=False,
)
results.append({
"ligand_id": boltz_result.ligand_id,
"smiles": row["smiles_canonical"],
"affinity_log_ic50": boltz_result.affinity_log_ic50,
"plddt": boltz_result.plddt_mean,
"iptm": boltz_result.iptm,
"pdb_path": str(boltz_result.pdb_path),
"inference_sec": boltz_result.inference_time_sec,
"MW": row.get("MW"),
"LogP": row.get("LogP"),
})
except Exception as e:
failures.append({"ligand_id": row["ligand_id"], "error": str(e)})
df_results = pd.DataFrame(results).sort_values("affinity_log_ic50")
df_failures = pd.DataFrame(failures)
df_failures.to_csv(work_dir / "failures.csv", index=False)
return df_results

Step 6. Top-K PyMOL 시각화

상위 후보의 결합 포즈를 자동으로 렌더링.

python
def render_pymol(
pdb_path: Path,
output_png: Path,
ray_trace: bool = True,
) -> None:
"""PyMOL 헤드리스 렌더링."""
script = f"""
load {pdb_path}, complex
hide everything
show cartoon, complex and polymer
show sticks, complex and organic
color grey70, complex and polymer
color yellow, complex and organic
bg_color white
zoom complex and organic, 5
{"ray 1200, 900" if ray_trace else ""}
png {output_png}, dpi=150
quit
"""
script_path = output_png.parent / "render.pml"
script_path.write_text(script)
subprocess.run(["pymol", "-cq", str(script_path)], check=True)
def render_top_k(results_df: pd.DataFrame, output_dir: Path, k: int = 20) -> None:
output_dir.mkdir(parents=True, exist_ok=True)
for _, row in results_df.head(k).iterrows():
render_pymol(
pdb_path=Path(row["pdb_path"]),
output_png=output_dir / f"{row['ligand_id']}.png",
)

통합 파이프라인

python
def full_screening_pipeline(
protein_sequence: str,
protein_id: str,
smiles_library: list[str],
work_dir: Path,
device: str = "cuda",
top_k: int = 20,
) -> pd.DataFrame:
"""FASTA + SMILES 라이브러리 → 랭킹된 결과 DataFrame."""
print(f"[1/6] MSA 구축 원격 요청 (query length={len(protein_sequence)})")
msa = build_msa_remote(protein_sequence, query_id=protein_id)
print(f" MSA depth={msa.depth}")
print(f"[2/6] 리간드 라이브러리 정규화 (raw={len(smiles_library)})")
library = prepare_ligand_library(smiles_library)
print(f" Lipinski 통과={len(library)}")
print(f"[3/6] 배치 스크리닝 시작")
results = batch_screen(protein_sequence, msa, library, work_dir, device)
results.to_csv(work_dir / "screening_results.csv", index=False)
print(f"[4/6] Top-{top_k} PyMOL 렌더링")
render_top_k(results, work_dir / "top_k_renders", k=top_k)
print(f"[5/6] 완료: work_dir={work_dir}")
print(f"[6/6] 상위 5개 리간드 요약:")
print(results.head(5)[["ligand_id", "affinity_log_ic50", "plddt", "iptm"]].to_string(index=False))
return results

Chai-1 앙상블 검증 (선택적 강화)

Chai-1은 Boltz와 유사한 SOTA 오픈 파운데이션이라 상위 후보를 Chai-1으로 재검증하면 위양성을 줄일 수 있습니다 [2].

python
from chai_lab.chai1 import run_inference
def chai_reverification(
protein_sequence: str,
ligand_smiles: str,
output_dir: Path,
device: str = "cuda",
) -> dict:
"""Chai-1로 동일 페어 재예측. Boltz 결과와 앙상블 스코어링."""
fasta_str = f">protein|name=A\n{protein_sequence}\n>ligand|name=L\n{ligand_smiles}\n"
fasta_path = output_dir / "chai_input.fasta"
fasta_path.write_text(fasta_str)
output_dir.mkdir(parents=True, exist_ok=True)
result = run_inference(
fasta_file=fasta_path,
output_dir=output_dir,
num_trunk_recycles=3,
num_diffn_timesteps=200,
seed=42,
device=device,
use_esm_embeddings=True,
)
return {
"pdb_path": result[0],
"iptm": float(result[0].iptm), # 예시 필드, 실제 API 참조
}
def ensemble_ranking(
boltz_score: float,
chai_score: float,
weight: float = 0.5,
) -> float:
"""두 모델의 스코어 가중 평균 (낮을수록 강력)."""
return weight * boltz_score + (1 - weight) * chai_score

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

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

접근벤치Structure RMSD (Å)Affinity Pearson r시간/페어출처
Docking (AutoDock Vina)PDBbind core3.5~5.00.4~0.5Legacy
Glide XPPDBbind core2.5~3.50.55~0.65Schrödinger
FEP+ (Schrödinger)정선 subset0.75~0.85며칠상용
AlphaFold3 (구조만)Recent PDB1.5~2.5별도 scoring 필요초~분Google DeepMind 2024
Chai-1벤치 subset~2.0~0.65초~분Chai Discovery 2024 [2]
Boltz-1Recent PDB2.0~3.0~0.6MIT/Genentech 2024 [1]
Boltz-2PDBbind 등~2.0~0.80 (FEP+ 수준)밀리초~초Wohlwend et al. 2025 [3]

학습자 재현 예상 비용

  • API 비용 0(로컬 GPU 사용 시). MMseqs2 원격 API 무료 tier 활용.
  • GPU 시간: 표적 하나 + 리간드 1000개 스크리닝 약 2~4시간 (24GB VRAM GPU 기준).
  • 클라우드 온디맨드 사용 시 시간당 요금표 확인 필요.
  • 디스크: Boltz weights 2~5GB, 결과 PDB + 렌더 약 500MB.

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

  1. 큰 복합체(> 2000 residues)에서 OOM(Out of Memory)
    증상: 24GB VRAM에서도 대형 다중 서브유닛 복합체는 메모리 초과.
    원인: Boltz-2의 attention은 residue 수에 O(N²) 메모리.
    회피: (a) --use_flash_attention 옵션 활성화, (b) fp16 강제, (c) 큰 복합체는 서브유닛별 분할 예측 후 후처리 결합, (d) 48GB+ VRAM GPU 필요.
    출처: Boltz GitHub Issues — "OOM for large complexes" 스레드 [5].

  2. MSA depth 부족으로 정확도 급락
    증상: 신규 orphan 서열이나 metagenomic 서열은 MMseqs2 원격에서 MSA depth < 32로 나오는 경우가 있고 이때 Boltz-2 pLDDT · affinity 신뢰도 급락.
    원인: 파운데이션 모델도 co-evolution 신호에 여전히 의존.
    회피: (a) MSA depth < 100이면 결과에 경고 플래그, (b) --msa_server 옵션으로 여러 서버 시도, (c) single-sequence 모드(no MSA)로 baseline 비교해 신뢰도 판단.
    출처: Boltz 공식 documentation "MSA quality" 섹션 [6].

  3. 리간드 stereochemistry 무시로 잘못된 결합 pose
    증상: SMILES에 stereochemistry가 명시되어 있어도 예측이 여러 stereoisomer를 임의로 선택.
    원인: SMILES canonicalization 단계에서 chirality tag 유실 or 파운데이션 모델 학습 데이터 편향.
    회피: (a) Chem.MolFromSmiles(canonical=False)로 원본 stereochemistry 보존, (b) 3D conformer를 RDKit AllChem.EmbedMolecule로 생성 후 SDF 입력 시도, (c) 예측 결과 pose의 chirality를 사후 검증.
    출처: RDKit Discussions + Boltz Issues (stereochemistry 관련 여러 스레드) [7].

확장 아이디어

  • Fragment-based drug discovery: 리간드 라이브러리를 fragment(< 300 Da) 위주로 구성 후 Boltz-2로 hit fragment 스크리닝 → linker 설계.
  • Target class 벤치: 하나의 kinase family(예: MAPK) 전체 서열에 대해 동일 리간드 라이브러리 반복 스크리닝 → selectivity map.
  • Active learning loop: 예측 상위를 실제 실험 assay → 결과 피드백 → 모델 fine-tuning (도메인 특화).
  • Protein-Protein complex + ligand: Boltz는 다중 chain 지원. 항체-항원 + 저분자 조합 시나리오 확장.
  • MCP tool 노출: 편 14의 MCP Agent가 이 파이프라인을 tool로 호출 → 자율 신약 후보 리서치.

다음 편

  • 편 08 docking-hybrid-diffusion: DiffDock-Glide 하이브리드로 이 편의 top-k 후보에 pose 정밀화.
  • 편 13 protein-design-multimodal: ESM3로 신규 표적 단백질 설계 → Boltz-2로 결합 후보 스크리닝.
  • 편 14 bio-mcp-agent: 이 파이프라인을 MCP tool로 감싸 자율 신약 리서치 에이전트.
  • 편 15 bio-mcp-server-suite: PDBbind·ChEMBL 데이터 접근용 커스텀 MCP 서버.

참고 문헌

  1. Wohlwend J, Corso G, Passaro S, et al. Boltz-1: 오픈 파운데이션 구조 예측. MIT/Genentech 2024. GitHub: https://github.com/jwohlwend/boltz
  2. Chai Discovery. "Chai-1: Decoding the molecular interactions of life." bioRxiv 2024. https://www.biorxiv.org/content/10.1101/2024.10.10.615955v2 / GitHub: https://github.com/chaidiscovery/chai-lab
  3. Passaro S, Corso G, Wohlwend J, et al. "Boltz-2: Towards Accurate and Efficient Binding Affinity Prediction." bioRxiv 2025. https://www.biorxiv.org/content/10.1101/2025.06.14.659707v1
  4. Boltz 공식 문서 (prediction 사용법): https://github.com/jwohlwend/boltz/blob/main/docs/prediction.md
  5. Boltz GitHub Issues (OOM, MSA, stereochemistry): https://github.com/jwohlwend/boltz/issues
  6. Boltz 공식 문서 MSA 섹션: https://github.com/jwohlwend/boltz/blob/main/docs/msa.md
  7. RDKit Discussions (stereochemistry canonicalization): https://github.com/rdkit/rdkit/discussions
  8. Abramson J, Adler J, Dunger J, et al. "Accurate structure prediction of biomolecular interactions with AlphaFold 3." Nature 2024. https://www.nature.com/articles/s41586-024-07487-w
  9. Schrödinger FEP+ (상용): https://www.schrodinger.com/products/fep
  10. PDBbind: http://www.pdbbind.org.cn/
  11. MMseqs2: https://github.com/soedinglab/MMseqs2
  12. ColabFold MSA server (오픈 tier): https://github.com/sokrypton/ColabFold
  13. BioLM API: https://biolm.ai/models/
  14. RDKit: https://www.rdkit.org/
  15. PyMOL open-source: https://github.com/schrodinger/pymol-open-source
  16. AutoDock Vina: https://vina.scripps.edu/
  17. Recent PDB 벤치 세트: https://www.rcsb.org/