Spaces:
Paused
Paused
File size: 5,715 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 | """
In Silico Lead Optimization & Molecular Analog Generator.
Systematically generates bioisosteric and R-group substituted chemical analogs,
evaluates property shifts (Delta logS, Delta BBBP, Delta Tox), and discovers Pareto-optimal leads.
"""
from typing import List, Dict, Any, Optional
from rdkit import Chem, RDLogger
from rdkit.Chem import AllChem, Descriptors, QED
from src.admet import compute_admet_profile
from src.ensemble import predict_ensemble
RDLogger.DisableLog('rdApp.*')
# Common medicinal chemistry transformation rules
MEDCHEM_RULES = [
# Aromatic functional group replacements
{"name": "+Fluorine (-F)", "smarts": "[c:1][H]", "repl": "[c:1]F", "rationale": "Improves metabolic stability & lipophilicity"},
{"name": "+Chlorine (-Cl)", "smarts": "[c:1][H]", "repl": "[c:1]Cl", "rationale": "Enhances binding cavity packing"},
{"name": "+Methyl (-CH3)", "smarts": "[c:1][H]", "repl": "[c:1]C", "rationale": "Fills hydrophobic subpocket"},
{"name": "+Trifluoromethyl (-CF3)", "smarts": "[c:1][H]", "repl": "[c:1]C(F)(F)F", "rationale": "Strongly enhances lipophilic binding"},
{"name": "+Hydroxyl (-OH)", "smarts": "[c:1][H]", "repl": "[c:1]O", "rationale": "Improves aqueous solubility (H-bond donor)"},
{"name": "+Methoxy (-OCH3)", "smarts": "[c:1][H]", "repl": "[c:1]OC", "rationale": "H-bond acceptor without acidic proton"},
{"name": "+Amino (-NH2)", "smarts": "[c:1][H]", "repl": "[c:1]N", "rationale": "Basic center, enhances ionic interactions"},
{"name": "+Cyano (-C#N)", "smarts": "[c:1][H]", "repl": "[c:1]C#N", "rationale": "Linear polar group, bioisosteric to halide"},
{"name": "+Methylsulfonyl (-SO2CH3)", "smarts": "[c:1][H]", "repl": "[c:1]S(=O)(=O)C", "rationale": "High polarity, decreases lipophilicity"},
# Functional group bioisosteres
{"name": "Ester -> Amide", "smarts": "[C:1](=O)[O:2][C:3]", "repl": "[C:1](=O)[N:2][C:3]", "rationale": "Improves hydrolytic stability in plasma"},
{"name": "Acid -> Tetrazole (Bioisostere)", "smarts": "[C:1](=O)[OH]", "repl": "[C:1]c1nnnn1", "rationale": "Non-classical carboxylic acid bioisostere"},
{"name": "Phenol -> Fluoro-benzene", "smarts": "[c:1][OH]", "repl": "[c:1]F", "rationale": "Blocks Phase-II glucuronidation metabolism"}
]
def generate_analogs(parent_smiles: str, max_analogs: int = 15) -> List[Dict[str, Any]]:
"""
Generate chemically plausible analogs of the parent molecule.
Args:
parent_smiles: SMILES string of the lead compound.
max_analogs: Maximum number of distinct valid analogs to generate.
Returns:
List of generated analog dictionaries with SMILES and transformation details.
"""
mol = Chem.MolFromSmiles(parent_smiles)
if mol is None:
return []
# Explicit hydrogens for aromatic replacement rules
mol_with_h = Chem.AddHs(mol)
generated = {}
for rule in MEDCHEM_RULES:
rxn_smarts = f"({rule['smarts']})>>({rule['repl']})"
try:
rxn = AllChem.ReactionFromSmarts(rxn_smarts)
# Try on mol or mol_with_h
reactants = (mol_with_h,) if "[H]" in rule['smarts'] else (mol,)
products = rxn.RunReactants(reactants)
for prod_tuple in products:
prod = prod_tuple[0]
try:
prod = Chem.RemoveHs(prod)
Chem.SanitizeMol(prod)
smi = Chem.MolToSmiles(prod)
# Filter out identity or duplicates
if smi and smi != parent_smiles and smi not in generated:
generated[smi] = {
"smiles": smi,
"transformation": rule["name"],
"rationale": rule["rationale"]
}
if len(generated) >= max_analogs:
break
except Exception:
continue
except Exception:
continue
if len(generated) >= max_analogs:
break
return list(generated.values())
def evaluate_lead_optimization(
parent_smiles: str,
target_dataset: str = "esol",
max_analogs: int = 12
) -> Dict[str, Any]:
"""
Generate analogs and evaluate property shifts against the parent compound.
"""
parent_admet = compute_admet_profile(parent_smiles)
parent_pred = predict_ensemble(parent_smiles, target_dataset)
parent_val = float(parent_pred["mean"][0])
analogs = generate_analogs(parent_smiles, max_analogs=max_analogs)
analog_results = []
for item in analogs:
smi = item["smiles"]
try:
admet = compute_admet_profile(smi)
pred = predict_ensemble(smi, target_dataset)
val = float(pred["mean"][0])
delta = val - parent_val
analog_results.append({
"SMILES": smi,
"Transformation": item["transformation"],
"Rationale": item["rationale"],
"Predicted Value": val,
"Delta": delta,
"QED": admet["descriptors"]["QED"],
"MW": admet["descriptors"]["Molecular Weight"],
"LogP": admet["descriptors"]["LogP"],
"TPSA": admet["descriptors"]["TPSA"],
"Lipinski Pass": admet["filters"]["Lipinski"]["passed"]
})
except Exception:
continue
return {
"parent_smiles": parent_smiles,
"parent_val": parent_val,
"parent_qed": parent_admet["descriptors"]["QED"],
"target_dataset": target_dataset,
"analogs": analog_results
}
|