Skip to content

Fix scrambled-molecule docking bug + populate Layer 1.7 blind validation#11

Open
Rockman6 wants to merge 5 commits into
mainfrom
fix/dock-pose-reconstruction
Open

Fix scrambled-molecule docking bug + populate Layer 1.7 blind validation#11
Rockman6 wants to merge 5 commits into
mainfrom
fix/dock-pose-reconstruction

Conversation

@Rockman6

Copy link
Copy Markdown
Owner

The bug (load-bearing)

The docking layer assumed Meeko "preserves SMILES atom order." It does not — Meeko reorders atoms during ligand prep (biotin: SMILES OCOCCCCC… → PDBQT CCCOOCCC…). Both consumers that need a correct 3D molecule — _build_pose_mol (PoseBusters) and pose_rmsd_symmetry_aware (crystal RMSD) — mapped pose coordinates onto a MolFromSmiles template by index, producing a topologically scrambled molecule with physically impossible 3–5 Å "bonds" (12/17 wrong on biotin).

Impact: this silently corrupted both headline numbers of Layer 1.3a — pose recovery and PoseBusters validity. Biotin/1STP was reported at 2.06 Å (borderline fail) when the real redock is 0.69 Å (excellent). Good poses were scored as bad.

The fixes (5 commits)

  1. 2d222e4 — reconstruct poses via Meeko's reverse conversion (PDBQTMolecule + RDKitMolCreate.from_pdbqt_mol), storing a correct mol block on each pose. 41/41 dock tests pass; regression test guards against the scramble returning.
  2. 1e9e845 — new blind-validation harness (scripts/run_blind_dock_bench.py): fetches structures and authoritative ligand SMILES from RCSB at run time (not from memory — so it's not circular on our inputs), auto-picks the drug ligand, re-docks blind, scores RMSD + PoseBusters. Populates the previously-empty Layer 1.7.
  3. 7e52547 — robust crystal-RMSD: one-copy extraction (multi-copy crystals) + MCS common-core matching (off-by-one atom counts) + ring-info init. All 15/15 blind entries now score.
  4. 0c8f265 — centre the docking box on one ligand copy (multi-copy crystals were centred between binding sites → docked into empty space). 2hyy 2.77 → 0.80 Å.
  5. docs — correct the README/BENCHMARKS/status numbers that were computed on scrambled molecules.

Honest Layer-1.7 results (15 diverse cocrystals)

Criterion Result Gate Verdict
#3 PoseBusters physical validity 100% ≥95% ✅ PASS
#1 Pose recovery top-1 ≤2 Å 73% (11/15) ≥75% ⚠️ just under
Pose recovery top-3 ≤2 Å 87% (13/15) (sampling)

The top-1/top-3 gap is the finding: Vina's sampling finds the correct pose 87% of the time; its scoring ranks it #1 only 73%. A UFF-strain re-rank does not recover the ranking failures. This is the same limitation as criterion #2 (kinase ranking) — Vina scoring — which is exactly what FEP replaces.

tests/dock: 43 passed. Separate from the FEP audit branch (PR #10).

Rockman6 added 5 commits July 11, 2026 15:26
…lecule bug

The pose→molecule reconstruction assumed Meeko "preserves SMILES atom
order". It does NOT — Meeko reorders atoms during ligand prep (biotin
SMILES order OCOCCCCC… becomes PDBQT order CCCOOCCC…). Both consumers
that need a chemically-correct 3D mol — _build_pose_mol (PoseBusters)
and pose_rmsd_symmetry_aware (crystal RMSD) — mapped the pose's
PDBQT-order coordinates onto a MolFromSmiles template BY INDEX, so they
built a topologically scrambled molecule with physically impossible
3–5 Å "bonds" (12 of 17 heavy-atom bonds wrong on biotin).

Impact: this silently corrupted BOTH pose-recovery RMSD and PoseBusters
validity — the two headline numbers of the docking layer. On 1stp/biotin
the scramble reported top-1 RMSD 2.06 Å (borderline fail) and 0%
PoseBusters; with correct reconstruction the SAME pose is 0.69 Å
(excellent) and passes PoseBusters. Good poses were being scored as bad.

Fix: reconstruct each pose with Meeko's own reverse conversion
(PDBQTMolecule + RDKitMolCreate.from_pdbqt_mol), which carries the true
atom mapping + hydrogens, and store it on DockingPose.rdkit_molblock.
_build_pose_mol and pose_rmsd now use that; the legacy template path is
kept only as a fallback for poses lacking a molblock. This also makes
the separate OpenMM pose-refine step unnecessary for validity.

Regression test docks biotin (a ligand Meeko reorders) and asserts every
reconstructed heavy-atom bond is physically plausible. Full tests/dock
suite: 41 passed.
…st results

Populates the previously-empty Layer 1.7 (blind validation) with a real,
reproducible benchmark — the anti-circular-validation gate that was
missing. scripts/run_blind_dock_bench.py fetches structures AND
authoritative ligand SMILES from RCSB at run time (never from memory or
our own inputs), auto-picks the drug ligand (skipping waters/ions/
buffers), re-docks blind, and scores top-1 RMSD (criterion #1, gate
>= 75%) + PoseBusters physical validity (criterion #3, gate >= 95%).
Bad entries are reported and excluded from the denominators, never
silently counted.

First run on a 15-cocrystal diverse set (benchmarks/pdbbind/blind_set.yaml),
after the Meeko pose-reconstruction fix:
  - PoseBusters validity: 100% (10/10 scored) -> PASSES #3.
  - Pose recovery (top-1 <= 2.0 A): 70% (7/10) -> just under #1's 75%
    gate. 5 entries failed the crystal-RMSD computation (docking + PB
    succeeded; only the RMSD-vs-crystal atom match failed) and are
    excluded — a known robustness gap to fix next. 3 more are borderline
    top-1 but good top-3 (1m17, 1fkg, 1hpx).

This is the first honest, external measurement of criteria #1/#3.
Structures cached under benchmarks/pdbbind/structures/ (gitignored,
re-fetched on demand).
The crystal-RMSD path dropped 5/15 blind-set entries to None (docking +
PoseBusters succeeded; only the RMSD-vs-crystal atom match failed):
  - multiple ligand copies in the crystal (imatinib in 2hyy appears 4x,
    raloxifene in 1err 2x) -> extract_hetatm_ligand grabbed all of them,
    so the atom count was a multiple of one ligand;
  - crystal missing a disordered atom, or the deposited SMILES carrying
    an extra (3ptb 10 vs 9, 1dwd 38 vs 37) -> exact-count match refused;
  - a subtle topology mismatch (1hvr) even at equal counts.

Fixes in pose_rmsd_symmetry_aware:
  - extract_hetatm_ligand(first_copy_only=True): one ligand copy (first
    chain/resSeq, blank/'A' altloc) for RMSD references.
  - a robust matcher that prefers the Meeko-reconstructed pose mol and
    falls back to an MCS common-core in-place RMSD when atom counts
    differ, so near-misses score instead of vanishing. (Ring perception
    is initialised before FMCS to avoid the sanitize=False RingInfo
    error.)
  - the legacy exact-count template path is kept only as a last resort.

Result: all 15/15 blind cocrystals now score. Harness also reports top-3
recovery (sampling power, ranking-independent) alongside top-1.

Honest Layer-1.7 numbers on the 15-cocrystal diverse blind set:
  - PoseBusters validity:        100%  -> PASSES criterion #3 (>= 95%).
  - Pose recovery top-1 <= 2 A:   67%  -> under criterion #1 (>= 75%).
  - Pose recovery top-3 <= 2 A:   87%  -> Vina SAMPLES the right pose
    13/15; its SCORING just doesn't rank it #1. Criterion #1's shortfall
    is a ranking limitation, the same root as criterion #2 (kinase
    ranking) — which is exactly the gap FEP is meant to close.

tests/dock: 43 passed.
…tals)

run_one derived the box centre from extract_hetatm_ligand over ALL
HETATM copies of the ligand. For crystals with the ligand in several
chains (imatinib in 2hyy = 4 copies spread across the structure) the
centroid landed BETWEEN the binding sites, so Vina docked into empty
space and the "pose recovery" for those entries was an artefact of the
wrong box, not of the docking. Use first_copy_only for the centre.

Effect on the 15-cocrystal blind set: 2hyy 2.77 -> 0.80 A (fail -> pass),
1err 0.83 -> 0.60 A. Updated honest numbers:
  - PoseBusters validity:      100%  -> PASSES criterion #3.
  - Pose recovery top-1 <= 2A:  73%  (11/15) -> just under criterion #1.
  - Pose recovery top-3 <= 2A:  87%  (13/15) -> sampling power.

Remaining top-1 misses: 1m17 + 1hpx are ranking failures (correct pose
at rank 2-3), 1fkg + 1dwd are genuine sampling near-misses (top-3 best
2.26 / 2.34 A). Confirmed separately that UFF-strain re-ranking does NOT
recover the ranking failures (the correct pose is not lower-strain), so
top-1 is a Vina-scoring limit, not fixable by a strain gate — the same
root as criterion #2 (kinase ranking), which is what FEP addresses.
…ed mols)

README, BENCHMARKS, and campaign1_status quoted "2/3 = 67% pose
recovery" and per-system RMSDs (1STP 2.02 Å, 1M17 4.23 Å) that were
computed on molecules scrambled by the Meeko atom-order bug. Replace
with the honest blind-set numbers on correct molecules: PoseBusters
100% (criterion #3 PASS), pose recovery top-3 87% / top-1 73%. Note the
fix and the top-1/top-3 (scoring-vs-sampling) story throughout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant