Docking de moléculas pequeñas híbrido difusión-física — Revalidación de candidatos para Mpro de SARS-CoV-2 con DiffDock-Glide
En el episodio 07, los candidatos a ligandos para Mpro de SARS-CoV-2 seleccionados como los mejores en la predicción DTI mediante GNN solo proporcionan una puntuación de probabilidad que indica la "probabilidad de unión". Antes de solicitar experimentos reales, se requiere la pose 3D de "cómo se une" y la base física de "qué tan establemente se une". El docking basado en física, como AutoDock Vina o Glide, es un estándar con 30 años de trayectoria, pero es lento para explorar exhaustivamente un espacio conformacional amplio. Por otro lado, los modelos de difusión como DiffDock pueden generar múltiples candidatos de pose en segundos, pero a menudo presentan choques (clashes) físicos. Este episodio presenta un pipeline híbrido que combina las fortalezas de ambos enfoques, específicamente DiffDock-Glide (2025), para asegurar simultáneamente la precisión de la pose y la velocidad en un entorno práctico.
📚 Recomendación de episodios previos (Altamente recomendada)
Este episodio es una profundización técnica avanzada de IA × Biología. Antes de comenzar, se recomienda encarecidamente ver y escuchar primero los siguientes episodios de DryBench:
Si se intenta seguir este episodio sin los conocimientos previos, será difícil avanzar, ya que el código práctico comienza directamente sin reexplicar los principios de eliminación de ruido de los modelos de difusión, los grafos SE(3)-equivariantes ni el procesamiento práctico de tensores de gran escala en PyTorch.
Lo que ya aprendimos en DryBench
En DryBench ai-native #2 aprendimos los principios de los modelos generativos que aprenden distribuciones de probabilidad, y en el #12 adquirimos las bases de la manipulación de tensores de gran escala con PyTorch y la optimización de precisión mixta (mixed precision).
Los modelos de difusión han logrado un gran éxito en imagen, audio y video mediante un método de aprendizaje que restaura progresivamente los datos a partir del ruido. Sin embargo, el docking de ligandos es un problema ligeramente distinto: consiste en "restaurar" las coordenadas 3D del ligando desde el ruido hasta su posición exacta dentro del bolsillo (pocket) del objetivo. La clave reside en diseñarlo para que sea SE(3)-equivariante (preservando la simetría de rotación y traslación). Tras DiffDock (2022), varios modelos posteriores han refinado esta idea. No obstante, la limitación fundamental de los modelos de difusión (no imponen explícitamente las leyes físicas) se manifiesta en la práctica como choques (clashes) o geometrías irreales. Por ello, una capa de revalidación física es indispensable.
Definición del problema complejo
Escenario práctico: Revalidación de ligandos candidatos para Mpro de SARS-CoV-2
Aquí se revalidan mediante acoplamiento molecular (docking) los 20 mejores candidatos de la biblioteca de fármacos aprobados, puntuados con GNN en el episodio 07.
- Entrada: Estructura 3D de Mpro (PDB 6LU7 · 7BQY, etc., múltiples conformeros apo · holo), SMILES de los 20 ligandos candidatos.
- Salida: Top-3 poses de unión para cada candidato, energía de unión de cada pose, validez geométrica y colisiones (clash), puntuación final del conjunto (ensemble score).
- Validación: Consistencia con inhibidores conocidos de Mpro (nirmatrelvi · ensitrelvi · N3, etc.), benchmark con el subconjunto Mpro de PDBbind.
- Coste: Menos de 60 segundos por ligando (30 s de DL + 30 s de revalidación física).
Requisitos fundamentales del docking
Para una estructura 3D de proteína diana y una biblioteca de miles de ligandos:
- Predicción de la pose de unión: Determinar en qué bolsillo se une cada ligando y con qué orientación.
- Puntuación de la afinidad de unión: Cuantificación de la estabilidad físico-química de la pose.
- Filtro de falsos positivos: Eliminación automática de poses irreales (clash, bolsillo incorrecto, geometría distorsionada).
- Velocidad de procesamiento: De segundos a minutos por ligando.
Espectro de enfoques existentes
- AutoDock Vina (2010, múltiples revisiones): Gratuito y de código abierto. Estándar de docking. 1–10 ligandos por minuto [1].
- Glide (Schrödinger): Comercial. Precisión en etapas SP < XP. Estándar industrial.
- DiffDock (MIT 2022): Primer SOTA basado en difusión. 30 segundos por ligando, precisión top-1 del 38% [2].
- DiffDock-L (MIT 2024): Versión expandida, top-1 del 43%.
- DiffDock-Pocket (2024): Utiliza información del bolsillo, mejora la precisión.
- DiffDock-Glide (bioRxiv 2025): Pose por DL + minimización con Glide, top-1 superior al 55% [3].
- RLDiff (2024, Oxford): Difusión guiada por aprendizaje por refuerzo [4].
- Boltz-2 (episodio 11): Predicción integrada que incluye docking. Este episodio se centra en enfoques especializados en docking, complementando a Boltz-2.
- PoseBusters (2024): Framework de evaluación cuantitativa de la validez de las poses de docking por DL [5].
Este episodio combina la predicción de poses con DiffDock (o DiffDock-Glide) + revalidación con Vina/Glide + filtrado con PoseBusters.
Indicadores objetivo de este episodio
- Generar las top-3 poses para cada uno de los 20 candidatos de Mpro, en menos de 30 minutos de tiempo real (wall-clock).
- El filtro físico PoseBusters elimina colisiones y poses poco realistas en un 30%+.
- Cuantificación de la puntuación de ensamble (confianza de DiffDock + puntuación de Vina + validez geométrica).
- Reproducción del top 3 de inhibidores conocidos (unión superior de nirmatrelvir, etc.).
- ROC AUC superior a 0.85 en el benchmark DUD-E (distinción entre activos y decoys).
Stack de herramientas y requisitos de infraestructura
| Herramienta | Función | Licencia |
|---|---|---|
| DiffDock (o DiffDock-L) | Predicción de pose basada en difusión | MIT |
| DiffDock-Glide (código bioRxiv 2025) | Minimización híbrida | Licencia de cada autor |
| AutoDock Vina | Docking físico · revalidación | Apache 2.0 |
| PoseBusters | Validación de validez de pose | MIT |
| OpenBabel | Conversión de formatos de archivo (SMI · SDF · PDB · PDBQT) | GPL |
| RDKit | SMILES · coordenadas 3D · normalización | BSD-3-Clause |
| PyMOL (versión open source) | Visualización | LGPL |
| MDAnalysis (opcional) | Análisis de trayectoria · postprocesamiento | GPL |
| PDBFixer (familia OpenMM) | Preprocesamiento de proteínas | LGPL |
| PDBbind / DUD-E / COVID Moonshot | Datasets de benchmark | Gratuito para uso académico |
Requisitos de infraestructura:
- GPU de centro de datos con 24-48 GB de VRAM (para DiffDock y versiones posteriores, ligandos grandes · pockets grandes).
- También es posible con la mayoría de las GPUs de consumo de gama alta (RTX 4090 24 GB). Los complejos grandes requieren GPU de centro de datos.
- Revalidación con Vina · Glide: se recomiendan 8 núcleos de CPU o más (procesamiento paralelo).
- RAM de 16 GB o más.
- Disco: pesos de DiffDock aprox. 2 GB, PDBbind aprox. 10 GB, DUD-E aprox. 30 GB, varios archivos PDB de Mpro aprox. 100 MB.
Costo estimado de reproducción para el estudiante: Costo de API 0 al usar GPU · CPU locales. El cribado de 100 ligandos tarda aproximadamente 30~60 minutos. Consulte las tarifas por hora si utiliza GPUs en la nube.
Implementación práctica del pipeline
Flujo completo:
Paso 1. Preprocesamiento de proteínas
Antes del docking, es fundamental definir el estado de protonación, añadir átomos de hidrógeno, eliminar agua e iones y retirar los ligandos. PDBFixer es el estándar.
import subprocessfrom pathlib import Path
def prepare_protein(input_pdb: Path, output_pdb: Path, remove_hetatoms: bool = True) -> None: """Preprocesa el PDB: elimina agua, iones y ligandos; añade H y conserva cargas parciales.
Opciones prácticas: pdb2pqr, PDBFixer, Schrödinger Protein Prep y 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: """Convierte al formato PDBQT para AutoDock Vina mediante OpenBabel.
-xr: receptor rígido, excluye la flexibilidad de cadenas laterales y acelera el docking. -xh: conserva H; -xn: carga de N amida; -xr r: rígido. """ 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]: """Centro de la caja de docking: usa el centro de un ligando conocido o predice el pocket.
Escenario Mpro 6LU7: usa el centro del inhibidor cocristalizado. """ if known_ligand_pdb: # Coordenadas medias de los átomos del ligando 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: # Usa fpocket, PocketFinder, etc.; aquí solo es un stub conceptual raise NotImplementedError("Sin ligando conocido se requiere integrar fpocket")Paso 2. Generación de conformeros 3D del ligando
from rdkit import Chemfrom rdkit.Chem import AllChem
def smiles_to_sdf(smiles: str, sdf_path: Path, num_conformers: int = 3) -> Path: """SMILES → varios confórmeros 3D → SDF.
Varios confórmeros aportan diversidad a DiffDock, aunque algunos enfoques usan solo uno. """ mol = Chem.MolFromSmiles(smiles) if mol is None: raise ValueError(f"Invalid SMILES: {smiles}") mol = Chem.AddHs(mol)
# Genera varios confórmeros y minimiza su energía 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_pathPaso 3. Generación de poses con DiffDock
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]: """Ejecuta DiffDock y devuelve varios archivos SDF de poses.
Invoca la CLI inference.py del repositorio de DiffDock. """ 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: # Extensión DiffDock-Pocket con pista de coordenadas 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"Fallo de DiffDock, STDERR: {result.stderr[:500]}") raise RuntimeError("Falló la ejecución de DiffDock")
pose_files = sorted(output_dir.glob("complex_0/rank*.sdf")) return pose_filesPaso 4. Filtro físico de PoseBusters
PoseBusters es un marco de trabajo avanzado para la evaluación cuantitativa de la validez físico-química de las poses de acoplamiento [5].
def validate_pose_with_posebusters( pose_sdf: Path, protein_pdb: Path,) -> dict: """Comprueba la validez de las poses con PoseBusters.
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 es un DataFrame de pandas 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: # Si PoseBusters no está instalado, usa una comprobación simple de clashes 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: """Devuelve True si hay un clash; alternativa a PoseBusters.""" 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 FalsePaso 5. Minimización local de Vina + puntuación
Se ejecuta la optimización local de Vina utilizando la pose de DiffDock como coordenadas iniciales → refinamiento a una pose físicamente estable + obtención de puntuación.
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, # Bajo porque es optimización local output_pdbqt: Path | None = None,) -> float: """Optimización local y puntuación con Vina; exhaustiveness=1 para minimizar localmente.
Devuelve la energía de unión en kcal/mol; cuanto más negativa, más fuerte. """ 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) # Analiza la afinidad del primer modo de la salida de Vina 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.0Paso 6. Puntuación de ensamble
Combinación ponderada de la confianza de DiffDock + la validez de PoseBusters + la puntuación de Vina.
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: """Puntuación de ensemble: cuanto menor, más fuerte según Vina.""" # Convierte aproximadamente la puntuación DL a escala Vina: cerca de -5,0 es incierto; ≤ -8,0 es fuerte dl_component = -diffdock_conf * 3.0 combined = weight_vina * vina_score + weight_dl * dl_component if not posebusters_passes: combined += invalid_penalty # Penaliza poses físicamente anómalas 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]: """Revalida varias poses de un ligando con Vina, PoseBusters y ensemble.""" 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)Paso 7. Renderizado automático en PyMOL
Sigue el mismo patrón que el renderizado de la Parte 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)Paso 8. Ejecución del escenario de Mpro con el pipeline integrado
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: """Cribado completo de docking híbrido.""" work_dir.mkdir(parents=True, exist_ok=True)
print("[1/5] Preprocesado de la proteína") 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" Fallo ({ligand_id}): {e}") continue
# La confianza de DiffDock se obtiene del nombre rank o de metadatos separados diffdock_poses = [ {"pose_path": p, "confidence": max(0.0, 1.0 - 0.05 * i)} # Un rank menor implica mayor confianza for i, p in enumerate(pose_files) ]
print(f"[3/5] {ligand_id}: revalidación con PoseBusters y Vina") ranked = rank_ligand_poses( ligand_id, diffdock_poses, prepared_pdb, receptor_pdbqt, binding_center, ) all_results.extend(ranked[:3]) # Solo las tres mejores poses por ligando
# 4. Ranking global 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] Renderizado en PyMOL del Top-{top_k}") render_top_poses(prepared_pdb, all_results[:top_k], work_dir / "renders")
print("[5/5] Completado") return df
# Ejemplo de ejecución para 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", "..."),# # Añade los 20 mejores candidatos de la GNN de la parte 07# ],# work_dir=Path("./mpro_docking_output"),# )Rendimiento, costo y casos de fallo conocidos
Referencia de rendimiento (citas de benchmarks públicos)
| Modelo | Benchmark | Top-1 RMSD ≤ 2Å | Tasa de aprobación PoseBusters | Tiempo/par | Fuente |
|---|---|---|---|---|---|
| AutoDock Vina | PDBbind core | 20–30% | 85–90% (baseline físico) | min | Trott & Olson 2010 [1] |
| Glide SP | PDBbind core | 35–40% | 90%+ | s–min | Schrödinger |
| Glide XP | PDBbind core | 40–50% | 92%+ | min | Schrödinger |
| DiffDock | PDBbind core | 38% | 60–70% (problemas físicos) | 30s GPU | Corso et al., ICLR 2023 [2] |
| DiffDock-L | PDBbind core | 43% | 65–75% | 30s GPU | Corso et al. 2024 |
| DiffDock-Pocket | PDBbind core | 45%+ | 70%+ | 40s GPU | 2024 |
| DiffDock-Glide | PDBbind core | 55–60% | 85%+ (corrección de Glide) | 60s GPU+CPU | Miller et al., bioRxiv 2025 [3] |
| RLDiff | Subconjunto de PDBbind | ~50% | 78% | 40s GPU | Zhang et al. 2024 [4] |
| Boltz-2 (cap. 11) | Benchmark similar | 50%+ | 90%+ (diseño con física integrada) | s GPU | MIT/Genentech 2025 |
Costo estimado de reproducción para el estudiante
- Costo de API: 0 (completamente local).
- Cribado de 100 ligandos: DiffDock aprox. 30 min (GPU) + revalidación con Vina aprox. 30 min (CPU paralelo 4-8 núcleos).
- Cribado de 20 candidatos de Mpro: 15–30 min.
- Descarga de pesos de DiffDock: aprox. 2 GB (en la primera ejecución).
- Vina, OpenBabel y PoseBusters son gratuitos.
5 casos de fallo conocidos (recopilados de la comunidad y literatura)
-
Choque atómico y geometría irreal en poses de DiffDock (60% de fallos en PoseBusters) Síntoma: En las mejores poses de DiffDock, los átomos del ligando se superponen con los de la proteína (interpenetración), distorsión de ángulos de enlace y anillos aromáticos no planos. Causa: El modelo de difusión no impone explícitamente las leyes físicas. El objetivo de entrenamiento es la restauración de coordenadas, sin una validación separada de validez geométrica. Evitar: (a) usar obligatoriamente el filtro PoseBusters (Paso 4 de este episodio), (b) refinamiento mediante optimización local de Vina, (c) seleccionar las poses válidas mediante un ensamblado de múltiples poses, (d) utilizar versiones posteriores con física integrada como DiffDock-Glide, (e) sustituir por Boltz-2 (aprendizaje con física integrada) (episodio 11). Fuente: Buttenschoen et al. "PoseBusters" Chem Sci 2024 [5].
-
Selección incorrecta del pocket (docking ciego vs. dirigido) Síntoma: En el modo ciego de DiffDock, el ligando se coloca en un pocket distinto al sitio ortostérico conocido. Causa: DiffDock permite buscar el pocket automáticamente o especificarlo de forma explícita, pero en modo ciego es frecuente asignar el sitio equivocado. Evitar: (a) predefinir las coordenadas del pocket (identificación del sitio de unión en este episodio), (b) realizar una búsqueda previa de pockets candidatos mediante fpocket, etc., (c) utilizar DiffDock-Pocket, (d) si existe un ligando conocido, utilizar su posición como semilla (seed). Fuente: DiffDock GitHub Issues [6].
-
Sesgo conocido en el benchmark DUD-E (sesgo entre activos vs. decoys) Síntoma: Se obtienen valores de AUC superiores a 0.9 en DUD-E, pero el rendimiento es mucho menor en el cribado de fármacos reales. Causa: Los decoys de DUD-E se generan mediante emparejamiento de propiedades, por lo que el evaluador (scorer) solo aprende las diferencias fisicoquímicas entre activos y decoys (careciendo de capacidad de discriminación de unión real). Evitar: (a) utilizar benchmarks no sesgados como LIT-PCBA, (b) validación mediante experimentos prospectivos, (c) evaluación mediante un ensamblado de múltiples benchmarks, (d) conjuntos de datos con mediciones reales como COVID Moonshot. Fuente: Chen et al. "Hidden bias in the DUD-E dataset." PLoS ONE 2019 [7].
-
Ignorar la flexibilidad de la proteína objetivo (receptor rígido) Síntoma: El docking asume un receptor rígido, pero las proteínas reales son flexibles, especialmente en términos de movimiento de loops y cadenas laterales. Causa: La mayoría de los modos de receptor rígido son el estándar en Vina y DiffDock. Evitar: (a) generar múltiples conformeros mediante simulación de MD y realizar el docking de cada uno (docking de ensamblado), (b) ejecutar múltiples estructuras apo/holo en paralelo en DiffDock, (c) utilizar software comercial de IFD (Induced Fit Docking), (d) enfoques de co-folding como Boltz-2 o Chai-1 (episodio 11). Fuente: Amaro RE et al. "Ensemble Docking in Drug Discovery." Biophys J 2018 [8].
-
Omisión de estereoquímica y tautómeros del ligando Síntoma: Aunque el SMILES especifique la estereoquímica, la predicción selecciona estereoisómeros de forma arbitraria. O bien, se realiza el docking con un tautómero que no es el predominante a un pH determinado. Causa: Pérdida de información durante la canonicalización de SMILES y la generación de conformeros 3D. Solución: (a) Preservar la quiralidad en
Chem.MolFromSmiles, (b) canonicalizar el tautómero enMolStandardize, (c) realizar el docking de cada estereoisómero y tautómero por separado para luego crear un ensemble, (d) verificar la quiralidad de la pose predicha mediante validación posterior. Fuente: Documentación de RDKit MolStandardize [9]; Boltz Issues (estereoquímica) [6].
Ideas de expansión
- Docking basado en fragmentos: Reducir bibliotecas grandes a unidades de fragmentos (< 300 Da) y realizar el docking → diseño de linkers.
- Ensemble docking: Realizar el docking con múltiples conformeros de la proteína (simulaciones de MD o múltiples modelos de AlphaFold) → agregación.
- Integración de cálculo de energía libre: Postprocesamiento con MM-GBSA · FEP para los principales candidatos del docking (afinidad más precisa).
- Diseño de linkers de novo: Generar linkers con RFdiffusion (Episodio 13) y validarlos mediante docking.
- Aprendizaje activo + auto-docking: Experimentación de ligandos con alta incertidumbre de predicción → reentrenamiento.
- Ensemble Boltz-2 vs DiffDock-Glide: Comparar resultados de Boltz-2 (Episodio 11) y realizar un ensemble.
Próximo episodio
- Episodio 11
structure-affinity-boltz: Integración del docking con Boltz-2 (flujo que sustituye al episodio de docking independiente). - Episodio 07
drug-target-gnn: Filtrado previo de candidatos de docking mediante predicción GNN. - Episodio 12
single-cell-perturbation: Predicción in silico de la respuesta celular de los principales ligandos del docking. - Episodio 14
bio-mcp-agent: Exponer el pipeline de docking como herramienta MCP.
Referencias
- 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 (código abierto):
https://github.com/schrodinger/pymol-open-source - PDBbind:
http://www.pdbbind.org.cn/ - DUD-E:
http://dude.docking.org/ - LIT-PCBA (benchmark sin sesgo): Tran-Nguyen VK et al. J Chem Inf Model 2020.
- PDBFixer (familia OpenMM):
https://github.com/openmm/pdbfixer - PoseBusters GitHub:
https://github.com/maabuu/posebusters - Consorcio COVID Moonshot:
https://postera.ai/moonshot/ - Estructuras de Mpro de SARS-CoV-2 (PDB 6LU7, etc.):
https://www.rcsb.org/