Download CupOFMoCA-PROTAC/md/psa_ensemble.py from ChatterjeeLab/CupOFMoCA: direct link, hf CLI and curl.
- Browser
- Download file 2.92 kB
-
https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/psa_ensemble.py
- Command line
-
hf download hf://ChatterjeeLab/CupOFMoCA/CupOFMoCA-PROTAC/md/psa_ensemble.py
-
curl -L -o psa_ensemble.py https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/md/psa_ensemble.py
2.92 kB
| #!/usr/bin/env python | |
| """3D polar surface area over an MD ensemble. | |
| Element-based PSA: N, O and polar H, Bondi radii, 1.4 A probe, mdtraj | |
| shrake_rupley. validate_psa.py checks this code path against the published | |
| grazoprevir numbers. Lower PSA in the membrane mimic tracks higher passive | |
| permeability. | |
| Polar atoms are indexed from each run's SDF, so the SDF and MD atom orders must | |
| agree. A run whose ligand atom count does not match its SDF is reported and | |
| dropped from the table rather than summed over the wrong atoms. | |
| Usage: | |
| python psa_ensemble.py egfrvhl_rank4 ms39_ref --solvent chloroform --out psa.npy | |
| """ | |
| import argparse, glob, os | |
| import numpy as np | |
| import mdtraj as md | |
| from rdkit import Chem | |
| def polar_idx_from_sdf(sdf): | |
| m = Chem.MolFromMolFile(sdf, removeHs=False) | |
| idx = [] | |
| for a in m.GetAtoms(): | |
| s = a.GetSymbol() | |
| if s in ("N", "O"): | |
| idx.append(a.GetIdx()) | |
| elif s == "H" and any(nb.GetSymbol() in ("N", "O") for nb in a.GetNeighbors()): | |
| idx.append(a.GetIdx()) | |
| return idx, m.GetNumAtoms() | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("runs", nargs="+", help="run directories, one per compound") | |
| ap.add_argument("--solvent", default="chloroform") | |
| ap.add_argument("--stride", type=int, default=20) | |
| ap.add_argument("--out", default=None, help="optional .npy dump of the per-frame values") | |
| a = ap.parse_args() | |
| print(f"{'compound':16s}{'n_pol':6s}{'3D PSA (A^2) mean(std)':24s}{'frames':8s}") | |
| print("-" * 56) | |
| out = {} | |
| for run in a.runs: | |
| name = os.path.basename(run.rstrip("/")) | |
| pol, nat = polar_idx_from_sdf(os.path.join(run, f"{name}.sdf")) | |
| tops = glob.glob(os.path.join(run, a.solvent, "*_solvated.pdb")) | |
| if not tops: | |
| print(f"{name:16s} no {a.solvent} topology"); continue | |
| vals = [] | |
| for dcd in sorted(glob.glob(os.path.join(run, a.solvent, "rep*", f"{name}_prod.dcd"))): | |
| tr = md.load(dcd, top=tops[0], stride=a.stride) | |
| lig = [at.index for r in tr.topology.residues | |
| if not r.is_water and r.n_atoms > 5 for at in r.atoms] | |
| sub = tr.atom_slice(lig) | |
| if sub.n_atoms != nat: | |
| print(f"{name:16s} atom count mismatch md={sub.n_atoms} sdf={nat}") | |
| vals = None | |
| break | |
| sasa = md.shrake_rupley(sub, mode="atom", probe_radius=0.14) # nm^2 | |
| vals.extend((sasa[:, pol].sum(1) * 100.0).tolist()) # A^2 per frame | |
| if vals: | |
| v = np.array(vals) | |
| print(f"{name:16s}{len(pol):<6d}{f'{v.mean():.0f} ({v.std():.0f})':24s}{len(v):<8d}") | |
| out[name] = dict(mean=float(v.mean()), std=float(v.std()), vals=v.tolist()) | |
| if a.out and out: | |
| np.save(a.out, out, allow_pickle=True) | |
| print(f"\nsaved {a.out}") | |
| if __name__ == "__main__": | |
| main() | |