Capture structure-track deps + pose entrypoint

- gpu/modal_app.py: add the `pose` local entrypoint used for the HDAC2
  pose-RMSD validation (run: `modal run gpu/modal_app.py::pose`).
- pyproject [structure] extra: add the deps we actually use locally
  (gemmi, spyrmsd, meeko, modal) for reproducibility; document the non-pip
  tools (Vina binary, open-babel) and that Boltz/cuequivariance are
  Modal-image-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 19:56:40 +02:00
parent 9efdc0acf1
commit 907a39aaba
2 changed files with 31 additions and 4 deletions

View File

@@ -164,6 +164,27 @@ def cofold(label: str, protein_seq: str, ligand_smiles: str, cofactor_ccds: list
# ------------------------------------------------------------------------------- driver (local)
@app.local_entrypoint()
def pose() -> None:
"""Save the predicted HDAC2/vorinostat complex for local pose-RMSD validation.
Run: `modal run gpu/modal_app.py::pose`. Weights are cached, so this is one fast GPU call.
The returned PDB (protein + vorinostat + Zn) is scored locally against 4LXZ by
scripts/pose_rmsd.py (align predicted protein to crystal, compare ligand).
"""
target = "HDAC2"
pdb, res, drug, cofactors = TARGETS[target]
seq = binding_chain_sequence(pdb, res)
r = cofold.remote(f"{target}_{drug}_pose", seq, pubchem_smiles(drug), cofactors)
out = Path("data/processed/binding"); out.mkdir(parents=True, exist_ok=True)
if r.get("structure"):
dest = out / f"{target}_{drug}_pred.pdb"
dest.write_text(r["structure"])
print(f"saved {dest}; affinity={r['affinity']}, P(binder)={r['prob_binder']}")
else:
print("no structure returned")
@app.local_entrypoint()
def main() -> None:
"""Fan out one GPU call per (target, ligand) pair; tabulate affinities; positive-control test."""