""" 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")