Samad14 commited on
Commit
3654487
·
verified ·
1 Parent(s): 8332616

Upload folder using huggingface_hub

Browse files
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 best RMSD. 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,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 GetBestRMS symmetry-aware heavy-atom RMSD"
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. RDKit's GetBestRMS searches symmetry-equivalent atom maps,
71
- avoiding inflated RMSD from arbitrary numbering of equivalent atoms.
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
- return float(rdMolAlign.GetBestRMS(pred, ref))
 
 
 
 
 
 
 
 
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
- box = result.evidence.get("box_size") or {}
111
- box_ok = all(_fnum(box.get(k)) > 0 for k in ("x", "y", "z")) if box else True
 
 
 
 
 
 
 
 
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()