torch-molecule 0.1.0.dev2__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 (148) hide show
  1. torch_molecule/__init__.py +70 -0
  2. torch_molecule/base/__init__.py +8 -0
  3. torch_molecule/base/base.py +396 -0
  4. torch_molecule/base/encoder.py +44 -0
  5. torch_molecule/base/generator.py +24 -0
  6. torch_molecule/base/old_encoder.py +322 -0
  7. torch_molecule/base/old_predictor.py +904 -0
  8. torch_molecule/base/old_predictor2.py +436 -0
  9. torch_molecule/base/predictor.py +112 -0
  10. torch_molecule/encoder/attrmask/__init__.py +3 -0
  11. torch_molecule/encoder/attrmask/model.py +113 -0
  12. torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
  13. torch_molecule/encoder/constant.py +13 -0
  14. torch_molecule/encoder/contextpred/__init__.py +3 -0
  15. torch_molecule/encoder/contextpred/model.py +207 -0
  16. torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
  17. torch_molecule/encoder/contextpred/utils.py +206 -0
  18. torch_molecule/encoder/edgepred/__init__.py +3 -0
  19. torch_molecule/encoder/edgepred/model.py +104 -0
  20. torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
  21. torch_molecule/encoder/graphmae/__init__.py +3 -0
  22. torch_molecule/encoder/graphmae/dataloader.py +206 -0
  23. torch_molecule/encoder/graphmae/model.py +137 -0
  24. torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
  25. torch_molecule/encoder/infograph/__init__.py +3 -0
  26. torch_molecule/encoder/infograph/loss_function.py +128 -0
  27. torch_molecule/encoder/infograph/model.py +92 -0
  28. torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
  29. torch_molecule/encoder/infograph/module.py +31 -0
  30. torch_molecule/encoder/moama/__init__.py +3 -0
  31. torch_molecule/encoder/moama/model.py +131 -0
  32. torch_molecule/encoder/moama/modeling_moama.py +307 -0
  33. torch_molecule/encoder/moama/utils.py +143 -0
  34. torch_molecule/encoder/pretrained/__init__.py +3 -0
  35. torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
  36. torch_molecule/encoder/supervised/__init__.py +3 -0
  37. torch_molecule/encoder/supervised/model.py +97 -0
  38. torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
  39. torch_molecule/generator/digress/__init__.py +3 -0
  40. torch_molecule/generator/digress/diffusion.py +194 -0
  41. torch_molecule/generator/digress/modeling_digress.py +503 -0
  42. torch_molecule/generator/digress/transformer.py +360 -0
  43. torch_molecule/generator/digress/utils.py +196 -0
  44. torch_molecule/generator/gdss/__init__.py +3 -0
  45. torch_molecule/generator/gdss/layers.py +257 -0
  46. torch_molecule/generator/gdss/model.py +204 -0
  47. torch_molecule/generator/gdss/modeling_gdss.py +474 -0
  48. torch_molecule/generator/gdss/sde.py +334 -0
  49. torch_molecule/generator/gdss/solver.py +227 -0
  50. torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
  51. torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
  52. torch_molecule/generator/gdss/utils.py +240 -0
  53. torch_molecule/generator/graph_dit/__init__.py +3 -0
  54. torch_molecule/generator/graph_dit/diffusion.py +188 -0
  55. torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
  56. torch_molecule/generator/graph_dit/transformer.py +207 -0
  57. torch_molecule/generator/graph_dit/utils.py +220 -0
  58. torch_molecule/generator/graph_ga/__init__.py +3 -0
  59. torch_molecule/generator/graph_ga/crossover.py +182 -0
  60. torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
  61. torch_molecule/generator/graph_ga/mutate.py +136 -0
  62. torch_molecule/generator/graph_ga/oracle.py +95 -0
  63. torch_molecule/generator/jtvae/__init__.py +3 -0
  64. torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
  65. torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
  66. torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
  67. torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
  68. torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
  69. torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
  70. torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
  71. torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
  72. torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
  73. torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
  74. torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
  75. torch_molecule/generator/lstm/__init__.py +3 -0
  76. torch_molecule/generator/lstm/action_sampler.py +91 -0
  77. torch_molecule/generator/lstm/lstm.py +59 -0
  78. torch_molecule/generator/lstm/modeling_lstm.py +263 -0
  79. torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
  80. torch_molecule/generator/lstm/utils.py +62 -0
  81. torch_molecule/generator/molgpt/__init__.py +3 -0
  82. torch_molecule/generator/molgpt/dataset.py +87 -0
  83. torch_molecule/generator/molgpt/gpt.py +251 -0
  84. torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
  85. torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
  86. torch_molecule/nn/__init__.py +16 -0
  87. torch_molecule/nn/attention.py +63 -0
  88. torch_molecule/nn/embedder.py +183 -0
  89. torch_molecule/nn/gnn.py +631 -0
  90. torch_molecule/nn/mlp.py +34 -0
  91. torch_molecule/predictor/bfgnn/__init__.py +3 -0
  92. torch_molecule/predictor/bfgnn/model.py +116 -0
  93. torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
  94. torch_molecule/predictor/dir/__init__.py +3 -0
  95. torch_molecule/predictor/dir/model.py +235 -0
  96. torch_molecule/predictor/dir/modeling_dir.py +161 -0
  97. torch_molecule/predictor/dir/utils.py +40 -0
  98. torch_molecule/predictor/gnn/__init__.py +3 -0
  99. torch_molecule/predictor/gnn/model.py +114 -0
  100. torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
  101. torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
  102. torch_molecule/predictor/grea/__init__.py +3 -0
  103. torch_molecule/predictor/grea/model.py +190 -0
  104. torch_molecule/predictor/grea/modeling_grea.py +107 -0
  105. torch_molecule/predictor/grea/old1_features.py +229 -0
  106. torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
  107. torch_molecule/predictor/grea/utils.py +97 -0
  108. torch_molecule/predictor/grin/__init__.py +3 -0
  109. torch_molecule/predictor/grin/model.py +116 -0
  110. torch_molecule/predictor/grin/modeling_grin.py +91 -0
  111. torch_molecule/predictor/irm/__init__.py +3 -0
  112. torch_molecule/predictor/irm/model.py +127 -0
  113. torch_molecule/predictor/irm/modeling_irm.py +179 -0
  114. torch_molecule/predictor/lstm/__init__.py +3 -0
  115. torch_molecule/predictor/lstm/model.py +145 -0
  116. torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
  117. torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
  118. torch_molecule/predictor/old_modeling_graph.py +650 -0
  119. torch_molecule/predictor/rpgnn/__init__.py +3 -0
  120. torch_molecule/predictor/rpgnn/model.py +185 -0
  121. torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
  122. torch_molecule/predictor/sgir/__init__.py +3 -0
  123. torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
  124. torch_molecule/predictor/sgir/strategy.py +266 -0
  125. torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
  126. torch_molecule/predictor/smiles_transformer/model.py +424 -0
  127. torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
  128. torch_molecule/predictor/ssr/__init__.py +3 -0
  129. torch_molecule/predictor/ssr/model.py +283 -0
  130. torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
  131. torch_molecule/predictor/ssr/utils.py +185 -0
  132. torch_molecule/utils/__init__.py +31 -0
  133. torch_molecule/utils/checker.py +158 -0
  134. torch_molecule/utils/checkpoint.py +294 -0
  135. torch_molecule/utils/format.py +140 -0
  136. torch_molecule/utils/generic/metrics.py +530 -0
  137. torch_molecule/utils/generic/pseudo_tasks.py +11 -0
  138. torch_molecule/utils/generic/weights.py +30 -0
  139. torch_molecule/utils/graph/features.py +178 -0
  140. torch_molecule/utils/graph/graph_from_smiles.py +134 -0
  141. torch_molecule/utils/graph/graph_to_smiles.py +312 -0
  142. torch_molecule/utils/hf.py +232 -0
  143. torch_molecule/utils/search.py +64 -0
  144. torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
  145. torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
  146. torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
  147. torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
  148. torch_molecule-0.1.0.dev2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,450 @@
1
+ import rdkit
2
+ import rdkit.Chem as Chem
3
+ from scipy.sparse import csr_matrix
4
+ from scipy.sparse.csgraph import minimum_spanning_tree
5
+ from collections import defaultdict
6
+ from rdkit.Chem.EnumerateStereoisomers import EnumerateStereoisomers, StereoEnumerationOptions
7
+ try:
8
+ from vocab import Vocab
9
+ except:
10
+ from .vocab import Vocab
11
+
12
+ import re
13
+
14
+ MST_MAX_WEIGHT = 100
15
+ MAX_NCAND = 2000
16
+
17
+ def set_atommap(mol, num=0):
18
+ for atom in mol.GetAtoms():
19
+ atom.SetAtomMapNum(num)
20
+
21
+ def get_mol(smiles):
22
+ mol = Chem.MolFromSmiles(smiles)
23
+ if mol is None:
24
+ return None
25
+ Chem.Kekulize(mol)
26
+ return mol
27
+
28
+ def get_smiles(mol):
29
+ return Chem.MolToSmiles(mol, kekuleSmiles=True)
30
+
31
+ def decode_stereo(smiles2D):
32
+ mol = Chem.MolFromSmiles(smiles2D)
33
+ dec_isomers = list(EnumerateStereoisomers(mol))
34
+
35
+ dec_isomers = [Chem.MolFromSmiles(Chem.MolToSmiles(mol, isomericSmiles=True)) for mol in dec_isomers]
36
+ smiles3D = [Chem.MolToSmiles(mol, isomericSmiles=True) for mol in dec_isomers]
37
+
38
+ chiralN = [atom.GetIdx() for atom in dec_isomers[0].GetAtoms() if int(atom.GetChiralTag()) > 0 and atom.GetSymbol() == "N"]
39
+ if len(chiralN) > 0:
40
+ for mol in dec_isomers:
41
+ for idx in chiralN:
42
+ mol.GetAtomWithIdx(idx).SetChiralTag(Chem.rdchem.ChiralType.CHI_UNSPECIFIED)
43
+ smiles3D.append(Chem.MolToSmiles(mol, isomericSmiles=True))
44
+
45
+ return smiles3D
46
+
47
+ def sanitize(mol):
48
+ try:
49
+ smiles = get_smiles(mol)
50
+ mol = get_mol(smiles)
51
+ except Exception as e:
52
+ return None
53
+ return mol
54
+
55
+ def copy_atom(atom):
56
+ new_atom = Chem.Atom(atom.GetSymbol())
57
+ new_atom.SetFormalCharge(atom.GetFormalCharge())
58
+ new_atom.SetAtomMapNum(atom.GetAtomMapNum())
59
+ return new_atom
60
+
61
+ def copy_edit_mol(mol):
62
+ new_mol = Chem.RWMol(Chem.MolFromSmiles(''))
63
+ for atom in mol.GetAtoms():
64
+ new_atom = copy_atom(atom)
65
+ new_mol.AddAtom(new_atom)
66
+ for bond in mol.GetBonds():
67
+ a1 = bond.GetBeginAtom().GetIdx()
68
+ a2 = bond.GetEndAtom().GetIdx()
69
+ bt = bond.GetBondType()
70
+ new_mol.AddBond(a1, a2, bt)
71
+ return new_mol
72
+
73
+ def get_clique_mol(mol, atoms):
74
+ # smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
75
+ # new_mol = Chem.MolFromSmiles(smiles, sanitize=False)
76
+ # new_mol = copy_edit_mol(new_mol).GetMol()
77
+ # new_mol = sanitize(new_mol) #We assume this is not None
78
+ # return new_mol
79
+ try:
80
+ smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
81
+ except Exception as e:
82
+ numbers = [int(num) for num in re.findall(r'\b\d+\b', str(e))]
83
+ # print('e', e)
84
+ # print('numbers', numbers, 'atoms', atoms)
85
+ # print('moltosmie', Chem.MolToSmiles(mol))
86
+ # print('Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=False)', Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=False))
87
+ for num in numbers:
88
+ mol.GetAtomWithIdx(num).SetNumExplicitHs(1)
89
+ smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
90
+ smiles = Chem.MolToSmiles(Chem.MolFromSmiles(smiles), kekuleSmiles=True)
91
+ new_mol = Chem.MolFromSmiles(smiles, sanitize=False)
92
+ new_mol = copy_edit_mol(new_mol).GetMol()
93
+ new_mol = sanitize(new_mol) #We assume this is not None
94
+ return new_mol
95
+
96
+ def tree_decomp(mol):
97
+ n_atoms = mol.GetNumAtoms()
98
+ if n_atoms == 1: #special case
99
+ return [[0]], []
100
+
101
+ cliques = []
102
+ for bond in mol.GetBonds():
103
+ a1 = bond.GetBeginAtom().GetIdx()
104
+ a2 = bond.GetEndAtom().GetIdx()
105
+ if not bond.IsInRing():
106
+ cliques.append([a1,a2])
107
+
108
+ ssr = [list(x) for x in Chem.GetSymmSSSR(mol)]
109
+ cliques.extend(ssr)
110
+
111
+ nei_list = [[] for i in range(n_atoms)]
112
+ for i in range(len(cliques)):
113
+ for atom in cliques[i]:
114
+ nei_list[atom].append(i)
115
+
116
+ #Merge Rings with intersection > 2 atoms
117
+ for i in range(len(cliques)):
118
+ if len(cliques[i]) <= 2: continue
119
+ for atom in cliques[i]:
120
+ for j in nei_list[atom]:
121
+ if i >= j or len(cliques[j]) <= 2: continue
122
+ inter = set(cliques[i]) & set(cliques[j])
123
+ if len(inter) > 2:
124
+ cliques[i].extend(cliques[j])
125
+ cliques[i] = list(set(cliques[i]))
126
+ cliques[j] = []
127
+
128
+ cliques = [c for c in cliques if len(c) > 0]
129
+ nei_list = [[] for i in range(n_atoms)]
130
+ for i in range(len(cliques)):
131
+ for atom in cliques[i]:
132
+ nei_list[atom].append(i)
133
+
134
+ #Build edges and add singleton cliques
135
+ edges = defaultdict(int)
136
+ for atom in range(n_atoms):
137
+ if len(nei_list[atom]) <= 1:
138
+ continue
139
+ cnei = nei_list[atom]
140
+ bonds = [c for c in cnei if len(cliques[c]) == 2]
141
+ rings = [c for c in cnei if len(cliques[c]) > 4]
142
+ if len(bonds) > 2 or (len(bonds) == 2 and len(cnei) > 2): #In general, if len(cnei) >= 3, a singleton should be added, but 1 bond + 2 ring is currently not dealt with.
143
+ cliques.append([atom])
144
+ c2 = len(cliques) - 1
145
+ for c1 in cnei:
146
+ edges[(c1,c2)] = 1
147
+ elif len(rings) > 2: #Multiple (n>2) complex rings
148
+ cliques.append([atom])
149
+ c2 = len(cliques) - 1
150
+ for c1 in cnei:
151
+ edges[(c1,c2)] = MST_MAX_WEIGHT - 1
152
+ else:
153
+ for i in range(len(cnei)):
154
+ for j in range(i + 1, len(cnei)):
155
+ c1,c2 = cnei[i],cnei[j]
156
+ inter = set(cliques[c1]) & set(cliques[c2])
157
+ if edges[(c1,c2)] < len(inter):
158
+ edges[(c1,c2)] = len(inter) #cnei[i] < cnei[j] by construction
159
+
160
+ edges = [u + (MST_MAX_WEIGHT-v,) for u,v in edges.items()]
161
+ if len(edges) == 0:
162
+ return cliques, edges
163
+
164
+ #Compute Maximum Spanning Tree
165
+ row,col,data = zip(*edges)
166
+ n_clique = len(cliques)
167
+ clique_graph = csr_matrix( (data,(row,col)), shape=(n_clique,n_clique) )
168
+ junc_tree = minimum_spanning_tree(clique_graph)
169
+ row,col = junc_tree.nonzero()
170
+ edges = [(row[i],col[i]) for i in range(len(row))]
171
+ return (cliques, edges)
172
+
173
+ def atom_equal(a1, a2):
174
+ return a1.GetSymbol() == a2.GetSymbol() and a1.GetFormalCharge() == a2.GetFormalCharge()
175
+
176
+ #Bond type not considered because all aromatic (so SINGLE matches DOUBLE)
177
+ def ring_bond_equal(b1, b2, reverse=False):
178
+ b1 = (b1.GetBeginAtom(), b1.GetEndAtom())
179
+ if reverse:
180
+ b2 = (b2.GetEndAtom(), b2.GetBeginAtom())
181
+ else:
182
+ b2 = (b2.GetBeginAtom(), b2.GetEndAtom())
183
+ return atom_equal(b1[0], b2[0]) and atom_equal(b1[1], b2[1])
184
+
185
+ def attach_mols(ctr_mol, neighbors, prev_nodes, nei_amap):
186
+ prev_nids = [node.nid for node in prev_nodes]
187
+ for nei_node in prev_nodes + neighbors:
188
+ nei_id,nei_mol = nei_node.nid,nei_node.mol
189
+ amap = nei_amap[nei_id]
190
+ for atom in nei_mol.GetAtoms():
191
+ if atom.GetIdx() not in amap:
192
+ new_atom = copy_atom(atom)
193
+ amap[atom.GetIdx()] = ctr_mol.AddAtom(new_atom)
194
+
195
+ if nei_mol.GetNumBonds() == 0:
196
+ nei_atom = nei_mol.GetAtomWithIdx(0)
197
+ ctr_atom = ctr_mol.GetAtomWithIdx(amap[0])
198
+ ctr_atom.SetAtomMapNum(nei_atom.GetAtomMapNum())
199
+ else:
200
+ for bond in nei_mol.GetBonds():
201
+ a1 = amap[bond.GetBeginAtom().GetIdx()]
202
+ a2 = amap[bond.GetEndAtom().GetIdx()]
203
+ if ctr_mol.GetBondBetweenAtoms(a1, a2) is None:
204
+ ctr_mol.AddBond(a1, a2, bond.GetBondType())
205
+ elif nei_id in prev_nids: #father node overrides
206
+ ctr_mol.RemoveBond(a1, a2)
207
+ ctr_mol.AddBond(a1, a2, bond.GetBondType())
208
+ return ctr_mol
209
+
210
+ def local_attach(ctr_mol, neighbors, prev_nodes, amap_list):
211
+ ctr_mol = copy_edit_mol(ctr_mol)
212
+ nei_amap = {nei.nid:{} for nei in prev_nodes + neighbors}
213
+
214
+ for nei_id,ctr_atom,nei_atom in amap_list:
215
+ nei_amap[nei_id][nei_atom] = ctr_atom
216
+
217
+ ctr_mol = attach_mols(ctr_mol, neighbors, prev_nodes, nei_amap)
218
+ return ctr_mol.GetMol()
219
+
220
+ #This version records idx mapping between ctr_mol and nei_mol
221
+ def enum_attach(ctr_mol, nei_node, amap, singletons):
222
+ nei_mol,nei_idx = nei_node.mol,nei_node.nid
223
+ att_confs = []
224
+ black_list = [atom_idx for nei_id,atom_idx,_ in amap if nei_id in singletons]
225
+ ctr_atoms = [atom for atom in ctr_mol.GetAtoms() if atom.GetIdx() not in black_list]
226
+ ctr_bonds = [bond for bond in ctr_mol.GetBonds()]
227
+
228
+ if nei_mol.GetNumBonds() == 0: #neighbor singleton
229
+ nei_atom = nei_mol.GetAtomWithIdx(0)
230
+ used_list = [atom_idx for _,atom_idx,_ in amap]
231
+ for atom in ctr_atoms:
232
+ if atom_equal(atom, nei_atom) and atom.GetIdx() not in used_list:
233
+ new_amap = amap + [(nei_idx, atom.GetIdx(), 0)]
234
+ att_confs.append( new_amap )
235
+
236
+ elif nei_mol.GetNumBonds() == 1: #neighbor is a bond
237
+ bond = nei_mol.GetBondWithIdx(0)
238
+ bond_val = int(bond.GetBondTypeAsDouble())
239
+ b1,b2 = bond.GetBeginAtom(), bond.GetEndAtom()
240
+
241
+ for atom in ctr_atoms:
242
+ #Optimize if atom is carbon (other atoms may change valence)
243
+ if atom.GetAtomicNum() == 6 and atom.GetTotalNumHs() < bond_val:
244
+ continue
245
+ if atom_equal(atom, b1):
246
+ new_amap = amap + [(nei_idx, atom.GetIdx(), b1.GetIdx())]
247
+ att_confs.append( new_amap )
248
+ elif atom_equal(atom, b2):
249
+ new_amap = amap + [(nei_idx, atom.GetIdx(), b2.GetIdx())]
250
+ att_confs.append( new_amap )
251
+ else:
252
+ #intersection is an atom
253
+ for a1 in ctr_atoms:
254
+ for a2 in nei_mol.GetAtoms():
255
+ if atom_equal(a1, a2):
256
+ #Optimize if atom is carbon (other atoms may change valence)
257
+ if a1.GetAtomicNum() == 6 and a1.GetTotalNumHs() + a2.GetTotalNumHs() < 4:
258
+ continue
259
+ new_amap = amap + [(nei_idx, a1.GetIdx(), a2.GetIdx())]
260
+ att_confs.append( new_amap )
261
+
262
+ #intersection is an bond
263
+ if ctr_mol.GetNumBonds() > 1:
264
+ for b1 in ctr_bonds:
265
+ for b2 in nei_mol.GetBonds():
266
+ if ring_bond_equal(b1, b2):
267
+ new_amap = amap + [(nei_idx, b1.GetBeginAtom().GetIdx(), b2.GetBeginAtom().GetIdx()), (nei_idx, b1.GetEndAtom().GetIdx(), b2.GetEndAtom().GetIdx())]
268
+ att_confs.append( new_amap )
269
+
270
+ if ring_bond_equal(b1, b2, reverse=True):
271
+ new_amap = amap + [(nei_idx, b1.GetBeginAtom().GetIdx(), b2.GetEndAtom().GetIdx()), (nei_idx, b1.GetEndAtom().GetIdx(), b2.GetBeginAtom().GetIdx())]
272
+ att_confs.append( new_amap )
273
+ return att_confs
274
+
275
+ #Try rings first: Speed-Up
276
+ def enum_assemble(node, neighbors, prev_nodes=[], prev_amap=[]):
277
+ all_attach_confs = []
278
+ singletons = [nei_node.nid for nei_node in neighbors + prev_nodes if nei_node.mol.GetNumAtoms() == 1]
279
+
280
+ def search(cur_amap, depth):
281
+ if len(all_attach_confs) > MAX_NCAND:
282
+ return
283
+ if depth == len(neighbors):
284
+ all_attach_confs.append(cur_amap)
285
+ return
286
+
287
+ nei_node = neighbors[depth]
288
+ cand_amap = enum_attach(node.mol, nei_node, cur_amap, singletons)
289
+ cand_smiles = set()
290
+ candidates = []
291
+ for amap in cand_amap:
292
+ cand_mol = local_attach(node.mol, neighbors[:depth+1], prev_nodes, amap)
293
+ cand_mol = sanitize(cand_mol)
294
+ if cand_mol is None:
295
+ continue
296
+ smiles = get_smiles(cand_mol)
297
+ if smiles in cand_smiles:
298
+ continue
299
+ cand_smiles.add(smiles)
300
+ candidates.append(amap)
301
+
302
+ if len(candidates) == 0:
303
+ return
304
+
305
+ for new_amap in candidates:
306
+ search(new_amap, depth + 1)
307
+
308
+ search(prev_amap, 0)
309
+ cand_smiles = set()
310
+ candidates = []
311
+ aroma_score = []
312
+ for amap in all_attach_confs:
313
+ cand_mol = local_attach(node.mol, neighbors, prev_nodes, amap)
314
+ cand_mol = Chem.MolFromSmiles(Chem.MolToSmiles(cand_mol))
315
+ smiles = Chem.MolToSmiles(cand_mol)
316
+ if smiles in cand_smiles or check_singleton(cand_mol, node, neighbors) == False:
317
+ continue
318
+ cand_smiles.add(smiles)
319
+ candidates.append( (smiles,amap) )
320
+ aroma_score.append( check_aroma(cand_mol, node, neighbors) )
321
+
322
+ return candidates, aroma_score
323
+
324
+ def check_singleton(cand_mol, ctr_node, nei_nodes):
325
+ rings = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() > 2]
326
+ singletons = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() == 1]
327
+ if len(singletons) > 0 or len(rings) == 0: return True
328
+
329
+ n_leaf2_atoms = 0
330
+ for atom in cand_mol.GetAtoms():
331
+ nei_leaf_atoms = [a for a in atom.GetNeighbors() if not a.IsInRing()] #a.GetDegree() == 1]
332
+ if len(nei_leaf_atoms) > 1:
333
+ n_leaf2_atoms += 1
334
+
335
+ return n_leaf2_atoms == 0
336
+
337
+ def check_aroma(cand_mol, ctr_node, nei_nodes):
338
+ rings = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() >= 3]
339
+ if len(rings) < 2: return 0 #Only multi-ring system needs to be checked
340
+
341
+ get_nid = lambda x: 0 if x.is_leaf else x.nid
342
+ benzynes = [get_nid(node) for node in nei_nodes + [ctr_node] if node.smiles in Vocab.benzynes]
343
+ penzynes = [get_nid(node) for node in nei_nodes + [ctr_node] if node.smiles in Vocab.penzynes]
344
+ if len(benzynes) + len(penzynes) == 0:
345
+ return 0 #No specific aromatic rings
346
+
347
+ n_aroma_atoms = 0
348
+ for atom in cand_mol.GetAtoms():
349
+ if atom.GetAtomMapNum() in benzynes+penzynes and atom.GetIsAromatic():
350
+ n_aroma_atoms += 1
351
+
352
+ if n_aroma_atoms >= len(benzynes) * 4 + len(penzynes) * 3:
353
+ return 1000
354
+ else:
355
+ return -0.001
356
+
357
+ #Only used for debugging purpose
358
+ def dfs_assemble(cur_mol, global_amap, fa_amap, cur_node, fa_node):
359
+ fa_nid = fa_node.nid if fa_node is not None else -1
360
+ prev_nodes = [fa_node] if fa_node is not None else []
361
+
362
+ children = [nei for nei in cur_node.neighbors if nei.nid != fa_nid]
363
+ neighbors = [nei for nei in children if nei.mol.GetNumAtoms() > 1]
364
+ neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
365
+ singletons = [nei for nei in children if nei.mol.GetNumAtoms() == 1]
366
+ neighbors = singletons + neighbors
367
+
368
+ cur_amap = [(fa_nid,a2,a1) for nid,a1,a2 in fa_amap if nid == cur_node.nid]
369
+ cands = enum_assemble(cur_node, neighbors, prev_nodes, cur_amap)
370
+
371
+ cand_smiles,cand_amap = zip(*cands)
372
+ label_idx = cand_smiles.index(cur_node.label)
373
+ label_amap = cand_amap[label_idx]
374
+
375
+ for nei_id,ctr_atom,nei_atom in label_amap:
376
+ if nei_id == fa_nid:
377
+ continue
378
+ global_amap[nei_id][nei_atom] = global_amap[cur_node.nid][ctr_atom]
379
+
380
+ cur_mol = attach_mols(cur_mol, children, [], global_amap) #father is already attached
381
+ for nei_node in children:
382
+ if not nei_node.is_leaf:
383
+ dfs_assemble(cur_mol, global_amap, label_amap, nei_node, cur_node)
384
+
385
+ if __name__ == "__main__":
386
+ import sys
387
+ from mol_tree import MolTree
388
+ lg = rdkit.RDLogger.logger()
389
+ lg.setLevel(rdkit.RDLogger.CRITICAL)
390
+
391
+ smiles = ["O=C1[C@@H]2C=C[C@@H](C=CC2)C1(c1ccccc1)c1ccccc1","O=C([O-])CC[C@@]12CCCC[C@]1(O)OC(=O)CC2", "ON=C1C[C@H]2CC3(C[C@@H](C1)c1ccccc12)OCCO3", "C[C@H]1CC(=O)[C@H]2[C@@]3(O)C(=O)c4cccc(O)c4[C@@H]4O[C@@]43[C@@H](O)C[C@]2(O)C1", 'Cc1cc(NC(=O)CSc2nnc3c4ccccc4n(C)c3n2)ccc1Br', 'CC(C)(C)c1ccc(C(=O)N[C@H]2CCN3CCCc4cccc2c43)cc1', "O=c1c2ccc3c(=O)n(-c4nccs4)c(=O)c4ccc(c(=O)n1-c1nccs1)c2c34", "O=C(N1CCc2c(F)ccc(F)c2C1)C1(O)Cc2ccccc2C1"]
392
+
393
+ def tree_test():
394
+ for s in sys.stdin:
395
+ s = s.split()[0]
396
+ tree = MolTree(s)
397
+ print('-------------------------------------------')
398
+ print(s)
399
+ for node in tree.nodes:
400
+ print(node.smiles, [x.smiles for x in node.neighbors])
401
+
402
+ def decode_test():
403
+ wrong = 0
404
+ for tot,s in enumerate(sys.stdin):
405
+ s = s.split()[0]
406
+ tree = MolTree(s)
407
+ tree.recover()
408
+
409
+ cur_mol = copy_edit_mol(tree.nodes[0].mol)
410
+ global_amap = [{}] + [{} for node in tree.nodes]
411
+ global_amap[1] = {atom.GetIdx():atom.GetIdx() for atom in cur_mol.GetAtoms()}
412
+
413
+ dfs_assemble(cur_mol, global_amap, [], tree.nodes[0], None)
414
+
415
+ cur_mol = cur_mol.GetMol()
416
+ cur_mol = Chem.MolFromSmiles(Chem.MolToSmiles(cur_mol))
417
+ set_atommap(cur_mol)
418
+ dec_smiles = Chem.MolToSmiles(cur_mol)
419
+
420
+ gold_smiles = Chem.MolToSmiles(Chem.MolFromSmiles(s))
421
+ if gold_smiles != dec_smiles:
422
+ print(gold_smiles, dec_smiles)
423
+ wrong += 1
424
+ print(wrong, tot + 1)
425
+
426
+ def enum_test():
427
+ for s in sys.stdin:
428
+ s = s.split()[0]
429
+ tree = MolTree(s)
430
+ tree.recover()
431
+ tree.assemble()
432
+ for node in tree.nodes:
433
+ if node.label not in node.cands:
434
+ print(tree.smiles)
435
+ print(node.smiles, [x.smiles for x in node.neighbors])
436
+ print(node.label, len(node.cands))
437
+
438
+ def count():
439
+ cnt,n = 0,0
440
+ for s in sys.stdin:
441
+ s = s.split()[0]
442
+ tree = MolTree(s)
443
+ tree.recover()
444
+ tree.assemble()
445
+ for node in tree.nodes:
446
+ cnt += len(node.cands)
447
+ n += len(tree.nodes)
448
+ #print(cnt * 1.0 / n)
449
+
450
+ count()
@@ -0,0 +1,127 @@
1
+ import torch
2
+ from torch.utils.data import Dataset, DataLoader
3
+ from .mol_tree import MolTree
4
+ import numpy as np
5
+ from .jtnn_enc import JTNNEncoder
6
+ from .mpn import MPN
7
+ from .jtmpn import JTMPN
8
+ import pickle
9
+ import os, random
10
+
11
+ # class PairTreeFolder(object):
12
+ # def __init__(self, data_folder, vocab, batch_size, num_workers=4, shuffle=True, y_assm=True, replicate=None):
13
+ # self.data_folder = data_folder
14
+ # self.data_files = [fn for fn in os.listdir(data_folder)]
15
+ # self.batch_size = batch_size
16
+ # self.vocab = vocab
17
+ # self.num_workers = num_workers
18
+ # self.y_assm = y_assm
19
+ # self.shuffle = shuffle
20
+
21
+ # if replicate is not None: #expand is int
22
+ # self.data_files = self.data_files * replicate
23
+
24
+ # def __iter__(self):
25
+ # for fn in self.data_files:
26
+ # fn = os.path.join(self.data_folder, fn)
27
+ # with open(fn) as f:
28
+ # data = pickle.load(f)
29
+
30
+ # if self.shuffle:
31
+ # random.shuffle(data) #shuffle data before batch
32
+
33
+ # batches = [data[i : i + self.batch_size] for i in range(0, len(data), self.batch_size)]
34
+ # if len(batches[-1]) < self.batch_size:
35
+ # batches.pop()
36
+
37
+ # dataset = PairTreeDataset(batches, self.vocab, self.y_assm)
38
+ # dataloader = DataLoader(dataset, batch_size=1, shuffle=False, num_workers=self.num_workers, collate_fn=lambda x:x[0])
39
+
40
+ # for b in dataloader:
41
+ # yield b
42
+
43
+ # del data, batches, dataset, dataloader
44
+
45
+ class MolTreeFolder(object):
46
+ def __init__(self, data, vocab, batch_size, num_workers=0, shuffle=True, assm=True):
47
+ self.data = data
48
+ self.batch_size = batch_size
49
+ self.vocab = vocab
50
+ self.num_workers = num_workers
51
+ self.shuffle = shuffle
52
+ self.assm = assm
53
+
54
+ def __iter__(self):
55
+ data = self.data
56
+ if self.shuffle:
57
+ random.shuffle(data) # shuffle data before batch
58
+
59
+ batches = [data[i : i + self.batch_size] for i in range(0, len(data), self.batch_size)]
60
+ if len(batches[-1]) < self.batch_size:
61
+ batches.pop()
62
+
63
+ dataset = MolTreeDataset(batches, self.vocab, self.assm)
64
+ dataloader = DataLoader(dataset, batch_size=1, shuffle=False, num_workers=self.num_workers, collate_fn=lambda x:x[0])
65
+
66
+ for b in dataloader:
67
+ yield b
68
+
69
+ del data, batches, dataset, dataloader
70
+
71
+ class PairTreeDataset(Dataset):
72
+
73
+ def __init__(self, data, vocab, y_assm):
74
+ self.data = data
75
+ self.vocab = vocab
76
+ self.y_assm = y_assm
77
+
78
+ def __len__(self):
79
+ return len(self.data)
80
+
81
+ def __getitem__(self, idx):
82
+ batch0, batch1 = zip(*self.data[idx])
83
+ return tensorize(batch0, self.vocab, assm=False), tensorize(batch1, self.vocab, assm=self.y_assm)
84
+
85
+ class MolTreeDataset(Dataset):
86
+ def __init__(self, data, vocab, assm=True):
87
+ self.data = data
88
+ self.vocab = vocab
89
+ self.assm = assm
90
+
91
+ def __len__(self):
92
+ return len(self.data)
93
+
94
+ def __getitem__(self, idx):
95
+ return tensorize(self.data[idx], self.vocab, assm=self.assm)
96
+
97
+ def tensorize(tree_batch, vocab, assm=True):
98
+ set_batch_nodeID(tree_batch, vocab)
99
+ smiles_batch = [tree.smiles for tree in tree_batch]
100
+ jtenc_holder,mess_dict = JTNNEncoder.tensorize(tree_batch)
101
+ jtenc_holder = jtenc_holder
102
+ mpn_holder = MPN.tensorize(smiles_batch)
103
+
104
+ if assm is False:
105
+ return tree_batch, jtenc_holder, mpn_holder
106
+
107
+ cands = []
108
+ batch_idx = []
109
+ for i,mol_tree in enumerate(tree_batch):
110
+ for node in mol_tree.nodes:
111
+ #Leaf node's attachment is determined by neighboring node's attachment
112
+ if node.is_leaf or len(node.cands) == 1: continue
113
+ cands.extend( [(cand, mol_tree.nodes, node) for cand in node.cands] )
114
+ batch_idx.extend([i] * len(node.cands))
115
+
116
+ jtmpn_holder = JTMPN.tensorize(cands, mess_dict)
117
+ batch_idx = torch.LongTensor(batch_idx)
118
+
119
+ return tree_batch, jtenc_holder, mpn_holder, (jtmpn_holder,batch_idx)
120
+
121
+ def set_batch_nodeID(mol_batch, vocab):
122
+ tot = 0
123
+ for mol_tree in mol_batch:
124
+ for node in mol_tree.nodes:
125
+ node.idx = tot
126
+ node.wid = vocab.get_index(node.smiles)
127
+ tot += 1