Download CupOFMoCA-PROTAC/md/validate_psa.py from ChatterjeeLab/CupOFMoCA: direct link, hf CLI and curl.
- Browser
- Download file 2.64 kB
-
https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/validate_psa.py
- Command line
-
hf download hf://ChatterjeeLab/CupOFMoCA/CupOFMoCA-PROTAC/md/validate_psa.py
-
curl -L -o validate_psa.py https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/validate_psa.py
2.64 kB
| #!/usr/bin/env python | |
| """Sanity check for the 3D PSA code path used by psa_ensemble.py. | |
| Runs the same element-based PSA (N, O, polar H; Bondi radii; 1.4 A probe; mdtraj | |
| shrake_rupley) on grazoprevir, whose solution-accessible 3D PSA is reported as | |
| 144-205 A^2 (174 A^2 for the crystal conformer) using a charge-threshold | |
| selection in PyMOL. Agreement means the two conventions land on the same scale, | |
| so PROTAC numbers from psa_ensemble.py can be read against published values. | |
| Needs no finished run: grazoprevir and its conformers are generated here, so it | |
| takes seconds. | |
| Usage: | |
| python validate_psa.py | |
| """ | |
| import numpy as np, tempfile, os | |
| from rdkit import Chem | |
| from rdkit.Chem import AllChem | |
| import mdtraj as md | |
| # grazoprevir, PubChem CID 44603531 (SMILES field, verbatim) | |
| SMI = "CC(C)(C)[C@H]1C(=O)N2C[C@@H](C[C@H]2C(=O)N[C@@]3(C[C@H]3C=C)C(=O)NS(=O)(=O)C4CC4)OC5=NC6=C(C=CC(=C6)OC)N=C5CCCCC[C@@H]7C[C@H]7OC(=O)N1" | |
| m = Chem.AddHs(Chem.MolFromSmiles(SMI)) | |
| p = AllChem.ETKDGv3(); p.randomSeed = 1; p.useRandomCoords = True | |
| cids = AllChem.EmbedMultipleConfs(m, numConfs=25, params=p) | |
| AllChem.MMFFOptimizeMoleculeConfs(m) | |
| print(f"grazoprevir: {m.GetNumAtoms()} atoms, {len(cids)} conformers embedded") | |
| def is_polar(atom): | |
| s = atom.GetSymbol() | |
| if s in ("N", "O"): return True | |
| if s == "H": | |
| return any(nb.GetSymbol() in ("N", "O") for nb in atom.GetNeighbors()) | |
| return False | |
| polar = [a.GetIdx() for a in m.GetAtoms() if is_polar(a)] | |
| print(f"polar atoms (N,O,polar-H): {len(polar)}") | |
| vals = [] | |
| for cid in cids: | |
| pdb = Chem.MolToPDBBlock(m, confId=cid) | |
| with tempfile.NamedTemporaryFile("w", suffix=".pdb", delete=False) as fh: | |
| fh.write(pdb); path = fh.name | |
| t = md.load(path); os.unlink(path) | |
| sasa = md.shrake_rupley(t, mode="atom", probe_radius=0.14)[0] # nm^2 | |
| vals.append(sasa[polar].sum() * 100.0) # A^2 | |
| vals = np.array(vals) | |
| rg = [] | |
| for cid in cids: | |
| conf = m.GetConformer(cid) | |
| xyz = np.array([list(conf.GetAtomPosition(i)) for i in range(m.GetNumAtoms())]) | |
| xyz -= xyz.mean(0); rg.append(np.sqrt((xyz**2).sum(1).mean())) | |
| rg = np.array(rg) | |
| print(f"\nelement-based 3D PSA over {len(vals)} conformers:") | |
| print(f" min {vals.min():.0f} median {np.median(vals):.0f} max {vals.max():.0f} A2") | |
| print(f" (most folded conformer, Rg={rg.min():.2f} A): PSA = {vals[np.argmin(rg)]:.0f} A2") | |
| print(f"\nreported (PyMOL, |q|>1 selection): grazoprevir SA 3D PSA = 144-205 A2, crystal 174") | |
| print(f" grazoprevir 2D TPSA (PubChem) = 204 A2") | |
| inrange = ((vals >= 140) & (vals <= 210)).mean() * 100 | |
| print(f"\n-> {inrange:.0f}% of the conformers fall in the reported 144-205 range") | |