polyscript-pypi 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- MCP/server.py +770 -0
- polyscript/__init__.py +12 -0
- polyscript/brics/BRICSMod.py +1164 -0
- polyscript/brics/BRICS_utils.py +189 -0
- polyscript/brics/__init__.py +48 -0
- polyscript/classifier/__init__.py +270 -0
- polyscript/classifier/class_reference.py +951 -0
- polyscript/depolymerizer/__init__.py +576 -0
- polyscript/depolymerizer/mpi.py +390 -0
- polyscript/depolymerizer/reaction_reference.py +447 -0
- polyscript/polymerizer/__init__.py +22 -0
- polyscript/polymerizer/core.py +648 -0
- polyscript/polymerizer/mpi.py +860 -0
- polyscript/polymerizer/mpi_df.py +378 -0
- polyscript/polymerizer/utils.py +795 -0
- polyscript/utils/__init__.py +80 -0
- polyscript/utils/adapters.py +357 -0
- polyscript/utils/canon_analyze.py +193 -0
- polyscript/utils/executor.py +66 -0
- polyscript/utils/logger.py +331 -0
- polyscript/utils/mpi/__init__.py +33 -0
- polyscript/utils/mpi/checkpoint.py +263 -0
- polyscript/utils/mpi/logger.py +138 -0
- polyscript/utils/mpi/progress.py +134 -0
- polyscript/utils/mpi/protocol.py +113 -0
- polyscript/utils/mpi/resource.py +94 -0
- polyscript/utils/mpi/splitter.py +42 -0
- polyscript/utils/mpi_executor.py +167 -0
- polyscript/utils/mpi_validator.py +940 -0
- polyscript/utils/parsers.py +177 -0
- polyscript/utils/validators.py +201 -0
- polyscript_pypi-0.1.0.dist-info/METADATA +82 -0
- polyscript_pypi-0.1.0.dist-info/RECORD +57 -0
- polyscript_pypi-0.1.0.dist-info/WHEEL +5 -0
- polyscript_pypi-0.1.0.dist-info/licenses/LICENSE +21 -0
- polyscript_pypi-0.1.0.dist-info/top_level.txt +4 -0
- runners/adapt_dataframe.py +105 -0
- runners/brics_decompose.py +28 -0
- runners/canonicalization.py +46 -0
- runners/filter_candidates.py +54 -0
- runners/mpi_depolymerize.py +51 -0
- runners/mpi_runner.py +66 -0
- runners/mpi_runner_df.py +49 -0
- runners/mpi_validator.py +45 -0
- tests/conftest.py +10 -0
- tests/sample_gen.py +10 -0
- tests/test_brics.py +275 -0
- tests/test_classifier.py +201 -0
- tests/test_mpi_polymerize.py +170 -0
- tests/test_polymerize.py +206 -0
- tests/utils/test_adaptors.py +182 -0
- tests/utils/test_canonicalization.py +62 -0
- tests/utils/test_executor.py +101 -0
- tests/utils/test_logger.py +426 -0
- tests/utils/test_mpi_validator.py +159 -0
- tests/utils/test_parsers.py +150 -0
- tests/utils/test_validator.py +105 -0
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
# Copyright (c) 2009, Novartis Institutes for BioMedical Research Inc.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
|
5
|
+
# modification, are permitted provided that the following conditions are
|
|
6
|
+
# met:
|
|
7
|
+
#
|
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
|
11
|
+
# copyright notice, this list of conditions and the following
|
|
12
|
+
# disclaimer in the documentation and/or other materials provided
|
|
13
|
+
# with the distribution.
|
|
14
|
+
# * Neither the name of Novartis Institutes for BioMedical Research Inc.
|
|
15
|
+
# nor the names of its contributors may be used to endorse or promote
|
|
16
|
+
# products derived from this software without specific prior written permission.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
#
|
|
30
|
+
# Created by Greg Landrum, Nov 2008
|
|
31
|
+
"""Implementation of the BRICS algorithm from Degen et al. ChemMedChem *3* 1503-7 (2008).
|
|
32
|
+
|
|
33
|
+
This is a direct replica of BRICS from RDKit but modified to work with
|
|
34
|
+
PolyScript by adapting the execution for a given depth.
|
|
35
|
+
"""
|
|
36
|
+
import copy
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
|
|
42
|
+
import joblib
|
|
43
|
+
from rdkit import Chem
|
|
44
|
+
from rdkit import RDRandom as random
|
|
45
|
+
from rdkit.Chem import rdChemReactions as Reactions
|
|
46
|
+
|
|
47
|
+
# These are the definitions that will be applied to fragment molecules:
|
|
48
|
+
environs = {
|
|
49
|
+
"L1": "[C;D3]([#0,#6,#7,#8])(=O)",
|
|
50
|
+
#
|
|
51
|
+
# After some discussion, the L2 definitions ("N.pl3" in the original
|
|
52
|
+
# paper) have been removed and incorporated into a (almost) general
|
|
53
|
+
# purpose amine definition in L5 ("N.sp3" in the paper).
|
|
54
|
+
#
|
|
55
|
+
# The problem is one of consistency.
|
|
56
|
+
# Based on the original definitions you should get the following
|
|
57
|
+
# fragmentations:
|
|
58
|
+
# C1CCCCC1NC(=O)C -> C1CCCCC1N[2*].[1*]C(=O)C
|
|
59
|
+
# c1ccccc1NC(=O)C -> c1ccccc1[16*].[2*]N[2*].[1*]C(=O)C
|
|
60
|
+
# This difference just didn't make sense to us. By switching to
|
|
61
|
+
# the unified definition we end up with:
|
|
62
|
+
# C1CCCCC1NC(=O)C -> C1CCCCC1[15*].[5*]N[5*].[1*]C(=O)C
|
|
63
|
+
# c1ccccc1NC(=O)C -> c1ccccc1[16*].[5*]N[5*].[1*]C(=O)C
|
|
64
|
+
#
|
|
65
|
+
# 'L2':'[N;!R;!D1;!$(N=*)]-;!@[#0,#6]',
|
|
66
|
+
# this one turned out to be too tricky to define above, so we set it off
|
|
67
|
+
# in its own definition:
|
|
68
|
+
# 'L2a':'[N;D3;R;$(N(@[C;!$(C=*)])@[C;!$(C=*)])]',
|
|
69
|
+
"L3": "[O;D2]-;!@[#0,#6,#1]",
|
|
70
|
+
"L4": "[C;!D1;!$(C=*)]-;!@[#6]",
|
|
71
|
+
# 'L5':'[N;!D1;!$(N*!-*);!$(N=*);!$(N-[!C;!#0])]-[#0,C]',
|
|
72
|
+
"L5": "[N;!D1;!$(N=*);!$(N-[!#6;!#16;!#0;!#1]);!$([N;R]@[C;R]=O)]",
|
|
73
|
+
"L6": "[C;D3;!R](=O)-;!@[#0,#6,#7,#8]",
|
|
74
|
+
"L7a": "[C;D2,D3]-[#6]",
|
|
75
|
+
"L7b": "[C;D2,D3]-[#6]",
|
|
76
|
+
"#L8": "[C;!R;!D1]-;!@[#6]",
|
|
77
|
+
"L8": "[C;!R;!D1;!$(C!-*)]",
|
|
78
|
+
"L9": "[n;+0;$(n(:[c,n,o,s]):[c,n,o,s])]",
|
|
79
|
+
"L10": "[N;R;$(N(@C(=O))@[C,N,O,S])]",
|
|
80
|
+
"L11": "[S;D2](-;!@[#0,#6])",
|
|
81
|
+
"L12": "[S;D4]([#6,#0])(=O)(=O)",
|
|
82
|
+
"L13": "[C;$(C(-;@[C,N,O,S])-;@[N,O,S])]",
|
|
83
|
+
"L14": "[c;$(c(:[c,n,o,s]):[n,o,s])]",
|
|
84
|
+
"L14b": "[c;$(c(:[c,n,o,s]):[n,o,s])]",
|
|
85
|
+
"L15": "[C;$(C(-;@C)-;@C)]",
|
|
86
|
+
"L16": "[c;$(c(:c):c)]",
|
|
87
|
+
"L16b": "[c;$(c(:c):c)]",
|
|
88
|
+
}
|
|
89
|
+
reactionDefs = (
|
|
90
|
+
# L1
|
|
91
|
+
[
|
|
92
|
+
("1", "3", "-"),
|
|
93
|
+
("1", "5", "-"),
|
|
94
|
+
("1", "10", "-"),
|
|
95
|
+
],
|
|
96
|
+
# L3
|
|
97
|
+
[
|
|
98
|
+
("3", "4", "-"),
|
|
99
|
+
("3", "13", "-"),
|
|
100
|
+
("3", "14", "-"),
|
|
101
|
+
("3", "15", "-"),
|
|
102
|
+
("3", "16", "-"),
|
|
103
|
+
],
|
|
104
|
+
# L4
|
|
105
|
+
[
|
|
106
|
+
("4", "5", "-"),
|
|
107
|
+
("4", "11", "-"),
|
|
108
|
+
],
|
|
109
|
+
# L5
|
|
110
|
+
[
|
|
111
|
+
("5", "12", "-"),
|
|
112
|
+
("5", "14", "-"),
|
|
113
|
+
("5", "16", "-"),
|
|
114
|
+
("5", "13", "-"),
|
|
115
|
+
("5", "15", "-"),
|
|
116
|
+
],
|
|
117
|
+
# L6
|
|
118
|
+
[
|
|
119
|
+
("6", "13", "-"),
|
|
120
|
+
("6", "14", "-"),
|
|
121
|
+
("6", "15", "-"),
|
|
122
|
+
("6", "16", "-"),
|
|
123
|
+
],
|
|
124
|
+
# L7
|
|
125
|
+
[
|
|
126
|
+
("7a", "7b", "="),
|
|
127
|
+
],
|
|
128
|
+
# L8
|
|
129
|
+
[
|
|
130
|
+
("8", "9", "-"),
|
|
131
|
+
("8", "10", "-"),
|
|
132
|
+
("8", "13", "-"),
|
|
133
|
+
("8", "14", "-"),
|
|
134
|
+
("8", "15", "-"),
|
|
135
|
+
("8", "16", "-"),
|
|
136
|
+
],
|
|
137
|
+
# L9
|
|
138
|
+
[
|
|
139
|
+
("9", "13", "-"), # not in original paper
|
|
140
|
+
("9", "14", "-"), # not in original paper
|
|
141
|
+
("9", "15", "-"),
|
|
142
|
+
("9", "16", "-"),
|
|
143
|
+
],
|
|
144
|
+
# L10
|
|
145
|
+
[
|
|
146
|
+
("10", "13", "-"),
|
|
147
|
+
("10", "14", "-"),
|
|
148
|
+
("10", "15", "-"),
|
|
149
|
+
("10", "16", "-"),
|
|
150
|
+
],
|
|
151
|
+
# L11
|
|
152
|
+
[
|
|
153
|
+
("11", "13", "-"),
|
|
154
|
+
("11", "14", "-"),
|
|
155
|
+
("11", "15", "-"),
|
|
156
|
+
("11", "16", "-"),
|
|
157
|
+
],
|
|
158
|
+
# L12
|
|
159
|
+
# none left
|
|
160
|
+
# L13
|
|
161
|
+
[
|
|
162
|
+
("13", "14", "-"),
|
|
163
|
+
("13", "15", "-"),
|
|
164
|
+
("13", "16", "-"),
|
|
165
|
+
],
|
|
166
|
+
# L14
|
|
167
|
+
[
|
|
168
|
+
("14", "14", "-"), # not in original paper
|
|
169
|
+
("14", "15", "-"),
|
|
170
|
+
("14", "16", "-"),
|
|
171
|
+
],
|
|
172
|
+
# L15
|
|
173
|
+
[
|
|
174
|
+
("15", "16", "-"),
|
|
175
|
+
],
|
|
176
|
+
# L16
|
|
177
|
+
[
|
|
178
|
+
("16", "16", "-"), # not in original paper
|
|
179
|
+
],
|
|
180
|
+
)
|
|
181
|
+
smartsGps = copy.deepcopy(reactionDefs)
|
|
182
|
+
for gp in smartsGps:
|
|
183
|
+
for j, defn in enumerate(gp):
|
|
184
|
+
g1, g2, bnd = defn
|
|
185
|
+
r1 = environs["L" + g1]
|
|
186
|
+
r2 = environs["L" + g2]
|
|
187
|
+
g1 = re.sub("[a-z,A-Z]", "", g1)
|
|
188
|
+
g2 = re.sub("[a-z,A-Z]", "", g2)
|
|
189
|
+
sma = "[$(%s):1]%s;!@[$(%s):2]>>[%s*]-[*:1].[%s*]-[*:2]" % (r1, bnd, r2, g1, g2)
|
|
190
|
+
gp[j] = sma
|
|
191
|
+
|
|
192
|
+
for gp in smartsGps:
|
|
193
|
+
for defn in gp:
|
|
194
|
+
try:
|
|
195
|
+
t = Reactions.ReactionFromSmarts(defn)
|
|
196
|
+
t.Initialize()
|
|
197
|
+
except Exception:
|
|
198
|
+
print(defn)
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
environMatchers = {}
|
|
202
|
+
for env, sma in environs.items():
|
|
203
|
+
environMatchers[env] = Chem.MolFromSmarts(sma)
|
|
204
|
+
|
|
205
|
+
bondMatchers = []
|
|
206
|
+
for i, compats in enumerate(reactionDefs):
|
|
207
|
+
tmp = []
|
|
208
|
+
for i1, i2, bType in compats:
|
|
209
|
+
e1 = environs["L%s" % i1]
|
|
210
|
+
e2 = environs["L%s" % i2]
|
|
211
|
+
patt = "[$(%s)]%s;!@[$(%s)]" % (e1, bType, e2)
|
|
212
|
+
patt = Chem.MolFromSmarts(patt)
|
|
213
|
+
tmp.append((i1, i2, bType, patt))
|
|
214
|
+
bondMatchers.append(tmp)
|
|
215
|
+
|
|
216
|
+
reactions = tuple([[Reactions.ReactionFromSmarts(y) for y in x] for x in smartsGps])
|
|
217
|
+
reverseReactions = []
|
|
218
|
+
for i, rxnSet in enumerate(smartsGps):
|
|
219
|
+
for j, sma in enumerate(rxnSet):
|
|
220
|
+
rs, ps = sma.split(">>")
|
|
221
|
+
sma = "%s>>%s" % (ps, rs)
|
|
222
|
+
rxn = Reactions.ReactionFromSmarts(sma)
|
|
223
|
+
labels = re.findall(r"\[([0-9]+?)\*\]", ps)
|
|
224
|
+
rxn._matchers = [Chem.MolFromSmiles("[%s*]" % x) for x in labels]
|
|
225
|
+
reverseReactions.append(rxn)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def FindBRICSBonds(mol, randomizeOrder=False, silent=True):
|
|
229
|
+
"""returns the bonds in a molecule that BRICS would cleave
|
|
230
|
+
|
|
231
|
+
>>> from rdkit import Chem
|
|
232
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
233
|
+
>>> res = list(FindBRICSBonds(m))
|
|
234
|
+
>>> res
|
|
235
|
+
[((3, 2), ('3', '4')), ((3, 4), ('3', '4'))]
|
|
236
|
+
|
|
237
|
+
a more complicated case:
|
|
238
|
+
|
|
239
|
+
>>> m = Chem.MolFromSmiles('CCCOCCC(=O)c1ccccc1')
|
|
240
|
+
>>> res = list(FindBRICSBonds(m))
|
|
241
|
+
>>> res
|
|
242
|
+
[((3, 2), ('3', '4')), ((3, 4), ('3', '4')), ((6, 8), ('6', '16'))]
|
|
243
|
+
|
|
244
|
+
we can also randomize the order of the results:
|
|
245
|
+
|
|
246
|
+
>>> random.seed(23)
|
|
247
|
+
>>> res = list(FindBRICSBonds(m,randomizeOrder=True))
|
|
248
|
+
>>> sorted(res)
|
|
249
|
+
[((3, 2), ('3', '4')), ((3, 4), ('3', '4')), ((6, 8), ('6', '16'))]
|
|
250
|
+
|
|
251
|
+
Note that this is a generator function :
|
|
252
|
+
|
|
253
|
+
>>> res = FindBRICSBonds(m)
|
|
254
|
+
>>> res
|
|
255
|
+
<generator object ...>
|
|
256
|
+
>>> next(res)
|
|
257
|
+
((3, 2), ('3', '4'))
|
|
258
|
+
|
|
259
|
+
>>> m = Chem.MolFromSmiles('CC=CC')
|
|
260
|
+
>>> res = list(FindBRICSBonds(m))
|
|
261
|
+
>>> sorted(res)
|
|
262
|
+
[((1, 2), ('7', '7'))]
|
|
263
|
+
|
|
264
|
+
make sure we don't match ring bonds:
|
|
265
|
+
|
|
266
|
+
>>> m = Chem.MolFromSmiles('O=C1NCCC1')
|
|
267
|
+
>>> list(FindBRICSBonds(m))
|
|
268
|
+
[]
|
|
269
|
+
|
|
270
|
+
another nice one, make sure environment 8 doesn't match something connected
|
|
271
|
+
to a ring atom:
|
|
272
|
+
|
|
273
|
+
>>> m = Chem.MolFromSmiles('CC1(C)CCCCC1')
|
|
274
|
+
>>> list(FindBRICSBonds(m))
|
|
275
|
+
[]
|
|
276
|
+
|
|
277
|
+
"""
|
|
278
|
+
letter = re.compile("[a-z,A-Z]")
|
|
279
|
+
indices = list(range(len(bondMatchers)))
|
|
280
|
+
bondsDone = set()
|
|
281
|
+
if randomizeOrder:
|
|
282
|
+
random.shuffle(indices, random=random.random)
|
|
283
|
+
|
|
284
|
+
envMatches = {}
|
|
285
|
+
for env, patt in environMatchers.items():
|
|
286
|
+
envMatches[env] = mol.HasSubstructMatch(patt)
|
|
287
|
+
for gpIdx in indices:
|
|
288
|
+
if randomizeOrder:
|
|
289
|
+
compats = bondMatchers[gpIdx][:]
|
|
290
|
+
random.shuffle(compats, random=random.random)
|
|
291
|
+
else:
|
|
292
|
+
compats = bondMatchers[gpIdx]
|
|
293
|
+
for i1, i2, bType, patt in compats:
|
|
294
|
+
if not envMatches["L" + i1] or not envMatches["L" + i2]:
|
|
295
|
+
continue
|
|
296
|
+
matches = mol.GetSubstructMatches(patt)
|
|
297
|
+
i1 = letter.sub("", i1)
|
|
298
|
+
i2 = letter.sub("", i2)
|
|
299
|
+
for match in matches:
|
|
300
|
+
if match not in bondsDone and (match[1], match[0]) not in bondsDone:
|
|
301
|
+
bondsDone.add(match)
|
|
302
|
+
yield (((match[0], match[1]), (i1, i2)))
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def BreakBRICSBonds(mol, bonds=None, sanitize=True, silent=True):
|
|
306
|
+
"""breaks the BRICS bonds in a molecule and returns the results
|
|
307
|
+
|
|
308
|
+
>>> from rdkit import Chem
|
|
309
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
310
|
+
>>> m2=BreakBRICSBonds(m)
|
|
311
|
+
>>> Chem.MolToSmiles(m2,True)
|
|
312
|
+
'[3*]O[3*].[4*]CC.[4*]CCC'
|
|
313
|
+
|
|
314
|
+
a more complicated case:
|
|
315
|
+
|
|
316
|
+
>>> m = Chem.MolFromSmiles('CCCOCCC(=O)c1ccccc1')
|
|
317
|
+
>>> m2=BreakBRICSBonds(m)
|
|
318
|
+
>>> Chem.MolToSmiles(m2,True)
|
|
319
|
+
'[16*]c1ccccc1.[3*]O[3*].[4*]CCC.[4*]CCC([6*])=O'
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
can also specify a limited set of bonds to work with:
|
|
323
|
+
|
|
324
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
325
|
+
>>> m2 = BreakBRICSBonds(m,[((3, 2), ('3', '4'))])
|
|
326
|
+
>>> Chem.MolToSmiles(m2,True)
|
|
327
|
+
'[3*]OCC.[4*]CCC'
|
|
328
|
+
|
|
329
|
+
this can be used as an alternate approach for doing a BRICS decomposition by
|
|
330
|
+
following BreakBRICSBonds with a call to Chem.GetMolFrags:
|
|
331
|
+
|
|
332
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
333
|
+
>>> m2=BreakBRICSBonds(m)
|
|
334
|
+
>>> frags = Chem.GetMolFrags(m2,asMols=True)
|
|
335
|
+
>>> [Chem.MolToSmiles(x,True) for x in frags]
|
|
336
|
+
['[4*]CCC', '[3*]O[3*]', '[4*]CC']
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
if not bonds:
|
|
340
|
+
# bonds = FindBRICSBonds(mol)
|
|
341
|
+
res = Chem.FragmentOnBRICSBonds(mol)
|
|
342
|
+
if sanitize:
|
|
343
|
+
Chem.SanitizeMol(res)
|
|
344
|
+
return res
|
|
345
|
+
eMol = Chem.EditableMol(mol)
|
|
346
|
+
nAts = mol.GetNumAtoms()
|
|
347
|
+
|
|
348
|
+
dummyPositions = []
|
|
349
|
+
for indices, dummyTypes in bonds:
|
|
350
|
+
ia, ib = indices
|
|
351
|
+
obond = mol.GetBondBetweenAtoms(ia, ib)
|
|
352
|
+
bondType = obond.GetBondType()
|
|
353
|
+
eMol.RemoveBond(ia, ib)
|
|
354
|
+
|
|
355
|
+
da, db = dummyTypes
|
|
356
|
+
atoma = Chem.Atom(0)
|
|
357
|
+
atoma.SetIsotope(int(da))
|
|
358
|
+
atoma.SetNoImplicit(True)
|
|
359
|
+
idxa = nAts
|
|
360
|
+
nAts += 1
|
|
361
|
+
eMol.AddAtom(atoma)
|
|
362
|
+
eMol.AddBond(ia, idxa, bondType)
|
|
363
|
+
|
|
364
|
+
atomb = Chem.Atom(0)
|
|
365
|
+
atomb.SetIsotope(int(db))
|
|
366
|
+
atomb.SetNoImplicit(True)
|
|
367
|
+
idxb = nAts
|
|
368
|
+
nAts += 1
|
|
369
|
+
eMol.AddAtom(atomb)
|
|
370
|
+
eMol.AddBond(ib, idxb, bondType)
|
|
371
|
+
if mol.GetNumConformers():
|
|
372
|
+
dummyPositions.append((idxa, ib))
|
|
373
|
+
dummyPositions.append((idxb, ia))
|
|
374
|
+
res = eMol.GetMol()
|
|
375
|
+
if sanitize:
|
|
376
|
+
Chem.SanitizeMol(res)
|
|
377
|
+
if mol.GetNumConformers():
|
|
378
|
+
for conf in mol.GetConformers():
|
|
379
|
+
resConf = res.GetConformer(conf.GetId())
|
|
380
|
+
for ia, pa in dummyPositions:
|
|
381
|
+
resConf.SetAtomPosition(ia, conf.GetAtomPosition(pa))
|
|
382
|
+
return res
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def BRICSDecompose(
|
|
386
|
+
mol,
|
|
387
|
+
allNodes=None,
|
|
388
|
+
minFragmentSize=1,
|
|
389
|
+
onlyUseReactions=None,
|
|
390
|
+
silent=True,
|
|
391
|
+
keepNonLeafNodes=False,
|
|
392
|
+
singlePass=False,
|
|
393
|
+
returnMols=False,
|
|
394
|
+
):
|
|
395
|
+
"""returns the BRICS decomposition for a molecule
|
|
396
|
+
|
|
397
|
+
>>> from rdkit import Chem
|
|
398
|
+
>>> m = Chem.MolFromSmiles('CCCOCc1cc(c2ncccc2)ccc1')
|
|
399
|
+
>>> res = list(BRICSDecompose(m))
|
|
400
|
+
>>> sorted(res)
|
|
401
|
+
['[14*]c1ccccn1', '[16*]c1cccc([16*])c1', '[3*]O[3*]', '[4*]CCC', '[4*]C[8*]']
|
|
402
|
+
|
|
403
|
+
>>> res = list(BRICSDecompose(m,returnMols=True))
|
|
404
|
+
>>> res[0]
|
|
405
|
+
<rdkit.Chem.rdchem.Mol object ...>
|
|
406
|
+
>>> smis = [Chem.MolToSmiles(x,True) for x in res]
|
|
407
|
+
>>> sorted(smis)
|
|
408
|
+
['[14*]c1ccccn1', '[16*]c1cccc([16*])c1', '[3*]O[3*]', '[4*]CCC', '[4*]C[8*]']
|
|
409
|
+
|
|
410
|
+
nexavar, an example from the paper (corrected):
|
|
411
|
+
|
|
412
|
+
>>> m = Chem.MolFromSmiles('CNC(=O)C1=NC=CC(OC2=CC=C(NC(=O)NC3=CC(=C(Cl)C=C3)C(F)(F)F)C=C2)=C1')
|
|
413
|
+
>>> res = list(BRICSDecompose(m))
|
|
414
|
+
>>> sorted(res)
|
|
415
|
+
['[1*]C([1*])=O', '[1*]C([6*])=O', '[14*]c1cc([16*])ccn1', '[16*]c1ccc(Cl)c([16*])c1', '[16*]c1ccc([16*])cc1', '[3*]O[3*]', '[5*]NC', '[5*]N[5*]', '[8*]C(F)(F)F']
|
|
416
|
+
|
|
417
|
+
it's also possible to keep pieces that haven't been fully decomposed:
|
|
418
|
+
|
|
419
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
420
|
+
>>> res = list(BRICSDecompose(m,keepNonLeafNodes=True))
|
|
421
|
+
>>> sorted(res)
|
|
422
|
+
['CCCOCC', '[3*]OCC', '[3*]OCCC', '[3*]O[3*]', '[4*]CC', '[4*]CCC']
|
|
423
|
+
|
|
424
|
+
>>> m = Chem.MolFromSmiles('CCCOCc1cc(c2ncccc2)ccc1')
|
|
425
|
+
>>> res = list(BRICSDecompose(m,keepNonLeafNodes=True))
|
|
426
|
+
>>> sorted(res)
|
|
427
|
+
['CCCOCc1cccc(-c2ccccn2)c1', '[14*]c1ccccn1', '[16*]c1cccc(-c2ccccn2)c1', '[16*]c1cccc(COCCC)c1', '[16*]c1cccc([16*])c1', '[3*]OCCC', '[3*]OC[8*]', '[3*]OCc1cccc(-c2ccccn2)c1', '[3*]OCc1cccc([16*])c1', '[3*]O[3*]', '[4*]CCC', '[4*]C[8*]', '[4*]Cc1cccc(-c2ccccn2)c1', '[4*]Cc1cccc([16*])c1', '[8*]COCCC']
|
|
428
|
+
|
|
429
|
+
or to only do a single pass of decomposition:
|
|
430
|
+
|
|
431
|
+
>>> m = Chem.MolFromSmiles('CCCOCc1cc(c2ncccc2)ccc1')
|
|
432
|
+
>>> res = list(BRICSDecompose(m,singlePass=True))
|
|
433
|
+
>>> sorted(res)
|
|
434
|
+
['CCCOCc1cccc(-c2ccccn2)c1', '[14*]c1ccccn1', '[16*]c1cccc(-c2ccccn2)c1', '[16*]c1cccc(COCCC)c1', '[3*]OCCC', '[3*]OCc1cccc(-c2ccccn2)c1', '[4*]CCC', '[4*]Cc1cccc(-c2ccccn2)c1', '[8*]COCCC']
|
|
435
|
+
|
|
436
|
+
setting a minimum size for the fragments:
|
|
437
|
+
|
|
438
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
439
|
+
>>> res = list(BRICSDecompose(m,keepNonLeafNodes=True,minFragmentSize=2))
|
|
440
|
+
>>> sorted(res)
|
|
441
|
+
['CCCOCC', '[3*]OCC', '[3*]OCCC', '[4*]CC', '[4*]CCC']
|
|
442
|
+
>>> m = Chem.MolFromSmiles('CCCOCC')
|
|
443
|
+
>>> res = list(BRICSDecompose(m,keepNonLeafNodes=True,minFragmentSize=3))
|
|
444
|
+
>>> sorted(res)
|
|
445
|
+
['CCCOCC', '[3*]OCC', '[4*]CCC']
|
|
446
|
+
>>> res = list(BRICSDecompose(m,minFragmentSize=2))
|
|
447
|
+
>>> sorted(res)
|
|
448
|
+
['[3*]OCC', '[3*]OCCC', '[4*]CC', '[4*]CCC']
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
"""
|
|
452
|
+
global reactions
|
|
453
|
+
mSmi = Chem.MolToSmiles(mol, 1)
|
|
454
|
+
|
|
455
|
+
if allNodes is None:
|
|
456
|
+
allNodes = set()
|
|
457
|
+
|
|
458
|
+
if mSmi in allNodes:
|
|
459
|
+
return set()
|
|
460
|
+
|
|
461
|
+
activePool = {mSmi: mol}
|
|
462
|
+
allNodes.add(mSmi)
|
|
463
|
+
foundMols = {mSmi: mol}
|
|
464
|
+
for gpIdx, reactionGp in enumerate(reactions):
|
|
465
|
+
newPool = {}
|
|
466
|
+
while activePool:
|
|
467
|
+
matched = False
|
|
468
|
+
nSmi = next(iter(activePool))
|
|
469
|
+
mol = activePool.pop(nSmi)
|
|
470
|
+
for rxnIdx, reaction in enumerate(reactionGp):
|
|
471
|
+
if onlyUseReactions and (gpIdx, rxnIdx) not in onlyUseReactions:
|
|
472
|
+
continue
|
|
473
|
+
if not silent:
|
|
474
|
+
print("--------")
|
|
475
|
+
print(smartsGps[gpIdx][rxnIdx])
|
|
476
|
+
ps = reaction.RunReactants((mol,))
|
|
477
|
+
if ps:
|
|
478
|
+
if not silent:
|
|
479
|
+
print(nSmi, "->", len(ps), "products")
|
|
480
|
+
for prodSeq in ps:
|
|
481
|
+
seqOk = True
|
|
482
|
+
# we want to disqualify small fragments, so sort the product sequence by size
|
|
483
|
+
tSeq = [
|
|
484
|
+
(prod.GetNumAtoms(onlyExplicit=True), idx)
|
|
485
|
+
for idx, prod in enumerate(prodSeq)
|
|
486
|
+
]
|
|
487
|
+
tSeq.sort()
|
|
488
|
+
for nats, idx in tSeq:
|
|
489
|
+
prod = prodSeq[idx]
|
|
490
|
+
try:
|
|
491
|
+
Chem.SanitizeMol(prod)
|
|
492
|
+
except Exception:
|
|
493
|
+
continue
|
|
494
|
+
pSmi = Chem.MolToSmiles(prod, 1)
|
|
495
|
+
if minFragmentSize > 0:
|
|
496
|
+
nDummies = pSmi.count("*")
|
|
497
|
+
if nats - nDummies < minFragmentSize:
|
|
498
|
+
seqOk = False
|
|
499
|
+
break
|
|
500
|
+
prod.pSmi = pSmi
|
|
501
|
+
ts = [(x, prodSeq[y]) for x, y in tSeq]
|
|
502
|
+
prodSeq = ts
|
|
503
|
+
if seqOk:
|
|
504
|
+
matched = True
|
|
505
|
+
for nats, prod in prodSeq:
|
|
506
|
+
pSmi = prod.pSmi
|
|
507
|
+
# print('\t',nats,pSmi)
|
|
508
|
+
if pSmi not in allNodes:
|
|
509
|
+
if not singlePass:
|
|
510
|
+
activePool[pSmi] = prod
|
|
511
|
+
allNodes.add(pSmi)
|
|
512
|
+
foundMols[pSmi] = prod
|
|
513
|
+
if singlePass or keepNonLeafNodes or not matched:
|
|
514
|
+
newPool[nSmi] = mol
|
|
515
|
+
activePool = newPool
|
|
516
|
+
if not (singlePass or keepNonLeafNodes):
|
|
517
|
+
if not returnMols:
|
|
518
|
+
res = set(activePool.keys())
|
|
519
|
+
else:
|
|
520
|
+
res = activePool.values()
|
|
521
|
+
else:
|
|
522
|
+
if not returnMols:
|
|
523
|
+
res = allNodes
|
|
524
|
+
else:
|
|
525
|
+
res = foundMols.values()
|
|
526
|
+
return res
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
dummyPattern = Chem.MolFromSmiles("[*]")
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def BRICSBuild(
|
|
533
|
+
fragments,
|
|
534
|
+
onlyCompleteMols=True,
|
|
535
|
+
seeds=None,
|
|
536
|
+
uniquify=True,
|
|
537
|
+
scrambleReagents=True,
|
|
538
|
+
maxDepth=3,
|
|
539
|
+
):
|
|
540
|
+
"""Build new molecules from BRICS fragments.
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
Arguments:
|
|
544
|
+
- fragments: a sequence of BRICS fragments to use for building new molecules.
|
|
545
|
+
- onlyCompleteMols: if True, only molecules without attachment points will be yielded.
|
|
546
|
+
- seeds: an optional list of seed molecules to use as starting points.
|
|
547
|
+
- uniquify: if True, only unique molecules (determined by canonical SMILES) will be yielded.
|
|
548
|
+
- scrambleReagents: if True, the order of reagents will be randomized before enumeration.
|
|
549
|
+
- maxDepth: the maximum depth of the building process.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
a generator that yields the produced molecules.
|
|
553
|
+
|
|
554
|
+
>>> from rdkit import Chem
|
|
555
|
+
>>> frags = ['[14*]c1ccccn1', '[16*]c1cccc([16*])c1', '[3*]O[3*]', '[4*]CCC', '[4*]C[8*]']
|
|
556
|
+
>>> frags = [Chem.MolFromSmiles(x) for x in frags]
|
|
557
|
+
>>> res = BRICSBuild(frags, scrambleReagents=False)
|
|
558
|
+
>>> type(res)
|
|
559
|
+
<class 'generator'>
|
|
560
|
+
>>> res = sorted(Chem.MolToSmiles(x) for x in res)
|
|
561
|
+
>>> len(res)
|
|
562
|
+
21
|
|
563
|
+
>>> res[:3]
|
|
564
|
+
['CCCOCCC', 'CCCOCc1cccc(-c2ccccn2)c1', 'CCCOCc1ccccn1']
|
|
565
|
+
|
|
566
|
+
"""
|
|
567
|
+
seen = set()
|
|
568
|
+
if not seeds:
|
|
569
|
+
seeds = list(fragments)
|
|
570
|
+
if scrambleReagents:
|
|
571
|
+
seeds = list(seeds)
|
|
572
|
+
random.shuffle(seeds, random=random.random)
|
|
573
|
+
if scrambleReagents:
|
|
574
|
+
tempReactions = list(reverseReactions)
|
|
575
|
+
random.shuffle(tempReactions, random=random.random)
|
|
576
|
+
else:
|
|
577
|
+
tempReactions = reverseReactions
|
|
578
|
+
for seed in seeds:
|
|
579
|
+
seedIsR1 = False
|
|
580
|
+
seedIsR2 = False
|
|
581
|
+
nextSteps = []
|
|
582
|
+
for rxn in tempReactions:
|
|
583
|
+
if seed.HasSubstructMatch(rxn._matchers[0]):
|
|
584
|
+
seedIsR1 = True
|
|
585
|
+
if seed.HasSubstructMatch(rxn._matchers[1]):
|
|
586
|
+
seedIsR2 = True
|
|
587
|
+
for fragment in fragments:
|
|
588
|
+
ps = None
|
|
589
|
+
if fragment.HasSubstructMatch(rxn._matchers[0]):
|
|
590
|
+
if seedIsR2:
|
|
591
|
+
ps = rxn.RunReactants((fragment, seed))
|
|
592
|
+
if fragment.HasSubstructMatch(rxn._matchers[1]):
|
|
593
|
+
if seedIsR1:
|
|
594
|
+
ps = rxn.RunReactants((seed, fragment))
|
|
595
|
+
if ps:
|
|
596
|
+
for p in ps:
|
|
597
|
+
if uniquify:
|
|
598
|
+
pSmi = Chem.MolToSmiles(p[0], True)
|
|
599
|
+
if pSmi in seen:
|
|
600
|
+
continue
|
|
601
|
+
else:
|
|
602
|
+
seen.add(pSmi)
|
|
603
|
+
if p[0].HasSubstructMatch(dummyPattern):
|
|
604
|
+
nextSteps.append(p[0])
|
|
605
|
+
if not onlyCompleteMols:
|
|
606
|
+
yield p[0]
|
|
607
|
+
else:
|
|
608
|
+
yield (
|
|
609
|
+
"|fragment|"
|
|
610
|
+
+ Chem.MolToSmiles(fragment, True)
|
|
611
|
+
+ "|seed|"
|
|
612
|
+
+ Chem.MolToSmiles(seed, True)
|
|
613
|
+
+ "|rxn_smiles|"
|
|
614
|
+
+ Reactions.ReactionToCXSmiles(rxn)
|
|
615
|
+
+ "|rxn_smarts|"
|
|
616
|
+
+ Reactions.ReactionToCXSmarts(rxn),
|
|
617
|
+
p[0],
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
if nextSteps and maxDepth > 0:
|
|
621
|
+
for p in BRICSBuild(
|
|
622
|
+
fragments,
|
|
623
|
+
onlyCompleteMols=onlyCompleteMols,
|
|
624
|
+
seeds=nextSteps,
|
|
625
|
+
uniquify=uniquify,
|
|
626
|
+
maxDepth=maxDepth - 1,
|
|
627
|
+
scrambleReagents=scrambleReagents,
|
|
628
|
+
):
|
|
629
|
+
if uniquify:
|
|
630
|
+
if type(p) == tuple:
|
|
631
|
+
text, value = p
|
|
632
|
+
pSmi = Chem.MolToSmiles(value, True)
|
|
633
|
+
if pSmi in seen:
|
|
634
|
+
continue
|
|
635
|
+
else:
|
|
636
|
+
seen.add(pSmi)
|
|
637
|
+
yield p
|
|
638
|
+
|
|
639
|
+
# ------- ------- ------- ------- ------- ------- ------- -------
|
|
640
|
+
# Begin testing code
|
|
641
|
+
|
|
642
|
+
# ------------------------------------
|
|
643
|
+
#
|
|
644
|
+
# doctest boilerplate
|
|
645
|
+
#
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def BRICSBuildDistribWrite(
|
|
649
|
+
fragments,
|
|
650
|
+
seeds=None,
|
|
651
|
+
scrambleReagents=True,
|
|
652
|
+
write_path=None,
|
|
653
|
+
):
|
|
654
|
+
"""Build molecules from BRICS fragments and write the result to disk.
|
|
655
|
+
|
|
656
|
+
Similar to ``BRICSBuild`` but stops at depth 1 (does not recurse)
|
|
657
|
+
and collects recipe strings into a list. Optionally persists the
|
|
658
|
+
result via ``joblib.dump``.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
fragments: A sequence of RDKit ``Mol`` objects representing
|
|
662
|
+
BRICS fragments.
|
|
663
|
+
seeds: Optional list of seed molecules to use as starting
|
|
664
|
+
points. Defaults to *fragments*.
|
|
665
|
+
scrambleReagents: If True, randomize reagent and reaction order.
|
|
666
|
+
write_path: If provided, write the result list to this file path
|
|
667
|
+
using ``joblib.dump``.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
list[str]: Recipe strings describing the built molecules.
|
|
671
|
+
"""
|
|
672
|
+
seen = set()
|
|
673
|
+
result = []
|
|
674
|
+
if not seeds:
|
|
675
|
+
seeds = list(fragments)
|
|
676
|
+
if scrambleReagents:
|
|
677
|
+
seeds = list(seeds)
|
|
678
|
+
random.shuffle(seeds, random=random.random)
|
|
679
|
+
if scrambleReagents:
|
|
680
|
+
tempReactions = list(reverseReactions)
|
|
681
|
+
random.shuffle(tempReactions, random=random.random)
|
|
682
|
+
else:
|
|
683
|
+
tempReactions = reverseReactions
|
|
684
|
+
for seed in seeds:
|
|
685
|
+
seedIsR1 = False
|
|
686
|
+
seedIsR2 = False
|
|
687
|
+
for rxn in tempReactions:
|
|
688
|
+
if seed.HasSubstructMatch(rxn._matchers[0]):
|
|
689
|
+
seedIsR1 = True
|
|
690
|
+
if seed.HasSubstructMatch(rxn._matchers[1]):
|
|
691
|
+
seedIsR2 = True
|
|
692
|
+
for fragment in fragments:
|
|
693
|
+
ps = None
|
|
694
|
+
if fragment.HasSubstructMatch(rxn._matchers[0]):
|
|
695
|
+
if seedIsR2:
|
|
696
|
+
ps = rxn.RunReactants((fragment, seed))
|
|
697
|
+
if fragment.HasSubstructMatch(rxn._matchers[1]):
|
|
698
|
+
if seedIsR1:
|
|
699
|
+
ps = rxn.RunReactants((seed, fragment))
|
|
700
|
+
if ps:
|
|
701
|
+
for p in ps:
|
|
702
|
+
# need to check if the product list has more than one entry
|
|
703
|
+
pSmi = Chem.MolToSmiles(p[0], True)
|
|
704
|
+
if pSmi in seen:
|
|
705
|
+
continue
|
|
706
|
+
else:
|
|
707
|
+
seen.add(pSmi)
|
|
708
|
+
if p[0].HasSubstructMatch(dummyPattern):
|
|
709
|
+
continue
|
|
710
|
+
else:
|
|
711
|
+
result.append(
|
|
712
|
+
"|fragment|"
|
|
713
|
+
+ Chem.MolToSmiles(fragment, True)
|
|
714
|
+
+ "|seed|"
|
|
715
|
+
+ Chem.MolToSmiles(seed, True)
|
|
716
|
+
+ "|rxn_smiles|"
|
|
717
|
+
+ Reactions.ReactionToCXSmiles(rxn)
|
|
718
|
+
+ "|rxn_smarts|"
|
|
719
|
+
+ Reactions.ReactionToCXSmarts(rxn)
|
|
720
|
+
+ "|product|"
|
|
721
|
+
+ Chem.MolToSmiles(p[0], True)
|
|
722
|
+
)
|
|
723
|
+
# write the result to a file
|
|
724
|
+
if write_path and len(result) > 0:
|
|
725
|
+
joblib.dump(result, write_path)
|
|
726
|
+
return result
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def _test():
|
|
730
|
+
import doctest
|
|
731
|
+
import sys
|
|
732
|
+
|
|
733
|
+
return doctest.testmod(
|
|
734
|
+
sys.modules["__main__"],
|
|
735
|
+
optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE,
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
if __name__ == "__main__":
|
|
740
|
+
import unittest
|
|
741
|
+
|
|
742
|
+
class TestCase(unittest.TestCase):
|
|
743
|
+
def test1(self):
|
|
744
|
+
m = Chem.MolFromSmiles("CC(=O)OC")
|
|
745
|
+
res = BRICSDecompose(m)
|
|
746
|
+
self.assertTrue(res)
|
|
747
|
+
self.assertTrue(len(res) == 2)
|
|
748
|
+
|
|
749
|
+
m = Chem.MolFromSmiles("CC(=O)N1CCC1=O")
|
|
750
|
+
res = BRICSDecompose(m)
|
|
751
|
+
self.assertTrue(res)
|
|
752
|
+
self.assertTrue(len(res) == 2, res)
|
|
753
|
+
|
|
754
|
+
m = Chem.MolFromSmiles("c1ccccc1N(C)C")
|
|
755
|
+
res = BRICSDecompose(m)
|
|
756
|
+
self.assertTrue(res)
|
|
757
|
+
self.assertTrue(len(res) == 2, res)
|
|
758
|
+
|
|
759
|
+
m = Chem.MolFromSmiles("c1cccnc1N(C)C")
|
|
760
|
+
res = BRICSDecompose(m)
|
|
761
|
+
self.assertTrue(res)
|
|
762
|
+
self.assertTrue(len(res) == 2, res)
|
|
763
|
+
|
|
764
|
+
m = Chem.MolFromSmiles("o1ccnc1N(C)C")
|
|
765
|
+
res = BRICSDecompose(m)
|
|
766
|
+
self.assertTrue(res)
|
|
767
|
+
self.assertTrue(len(res) == 2)
|
|
768
|
+
|
|
769
|
+
m = Chem.MolFromSmiles("c1ccccc1OC")
|
|
770
|
+
res = BRICSDecompose(m)
|
|
771
|
+
self.assertTrue(res)
|
|
772
|
+
self.assertTrue(len(res) == 2)
|
|
773
|
+
|
|
774
|
+
m = Chem.MolFromSmiles("o1ccnc1OC")
|
|
775
|
+
res = BRICSDecompose(m)
|
|
776
|
+
self.assertTrue(res)
|
|
777
|
+
self.assertTrue(len(res) == 2)
|
|
778
|
+
|
|
779
|
+
m = Chem.MolFromSmiles("O1CCNC1OC")
|
|
780
|
+
res = BRICSDecompose(m)
|
|
781
|
+
self.assertTrue(res)
|
|
782
|
+
self.assertTrue(len(res) == 2)
|
|
783
|
+
|
|
784
|
+
m = Chem.MolFromSmiles("CCCSCC")
|
|
785
|
+
res = BRICSDecompose(m)
|
|
786
|
+
self.assertTrue(res)
|
|
787
|
+
self.assertTrue(len(res) == 3, res)
|
|
788
|
+
self.assertTrue("[11*]S[11*]" in res, res)
|
|
789
|
+
|
|
790
|
+
m = Chem.MolFromSmiles("CCNC(=O)C1CC1")
|
|
791
|
+
res = BRICSDecompose(m)
|
|
792
|
+
self.assertTrue(res)
|
|
793
|
+
self.assertTrue(len(res) == 4, res)
|
|
794
|
+
self.assertTrue("[5*]N[5*]" in res, res)
|
|
795
|
+
|
|
796
|
+
def test2(self):
|
|
797
|
+
# example from the paper, nexavar:
|
|
798
|
+
m = Chem.MolFromSmiles(
|
|
799
|
+
"CNC(=O)C1=NC=CC(OC2=CC=C(NC(=O)NC3=CC(=C(Cl)C=C3)C(F)(F)F)C=C2)=C1"
|
|
800
|
+
)
|
|
801
|
+
res = BRICSDecompose(m)
|
|
802
|
+
self.assertTrue(res)
|
|
803
|
+
self.assertTrue(len(res) == 9, res)
|
|
804
|
+
|
|
805
|
+
def test3(self):
|
|
806
|
+
m = Chem.MolFromSmiles("FC(F)(F)C1=C(Cl)C=CC(NC(=O)NC2=CC=CC=C2)=C1")
|
|
807
|
+
res = BRICSDecompose(m)
|
|
808
|
+
self.assertTrue(res)
|
|
809
|
+
self.assertTrue(len(res) == 5, res)
|
|
810
|
+
self.assertTrue("[5*]N[5*]" in res, res)
|
|
811
|
+
self.assertTrue("[16*]c1ccccc1" in res, res)
|
|
812
|
+
self.assertTrue("[8*]C(F)(F)F" in res, res)
|
|
813
|
+
|
|
814
|
+
def test4(self):
|
|
815
|
+
allNodes = set()
|
|
816
|
+
m = Chem.MolFromSmiles("c1ccccc1OCCC")
|
|
817
|
+
res = BRICSDecompose(m, allNodes=allNodes)
|
|
818
|
+
self.assertTrue(res)
|
|
819
|
+
leaves = res
|
|
820
|
+
self.assertTrue(len(leaves) == 3, leaves)
|
|
821
|
+
self.assertTrue(len(allNodes) == 6, allNodes)
|
|
822
|
+
res = BRICSDecompose(m, allNodes=allNodes)
|
|
823
|
+
self.assertFalse(res)
|
|
824
|
+
self.assertTrue(len(allNodes) == 6, allNodes)
|
|
825
|
+
|
|
826
|
+
m = Chem.MolFromSmiles("c1ccccc1OCCCC")
|
|
827
|
+
res = BRICSDecompose(m, allNodes=allNodes)
|
|
828
|
+
self.assertTrue(res)
|
|
829
|
+
leaves.update(res)
|
|
830
|
+
self.assertTrue(len(allNodes) == 9, allNodes)
|
|
831
|
+
self.assertTrue(len(leaves) == 4, leaves)
|
|
832
|
+
|
|
833
|
+
m = Chem.MolFromSmiles("c1cc(C(=O)NCC)ccc1OCCC")
|
|
834
|
+
res = BRICSDecompose(m, allNodes=allNodes)
|
|
835
|
+
self.assertTrue(res)
|
|
836
|
+
leaves.update(res)
|
|
837
|
+
self.assertTrue(len(leaves) == 8, leaves)
|
|
838
|
+
self.assertTrue(len(allNodes) == 18, allNodes)
|
|
839
|
+
|
|
840
|
+
def test5(self):
|
|
841
|
+
allNodes = set()
|
|
842
|
+
frags = [
|
|
843
|
+
"[14*]c1ncncn1",
|
|
844
|
+
"[16*]c1ccccc1",
|
|
845
|
+
"[14*]c1ncccc1",
|
|
846
|
+
]
|
|
847
|
+
frags = [Chem.MolFromSmiles(x) for x in frags]
|
|
848
|
+
res = BRICSBuild(frags)
|
|
849
|
+
self.assertTrue(res)
|
|
850
|
+
res = list(res)
|
|
851
|
+
self.assertTrue(len(res) == 6)
|
|
852
|
+
smis = [Chem.MolToSmiles(x, True) for x in res]
|
|
853
|
+
self.assertTrue("c1ccc(-c2ccccc2)cc1" in smis)
|
|
854
|
+
self.assertTrue("c1ccc(-c2ccccn2)cc1" in smis)
|
|
855
|
+
|
|
856
|
+
def test5a(self):
|
|
857
|
+
allNodes = set()
|
|
858
|
+
frags = [
|
|
859
|
+
"[3*]O[3*]",
|
|
860
|
+
"[16*]c1ccccc1",
|
|
861
|
+
]
|
|
862
|
+
frags = [Chem.MolFromSmiles(x) for x in frags]
|
|
863
|
+
res = BRICSBuild(frags)
|
|
864
|
+
self.assertTrue(res)
|
|
865
|
+
res = list(res)
|
|
866
|
+
smis = [Chem.MolToSmiles(x, True) for x in res]
|
|
867
|
+
self.assertTrue(len(smis) == 2, smis)
|
|
868
|
+
self.assertTrue("c1ccc(Oc2ccccc2)cc1" in smis)
|
|
869
|
+
self.assertTrue("c1ccc(-c2ccccc2)cc1" in smis)
|
|
870
|
+
|
|
871
|
+
def test6(self):
|
|
872
|
+
allNodes = set()
|
|
873
|
+
frags = [
|
|
874
|
+
"[16*]c1ccccc1",
|
|
875
|
+
"[3*]OC",
|
|
876
|
+
"[9*]n1cccc1",
|
|
877
|
+
]
|
|
878
|
+
frags = [Chem.MolFromSmiles(x) for x in frags]
|
|
879
|
+
res = BRICSBuild(frags)
|
|
880
|
+
self.assertTrue(res)
|
|
881
|
+
res = list(res)
|
|
882
|
+
self.assertTrue(len(res) == 3)
|
|
883
|
+
smis = [Chem.MolToSmiles(x, True) for x in res]
|
|
884
|
+
self.assertTrue("c1ccc(-c2ccccc2)cc1" in smis)
|
|
885
|
+
self.assertTrue("COc1ccccc1" in smis)
|
|
886
|
+
self.assertTrue("c1ccc(-n2cccc2)cc1" in smis, smis)
|
|
887
|
+
|
|
888
|
+
def test7(self):
|
|
889
|
+
allNodes = set()
|
|
890
|
+
frags = [
|
|
891
|
+
"[16*]c1ccccc1",
|
|
892
|
+
"[3*]OC",
|
|
893
|
+
"[3*]OCC(=O)[6*]",
|
|
894
|
+
]
|
|
895
|
+
frags = [Chem.MolFromSmiles(x) for x in frags]
|
|
896
|
+
res = BRICSBuild(frags)
|
|
897
|
+
self.assertTrue(res)
|
|
898
|
+
res = list(res)
|
|
899
|
+
smis = [Chem.MolToSmiles(x, True) for x in res]
|
|
900
|
+
self.assertTrue(len(res) == 3)
|
|
901
|
+
self.assertTrue("c1ccc(-c2ccccc2)cc1" in smis)
|
|
902
|
+
self.assertTrue("COc1ccccc1" in smis)
|
|
903
|
+
self.assertTrue("O=C(COc1ccccc1)c1ccccc1" in smis)
|
|
904
|
+
|
|
905
|
+
def test8(self):
|
|
906
|
+
random.seed(23)
|
|
907
|
+
base = Chem.MolFromSmiles("n1cncnc1OCC(C1CC1)OC1CNC1")
|
|
908
|
+
catalog = BRICSDecompose(base)
|
|
909
|
+
self.assertTrue(len(catalog) == 5, catalog)
|
|
910
|
+
catalog = [Chem.MolFromSmiles(x) for x in catalog]
|
|
911
|
+
ms = list(BRICSBuild(catalog, maxDepth=4, scrambleReagents=False))
|
|
912
|
+
for m in ms:
|
|
913
|
+
Chem.SanitizeMol(m)
|
|
914
|
+
ms = [Chem.MolToSmiles(x) for x in ms]
|
|
915
|
+
self.assertEqual(len(ms), 36)
|
|
916
|
+
|
|
917
|
+
ts = [
|
|
918
|
+
"n1cnc(C2CNC2)nc1",
|
|
919
|
+
"n1cnc(-c2ncncn2)nc1",
|
|
920
|
+
"C(OC1CNC1)C(C1CC1)OC1CNC1",
|
|
921
|
+
"n1cnc(OC(COC2CNC2)C2CC2)nc1",
|
|
922
|
+
"n1cnc(OCC(OC2CNC2)C2CNC2)nc1",
|
|
923
|
+
]
|
|
924
|
+
ts = [Chem.MolToSmiles(Chem.MolFromSmiles(x), True) for x in ts]
|
|
925
|
+
for t in ts:
|
|
926
|
+
self.assertTrue(t in ms, (t, ms))
|
|
927
|
+
|
|
928
|
+
ms2 = list(BRICSBuild(catalog, maxDepth=4, scrambleReagents=False))
|
|
929
|
+
for m in ms2:
|
|
930
|
+
Chem.SanitizeMol(m)
|
|
931
|
+
ms2 = [Chem.MolToSmiles(x) for x in ms2]
|
|
932
|
+
self.assertEqual(ms, ms2)
|
|
933
|
+
|
|
934
|
+
ms2 = list(BRICSBuild(catalog, maxDepth=4, scrambleReagents=True))
|
|
935
|
+
for m in ms2:
|
|
936
|
+
Chem.SanitizeMol(m)
|
|
937
|
+
ms2 = [Chem.MolToSmiles(x) for x in ms2]
|
|
938
|
+
self.assertNotEqual(ms, ms2)
|
|
939
|
+
|
|
940
|
+
def test9(self):
|
|
941
|
+
m = Chem.MolFromSmiles("CCOc1ccccc1c1ncc(c2nc(NCCCC)ncn2)cc1")
|
|
942
|
+
res = BRICSDecompose(m)
|
|
943
|
+
self.assertEqual(len(res), 7)
|
|
944
|
+
self.assertTrue("[3*]O[3*]" in res)
|
|
945
|
+
self.assertFalse("[14*]c1ncnc(NCCCC)n1" in res)
|
|
946
|
+
res = BRICSDecompose(m, singlePass=True)
|
|
947
|
+
self.assertEqual(len(res), 13)
|
|
948
|
+
self.assertTrue("[3*]OCC" in res)
|
|
949
|
+
self.assertTrue("[14*]c1ncnc(NCCCC)n1" in res)
|
|
950
|
+
|
|
951
|
+
def test10(self):
|
|
952
|
+
m = Chem.MolFromSmiles("C1CCCCN1c1ccccc1")
|
|
953
|
+
res = BRICSDecompose(m)
|
|
954
|
+
self.assertEqual(len(res), 2, res)
|
|
955
|
+
|
|
956
|
+
def test11(self):
|
|
957
|
+
# test coordinate preservation:
|
|
958
|
+
molblock = """
|
|
959
|
+
RDKit 3D
|
|
960
|
+
|
|
961
|
+
13 14 0 0 0 0 0 0 0 0999 V2000
|
|
962
|
+
-1.2004 0.5900 0.6110 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
963
|
+
-2.2328 1.3173 0.0343 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
964
|
+
-3.4299 0.6533 -0.1500 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
965
|
+
-3.3633 -0.7217 -0.3299 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
966
|
+
-2.1552 -1.3791 -0.2207 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
967
|
+
-1.1425 -0.7969 0.5335 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
968
|
+
0.1458 -1.4244 0.4108 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
969
|
+
1.2976 -0.7398 -0.1026 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
970
|
+
2.4889 -0.7939 0.5501 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
971
|
+
3.4615 0.1460 0.3535 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
972
|
+
3.0116 1.4034 -0.0296 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
973
|
+
1.9786 1.4264 -0.9435 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
974
|
+
1.1399 0.3193 -0.9885 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
975
|
+
1 2 2 0
|
|
976
|
+
2 3 1 0
|
|
977
|
+
3 4 2 0
|
|
978
|
+
4 5 1 0
|
|
979
|
+
5 6 2 0
|
|
980
|
+
6 7 1 0
|
|
981
|
+
7 8 1 0
|
|
982
|
+
8 9 2 0
|
|
983
|
+
9 10 1 0
|
|
984
|
+
10 11 2 0
|
|
985
|
+
11 12 1 0
|
|
986
|
+
12 13 2 0
|
|
987
|
+
6 1 1 0
|
|
988
|
+
13 8 1 0
|
|
989
|
+
M END
|
|
990
|
+
"""
|
|
991
|
+
m = Chem.MolFromMolBlock(molblock)
|
|
992
|
+
pieces = BreakBRICSBonds(m)
|
|
993
|
+
|
|
994
|
+
frags = Chem.GetMolFrags(pieces, asMols=True)
|
|
995
|
+
self.assertEqual(len(frags), 3)
|
|
996
|
+
self.assertEqual(frags[0].GetNumAtoms(), 7)
|
|
997
|
+
self.assertEqual(frags[1].GetNumAtoms(), 3)
|
|
998
|
+
self.assertEqual(frags[2].GetNumAtoms(), 7)
|
|
999
|
+
|
|
1000
|
+
c1 = m.GetConformer()
|
|
1001
|
+
c2 = frags[0].GetConformer()
|
|
1002
|
+
for i in range(6):
|
|
1003
|
+
p1 = c1.GetAtomPosition(i)
|
|
1004
|
+
p2 = c2.GetAtomPosition(i)
|
|
1005
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1006
|
+
p1 = c1.GetAtomPosition(6)
|
|
1007
|
+
p2 = c2.GetAtomPosition(6)
|
|
1008
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1009
|
+
|
|
1010
|
+
c2 = frags[2].GetConformer()
|
|
1011
|
+
for i in range(6):
|
|
1012
|
+
p1 = c1.GetAtomPosition(i + 7)
|
|
1013
|
+
p2 = c2.GetAtomPosition(i)
|
|
1014
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1015
|
+
p1 = c1.GetAtomPosition(6)
|
|
1016
|
+
p2 = c2.GetAtomPosition(6)
|
|
1017
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1018
|
+
|
|
1019
|
+
c2 = frags[1].GetConformer()
|
|
1020
|
+
for i in range(1):
|
|
1021
|
+
p1 = c1.GetAtomPosition(i + 6)
|
|
1022
|
+
p2 = c2.GetAtomPosition(i)
|
|
1023
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1024
|
+
p1 = c1.GetAtomPosition(5)
|
|
1025
|
+
p2 = c2.GetAtomPosition(1)
|
|
1026
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1027
|
+
p1 = c1.GetAtomPosition(6)
|
|
1028
|
+
p2 = c2.GetAtomPosition(0)
|
|
1029
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1030
|
+
|
|
1031
|
+
# make sure multiple conformations (include 2D) also work:
|
|
1032
|
+
molblock = """
|
|
1033
|
+
RDKit 2D
|
|
1034
|
+
|
|
1035
|
+
13 14 0 0 0 0 0 0 0 0999 V2000
|
|
1036
|
+
-1.2990 -0.8654 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1037
|
+
-2.5981 -1.6154 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1038
|
+
-3.8971 -0.8654 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1039
|
+
-3.8971 0.6346 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1040
|
+
-2.5981 1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1041
|
+
-1.2990 0.6346 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1042
|
+
-0.0000 1.3846 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1043
|
+
1.2990 0.6346 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1044
|
+
1.2990 -0.8654 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1045
|
+
2.5981 -1.6154 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1046
|
+
3.8971 -0.8654 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1047
|
+
3.8971 0.6346 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1048
|
+
2.5981 1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1049
|
+
1 2 2 0
|
|
1050
|
+
2 3 1 0
|
|
1051
|
+
3 4 2 0
|
|
1052
|
+
4 5 1 0
|
|
1053
|
+
5 6 2 0
|
|
1054
|
+
6 7 1 0
|
|
1055
|
+
7 8 1 0
|
|
1056
|
+
8 9 2 0
|
|
1057
|
+
9 10 1 0
|
|
1058
|
+
10 11 2 0
|
|
1059
|
+
11 12 1 0
|
|
1060
|
+
12 13 2 0
|
|
1061
|
+
6 1 1 0
|
|
1062
|
+
13 8 1 0
|
|
1063
|
+
M END
|
|
1064
|
+
"""
|
|
1065
|
+
m2 = Chem.MolFromMolBlock(molblock)
|
|
1066
|
+
m.AddConformer(m2.GetConformer(), assignId=True)
|
|
1067
|
+
self.assertEqual(m.GetNumConformers(), 2)
|
|
1068
|
+
|
|
1069
|
+
pieces = BreakBRICSBonds(m)
|
|
1070
|
+
frags = Chem.GetMolFrags(pieces, asMols=True)
|
|
1071
|
+
self.assertEqual(len(frags), 3)
|
|
1072
|
+
self.assertEqual(frags[0].GetNumAtoms(), 7)
|
|
1073
|
+
self.assertEqual(frags[1].GetNumAtoms(), 3)
|
|
1074
|
+
self.assertEqual(frags[2].GetNumAtoms(), 7)
|
|
1075
|
+
self.assertEqual(frags[0].GetNumConformers(), 2)
|
|
1076
|
+
self.assertEqual(frags[1].GetNumConformers(), 2)
|
|
1077
|
+
self.assertEqual(frags[2].GetNumConformers(), 2)
|
|
1078
|
+
|
|
1079
|
+
c1 = m.GetConformer(0)
|
|
1080
|
+
c2 = frags[0].GetConformer(0)
|
|
1081
|
+
for i in range(6):
|
|
1082
|
+
p1 = c1.GetAtomPosition(i)
|
|
1083
|
+
p2 = c2.GetAtomPosition(i)
|
|
1084
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1085
|
+
p1 = c1.GetAtomPosition(6)
|
|
1086
|
+
p2 = c2.GetAtomPosition(6)
|
|
1087
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1088
|
+
|
|
1089
|
+
c2 = frags[2].GetConformer(0)
|
|
1090
|
+
for i in range(6):
|
|
1091
|
+
p1 = c1.GetAtomPosition(i + 7)
|
|
1092
|
+
p2 = c2.GetAtomPosition(i)
|
|
1093
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1094
|
+
p1 = c1.GetAtomPosition(6)
|
|
1095
|
+
p2 = c2.GetAtomPosition(6)
|
|
1096
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1097
|
+
|
|
1098
|
+
c2 = frags[1].GetConformer(0)
|
|
1099
|
+
for i in range(1):
|
|
1100
|
+
p1 = c1.GetAtomPosition(i + 6)
|
|
1101
|
+
p2 = c2.GetAtomPosition(i)
|
|
1102
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1103
|
+
p1 = c1.GetAtomPosition(5)
|
|
1104
|
+
p2 = c2.GetAtomPosition(1)
|
|
1105
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1106
|
+
p1 = c1.GetAtomPosition(6)
|
|
1107
|
+
p2 = c2.GetAtomPosition(0)
|
|
1108
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1109
|
+
|
|
1110
|
+
c1 = m.GetConformer(1)
|
|
1111
|
+
c2 = frags[0].GetConformer(1)
|
|
1112
|
+
for i in range(6):
|
|
1113
|
+
p1 = c1.GetAtomPosition(i)
|
|
1114
|
+
p2 = c2.GetAtomPosition(i)
|
|
1115
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1116
|
+
p1 = c1.GetAtomPosition(6)
|
|
1117
|
+
p2 = c2.GetAtomPosition(6)
|
|
1118
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1119
|
+
|
|
1120
|
+
c2 = frags[2].GetConformer(1)
|
|
1121
|
+
for i in range(6):
|
|
1122
|
+
p1 = c1.GetAtomPosition(i + 7)
|
|
1123
|
+
p2 = c2.GetAtomPosition(i)
|
|
1124
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1125
|
+
p1 = c1.GetAtomPosition(6)
|
|
1126
|
+
p2 = c2.GetAtomPosition(6)
|
|
1127
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1128
|
+
|
|
1129
|
+
c2 = frags[1].GetConformer(1)
|
|
1130
|
+
for i in range(1):
|
|
1131
|
+
p1 = c1.GetAtomPosition(i + 6)
|
|
1132
|
+
p2 = c2.GetAtomPosition(i)
|
|
1133
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1134
|
+
p1 = c1.GetAtomPosition(5)
|
|
1135
|
+
p2 = c2.GetAtomPosition(1)
|
|
1136
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1137
|
+
p1 = c1.GetAtomPosition(6)
|
|
1138
|
+
p2 = c2.GetAtomPosition(0)
|
|
1139
|
+
self.assertEqual((p1 - p2).Length(), 0.0)
|
|
1140
|
+
|
|
1141
|
+
def test12(self):
|
|
1142
|
+
m = Chem.MolFromSmiles("CCS(=O)(=O)NCC")
|
|
1143
|
+
res = list(FindBRICSBonds(m))
|
|
1144
|
+
self.assertEqual(len(res), 2, res)
|
|
1145
|
+
atIds = [x[0] for x in res]
|
|
1146
|
+
atIds.sort()
|
|
1147
|
+
self.assertEqual(atIds, [(5, 2), (6, 5)])
|
|
1148
|
+
|
|
1149
|
+
def testGithub1734(self):
|
|
1150
|
+
m = Chem.MolFromSmiles("c1ccccc1[C@H](C)NC")
|
|
1151
|
+
res = BRICSDecompose(m)
|
|
1152
|
+
self.assertEqual(len(res), 3)
|
|
1153
|
+
self.assertTrue("[4*][C@H]([8*])C" in res)
|
|
1154
|
+
res = BreakBRICSBonds(m)
|
|
1155
|
+
self.assertEqual(
|
|
1156
|
+
Chem.MolToSmiles(res, isomericSmiles=True),
|
|
1157
|
+
"[16*]c1ccccc1.[4*][C@H]([8*])C.[5*]NC",
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
failed, tried = _test()
|
|
1161
|
+
if failed:
|
|
1162
|
+
sys.exit(failed)
|
|
1163
|
+
|
|
1164
|
+
unittest.main()
|