0
|
1 __all__ = ['weights_max', 'weights_mean', 'weights_none', 'default']
|
|
2
|
|
3 # Silicos-it
|
|
4 from errors import WrongArgument
|
|
5
|
|
6 # RDKit
|
|
7 from rdkit.Chem import Descriptors
|
|
8 from rdkit import Chem
|
|
9
|
|
10 # General
|
|
11 from copy import deepcopy
|
|
12 from math import exp, log
|
|
13 import sys, os, re
|
|
14 import argparse
|
|
15
|
|
16 def check_filetype(filepath):
|
|
17 mol = False
|
|
18 for line in open(filepath):
|
|
19 if line.find('$$$$') != -1:
|
|
20 return 'sdf'
|
|
21 elif line.find('@<TRIPOS>MOLECULE') != -1:
|
|
22 return 'mol2'
|
|
23 elif line.find('ligand id') != -1:
|
|
24 return 'drf'
|
|
25 elif re.findall('^InChI=', line):
|
|
26 return 'inchi'
|
|
27 elif re.findall('^M\s+END', line):
|
|
28 mol = True
|
|
29
|
|
30 if mol:
|
|
31 # END can occures before $$$$, so and SDF file will
|
|
32 # be recognised as mol, if you not using this hack'
|
|
33 return 'mol'
|
|
34 return 'smi'
|
|
35
|
|
36 AliphaticRings = Chem.MolFromSmarts('[$([A;R][!a])]')
|
|
37
|
|
38 AcceptorSmarts = [
|
|
39 '[oH0;X2]',
|
|
40 '[OH1;X2;v2]',
|
|
41 '[OH0;X2;v2]',
|
|
42 '[OH0;X1;v2]',
|
|
43 '[O-;X1]',
|
|
44 '[SH0;X2;v2]',
|
|
45 '[SH0;X1;v2]',
|
|
46 '[S-;X1]',
|
|
47 '[nH0;X2]',
|
|
48 '[NH0;X1;v3]',
|
|
49 '[$([N;+0;X3;v3]);!$(N[C,S]=O)]'
|
|
50 ]
|
|
51 Acceptors = []
|
|
52 for hba in AcceptorSmarts:
|
|
53 Acceptors.append(Chem.MolFromSmarts(hba))
|
|
54
|
|
55 StructuralAlertSmarts = [
|
|
56 '*1[O,S,N]*1',
|
|
57 '[S,C](=[O,S])[F,Br,Cl,I]',
|
|
58 '[CX4][Cl,Br,I]',
|
|
59 '[C,c]S(=O)(=O)O[C,c]',
|
|
60 '[$([CH]),$(CC)]#CC(=O)[C,c]',
|
|
61 '[$([CH]),$(CC)]#CC(=O)O[C,c]',
|
|
62 'n[OH]',
|
|
63 '[$([CH]),$(CC)]#CS(=O)(=O)[C,c]',
|
|
64 'C=C(C=O)C=O',
|
|
65 'n1c([F,Cl,Br,I])cccc1',
|
|
66 '[CH1](=O)',
|
|
67 '[O,o][O,o]',
|
|
68 '[C;!R]=[N;!R]',
|
|
69 '[N!R]=[N!R]',
|
|
70 '[#6](=O)[#6](=O)',
|
|
71 '[S,s][S,s]',
|
|
72 '[N,n][NH2]',
|
|
73 'C(=O)N[NH2]',
|
|
74 '[C,c]=S',
|
|
75 '[$([CH2]),$([CH][CX4]),$(C([CX4])[CX4])]=[$([CH2]),$([CH][CX4]),$(C([CX4])[CX4])]',
|
|
76 'C1(=[O,N])C=CC(=[O,N])C=C1',
|
|
77 'C1(=[O,N])C(=[O,N])C=CC=C1',
|
|
78 'a21aa3a(aa1aaaa2)aaaa3',
|
|
79 'a31a(a2a(aa1)aaaa2)aaaa3',
|
|
80 'a1aa2a3a(a1)A=AA=A3=AA=A2',
|
|
81 'c1cc([NH2])ccc1',
|
|
82 '[Hg,Fe,As,Sb,Zn,Se,se,Te,B,Si,Na,Ca,Ge,Ag,Mg,K,Ba,Sr,Be,Ti,Mo,Mn,Ru,Pd,Ni,Cu,Au,Cd,Al,Ga,Sn,Rh,Tl,Bi,Nb,Li,Pb,Hf,Ho]',
|
|
83 'I',
|
|
84 'OS(=O)(=O)[O-]',
|
|
85 '[N+](=O)[O-]',
|
|
86 'C(=O)N[OH]',
|
|
87 'C1NC(=O)NC(=O)1',
|
|
88 '[SH]',
|
|
89 '[S-]',
|
|
90 'c1ccc([Cl,Br,I,F])c([Cl,Br,I,F])c1[Cl,Br,I,F]',
|
|
91 'c1cc([Cl,Br,I,F])cc([Cl,Br,I,F])c1[Cl,Br,I,F]',
|
|
92 '[CR1]1[CR1][CR1][CR1][CR1][CR1][CR1]1',
|
|
93 '[CR1]1[CR1][CR1]cc[CR1][CR1]1',
|
|
94 '[CR2]1[CR2][CR2][CR2][CR2][CR2][CR2][CR2]1',
|
|
95 '[CR2]1[CR2][CR2]cc[CR2][CR2][CR2]1',
|
|
96 '[CH2R2]1N[CH2R2][CH2R2][CH2R2][CH2R2][CH2R2]1',
|
|
97 '[CH2R2]1N[CH2R2][CH2R2][CH2R2][CH2R2][CH2R2][CH2R2]1',
|
|
98 'C#C',
|
|
99 '[OR2,NR2]@[CR2]@[CR2]@[OR2,NR2]@[CR2]@[CR2]@[OR2,NR2]',
|
|
100 '[$([N+R]),$([n+R]),$([N+]=C)][O-]',
|
|
101 '[C,c]=N[OH]',
|
|
102 '[C,c]=NOC=O',
|
|
103 '[C,c](=O)[CX4,CR0X3,O][C,c](=O)',
|
|
104 'c1ccc2c(c1)ccc(=O)o2',
|
|
105 '[O+,o+,S+,s+]',
|
|
106 'N=C=O',
|
|
107 '[NX3,NX4][F,Cl,Br,I]',
|
|
108 'c1ccccc1OC(=O)[#6]',
|
|
109 '[CR0]=[CR0][CR0]=[CR0]',
|
|
110 '[C+,c+,C-,c-]',
|
|
111 'N=[N+]=[N-]',
|
|
112 'C12C(NC(N1)=O)CSC2',
|
|
113 'c1c([OH])c([OH,NH2,NH])ccc1',
|
|
114 'P',
|
|
115 '[N,O,S]C#N',
|
|
116 'C=C=O',
|
|
117 '[Si][F,Cl,Br,I]',
|
|
118 '[SX2]O',
|
|
119 '[SiR0,CR0](c1ccccc1)(c2ccccc2)(c3ccccc3)',
|
|
120 'O1CCCCC1OC2CCC3CCCCC3C2',
|
|
121 'N=[CR0][N,n,O,S]',
|
|
122 '[cR2]1[cR2][cR2]([Nv3X3,Nv4X4])[cR2][cR2][cR2]1[cR2]2[cR2][cR2][cR2]([Nv3X3,Nv4X4])[cR2][cR2]2',
|
|
123 'C=[C!r]C#N',
|
|
124 '[cR2]1[cR2]c([N+0X3R0,nX3R0])c([N+0X3R0,nX3R0])[cR2][cR2]1',
|
|
125 '[cR2]1[cR2]c([N+0X3R0,nX3R0])[cR2]c([N+0X3R0,nX3R0])[cR2]1',
|
|
126 '[cR2]1[cR2]c([N+0X3R0,nX3R0])[cR2][cR2]c1([N+0X3R0,nX3R0])',
|
|
127 '[OH]c1ccc([OH,NH2,NH])cc1',
|
|
128 'c1ccccc1OC(=O)O',
|
|
129 '[SX2H0][N]',
|
|
130 'c12ccccc1(SC(S)=N2)',
|
|
131 'c12ccccc1(SC(=S)N2)',
|
|
132 'c1nnnn1C=O',
|
|
133 's1c(S)nnc1NC=O',
|
|
134 'S1C=CSC1=S',
|
|
135 'C(=O)Onnn',
|
|
136 'OS(=O)(=O)C(F)(F)F',
|
|
137 'N#CC[OH]',
|
|
138 'N#CC(=O)',
|
|
139 'S(=O)(=O)C#N',
|
|
140 'N[CH2]C#N',
|
|
141 'C1(=O)NCC1',
|
|
142 'S(=O)(=O)[O-,OH]',
|
|
143 'NC[F,Cl,Br,I]',
|
|
144 'C=[C!r]O',
|
|
145 '[NX2+0]=[O+0]',
|
|
146 '[OR0,NR0][OR0,NR0]',
|
|
147 'C(=O)O[C,H1].C(=O)O[C,H1].C(=O)O[C,H1]',
|
|
148 '[CX2R0][NX3R0]',
|
|
149 'c1ccccc1[C;!R]=[C;!R]c2ccccc2',
|
|
150 '[NX3R0,NX4R0,OR0,SX2R0][CX4][NX3R0,NX4R0,OR0,SX2R0]',
|
|
151 '[s,S,c,C,n,N,o,O]~[n+,N+](~[s,S,c,C,n,N,o,O])(~[s,S,c,C,n,N,o,O])~[s,S,c,C,n,N,o,O]',
|
|
152 '[s,S,c,C,n,N,o,O]~[nX3+,NX3+](~[s,S,c,C,n,N])~[s,S,c,C,n,N]',
|
|
153 '[*]=[N+]=[*]',
|
|
154 '[SX3](=O)[O-,OH]',
|
|
155 'N#N',
|
|
156 'F.F.F.F',
|
|
157 '[R0;D2][R0;D2][R0;D2][R0;D2]',
|
|
158 '[cR,CR]~C(=O)NC(=O)~[cR,CR]',
|
|
159 'C=!@CC=[O,S]',
|
|
160 '[#6,#8,#16][C,c](=O)O[C,c]',
|
|
161 'c[C;R0](=[O,S])[C,c]',
|
|
162 'c[SX2][C;!R]',
|
|
163 'C=C=C',
|
|
164 'c1nc([F,Cl,Br,I,S])ncc1',
|
|
165 'c1ncnc([F,Cl,Br,I,S])c1',
|
|
166 'c1nc(c2c(n1)nc(n2)[F,Cl,Br,I])',
|
|
167 '[C,c]S(=O)(=O)c1ccc(cc1)F',
|
|
168 '[15N]',
|
|
169 '[13C]',
|
|
170 '[18O]',
|
|
171 '[34S]'
|
|
172 ]
|
|
173
|
|
174 StructuralAlerts = []
|
|
175 for smarts in StructuralAlertSmarts:
|
|
176 StructuralAlerts.append(Chem.MolFromSmarts(smarts))
|
|
177
|
|
178
|
|
179 # ADS parameters for the 8 molecular properties: [row][column]
|
|
180 # rows[8]: MW, ALOGP, HBA, HBD, PSA, ROTB, AROM, ALERTS
|
|
181 # columns[7]: A, B, C, D, E, F, DMAX
|
|
182 # ALOGP parameters from Gregory Gerebtzoff (2012, Roche)
|
|
183 pads1 = [ [2.817065973, 392.5754953, 290.7489764, 2.419764353, 49.22325677, 65.37051707, 104.9805561],
|
|
184 [0.486849448, 186.2293718, 2.066177165, 3.902720615, 1.027025453, 0.913012565, 145.4314800],
|
|
185 [2.948620388, 160.4605972, 3.615294657, 4.435986202, 0.290141953, 1.300669958, 148.7763046],
|
|
186 [1.618662227, 1010.051101, 0.985094388, 0.000000001, 0.713820843, 0.920922555, 258.1632616],
|
|
187 [1.876861559, 125.2232657, 62.90773554, 87.83366614, 12.01999824, 28.51324732, 104.5686167],
|
|
188 [0.010000000, 272.4121427, 2.558379970, 1.565547684, 1.271567166, 2.758063707, 105.4420403],
|
|
189 [3.217788970, 957.7374108, 2.274627939, 0.000000001, 1.317690384, 0.375760881, 312.3372610],
|
|
190 [0.010000000, 1199.094025, -0.09002883, 0.000000001, 0.185904477, 0.875193782, 417.7253140] ]
|
|
191 # ALOGP parameters from the original publication
|
|
192 pads2 = [ [2.817065973, 392.5754953, 290.7489764, 2.419764353, 49.22325677, 65.37051707, 104.9805561],
|
|
193 [3.172690585, 137.8624751, 2.534937431, 4.581497897, 0.822739154, 0.576295591, 131.3186604],
|
|
194 [2.948620388, 160.4605972, 3.615294657, 4.435986202, 0.290141953, 1.300669958, 148.7763046],
|
|
195 [1.618662227, 1010.051101, 0.985094388, 0.000000001, 0.713820843, 0.920922555, 258.1632616],
|
|
196 [1.876861559, 125.2232657, 62.90773554, 87.83366614, 12.01999824, 28.51324732, 104.5686167],
|
|
197 [0.010000000, 272.4121427, 2.558379970, 1.565547684, 1.271567166, 2.758063707, 105.4420403],
|
|
198 [3.217788970, 957.7374108, 2.274627939, 0.000000001, 1.317690384, 0.375760881, 312.3372610],
|
|
199 [0.010000000, 1199.094025, -0.09002883, 0.000000001, 0.185904477, 0.875193782, 417.7253140] ]
|
|
200
|
|
201 def ads(x, a, b, c, d, e, f, dmax):
|
|
202 return ((a+(b/(1+exp(-1*(x-c+d/2)/e))*(1-1/(1+exp(-1*(x-c-d/2)/f))))) / dmax)
|
|
203
|
|
204 def properties(mol):
|
|
205 """
|
|
206 Calculates the properties that are required to calculate the QED descriptor.
|
|
207 """
|
|
208 matches = []
|
|
209 if (mol is None):
|
|
210 raise WrongArgument("properties(mol)", "mol argument is \'None\'")
|
|
211 x = [0] * 9
|
|
212 x[0] = Descriptors.MolWt(mol) # MW
|
|
213 x[1] = Descriptors.MolLogP(mol) # ALOGP
|
|
214 for hba in Acceptors: # HBA
|
|
215 if (mol.HasSubstructMatch(hba)):
|
|
216 matches = mol.GetSubstructMatches(hba)
|
|
217 x[2] += len(matches)
|
|
218 x[3] = Descriptors.NumHDonors(mol) # HBD
|
|
219 x[4] = Descriptors.TPSA(mol) # PSA
|
|
220 x[5] = Descriptors.NumRotatableBonds(mol) # ROTB
|
|
221 x[6] = Chem.GetSSSR(Chem.DeleteSubstructs(deepcopy(mol), AliphaticRings)) # AROM
|
|
222 for alert in StructuralAlerts: # ALERTS
|
|
223 if (mol.HasSubstructMatch(alert)): x[7] += 1
|
|
224 ro5_failed = 0
|
|
225 if x[3] > 5:
|
|
226 ro5_failed += 1 #HBD
|
|
227 if x[2] > 10:
|
|
228 ro5_failed += 1 #HBA
|
|
229 if x[0] >= 500:
|
|
230 ro5_failed += 1
|
|
231 if x[1] > 5:
|
|
232 ro5_failed += 1
|
|
233 x[8] = ro5_failed
|
|
234 return x
|
|
235
|
|
236
|
|
237 def qed(w, p, gerebtzoff):
|
|
238 d = [0.00] * 8
|
|
239 if gerebtzoff:
|
|
240 for i in range(0, 8):
|
|
241 d[i] = ads(p[i], pads1[i][0], pads1[i][1], pads1[i][2], pads1[i][3], pads1[i][4], pads1[i][5], pads1[i][6])
|
|
242 else:
|
|
243 for i in range(0, 8):
|
|
244 d[i] = ads(p[i], pads2[i][0], pads2[i][1], pads2[i][2], pads2[i][3], pads2[i][4], pads2[i][5], pads2[i][6])
|
|
245 t = 0.0
|
|
246 for i in range(0, 8):
|
|
247 t += w[i] * log(d[i])
|
|
248 return (exp(t / sum(w)))
|
|
249
|
|
250
|
|
251 def weights_max(mol, gerebtzoff = True, props = False):
|
|
252 """
|
|
253 Calculates the QED descriptor using maximal descriptor weights.
|
|
254 If props is specified we skip the calculation step and use the props-list of properties.
|
|
255 """
|
|
256 if not props:
|
|
257 props = properties(mol)
|
|
258 return qed([0.50, 0.25, 0.00, 0.50, 0.00, 0.50, 0.25, 1.00], props, gerebtzoff)
|
|
259
|
|
260
|
|
261 def weights_mean(mol, gerebtzoff = True, props = False):
|
|
262 """
|
|
263 Calculates the QED descriptor using average descriptor weights.
|
|
264 If props is specified we skip the calculation step and use the props-list of properties.
|
|
265 """
|
|
266 if not props:
|
|
267 props = properties(mol)
|
|
268 return qed([0.66, 0.46, 0.05, 0.61, 0.06, 0.65, 0.48, 0.95], props, gerebtzoff)
|
|
269
|
|
270
|
|
271 def weights_none(mol, gerebtzoff = True, props = False):
|
|
272 """
|
|
273 Calculates the QED descriptor using unit weights.
|
|
274 If props is specified we skip the calculation step and use the props-list of properties.
|
|
275 """
|
|
276 if not props:
|
|
277 props = properties(mol)
|
|
278 return qed([1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00], props, gerebtzoff)
|
|
279
|
|
280
|
|
281 def default(mol, gerebtzoff = True):
|
|
282 """
|
|
283 Calculates the QED descriptor using average descriptor weights and Gregory Gerebtzoff parameters.
|
|
284 """
|
|
285 return weights_mean(mol, gerebtzoff)
|
|
286
|
|
287
|
|
288 if __name__ == "__main__":
|
|
289 parser = argparse.ArgumentParser()
|
|
290 parser.add_argument('-i', '--input', required=True, help='path to the input file name')
|
|
291 parser.add_argument("-m", "--method", dest="method", choices=['max', 'mean', 'unweighted'],
|
|
292 default="mean",
|
|
293 help="Specify the method you want to use.")
|
|
294
|
|
295 parser.add_argument('-o', '--outfile', type=argparse.FileType('w+'),
|
|
296 default=sys.stdout, help="path to the result file, default it sdtout")
|
|
297
|
|
298 parser.add_argument("--header", dest="header", action="store_true",
|
|
299 default=False,
|
|
300 help="Write header line.")
|
|
301
|
|
302
|
|
303 args = parser.parse_args()
|
|
304
|
|
305 # Elucidate filetype and open supplier
|
|
306 ifile = os.path.abspath(args.input)
|
|
307 if not os.path.isfile(ifile):
|
|
308 print "Error: ", ifile, " is not a file or cannot be found."
|
|
309 sys.exit(1)
|
|
310 if not os.path.exists(ifile):
|
|
311 print "Error: ", ifile, " does not exist or cannot be found."
|
|
312 sys.exit(1)
|
|
313 if not os.access(ifile, os.R_OK):
|
|
314 print "Error: ", ifile, " is not readable."
|
|
315 sys.exit(1)
|
|
316
|
|
317 filetype = check_filetype(ifile)
|
|
318
|
|
319
|
|
320 """
|
|
321 We want to store the original SMILES in the output. So in case of a SMILES file iterate over the file and convert each line separate.
|
|
322 """
|
|
323
|
|
324 if filetype == 'sdf':
|
|
325 supplier = Chem.SDMolSupplier(ifile)
|
|
326 # Process file
|
|
327 if args.header:
|
|
328 args.outfile.write("MW\tALOGP\tHBA\tHBD\tPSA\tROTB\tAROM\tALERTS\tLRo5\tQED\tNAME\n")
|
|
329 count = 0
|
|
330 for mol in supplier:
|
|
331 count += 1
|
|
332 if mol is None:
|
|
333 print "Warning: skipping molecule ", count, " and continuing with next."
|
|
334 continue
|
|
335 props = properties(mol)
|
|
336
|
|
337 if args.method == 'max':
|
|
338 calc_qed = weights_max(mol, True, props)
|
|
339 elif args.method == 'unweighted':
|
|
340 calc_qed = weights_none(mol, True, props)
|
|
341 else:
|
|
342 calc_qed = weights_mean(mol, True, props)
|
|
343
|
|
344 args.outfile.write( "%.2f\t%.3f\t%d\t%d\t%.2f\t%d\t%d\t%d\t%s\t%.3f\t%-s\n" % (
|
|
345 props[0],
|
|
346 props[1],
|
|
347 props[2],
|
|
348 props[3],
|
|
349 props[4],
|
|
350 props[5],
|
|
351 props[6],
|
|
352 props[7],
|
|
353 props[8],
|
|
354 calc_qed,
|
|
355 mol.GetProp("_Name"),
|
|
356 ))
|
|
357 elif filetype == 'smi':
|
|
358 supplier = Chem.SmilesMolSupplier(ifile, " \t", 0, 1, False, True)
|
|
359
|
|
360 # Process file
|
|
361 if args.header:
|
|
362 args.outfile.write("MW\tALOGP\tHBA\tHBD\tPSA\tROTB\tAROM\tALERTS\tLRo5\tQED\tNAME\tSMILES\n")
|
|
363 count = 0
|
|
364 for line in open(ifile):
|
|
365 tokens = line.strip().split('\t')
|
|
366 if len(tokens) > 1:
|
|
367 smiles, title = tokens
|
|
368 else:
|
|
369 smiles = tokens[0]
|
|
370 title = ''
|
|
371 mol = Chem.MolFromSmiles(smiles)
|
|
372 count += 1
|
|
373 if mol is None:
|
|
374 print "Warning: skipping molecule ", count, " and continuing with next."
|
|
375 continue
|
|
376 props = properties(mol)
|
|
377
|
|
378 if args.method == 'max':
|
|
379 calc_qed = weights_max(mol, True, props)
|
|
380 elif args.method == 'unweighted':
|
|
381 calc_qed = weights_none(mol, True, props)
|
|
382 else:
|
|
383 calc_qed = weights_mean(mol, True, props)
|
|
384
|
|
385 args.outfile.write( "%.2f\t%.3f\t%d\t%d\t%.2f\t%d\t%d\t%d\t%s\t%.3f\t%-s\t%s\n" % (
|
|
386 props[0],
|
|
387 props[1],
|
|
388 props[2],
|
|
389 props[3],
|
|
390 props[4],
|
|
391 props[5],
|
|
392 props[6],
|
|
393 props[7],
|
|
394 props[8],
|
|
395 calc_qed,
|
|
396 title,
|
|
397 smiles
|
|
398 ))
|
|
399
|
|
400 else:
|
|
401 print "Error: unknown file extension: ", extension
|
|
402 sys.exit(1)
|
|
403
|
|
404 sys.exit(0)
|