Download CupOFMoCA-PROTAC/src/protac/BRD4VHL.py from ChatterjeeLab/CupOFMoCA: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/src/protac/BRD4VHL.py
- Command line
-
hf download hf://ChatterjeeLab/CupOFMoCA/CupOFMoCA-PROTAC/src/protac/BRD4VHL.py
-
curl -L -o BRD4VHL.py https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PROTAC/src/protac/BRD4VHL.py
16.8 kB
| """ | |
| BRD4-VHL PROTAC Builder (double amide: warhead-COOH + linker-NH2 + linker-COOH + E3-NH2) | |
| Target: BRD4 (Bromodomain-containing protein 4) | |
| E3 Ligase: VHL | |
| Source: PROTAC-DB ID 1784 (PDB: 7KHH, DC50 = 0.095 nM, Dmax = 96%) | |
| Reference PROTAC (PROTAC-DB #1784, MW = 1096.3 Da): | |
| CC1=C(C2=CC=C(CNC(=O)[C@@H]3C[C@@H](O)CN3C(=O)[C@@H](NC(=O)CCCCCCCCCC | |
| NC(=O)C3=CC4=C(C=C3CS(C)(=O)=O)C3=CN(C)C(=O)C5=C3C(=C[NH]5)CN4C3=NC=C(F) | |
| C=C3F)C(C)(C)C)C=C2)SC=N1 | |
| Linker: NCCCCCCCCCCC(=O)O (11-aminoundecanoic acid, MW 201.3 Da) | |
| Linker chemistry: AMINO-ACID termini (NH2 on warhead end, COOH on VHL end) | |
| The generated linker has one free primary NH2 and one free COOH. | |
| Warhead provides a COOH electrophile; linker provides NH2 nucleophile (warhead side). | |
| Linker provides a COOH electrophile; E3 provides NH2 nucleophile (VHL side). | |
| Warhead-COOH + linker-NH2 β secondary amide (warhead side) | |
| Linker-COOH + E3-NH2 β secondary amide (E3 / VHL side) | |
| Assembly order: Step 1 = VHL-NH2 + tagged linker-COOH β amide intermediate; | |
| Step 2 = intermediate-NH2 + warhead-COOH β PROTAC. | |
| Warhead (BRD4 bromodomain inhibitor, COOH-armed, MW β 500.5 Da): | |
| Dihydropyrrolo-isoquinoline-difluoropyridyl tetracyclic BRD4 binder. | |
| COOH at the benzoic acid arm reacts with the linker primary NH2 β amide. | |
| E3 Ligand (VHL binder, NH2-armed, MW β 430.6 Da): | |
| (2S,4R)-4-hydroxy-proline + tert-leucine + 4-(4-methylthiazol-5-yl)benzyl cap. | |
| Free NH2 on tert-leucine alpha-amine is the reactive group. | |
| Usage with defog_guided.py: | |
| --protac_module BRD4VHL --fix_termini amine_acid | |
| (linker must expose one NH2 terminus and one COOH terminus) | |
| """ | |
| from rdkit import Chem | |
| from rdkit.Chem import AllChem, Descriptors | |
| from rdkit import RDLogger | |
| from typing import Optional | |
| try: | |
| from .protac_builder import PROTAC_Builder | |
| except ImportError: | |
| from protac_builder import PROTAC_Builder | |
| RDLogger.DisableLog("rdApp.error") | |
| # --------------------------------------------------------------------------- | |
| # Component SMILES β extracted from PROTAC-DB #1784 | |
| # --------------------------------------------------------------------------- | |
| # BRD4 bromodomain inhibitor: dihydropyrrolo-isoquinoline tetracyclic scaffold | |
| # with 3,5-difluoropyridin-2-yl at N8 and methylsulfonylmethyl at C4. | |
| # COOH at the benzoic acid arm is the reactive group (reacts with linker NH2 β amide). | |
| # MW β 500.5 Da; C23H18F2N4O5S | |
| BRD4_WARHEAD_SMILES = "OC(=O)C1=CC2=C(C=C1CS(C)(=O)=O)C3=CN(C)C(=O)C4=C3C(=C[NH]4)CN2C1=NC=C(F)C=C1F" | |
| # VHL binder: (2S,4R)-4-hydroxyproline + (S)-tert-leucine + 4-(4-methylthiazol-5-yl)benzyl cap. | |
| # Free NH2 on tert-leucine alpha-amine reacts with linker COOH β secondary amide. | |
| # MW β 430.6 Da; C22H30N4O3S | |
| VHL_E3_SMILES = "N[C@@H](C(=O)N1C[C@@H](O)C[C@@H]1C(=O)NCc1ccc(-c2scnc2C)cc1)C(C)(C)C" | |
| # Pre-built Mol objects | |
| BRD4_WARHEAD_MOL = Chem.MolFromSmiles(BRD4_WARHEAD_SMILES) | |
| VHL_E3_MOL = Chem.MolFromSmiles(VHL_E3_SMILES) | |
| # Standard interface aliases (used by defog_guided.py / batch_report.py) | |
| WARHEAD_MOL = BRD4_WARHEAD_MOL | |
| E3_MOL = VHL_E3_MOL | |
| WARHEAD_SMILES = BRD4_WARHEAD_SMILES | |
| E3_SMILES = VHL_E3_SMILES | |
| # Reactive SMARTS (used by batch_report.py to locate reactive atoms in free fragments) | |
| WH_REACTIVE_SMARTS = "[CX3](=[OX1])[OX2H1]" # warhead benzoic acid COOH | |
| E3_REACTIVE_SMARTS = "[NX3H2]" # tert-leucine primary NH2 | |
| # Substructure patterns for core detection and assembly check | |
| WARHEAD_PAT = Chem.MolFromSmarts("CS(C)(=O)=O") # methylsulfonylmethyl (BRD4) | |
| E3_PAT = Chem.MolFromSmarts("N1CC(O)CC1") # hydroxyproline ring (VHL) | |
| # SMARTS for reactive groups on linker | |
| _COOH_SMARTS = "[CX3](=[OX1])[OX2H1]" | |
| _NH2_SMARTS = "[NX3H2]" | |
| _AMIDE_SMARTS = "[NX3H0,NX3H1][CX3](=[OX1])[#6]" | |
| class BRD4VHL_PROTAC_Builder(PROTAC_Builder): | |
| """ | |
| PROTAC builder for BRD4-VHL (PROTAC-DB #1784). | |
| Warhead side: linker primary NH2 + warhead COOH β secondary amide. | |
| E3 side: linker COOH + VHL tert-leucine NH2 β secondary amide. | |
| Linker must expose one NH2 terminus (warhead end) and one COOH terminus (VHL end). | |
| """ | |
| _COOH_SMARTS = "[CX3](=[OX1])[OX2H1]" | |
| _NH2_SMARTS = "[NX3H2]" | |
| _AMIDE_SMARTS = "[NX3H0,NX3H1][CX3](=[OX1])[#6]" | |
| # BRD4 core: methylsulfonylmethyl + pyrrole-NH in 5-membered ring | |
| _BRD4_MS_SMARTS = "CS(C)(=O)=O" | |
| _BRD4_NH_SMARTS = "[nH;r5]" | |
| # VHL core: hydroxyproline | |
| _VHL_HYP_SMARTS = "N1CC(O)CC1" | |
| def __init__(self, warhead: Optional[Chem.Mol] = None, | |
| e3: Optional[Chem.Mol] = None): | |
| self._cooh_pat = Chem.MolFromSmarts(self._COOH_SMARTS) | |
| self._nh2_pat = Chem.MolFromSmarts(self._NH2_SMARTS) | |
| self._amide_pat = Chem.MolFromSmarts(self._AMIDE_SMARTS) | |
| self._brd4_ms = Chem.MolFromSmarts(self._BRD4_MS_SMARTS) | |
| self._brd4_nh = Chem.MolFromSmarts(self._BRD4_NH_SMARTS) | |
| self._vhl_hyp = Chem.MolFromSmarts(self._VHL_HYP_SMARTS) | |
| super().__init__( | |
| warhead=warhead if warhead is not None else BRD4_WARHEAD_MOL, | |
| e3=e3 if e3 is not None else VHL_E3_MOL, | |
| warhead_name="BRD4 inhibitor (COOH-armed, tetracyclic)", | |
| e3_name="VHL binder NH2 (VH032-type)", | |
| ) | |
| # Warhead side: linker-NH2 + warhead-COOH β secondary amide | |
| self._amide_rxn_wh = AllChem.ReactionFromSmarts( | |
| "[NX3H2:1].[CX3:2](=[OX1:3])[OX2H1]>>[NX3H1:1][CX3:2](=[OX1:3])" | |
| ) | |
| # E3 side (Step 1): VHL-NH2 + tagged linker-COOH β secondary amide | |
| self._amide_rxn_e3 = AllChem.ReactionFromSmarts( | |
| "[NX3H2:1].[CX3:99](=[OX1:3])[OX2H1]>>[NX3H1:1][CX3:99](=[OX1:3])" | |
| ) | |
| # ------------------------------------------------------------------ | |
| # Verification | |
| # ------------------------------------------------------------------ | |
| def _verify_warhead_reactive_group(self): | |
| if self.warhead_mol.HasSubstructMatch(self._cooh_pat): | |
| print(f"β {self.warhead_name}: COOH reactive group confirmed") | |
| else: | |
| print(f"β {self.warhead_name}: no COOH detected β check warhead SMILES") | |
| def _verify_e3_reactive_group(self): | |
| if self.e3_mol.HasSubstructMatch(self._nh2_pat): | |
| print(f"β {self.e3_name}: NH2 reactive group confirmed (tert-leucine)") | |
| else: | |
| print(f"β {self.e3_name}: no NH2 detected β check E3 SMILES") | |
| # ------------------------------------------------------------------ | |
| # Linker attachment site detection | |
| # ------------------------------------------------------------------ | |
| def _reactive_nh2_atoms(self, linker_mol: Chem.Mol) -> list: | |
| """Find primary NH2 atoms on the linker (warhead-side attachment).""" | |
| results, seen = [], set() | |
| for m in linker_mol.GetSubstructMatches(self._nh2_pat): | |
| n_idx = m[0] | |
| if n_idx not in seen: | |
| seen.add(n_idx) | |
| results.append({"idx": n_idx}) | |
| return results | |
| def _reactive_cooh_atoms(self, linker_mol: Chem.Mol) -> list: | |
| """Find COOH carbonyl-C atoms on the linker (VHL-side attachment).""" | |
| results, seen = [], set() | |
| for m in linker_mol.GetSubstructMatches(self._cooh_pat): | |
| c_idx = m[0] | |
| if c_idx not in seen: | |
| seen.add(c_idx) | |
| results.append({"idx": c_idx}) | |
| return results | |
| def find_linker_attachment_sites(self, linker_mol: Chem.Mol, | |
| amide_only: bool = False) -> list: | |
| """ | |
| Return all (NH2 warhead-site, COOH VHL-site) atom index pairs. | |
| Warhead end: primary amine (NH2) that will react with warhead COOH. | |
| VHL end: carboxylic acid (COOH) that will react with VHL NH2. | |
| """ | |
| nh2_cands = self._reactive_nh2_atoms(linker_mol) | |
| cooh_cands = self._reactive_cooh_atoms(linker_mol) | |
| sites, seen = [], set() | |
| for w in nh2_cands: | |
| for e in cooh_cands: | |
| key = (w["idx"], e["idx"]) | |
| if key not in seen: | |
| seen.add(key) | |
| sites.append({ | |
| "warhead_site": w["idx"], | |
| "warhead_type": "nh2", | |
| "warhead_restored": False, | |
| "e3_site": e["idx"], | |
| "e3_type": "cooh", | |
| "e3_restored": False, | |
| }) | |
| return sites | |
| # ------------------------------------------------------------------ | |
| # Assembly: two sequential amide bond formations | |
| # ------------------------------------------------------------------ | |
| def _assemble_protac(self, linker_mol: Chem.Mol, | |
| warhead_site_idx: int, e3_site_idx: int, | |
| warhead_type: str = "nh2", e3_type: str = "cooh", | |
| warhead_restored: bool = False, | |
| e3_restored: bool = False) -> Optional[str]: | |
| """ | |
| Step 1: VHL-NH2 + tagged linker-COOH (e3 site) β secondary amide intermediate. | |
| Step 2: intermediate-NH2 (warhead site) + warhead-COOH β PROTAC (secondary amide). | |
| """ | |
| # Tag the VHL-side COOH carbon on the linker for selective step-1 reaction | |
| rw = Chem.RWMol(linker_mol) | |
| rw.GetAtomWithIdx(e3_site_idx).SetAtomMapNum(99) | |
| try: | |
| Chem.SanitizeMol(rw) | |
| except Exception: | |
| return None | |
| tagged_linker = rw.GetMol() | |
| # Step 1: E3 NH2 + tagged linker COOH β secondary amide | |
| products1 = self._amide_rxn_e3.RunReactants((self.e3_mol, tagged_linker)) | |
| if not products1: | |
| return None | |
| intermediate = None | |
| for prod_tuple in products1: | |
| try: | |
| mol = prod_tuple[0] | |
| Chem.SanitizeMol(mol) | |
| intermediate = mol | |
| break | |
| except Exception: | |
| continue | |
| if intermediate is None: | |
| return None | |
| # Clear atom map tag | |
| rw2 = Chem.RWMol(intermediate) | |
| for atom in rw2.GetAtoms(): | |
| if atom.GetAtomMapNum() == 99: | |
| atom.SetAtomMapNum(0) | |
| intermediate = rw2.GetMol() | |
| # Step 2: intermediate-NH2 + warhead-COOH β secondary amide (PROTAC) | |
| products2 = self._amide_rxn_wh.RunReactants((intermediate, self.warhead_mol)) | |
| if not products2: | |
| return None | |
| for prod_tuple in products2: | |
| try: | |
| protac_mol = prod_tuple[0] | |
| Chem.SanitizeMol(protac_mol) | |
| return Chem.MolToSmiles(protac_mol) | |
| except Exception: | |
| continue | |
| return None | |
| # ------------------------------------------------------------------ | |
| # Validation | |
| # ------------------------------------------------------------------ | |
| def has_warhead_core(self, protac_mol: Chem.Mol) -> bool: | |
| """BRD4 warhead: methylsulfonylmethyl group AND pyrrole-NH in 5-membered ring.""" | |
| return (protac_mol.HasSubstructMatch(self._brd4_ms) and | |
| protac_mol.HasSubstructMatch(self._brd4_nh)) | |
| def has_e3_core(self, protac_mol: Chem.Mol) -> bool: | |
| """VHL binder: hydroxyproline ring.""" | |
| return protac_mol.HasSubstructMatch(self._vhl_hyp) | |
| def has_both_amide_bonds(self, protac_mol: Chem.Mol) -> bool: | |
| """β₯2 amide bonds formed (covers both linker junctions).""" | |
| return len(protac_mol.GetSubstructMatches(self._amide_pat)) >= 2 | |
| def no_unreacted_cooh(self, protac_mol: Chem.Mol) -> bool: | |
| """No free COOH remaining β warhead COOH and linker COOH both consumed.""" | |
| return len(protac_mol.GetSubstructMatches(self._cooh_pat)) == 0 | |
| def check_attachment_site_compatibility(self, protac_mol: Chem.Mol) -> bool: | |
| has_amide = self.has_both_amide_bonds(protac_mol) | |
| no_cooh = self.no_unreacted_cooh(protac_mol) | |
| has_cores = self.has_warhead_core(protac_mol) and self.has_e3_core(protac_mol) | |
| return (has_amide and no_cooh) or has_cores | |
| def _basic_validation(self, protac_mol: Chem.Mol) -> dict: | |
| checks = { | |
| "has_warhead_core": self.has_warhead_core(protac_mol), | |
| "has_e3_core": self.has_e3_core(protac_mol), | |
| "has_both_amide_bonds": self.has_both_amide_bonds(protac_mol), | |
| "no_unreacted_cooh": self.no_unreacted_cooh(protac_mol), | |
| "attachment_site_valid": self.check_attachment_site_compatibility(protac_mol), | |
| } | |
| checks["_passed"] = all([ | |
| checks["has_warhead_core"], | |
| checks["has_e3_core"], | |
| checks["has_both_amide_bonds"], | |
| checks["no_unreacted_cooh"], | |
| ]) | |
| return checks | |
| # --------------------------------------------------------------------------- | |
| # Standard interface aliases | |
| # --------------------------------------------------------------------------- | |
| Builder = BRD4VHL_PROTAC_Builder | |
| WH_REACTIVE_SMARTS = _COOH_SMARTS | |
| E3_REACTIVE_SMARTS = _NH2_SMARTS | |
| WARHEAD_PAT = Chem.MolFromSmarts("CS(C)(=O)=O") # methylsulfonylmethyl (BRD4) | |
| E3_PAT = Chem.MolFromSmarts("N1CC(O)CC1") # hydroxyproline (VHL) | |
| # --------------------------------------------------------------------------- | |
| # Self-test | |
| # --------------------------------------------------------------------------- | |
| if __name__ == "__main__": | |
| from rdkit import RDLogger | |
| from rdkit.Chem import Descriptors | |
| RDLogger.DisableLog("rdApp.error") | |
| print("=" * 70) | |
| print("BRD4-VHL PROTAC Builder β Self-Test (double amide chemistry)") | |
| print(f"Warhead : BRD4 tetracyclic inhibitor with COOH arm") | |
| print(f"E3 : VHL binder (VH032-type, NH2 reactive)") | |
| print(f"Linker : amino-acid type β NH2 (warhead end) + COOH (VHL end)") | |
| print(f"Chemistry: warhead-COOH + linker-NH2 β amide | linker-COOH + VHL-NH2 β amide") | |
| print("=" * 70) | |
| builder = BRD4VHL_PROTAC_Builder() | |
| test_linkers = [ | |
| ("NCCCCCCCCCCC(=O)O", "11-aminoundecanoic acid (PROTAC-DB #1784 reference, MW 201 Da)"), | |
| ("NCCCCCCCC(=O)O", "8-aminooctanoic acid (shorter, valid)"), | |
| ("NCCOCCC(=O)O", "PEG-type amino acid (valid)"), | |
| ("NCCCCCCCCNC(=O)O", "Secondary amine + carbamate β should fail or be unusual"), | |
| ("OC(=O)CCCCC(=O)O", "Diacid β no NH2, should fail"), | |
| ("NCCCCN", "Diamine β no COOH, should fail"), | |
| ("NCCCCCCC(=O)O", "7-aminoheptanoic acid (short valid)"), | |
| ] | |
| print() | |
| for smiles, desc in test_linkers: | |
| linker_mol = Chem.MolFromSmiles(smiles) | |
| print(f"Linker : {desc}") | |
| if linker_mol is None: | |
| print(" [SKIP] unparseable SMILES") | |
| print("-" * 70) | |
| continue | |
| nh2_count = len(linker_mol.GetSubstructMatches(Chem.MolFromSmarts("[NX3H2]"))) | |
| cooh_count = len(linker_mol.GetSubstructMatches(Chem.MolFromSmarts("[CX3](=[OX1])[OX2H1]"))) | |
| print(f" NH2 sites: {nh2_count}, COOH sites: {cooh_count}") | |
| try: | |
| protac_smi = builder.assemble(linker_mol, return_all=False) | |
| if protac_smi: | |
| protac_mol = Chem.MolFromSmiles(protac_smi) | |
| passed, checks = builder.validate_protac(protac_mol) | |
| tag = "VALID β" if passed else "ASSEMBLED but INVALID β" | |
| print(f" Assembly: {tag}") | |
| for k, v in checks.items(): | |
| print(f" {'β' if v else 'β'} {k}") | |
| props = builder.get_protac_properties(protac_mol) | |
| print(f" MW: {props['mw']} Da LogP: {props['logp']} " | |
| f"HeavyAtoms: {props['heavy_atoms']}") | |
| except Exception as e: | |
| print(f" Assembly: FAILED β {e}") | |
| print("-" * 70) | |
| # Validate the reference PROTAC (PROTAC-DB #1784) | |
| print("\nReference PROTAC (PROTAC-DB #1784) validation:") | |
| ref_smi = ("CC1=C(C2=CC=C(CNC(=O)[C@@H]3C[C@@H](O)CN3C(=O)[C@@H](NC(=O)" | |
| "CCCCCCCCCCNC(=O)C3=CC4=C(C=C3CS(C)(=O)=O)C3=CN(C)C(=O)C5=C3" | |
| "C(=C[NH]5)CN4C3=NC=C(F)C=C3F)C(C)(C)C)C=C2)SC=N1") | |
| ref_mol = Chem.MolFromSmiles(ref_smi) | |
| if ref_mol: | |
| mw = Descriptors.MolWt(ref_mol) | |
| val = builder._basic_validation(ref_mol) | |
| print(f" MW = {mw:.1f} Da (expected 1096.3 Da)") | |
| for k, v in val.items(): | |
| if not k.startswith("_"): | |
| print(f" {'β' if v else 'β'} {k}") | |
| print(f" {'β PASSED' if val['_passed'] else 'β FAILED'}") | |
| else: | |
| print(" [ERROR] could not parse reference SMILES") | |