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.
Files changed (57) hide show
  1. MCP/server.py +770 -0
  2. polyscript/__init__.py +12 -0
  3. polyscript/brics/BRICSMod.py +1164 -0
  4. polyscript/brics/BRICS_utils.py +189 -0
  5. polyscript/brics/__init__.py +48 -0
  6. polyscript/classifier/__init__.py +270 -0
  7. polyscript/classifier/class_reference.py +951 -0
  8. polyscript/depolymerizer/__init__.py +576 -0
  9. polyscript/depolymerizer/mpi.py +390 -0
  10. polyscript/depolymerizer/reaction_reference.py +447 -0
  11. polyscript/polymerizer/__init__.py +22 -0
  12. polyscript/polymerizer/core.py +648 -0
  13. polyscript/polymerizer/mpi.py +860 -0
  14. polyscript/polymerizer/mpi_df.py +378 -0
  15. polyscript/polymerizer/utils.py +795 -0
  16. polyscript/utils/__init__.py +80 -0
  17. polyscript/utils/adapters.py +357 -0
  18. polyscript/utils/canon_analyze.py +193 -0
  19. polyscript/utils/executor.py +66 -0
  20. polyscript/utils/logger.py +331 -0
  21. polyscript/utils/mpi/__init__.py +33 -0
  22. polyscript/utils/mpi/checkpoint.py +263 -0
  23. polyscript/utils/mpi/logger.py +138 -0
  24. polyscript/utils/mpi/progress.py +134 -0
  25. polyscript/utils/mpi/protocol.py +113 -0
  26. polyscript/utils/mpi/resource.py +94 -0
  27. polyscript/utils/mpi/splitter.py +42 -0
  28. polyscript/utils/mpi_executor.py +167 -0
  29. polyscript/utils/mpi_validator.py +940 -0
  30. polyscript/utils/parsers.py +177 -0
  31. polyscript/utils/validators.py +201 -0
  32. polyscript_pypi-0.1.0.dist-info/METADATA +82 -0
  33. polyscript_pypi-0.1.0.dist-info/RECORD +57 -0
  34. polyscript_pypi-0.1.0.dist-info/WHEEL +5 -0
  35. polyscript_pypi-0.1.0.dist-info/licenses/LICENSE +21 -0
  36. polyscript_pypi-0.1.0.dist-info/top_level.txt +4 -0
  37. runners/adapt_dataframe.py +105 -0
  38. runners/brics_decompose.py +28 -0
  39. runners/canonicalization.py +46 -0
  40. runners/filter_candidates.py +54 -0
  41. runners/mpi_depolymerize.py +51 -0
  42. runners/mpi_runner.py +66 -0
  43. runners/mpi_runner_df.py +49 -0
  44. runners/mpi_validator.py +45 -0
  45. tests/conftest.py +10 -0
  46. tests/sample_gen.py +10 -0
  47. tests/test_brics.py +275 -0
  48. tests/test_classifier.py +201 -0
  49. tests/test_mpi_polymerize.py +170 -0
  50. tests/test_polymerize.py +206 -0
  51. tests/utils/test_adaptors.py +182 -0
  52. tests/utils/test_canonicalization.py +62 -0
  53. tests/utils/test_executor.py +101 -0
  54. tests/utils/test_logger.py +426 -0
  55. tests/utils/test_mpi_validator.py +159 -0
  56. tests/utils/test_parsers.py +150 -0
  57. 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()