Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- Dockerfile +1 -0
- Dockerfile.worker +1 -0
- app/benchmarking/docking_redock.py +16 -7
- app/engines/docking_engine.py +13 -4
Dockerfile
CHANGED
|
@@ -95,6 +95,7 @@ RUN wget -q https://github.com/iqtree/iqtree2/releases/download/v2.3.6/iqtree-2.
|
|
| 95 |
|
| 96 |
# Download pre-compiled AutoDock Vina binary from GitHub releases
|
| 97 |
RUN wget -q "https://github.com/ccsb-scripps/AutoDock-Vina/releases/download/v1.2.7/vina_1.2.7_linux_x86_64" -O /usr/local/bin/vina && \
|
|
|
|
| 98 |
chmod +x /usr/local/bin/vina
|
| 99 |
|
| 100 |
WORKDIR /app
|
|
|
|
| 95 |
|
| 96 |
# Download pre-compiled AutoDock Vina binary from GitHub releases
|
| 97 |
RUN wget -q "https://github.com/ccsb-scripps/AutoDock-Vina/releases/download/v1.2.7/vina_1.2.7_linux_x86_64" -O /usr/local/bin/vina && \
|
| 98 |
+
echo "f31f774f723bba7bbe6e9d1c47577020eea9a8da16424284c043d22593570644 /usr/local/bin/vina" | sha256sum -c - && \
|
| 99 |
chmod +x /usr/local/bin/vina
|
| 100 |
|
| 101 |
WORKDIR /app
|
Dockerfile.worker
CHANGED
|
@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 8 |
rm -rf /var/lib/apt/lists/*
|
| 9 |
|
| 10 |
RUN wget -q "https://github.com/ccsb-scripps/AutoDock-Vina/releases/download/v1.2.7/vina_1.2.7_linux_x86_64" -O /usr/local/bin/vina && \
|
|
|
|
| 11 |
chmod +x /usr/local/bin/vina
|
| 12 |
|
| 13 |
# Gnina (CNN-based rescoring) — OFF BY DEFAULT, and the reason is not image size.
|
|
|
|
| 8 |
rm -rf /var/lib/apt/lists/*
|
| 9 |
|
| 10 |
RUN wget -q "https://github.com/ccsb-scripps/AutoDock-Vina/releases/download/v1.2.7/vina_1.2.7_linux_x86_64" -O /usr/local/bin/vina && \
|
| 11 |
+
echo "f31f774f723bba7bbe6e9d1c47577020eea9a8da16424284c043d22593570644 /usr/local/bin/vina" | sha256sum -c - && \
|
| 12 |
chmod +x /usr/local/bin/vina
|
| 13 |
|
| 14 |
# Gnina (CNN-based rescoring) — OFF BY DEFAULT, and the reason is not image size.
|
app/benchmarking/docking_redock.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""BBS-1 docking redocking benchmark helpers.
|
| 2 |
|
| 3 |
The benchmark compares a predicted ligand pose with the crystallographic
|
| 4 |
-
reference using RDKit's symmetry-aware
|
| 5 |
outside the production docking score path: docking affinity and pose RMSD are
|
| 6 |
separate quantities and must not be conflated.
|
| 7 |
"""
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
| 10 |
|
| 11 |
from copy import deepcopy
|
| 12 |
from dataclasses import asdict, dataclass
|
|
|
|
| 13 |
from typing import Any
|
| 14 |
|
| 15 |
|
|
@@ -49,7 +50,7 @@ class RedockingResult:
|
|
| 49 |
threshold_angstrom: float
|
| 50 |
passed: bool
|
| 51 |
atom_count: int
|
| 52 |
-
metric: str = "RDKit
|
| 53 |
|
| 54 |
def to_dict(self) -> dict[str, Any]:
|
| 55 |
return asdict(self)
|
|
@@ -67,8 +68,8 @@ def symmetry_aware_pose_rmsd(reference_mol, predicted_mol) -> float:
|
|
| 67 |
"""Return symmetry-aware heavy-atom RMSD in angstrom.
|
| 68 |
|
| 69 |
Both molecules must represent the same ligand graph and contain 3D
|
| 70 |
-
coordinates
|
| 71 |
-
|
| 72 |
"""
|
| 73 |
from rdkit.Chem import rdMolAlign
|
| 74 |
|
|
@@ -81,12 +82,20 @@ def symmetry_aware_pose_rmsd(reference_mol, predicted_mol) -> float:
|
|
| 81 |
)
|
| 82 |
if ref.GetNumConformers() == 0 or pred.GetNumConformers() == 0:
|
| 83 |
raise ValueError("Both ligands must contain 3D coordinates")
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
|
| 87 |
def evaluate_redocking(reference_mol, predicted_mol, threshold_angstrom: float = 2.0) -> RedockingResult:
|
| 88 |
-
if threshold_angstrom <= 0:
|
| 89 |
-
raise ValueError("threshold_angstrom must be > 0")
|
| 90 |
rmsd = symmetry_aware_pose_rmsd(reference_mol, predicted_mol)
|
| 91 |
atom_count = _remove_hydrogens(reference_mol).GetNumAtoms()
|
| 92 |
return RedockingResult(
|
|
|
|
| 1 |
"""BBS-1 docking redocking benchmark helpers.
|
| 2 |
|
| 3 |
The benchmark compares a predicted ligand pose with the crystallographic
|
| 4 |
+
reference using RDKit's symmetry-aware RMSD in the receptor frame. This is intentionally kept
|
| 5 |
outside the production docking score path: docking affinity and pose RMSD are
|
| 6 |
separate quantities and must not be conflated.
|
| 7 |
"""
|
|
|
|
| 10 |
|
| 11 |
from copy import deepcopy
|
| 12 |
from dataclasses import asdict, dataclass
|
| 13 |
+
import math
|
| 14 |
from typing import Any
|
| 15 |
|
| 16 |
|
|
|
|
| 50 |
threshold_angstrom: float
|
| 51 |
passed: bool
|
| 52 |
atom_count: int
|
| 53 |
+
metric: str = "RDKit CalcRMS symmetry-aware heavy-atom RMSD in receptor frame (no alignment)"
|
| 54 |
|
| 55 |
def to_dict(self) -> dict[str, Any]:
|
| 56 |
return asdict(self)
|
|
|
|
| 68 |
"""Return symmetry-aware heavy-atom RMSD in angstrom.
|
| 69 |
|
| 70 |
Both molecules must represent the same ligand graph and contain 3D
|
| 71 |
+
coordinates in the same receptor frame. CalcRMS searches equivalent atom
|
| 72 |
+
maps without moving the predicted pose onto the reference ligand.
|
| 73 |
"""
|
| 74 |
from rdkit.Chem import rdMolAlign
|
| 75 |
|
|
|
|
| 82 |
)
|
| 83 |
if ref.GetNumConformers() == 0 or pred.GetNumConformers() == 0:
|
| 84 |
raise ValueError("Both ligands must contain 3D coordinates")
|
| 85 |
+
if ref.GetNumAtoms() == 0:
|
| 86 |
+
raise ValueError("Both ligands must contain heavy atoms")
|
| 87 |
+
for mol in (ref, pred):
|
| 88 |
+
if not all(math.isfinite(float(v)) for xyz in mol.GetConformer().GetPositions() for v in xyz):
|
| 89 |
+
raise ValueError("Ligand coordinates must be finite")
|
| 90 |
+
rmsd = float(rdMolAlign.CalcRMS(pred, ref))
|
| 91 |
+
if not math.isfinite(rmsd):
|
| 92 |
+
raise ValueError("Pose RMSD must be finite")
|
| 93 |
+
return rmsd
|
| 94 |
|
| 95 |
|
| 96 |
def evaluate_redocking(reference_mol, predicted_mol, threshold_angstrom: float = 2.0) -> RedockingResult:
|
| 97 |
+
if not math.isfinite(threshold_angstrom) or threshold_angstrom <= 0:
|
| 98 |
+
raise ValueError("threshold_angstrom must be > 0 and finite")
|
| 99 |
rmsd = symmetry_aware_pose_rmsd(reference_mol, predicted_mol)
|
| 100 |
atom_count = _remove_hydrogens(reference_mol).GetNumAtoms()
|
| 101 |
return RedockingResult(
|
app/engines/docking_engine.py
CHANGED
|
@@ -21,6 +21,7 @@ Canonical input (a docking result object, e.g. from /api/docking):
|
|
| 21 |
from __future__ import annotations
|
| 22 |
|
| 23 |
from typing import Any
|
|
|
|
| 24 |
|
| 25 |
from app.engines.base import BaseEngine, EngineResult, ValidationReport
|
| 26 |
from app.figure.engine import bar_chart_panel, esc
|
|
@@ -107,8 +108,16 @@ class DockingEngine(BaseEngine):
|
|
| 107 |
(_fnum(p.get("rmsd_lb"), 0.0) >= 0) and (_fnum(p.get("rmsd_ub"), 0.0) >= 0)
|
| 108 |
for p in poses if isinstance(p, dict)
|
| 109 |
)
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
checks.extend([
|
| 114 |
{"name": "poses_present", "passed": s["pose_count"] >= 1, "detail": f"{s['pose_count']} poses"},
|
|
@@ -119,7 +128,7 @@ class DockingEngine(BaseEngine):
|
|
| 119 |
{"name": "rmsd_nonnegative", "passed": rmsd_ok, "detail": "all RMSD values >= 0"},
|
| 120 |
{"name": "interactions_nonnegative", "passed": all(v >= 0 for v in
|
| 121 |
(s["h_bonds"], s["hydrophobic"], s["pi_stacking"], s["salt_bridges"])), "detail": "interaction counts"},
|
| 122 |
-
{"name": "grid_box_valid", "passed": box_ok, "detail": "positive box sizes"},
|
| 123 |
])
|
| 124 |
return ValidationReport(checks, self.name)
|
| 125 |
|
|
@@ -156,4 +165,4 @@ class DockingEngine(BaseEngine):
|
|
| 156 |
)
|
| 157 |
|
| 158 |
|
| 159 |
-
docking_engine = DockingEngine()
|
|
|
|
| 21 |
from __future__ import annotations
|
| 22 |
|
| 23 |
from typing import Any
|
| 24 |
+
import math
|
| 25 |
|
| 26 |
from app.engines.base import BaseEngine, EngineResult, ValidationReport
|
| 27 |
from app.figure.engine import bar_chart_panel, esc
|
|
|
|
| 108 |
(_fnum(p.get("rmsd_lb"), 0.0) >= 0) and (_fnum(p.get("rmsd_ub"), 0.0) >= 0)
|
| 109 |
for p in poses if isinstance(p, dict)
|
| 110 |
)
|
| 111 |
+
def grid_values(value):
|
| 112 |
+
if isinstance(value, dict):
|
| 113 |
+
return [_fnum(value.get(k), float("nan")) for k in ("x", "y", "z")]
|
| 114 |
+
if isinstance(value, (list, tuple)) and len(value) == 3:
|
| 115 |
+
return [_fnum(v, float("nan")) for v in value]
|
| 116 |
+
return [float("nan")]*3
|
| 117 |
+
|
| 118 |
+
box = grid_values(result.evidence.get("box_size"))
|
| 119 |
+
center = grid_values(result.evidence.get("box_center"))
|
| 120 |
+
box_ok = all(math.isfinite(v) and v > 0 for v in box) and all(math.isfinite(v) for v in center)
|
| 121 |
|
| 122 |
checks.extend([
|
| 123 |
{"name": "poses_present", "passed": s["pose_count"] >= 1, "detail": f"{s['pose_count']} poses"},
|
|
|
|
| 128 |
{"name": "rmsd_nonnegative", "passed": rmsd_ok, "detail": "all RMSD values >= 0"},
|
| 129 |
{"name": "interactions_nonnegative", "passed": all(v >= 0 for v in
|
| 130 |
(s["h_bonds"], s["hydrophobic"], s["pi_stacking"], s["salt_bridges"])), "detail": "interaction counts"},
|
| 131 |
+
{"name": "grid_box_valid", "passed": box_ok, "detail": "finite center and finite positive box sizes"},
|
| 132 |
])
|
| 133 |
return ValidationReport(checks, self.name)
|
| 134 |
|
|
|
|
| 165 |
)
|
| 166 |
|
| 167 |
|
| 168 |
+
docking_engine = DockingEngine()
|