Download CupOFMoCA-PROTAC/md/ternary_span.py from ChatterjeeLab/CupOFMoCA: direct link, hf CLI and curl.
- Browser
- Download file 4.57 kB
-
https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/ternary_span.py
- Command line
-
hf download hf://ChatterjeeLab/CupOFMoCA/CupOFMoCA-PROTAC/md/ternary_span.py
-
curl -L -o ternary_span.py https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/ternary_span.py
4.57 kB
| #!/usr/bin/env python | |
| """Warhead-to-E3 span distribution from the water ensembles. | |
| Per frame, the distance between the centroid of the warhead core (the group that | |
| occupies the target pocket) and the centroid of the E3 ligand core. In a | |
| productive ternary complex that span has to bridge the two pockets, so the | |
| distribution shows which separations a linker can hold. Water, not chloroform: | |
| the ternary complex forms in the cytosol. | |
| This is a description of reach, not a degradation score; scoring it needs a | |
| target ternary geometry from docking. Replica spread is printed alongside so | |
| poorly converged runs are visible. | |
| Default SMARTS are for the EGFR-VHL series (anilinoquinazoline warhead, | |
| 4-hydroxyproline E3 anchor); override with --warhead-smarts / --e3-smarts. | |
| Usage: | |
| python ternary_span.py egfrvhl_rank4 ms39_ref --out span.png | |
| """ | |
| import argparse, glob, os | |
| import numpy as np | |
| import mdtraj as md | |
| from rdkit import Chem | |
| from scipy.stats import gaussian_kde | |
| import matplotlib; matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| WH_SMARTS = "c1cc2ncncc2cc1" # quinazoline bicyclic, target pocket | |
| E3_SMARTS = "[#6]1[#6][#6](O)[#6][#7]1" # 4-hydroxypyrrolidine, VHL pocket | |
| def anchors(sdf, wh_smarts, e3_smarts): | |
| m = Chem.MolFromMolFile(sdf, removeHs=False) | |
| wh = list(m.GetSubstructMatch(Chem.MolFromSmarts(wh_smarts))) | |
| e3 = list(m.GetSubstructMatch(Chem.MolFromSmarts(e3_smarts))) | |
| syms = [a.GetSymbol() for a in m.GetAtoms()] | |
| return wh, e3, syms | |
| def spans_for_run(run, solvent, wh_smarts, e3_smarts): | |
| """Per-replica span arrays (A), or None if the run can't be used.""" | |
| name = os.path.basename(run.rstrip("/")) | |
| tops = glob.glob(os.path.join(run, solvent, "*_solvated.pdb")) | |
| dcds = sorted(glob.glob(os.path.join(run, solvent, "rep*", f"{name}_prod.dcd"))) | |
| if not tops or not dcds: | |
| print(f"{name:14s} no {solvent} data") | |
| return None | |
| wh, e3, sdf_syms = anchors(os.path.join(run, f"{name}.sdf"), wh_smarts, e3_smarts) | |
| if not wh or not e3: | |
| print(f"{name:14s} SMARTS match failed (wh={len(wh)} e3={len(e3)})") | |
| return None | |
| out = [] | |
| for d in dcds: | |
| tr = md.load(d, top=tops[0]) | |
| lig = [a.index for r in tr.topology.residues | |
| if not r.is_water and r.n_atoms > 5 for a in r.atoms] | |
| sub = tr.atom_slice(lig) | |
| if [a.element.symbol for a in sub.topology.atoms] != sdf_syms: | |
| print(f"{name:14s} SDF/MD atom-order mismatch, skipping") | |
| return None | |
| xyz = sub.xyz * 10.0 # nm -> A | |
| out.append(np.linalg.norm(xyz[:, wh, :].mean(1) - xyz[:, e3, :].mean(1), axis=1)) | |
| return out | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("runs", nargs="+", help="run directories, one per compound") | |
| ap.add_argument("--solvent", default="water") | |
| ap.add_argument("--warhead-smarts", default=WH_SMARTS) | |
| ap.add_argument("--e3-smarts", default=E3_SMARTS) | |
| ap.add_argument("--out", default="ternary_span.png") | |
| a = ap.parse_args() | |
| fig, ax = plt.subplots(figsize=(11, 6.5), dpi=150) | |
| fig.patch.set_facecolor("white") | |
| cmap = plt.get_cmap("viridis") | |
| print(f"{'compound':14s}{'mean span':>10s}{'sd':>6s}{'min':>6s}{'max':>6s}" | |
| f"{'range':>7s}{'rep spread':>12s}") | |
| print("-" * 67) | |
| drawn = 0 | |
| for i, run in enumerate(a.runs): | |
| label = os.path.basename(run.rstrip("/")) | |
| per_rep = spans_for_run(run, a.solvent, a.warhead_smarts, a.e3_smarts) | |
| if not per_rep: | |
| continue | |
| s = np.concatenate(per_rep) | |
| spread = float(np.std([r.mean() for r in per_rep])) | |
| print(f"{label:14s}{s.mean():10.1f}{s.std():6.1f}{s.min():6.1f}" | |
| f"{s.max():6.1f}{s.max()-s.min():7.1f}{spread:12.1f}") | |
| grid = np.linspace(3, 25, 300) | |
| ax.plot(grid, gaussian_kde(s, bw_method=0.15)(grid), lw=2.2, | |
| color=cmap(i / max(1, len(a.runs) - 1)), | |
| label=f"{label} ({s.mean():.0f} A)") | |
| drawn += 1 | |
| if not drawn: | |
| return | |
| ax.set_xlabel("warhead to E3 pharmacophore separation (A)") | |
| ax.set_ylabel("probability density") | |
| ax.set_title("Warhead to E3 span\n" | |
| f"centroid distance between the pocket-binding groups ({a.solvent})", | |
| fontsize=12.5, weight="bold", loc="left") | |
| ax.legend(fontsize=8.5, frameon=False, ncol=2) | |
| ax.grid(alpha=0.25) | |
| fig.tight_layout() | |
| fig.savefig(a.out, facecolor="white") | |
| print(f"\nwrote {a.out}") | |
| if __name__ == "__main__": | |
| main() | |