OkayBoboLi's picture
Add the PROTAC release
f12d3b3 verified
Raw History Blame Contribute Delete
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")