Spaces:
Sleeping
Sleeping
from rdkit import Chem | |
from rdkit.Chem import Descriptors | |
from rdkit.Chem import rdMolDescriptors | |
from rdkit.Chem import RDConfig | |
import os | |
import sys | |
sys.path.append(os.path.join(RDConfig.RDContribDir, 'SA_Score')) | |
sys.path.append(os.path.join(RDConfig.RDContribDir, 'NP_Score')) | |
import sascorer | |
import npscorer | |
import pandas as pd | |
import numpy as np | |
from scopy.ScoFH import fh_filter | |
class Metrics(): | |
def __init__(self,smiles): | |
#load filters and scores | |
if not os.path.isfile('./wehi_pains.csv'): | |
_pains = pd.read_csv('https://raw.githubusercontent.com/rdkit/rdkit/master/Data/Pains/wehi_pains.csv',names=['smarts', 'names']) | |
else: | |
_pains = pd.read_csv('./wehi_pains.csv', | |
names=['smarts', 'names']) | |
self._pains_filters = [Chem.MolFromSmarts(x) for x in | |
_pains['smarts'].values] | |
self.fscore = npscorer.readNPModel() | |
# Decriptors | |
self.smiles = smiles | |
try: | |
mol = Chem.MolFromSmiles(smiles) | |
if mol is None: | |
raise ValueError('SMILES is not valid!') | |
else: | |
self.mol = mol | |
self.h_mol = Chem.AddHs(self.mol) | |
except ValueError as e: | |
print(e) | |
self.logp = Descriptors.MolLogP(self.mol) | |
self.mw = Descriptors.ExactMolWt(self.mol) | |
self.tpsa = Descriptors.TPSA(self.mol) | |
self.n_hba = Descriptors.NumHAcceptors(self.mol) | |
self.n_hbd = Descriptors.NumHDonors(self.mol) | |
pass | |
def ro5(self): | |
# True is good | |
""" | |
Test if input molecule (SMILES) fulfills Lipinski's rule of five. | |
Returns | |
------- | |
int | |
Number of rules fullfilled | |
""" | |
# Check if Ro5 conditions fulfilled | |
conditions = [self.mw <= 500, self.n_hba <= 10, self.n_hbd <= 5, self.logp <= 5, self.tpsa <= 140] | |
ro5_fulfilled = sum(conditions) | |
return ro5_fulfilled | |
def pfizer_rule_passed(self): | |
# True if pass, False if toxic | |
""" | |
Test if input molecule (SMILES) fulfills Pfizer Rule. | |
Returns | |
------- | |
bool | |
Pfizer Rule compliance for input molecule. | |
""" | |
# Check if Pfizer Rule conditions fulfilled | |
conditions = [self.logp > 3, self.tpsa < 75] | |
pfizer_pased = not (sum(conditions) == 2) | |
# Return False if 2 conditions are both fulfilled | |
return pfizer_pased | |
def gsk_rule_passed(self): | |
# True for favorable ADMET | |
""" | |
Test if input molecule (SMILES) fulfills GSK Rule. | |
Returns | |
------- | |
bool | |
GSK Rule compliance for input molecule. | |
""" | |
# Check if GSK Rule conditions fulfilled | |
conditions = [self.mw <= 400, self.logp <= 4] | |
gsk_fulfilled = sum(conditions) == 2 | |
# Return True if 2 conditions are fulfilled | |
return gsk_fulfilled | |
def goldentriangle_rule_passed(self): | |
# True for favorable ADMET | |
""" | |
Test if input molecule (SMILES) fulfills GoldenTriangle Rule. | |
Returns | |
------- | |
bool | |
GoldenTriangle Rule compliance for input molecule. | |
""" | |
# Check if GoldenTrianlge Rule conditions fulfilled | |
conditions = [200 <= self.mw <= 450,-2 <= self.logp <= 5] | |
goldentriangle_fulfilled = sum(conditions) == 2 | |
# Return True if 2 conditions are fulfilled | |
return goldentriangle_fulfilled | |
def qed(self): | |
""" | |
Calculate QED | |
Returns | |
------- | |
numpy.float64 | |
QED for input molecule | |
""" | |
# Calculate QED of input molecule | |
qed = Chem.QED.qed(self.mol) | |
return qed | |
def qed_passed(self): | |
# True if attractive | |
""" | |
Test if input molecule (SMILES) is 'attractive'. | |
Returns | |
------- | |
bool | |
QED 'attractive'-ness. | |
""" | |
# Check if QED conditions fulfilled | |
qed_excellent = self.qed() > 0.67 | |
# Return True if condition is fulfilled | |
return qed_excellent | |
def sascore(self): | |
""" | |
Calculate sascore | |
Returns | |
------- | |
float | |
SAscore for input molecule | |
""" | |
return sascorer.calculateScore(self.mol) | |
def sascore_passed(self): | |
# True if sa pass | |
""" | |
Test if input molecule (SMILES) is easy to synthesize. | |
Returns | |
------- | |
bool | |
synthetic accessibility. | |
""" | |
SAscore_excellent = self.sascore() <= 6 | |
# Return True if condition is fulfilled | |
return SAscore_excellent | |
def fsp3(self): | |
""" | |
Calculate Fsp3 | |
Returns | |
------- | |
float | |
Fsp3 for input molecule | |
""" | |
return Chem.rdMolDescriptors.CalcFractionCSP3(self.mol) | |
def fsp3_passed(self): | |
# True if if input molecule (SMILES) has suitable Fsp3 value. | |
""" | |
Test if input molecule (SMILES) has suitable Fsp3 value. | |
Returns | |
------- | |
bool | |
Fsp3 suitability. | |
""" | |
# Check if Fsp3 condition is fulfilled | |
fsp3_excellent = self.fsp3() >= 0.42 | |
# Return True if condition is fulfilled | |
return fsp3_excellent | |
def pains_filter(self, detail=False): | |
# True if passed | |
# Detail return bool, list name, list atoms | |
""" | |
PAINS filter for an input molecule (SMILES). | |
Returns | |
------- | |
[bool, list, list] | |
[pains_accepted, pains_matched_name, pains_matched_atoms] | |
Check if PAINS not violated and matched names, atoms. | |
""" | |
# Check PAINS | |
pains = fh_filter.Check_PAINS(self.h_mol, detail = True) | |
# pains_accepted = pains['Disposed'] == 'Accepted' # Return True if not violating PAINS | |
# pains_matched_atoms = pains['MatchedAtoms'] | |
# pains_matched_names = pains['MatchedNames'] | |
# Return PAINS | |
if detail: | |
return pains | |
else: | |
return pains['Disposed'] | |
# def pains_passed(self): | |
# h_mol = Chem.AddHs(self.mol) | |
# if any(h_mol.HasSubstructMatch(smarts) for smarts in self._pains_filters): | |
# return False | |
# else: | |
# return True | |
def mce18(self): | |
""" | |
Calculate MCE-18 | |
Returns | |
------- | |
float | |
MCE-18 for input molecule | |
""" | |
# Calculate MCE-18 relevant properties | |
AR = rdMolDescriptors.CalcNumAromaticRings(self.mol) > 0 | |
NAR = rdMolDescriptors.CalcNumAliphaticRings(self.mol) > 0 | |
CHIRAL = len(Chem.FindMolChiralCenters(self.mol, force = True, includeUnassigned = True)) > 0 | |
SPIRO = rdMolDescriptors.CalcNumSpiroAtoms(self.mol) > 0 | |
SP3 = self.fsp3() | |
# Calculate Cyc and Acyc | |
Csp3_cyclic = 0 | |
Csp3_acyclic = 0 | |
C_total = 0 | |
CYC = 0 | |
ACYC = 0 | |
for atom in self.mol.GetAtoms(): | |
if atom.GetAtomicNum() == 6: C_total+=1 | |
if sum([atom.GetAtomicNum() == 6, atom.IsInRing(), atom.GetHybridization() == Chem.HybridizationType.SP3]) == 3: | |
Csp3_cyclic += 1 | |
if sum([atom.GetAtomicNum() == 6, not atom.IsInRing(), atom.GetHybridization() == Chem.HybridizationType.SP3]) == 3: | |
Csp3_acyclic += 1 | |
if C_total>0: | |
CYC = Csp3_cyclic/C_total | |
ACYC = Csp3_acyclic/C_total | |
# Calculate Q1 | |
deltas=[x.GetDegree() for x in self.mol.GetAtoms()] | |
M = sum(np.array(deltas)**2) | |
N = self.mol.GetNumAtoms() | |
Q1 = 3-2*N+M/2.0 | |
# Calculate MCE-18 | |
mce18 = (AR + NAR + CHIRAL + SPIRO + (SP3 + CYC - ACYC)/(1 + SP3))*Q1 | |
return mce18 | |
def mce18_passed(self): | |
# True if interesting | |
""" | |
Test if input molecule (SMILES) is interesting. | |
Returns | |
------- | |
bool | |
MCE-18 suitability. | |
""" | |
# Check if MCE-18 condition is fulfilled | |
mce18_excellent = self.mce18() >= 45 | |
# Return True if condition is fulfilled | |
return mce18_excellent | |
def npscore(self): | |
# [-5,5], higher is more nature-like | |
""" | |
Calculate NPscore of molecule. | |
Returns | |
------- | |
float | |
NPscore for input molecule. | |
""" | |
# Calculate NPscore of input molecule | |
npscore = npscorer.scoreMol(self.mol, self.fscore) | |
# Return NPscore | |
return npscore | |
def alarm_nmr_filter(self,detail=False): | |
# True if passed | |
# Detail return bool, list name, list atoms | |
""" | |
ALARM NMR filter for an input molecule (SMILES). | |
Returns | |
------- | |
[bool, list, list] | |
[alarmnmr_accepted, alarmnmr_matched_names, alarmnmr_matched_atoms] | |
Check if ALARM NMR not violated and matched names, atoms. | |
""" | |
# Check ALARM NMR | |
alarmnmr = fh_filter.Check_Alarm_NMR(self.h_mol, detail = True) | |
# alarmnmr_accepted = alarmnmr['Disposed'] == 'Accepted' # Return True if not violating ALARM NMR | |
# alarmnmr_matched_atoms = alarmnmr['MatchedAtoms'] | |
# alarmnmr_matched_names = alarmnmr['MatchedNames'] | |
# Return ALARM NMR | |
if detail: | |
return alarmnmr | |
else: | |
return alarmnmr['Disposed'] | |
def bms_filter(self,detail=False): | |
# True if passed | |
# Detail return bool, list name, list atoms | |
""" | |
BMS filter for an input molecule (SMILES). | |
Returns | |
------- | |
[bool, list, list] | |
[bms_accepted, bms_matched_names, bms_matched_atoms] | |
Check if BMS not violated and matched names, atoms. | |
""" | |
bms = fh_filter.Check_BMS(self.h_mol, detail = True) | |
# bms_accepted = bms['Disposed'] == 'Accepted' # Return True if not violating BMS | |
# bms_matched_atoms = bms['MatchedAtoms'] | |
# bms_matched_names = bms['MatchedNames'] | |
# Return BMS | |
if detail: | |
return bms | |
else: | |
return bms['Disposed'] | |
def chelator_filter(self, detail=False): | |
""" | |
Chelator filter for an input molecule (SMILES). | |
Returns | |
------- | |
[bool, list, list] | |
[chelator_accepted, chelator_matched_names, chelator_matched_atoms] | |
Check if Chelator not violated and matched names, atoms. | |
""" | |
# Check Chelator | |
chelator = fh_filter.Check_Chelating(self.h_mol, detail = True) | |
# chelator_accepted = chelator['Disposed'] == 'Accepted' # Return True if not violating Chelator | |
# chelator_matched_atoms = chelator['MatchedAtoms'] | |
# chelator_matched_names = chelator['MatchedNames'] | |
# Return Chelator | |
if detail: | |
return chelator | |
else: | |
return chelator['Disposed'] | |
def calculate_all(self, descriptors = True,rules=True,scores = True,scores_passed = True,filters = True,detail = True): | |
""" | |
Calculate all rules. | |
Parameters | |
---------- | |
smiles : str | |
SMILES for a molecule. | |
descriptors : bool | |
Extract molecular descriptors of molecule. Default is 'False'. | |
Returns | |
------- | |
pandas.Series | |
All rules w/wo descriptors. | |
""" | |
# Calculate all rules of molecule | |
result = dict() | |
descrip_dict ={ | |
'logp':self.logp, | |
'mw':self.mw, | |
'tpsa':self.tpsa, | |
'n_hba':self.n_hba, | |
'n_hbd':self.n_hbd | |
} | |
rule_dict = { | |
'ro5':self.ro5, | |
'pfizer_rule_passed':self.pfizer_rule_passed, | |
'gsk_rule_passed':self.gsk_rule_passed, | |
'goldentriangle_rule':self.goldentriangle_rule_passed | |
} | |
score_dict ={ | |
'qed':self.qed, | |
'sascore' : self.sascore, | |
'fsp3' : self.fsp3, | |
'mce18' : self.mce18, | |
'npscore' : self.npscore | |
} | |
score_pass_dict = { | |
'qed_passed' : self.qed_passed, | |
'sascore_passed' : self.sascore_passed, | |
'fsp3_passed' : self.fsp3_passed, | |
'mce18_passed' : self.mce18_passed | |
} | |
filter_dict = { | |
'pains_filter' : self.pains_filter, | |
'alarm_nmr_filter' : self.alarm_nmr_filter, | |
'bms_filter' : self.bms_filter, | |
'chelator_filter' : self.chelator_filter | |
} | |
if descriptors: | |
for name, func in descrip_dict.items(): | |
result[name] = func | |
if rules: | |
for name, func in rule_dict.items(): | |
result[name] = func() | |
if scores: | |
for name, func in score_dict.items(): | |
result[name] = func() | |
if scores_passed: | |
for name, func in score_pass_dict.items(): | |
result[name] = func() | |
if filters: | |
for name, func in filter_dict.items(): | |
result[name] = func(detail=detail) | |
return result | |