File size: 6,519 Bytes
6c9c7cf bacc633 6c9c7cf c6997ea 608b51d c6997ea 608b51d bacc633 e21c3f3 608b51d bacc633 608b51d e97c3c4 c429a13 e21c3f3 c429a13 e5c6eb7 ebbf57a 608b51d bacc633 6c9c7cf c6997ea 6c9c7cf 608b51d c429a13 6c9c7cf | 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 | import sys
import pickle
import functools
import numpy as np
import pandas as pd
import sklearn
from quantile_forest import RandomForestQuantileRegressor
import mordred
import mordred.descriptors
import rdkit
from rdkit import Chem
# Build a single shared Calculator and descriptor lookup once at startup.
# This is cheaper than re-instantiating mordred.Calculator on every request.
_calc_all = mordred.Calculator(mordred.descriptors)
_desc_by_name = {str(d): d for d in _calc_all.descriptors}
QRF_T_list = np.array([25,30,35,37,40,45,50,55,60,65,70,75])
QRF_T_cut = 2.5
df_QRF = pd.read_parquet('qrf/db-D-interp-allT-semiclean.parquet')
df_desc = pd.read_parquet('qrf/mordred-descriptors.parquet')
# Use column names from df_desc directly instead of initializing a full mordred Calculator
colnames_mordred = [c for c in df_desc.columns if c != 'Solute_InChIKey']
df_QRF = pd.merge(df_QRF, df_desc[['Solute_InChIKey',*colnames_mordred]], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
@functools.lru_cache(maxsize=None)
def _load_qrf_model(T):
"""Load and cache QRF model bundle for a given temperature."""
with open(f'qrf/qrf_model_bundle_{int(T)}.pkl', 'rb') as f:
return pickle.load(f)
def QRF_Ceramic(density, polytg, quantiles=[0.03,0.5,0.97], T=37, worstcase='hi'):
nearest_T = QRF_T_list[np.abs(T-QRF_T_list).argmin()]
reg, imp, scaler_X, sub_desc_list = _load_qrf_model(nearest_T)
#df_X = pd.read_excel('qrf/qrf_x.xlsx')
#df_y = pd.read_excel('qrf/qrf_y.xlsx')
mask_T = (df_QRF['T']>nearest_T-QRF_T_cut) & (df_QRF['T']<nearest_T+QRF_T_cut)
df_X = df_QRF.loc[mask_T, sub_desc_list]
df_y = df_QRF.loc[mask_T, ['LogD']]
X_all = imp.transform(df_X)
X_all_scale = scaler_X.transform(X_all)
## use "worst-case" solute values
if worstcase == 'hi':
tmpq = np.array([0.95]*len(sub_desc_list))
tmpq[df_X.corrwith(df_y['LogD'])>0] = 0.05 # positive correlations (increase in variable increases D) use low values of variable, negative correlations use high values of variable
elif worstcase == 'lo':
tmpq = np.array([0.05]*len(sub_desc_list))
tmpq[df_X.corrwith(df_y['LogD'])>0] = 0.95 # inverse of above
tmpv = [np.nanquantile(X_all_scale[:,i], q) for i,q in enumerate(tmpq)] # "worst-case" values of scaled descriptors
tmps = [polytg if n == 'Polymer_Tg' else (density if n == 'Polymer_Density' else 0) for i,n in enumerate(sub_desc_list)]
tmps = scaler_X.transform([tmps])[0] # scaled values of polymer descriptors
tmpv = [tmps[i] if n == 'Polymer_Tg' else (tmps[i] if n == 'Polymer_Density' else tmpv[i]) for i,n in enumerate(sub_desc_list)] # merge scaled polymer descriptors with worst-case scaled solute descriptors
LogD_pred = reg.predict([tmpv], quantiles=quantiles)
D_pred = 10**LogD_pred
if len(D_pred.shape)>1:
# return 1D array regardless of quantiles setting
D_pred = D_pred[0]
## domain extrapolation check
dij = QRF_DomainExtrap(reg, X_all_scale, np.array([tmpv]))
domain_extrap = dij[0] > 0
return D_pred, domain_extrap
def QRF_Apply(density, polytg, smiles, quantiles=[0.03,0.5,0.97], T=37):
"""T and Tg in C"""
nearest_T = QRF_T_list[np.abs(T-QRF_T_list).argmin()]
reg, imp, scaler_X, sub_desc_list = _load_qrf_model(nearest_T)
# get list of descriptors to calculate
solute_desc_list = sub_desc_list.copy()
if 'Polymer_Tg' in solute_desc_list:
solute_desc_list.remove('Polymer_Tg')
if 'Polymer_Density' in solute_desc_list:
solute_desc_list.remove('Polymer_Density')
desc_funcs = {d: _desc_by_name[d] for d in solute_desc_list if d in _desc_by_name}
calc = mordred.Calculator(list(desc_funcs.values()))
m = Chem.MolFromSmiles(smiles)
m = Chem.AddHs(m)
descs_mordred = calc(m)
desc_vals = list(descs_mordred.values())
desc_vals = [d if (isinstance(d,float) or isinstance(d,int)) else np.nan for d in desc_vals] # filter out mordred errors
desc_vals = dict(zip(solute_desc_list,desc_vals))
descs = [polytg if n == 'Polymer_Tg' else (density if n == 'Polymer_Density' else desc_vals[n]) for n in sub_desc_list]
#descs = imp.transform([descs])
descs = imp.transform(pd.DataFrame([descs],columns=sub_desc_list)) # avoid warning about missing column names
descs_scale = scaler_X.transform(descs)
LogD_pred = reg.predict(descs_scale, quantiles=quantiles)
D_pred = 10**LogD_pred
if len(D_pred.shape)>1:
# return 1D array regardless of quantiles setting
D_pred = D_pred[0]
## domain extrapolation check
#df_X = pd.read_excel('qrf/qrf_x.xlsx')
mask_T = (df_QRF['T']>nearest_T-QRF_T_cut) & (df_QRF['T']<nearest_T+QRF_T_cut)
df_X = df_QRF.loc[mask_T, sub_desc_list]
X_all = imp.transform(df_X)
X_all_scale = scaler_X.transform(X_all)
dij = QRF_DomainExtrap(reg, X_all_scale, descs_scale)
domain_extrap = dij[0] > 0
return D_pred, domain_extrap
def QRF_DomainExtrap(estimator, X_train, X_test, D=0.0, return_features=False):
X_min = X_train.min(axis=0)
X_max = X_train.max(axis=0)
X_range = X_max-X_min
dij = np.zeros(len(X_test))
if return_features: dij_feats = {i:[] for i in range(len(X_test))}
for tree in estimator.estimators_:
feature = tree.tree_.feature
node_indicator = tree.decision_path(X_test)
node_indicator = node_indicator.toarray().astype(bool)
#node_indicator = node_indicator.A.astype(bool)
feats = [feature[r][:-1] for r in node_indicator]
mins = [np.array([X_min[i] for i in f]) for f in feats]
maxs = [np.array([X_max[i] for i in f]) for f in feats]
ranges = [np.array([X_range[i] for i in f]) for f in feats]
vals = [X_test[i][feats[i]] for i in range(len(feats))]
for i in range(len(X_test)):
dmax = (vals[i]-maxs[i])/ranges[i] + D
dmin = (mins[i]-vals[i])/ranges[i] + D
dmax[dmax<0] = 0
dmin[dmin<0] = 0
dij[i] += np.sum(dmax**2)
dij[i] += np.sum(dmin**2)
if return_features:
if any(dmax>0):
dij_feats[i].extend(list(feats[i][dmax>0]))
if any(dmin>0):
dij_feats[i].extend(list(feats[i][dmin>0]))
dij = dij**0.5
if return_features:
dij_feats = [list(set(dij_feats[i])) if len(dij_feats[i]) else None for i in range(len(X_test))]
return dij, dij_feats
else:
return dij
|