Spaces:
Paused
Paused
File size: 6,743 Bytes
2fd4f2f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
ADMET & In Silico Drug-Likeness Profiling Engine.
Computes physicochemical descriptors, Lipinski Rule of 5, Veber, Ghose, Egan,
QED, PAINS filter alerts, Brenk toxicophores, and Bioavailability Radar coordinates.
"""
from typing import Dict, Any, List, Tuple, Optional
import numpy as np
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski, Crippen, QED
from rdkit.Chem.FilterCatalog import FilterCatalog, FilterCatalogParams
# Initialize FilterCatalogs for PAINS and BRENK structural alerts
_pains_params = FilterCatalogParams()
_pains_params.AddCatalog(FilterCatalogParams.FilterCatalogs.PAINS)
PAINS_CATALOG = FilterCatalog(_pains_params)
_brenk_params = FilterCatalogParams()
_brenk_params.AddCatalog(FilterCatalogParams.FilterCatalogs.BRENK)
BRENK_CATALOG = FilterCatalog(_brenk_params)
def compute_admet_profile(mol_or_smiles: Any) -> Dict[str, Any]:
"""
Compute comprehensive in silico ADMET and drug-likeness profile.
Args:
mol_or_smiles: RDKit Mol object or SMILES string.
Returns:
Dictionary containing descriptors, rule evaluations, alerts, and scores.
"""
if isinstance(mol_or_smiles, str):
mol = Chem.MolFromSmiles(mol_or_smiles)
else:
mol = mol_or_smiles
if mol is None:
raise ValueError("Invalid molecule provided for ADMET calculation.")
# 1. Fundamental Physicochemical Descriptors
mw = float(Descriptors.MolWt(mol))
logp = float(Crippen.MolLogP(mol))
tpsa = float(Descriptors.TPSA(mol))
hbd = int(Lipinski.NumHDonors(mol))
hba = int(Lipinski.NumHAcceptors(mol))
rot_bonds = int(Lipinski.NumRotatableBonds(mol))
heavy_atoms = int(mol.GetNumHeavyAtoms())
aromatic_rings = int(Lipinski.NumAromaticRings(mol))
fraction_csp3 = float(Descriptors.FractionCSP3(mol))
molar_refractivity = float(Crippen.MolMR(mol))
qed_score = float(QED.qed(mol))
# Synthetic Accessibility Approximation (heuristic based on complexity, rings, chiral centers)
num_chiral = len(Chem.FindMolChiralCenters(mol, includeUnassigned=True))
num_rings = Descriptors.RingCount(mol)
bertz = Descriptors.BertzCT(mol)
# Normalized complexity heuristic between 1.0 (very easy) and 10.0 (very hard)
sascore_est = min(10.0, max(1.0, 1.0 + (bertz / 250.0) + (num_chiral * 0.4) + (max(0, num_rings - 2) * 0.5)))
# 2. Rule of 5 (Lipinski)
lipinski_violations = []
if mw > 500:
lipinski_violations.append(f"MW > 500 ({mw:.1f} Da)")
if logp > 5.0:
lipinski_violations.append(f"LogP > 5.0 ({logp:.2f})")
if hbd > 5:
lipinski_violations.append(f"HBD > 5 ({hbd})")
if hba > 10:
lipinski_violations.append(f"HBA > 10 ({hba})")
lipinski_pass = len(lipinski_violations) <= 1
# 3. Veber Rule (Oral Bioavailability)
veber_violations = []
if rot_bonds > 10:
veber_violations.append(f"Rotatable Bonds > 10 ({rot_bonds})")
if tpsa > 140.0:
veber_violations.append(f"TPSA > 140 Ų ({tpsa:.1f})")
veber_pass = len(veber_violations) == 0
# 4. Ghose Filter
ghose_violations = []
if not (160 <= mw <= 480):
ghose_violations.append(f"MW not in [160, 480] ({mw:.1f})")
if not (-0.4 <= logp <= 5.6):
ghose_violations.append(f"LogP not in [-0.4, 5.6] ({logp:.2f})")
if not (40 <= molar_refractivity <= 130):
ghose_violations.append(f"MR not in [40, 130] ({molar_refractivity:.1f})")
if not (20 <= heavy_atoms <= 70):
ghose_violations.append(f"Heavy atoms not in [20, 70] ({heavy_atoms})")
ghose_pass = len(ghose_violations) == 0
# 5. Egan Filter (Absorption)
egan_violations = []
if logp > 5.88:
egan_violations.append(f"LogP > 5.88 ({logp:.2f})")
if tpsa > 131.6:
egan_violations.append(f"TPSA > 131.6 ({tpsa:.1f})")
egan_pass = len(egan_violations) == 0
# 6. PAINS Filter (Pan-Assay Interference Compounds)
pains_matches = []
for entry in PAINS_CATALOG.GetMatches(mol):
pains_matches.append(entry.GetDescription())
# 7. Brenk Filter (Toxicophores & Reactive Groups)
brenk_matches = []
for entry in BRENK_CATALOG.GetMatches(mol):
brenk_matches.append(entry.GetDescription())
# 8. Bioavailability Radar Coordinates (Normalized to [0, 1] relative to drug-like box)
# Axes: LIPO (LogP), SIZE (MW), POLAR (TPSA), INSOLU (LogS proxy/LogP), INSATU (FractionCsp3), FLEX (RotBonds)
# Optimal ranges:
# LIPO: -0.7 to 5.0
# SIZE: 150 to 500
# POLAR: 20 to 130
# INSOLU: 0 to 6
# INSATU: 0.25 to 1.0
# FLEX: 0 to 9
radar_coords = {
"LIPO": float(np.clip((logp - (-0.7)) / (5.0 - (-0.7)), 0.05, 1.2)),
"SIZE": float(np.clip((mw - 150) / (500 - 150), 0.05, 1.2)),
"POLAR": float(np.clip((tpsa - 20) / (130 - 20), 0.05, 1.2)),
"INSOLU": float(np.clip(max(0, logp) / 6.0, 0.05, 1.2)),
"INSATU": float(np.clip(fraction_csp3, 0.05, 1.2)),
"FLEX": float(np.clip(rot_bonds / 9.0, 0.05, 1.2))
}
return {
"descriptors": {
"Molecular Weight": mw,
"LogP": logp,
"TPSA": tpsa,
"H-Bond Donors": hbd,
"H-Bond Acceptors": hba,
"Rotatable Bonds": rot_bonds,
"Heavy Atoms": heavy_atoms,
"Aromatic Rings": aromatic_rings,
"Fraction Csp3": fraction_csp3,
"Molar Refractivity": molar_refractivity,
"QED": qed_score,
"SA Score (est)": sascore_est
},
"filters": {
"Lipinski": {
"passed": lipinski_pass,
"num_violations": len(lipinski_violations),
"violations": lipinski_violations
},
"Veber": {
"passed": veber_pass,
"num_violations": len(veber_violations),
"violations": veber_violations
},
"Ghose": {
"passed": ghose_pass,
"num_violations": len(ghose_violations),
"violations": ghose_violations
},
"Egan": {
"passed": egan_pass,
"num_violations": len(egan_violations),
"violations": egan_violations
}
},
"alerts": {
"PAINS": {
"has_alerts": len(pains_matches) > 0,
"count": len(pains_matches),
"descriptions": pains_matches
},
"Brenk": {
"has_alerts": len(brenk_matches) > 0,
"count": len(brenk_matches),
"descriptions": brenk_matches
}
},
"radar": radar_coords
}
|