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,140 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from .nnutils import create_var, index_select_ND
5
+ from .chemutils import get_mol
6
+ import rdkit.Chem as Chem
7
+
8
+ ELEM_LIST = ['C', 'N', 'O', 'S', 'F', 'Si', 'P', 'Cl', 'Br', 'Mg', 'Na', 'Ca', 'Fe', 'Al', 'I', 'B', 'K', 'Se', 'Zn', 'H', 'Cu', 'Mn', 'unknown']
9
+
10
+ ATOM_FDIM = len(ELEM_LIST) + 6 + 5 + 1
11
+ BOND_FDIM = 5
12
+ MAX_NB = 15
13
+
14
+ def onek_encoding_unk(x, allowable_set):
15
+ if x not in allowable_set:
16
+ x = allowable_set[-1]
17
+ return list(map(lambda s: x == s, allowable_set))
18
+
19
+ def atom_features(atom):
20
+ return torch.Tensor(onek_encoding_unk(atom.GetSymbol(), ELEM_LIST)
21
+ + onek_encoding_unk(atom.GetDegree(), [0,1,2,3,4,5])
22
+ + onek_encoding_unk(atom.GetFormalCharge(), [-1,-2,1,2,0])
23
+ + [atom.GetIsAromatic()])
24
+
25
+ def bond_features(bond):
26
+ bt = bond.GetBondType()
27
+ return torch.Tensor([bt == Chem.rdchem.BondType.SINGLE, bt == Chem.rdchem.BondType.DOUBLE, bt == Chem.rdchem.BondType.TRIPLE, bt == Chem.rdchem.BondType.AROMATIC, bond.IsInRing()])
28
+
29
+ class JTMPN(nn.Module):
30
+
31
+ def __init__(self, hidden_size, depth):
32
+ super(JTMPN, self).__init__()
33
+ self.hidden_size = hidden_size
34
+ self.depth = depth
35
+
36
+ self.W_i = nn.Linear(ATOM_FDIM + BOND_FDIM, hidden_size, bias=False)
37
+ self.W_h = nn.Linear(hidden_size, hidden_size, bias=False)
38
+ self.W_o = nn.Linear(ATOM_FDIM + hidden_size, hidden_size)
39
+
40
+ def forward(self, fatoms, fbonds, agraph, bgraph, scope, tree_message): #tree_message[0] == vec(0)
41
+ fatoms = create_var(fatoms)
42
+ fbonds = create_var(fbonds)
43
+ agraph = create_var(agraph)
44
+ bgraph = create_var(bgraph)
45
+
46
+ binput = self.W_i(fbonds)
47
+ graph_message = F.relu(binput)
48
+
49
+ for i in range(self.depth - 1):
50
+ message = torch.cat([tree_message,graph_message], dim=0)
51
+ nei_message = index_select_ND(message, 0, bgraph)
52
+ nei_message = nei_message.sum(dim=1) #assuming tree_message[0] == vec(0)
53
+ nei_message = self.W_h(nei_message)
54
+ graph_message = F.relu(binput + nei_message)
55
+
56
+ message = torch.cat([tree_message,graph_message], dim=0)
57
+ nei_message = index_select_ND(message, 0, agraph)
58
+ nei_message = nei_message.sum(dim=1)
59
+ ainput = torch.cat([fatoms, nei_message], dim=1)
60
+ atom_hiddens = F.relu(self.W_o(ainput))
61
+
62
+ mol_vecs = []
63
+ for st,le in scope:
64
+ mol_vec = atom_hiddens.narrow(0, st, le).sum(dim=0) / le
65
+ mol_vecs.append(mol_vec)
66
+
67
+ mol_vecs = torch.stack(mol_vecs, dim=0)
68
+ return mol_vecs
69
+
70
+ @staticmethod
71
+ def tensorize(cand_batch, mess_dict):
72
+ fatoms,fbonds = [],[]
73
+ in_bonds,all_bonds = [],[]
74
+ total_atoms = 0
75
+ total_mess = len(mess_dict) + 1 #must include vec(0) padding
76
+ scope = []
77
+
78
+ for smiles,all_nodes,ctr_node in cand_batch:
79
+ mol = Chem.MolFromSmiles(smiles)
80
+ Chem.Kekulize(mol) #The original jtnn version kekulizes. Need to revisit why it is necessary
81
+ n_atoms = mol.GetNumAtoms()
82
+ ctr_bid = ctr_node.idx
83
+
84
+ for atom in mol.GetAtoms():
85
+ fatoms.append( atom_features(atom) )
86
+ in_bonds.append([])
87
+
88
+ for bond in mol.GetBonds():
89
+ a1 = bond.GetBeginAtom()
90
+ a2 = bond.GetEndAtom()
91
+ x = a1.GetIdx() + total_atoms
92
+ y = a2.GetIdx() + total_atoms
93
+ #Here x_nid,y_nid could be 0
94
+ x_nid,y_nid = a1.GetAtomMapNum(),a2.GetAtomMapNum()
95
+ x_bid = all_nodes[x_nid - 1].idx if x_nid > 0 else -1
96
+ y_bid = all_nodes[y_nid - 1].idx if y_nid > 0 else -1
97
+
98
+ bfeature = bond_features(bond)
99
+
100
+ b = total_mess + len(all_bonds) #bond idx offseted by total_mess
101
+ all_bonds.append((x,y))
102
+ fbonds.append( torch.cat([fatoms[x], bfeature], 0) )
103
+ in_bonds[y].append(b)
104
+
105
+ b = total_mess + len(all_bonds)
106
+ all_bonds.append((y,x))
107
+ fbonds.append( torch.cat([fatoms[y], bfeature], 0) )
108
+ in_bonds[x].append(b)
109
+
110
+ if x_bid >= 0 and y_bid >= 0 and x_bid != y_bid:
111
+ if (x_bid,y_bid) in mess_dict:
112
+ mess_idx = mess_dict[(x_bid,y_bid)]
113
+ in_bonds[y].append(mess_idx)
114
+ if (y_bid,x_bid) in mess_dict:
115
+ mess_idx = mess_dict[(y_bid,x_bid)]
116
+ in_bonds[x].append(mess_idx)
117
+
118
+ scope.append((total_atoms,n_atoms))
119
+ total_atoms += n_atoms
120
+
121
+ total_bonds = len(all_bonds)
122
+ fatoms = torch.stack(fatoms, 0)
123
+ fbonds = torch.stack(fbonds, 0)
124
+ agraph = torch.zeros(total_atoms,MAX_NB).long()
125
+ bgraph = torch.zeros(total_bonds,MAX_NB).long()
126
+
127
+ for a in range(total_atoms):
128
+ for i,b in enumerate(in_bonds[a]):
129
+ if i == MAX_NB: break
130
+ agraph[a,i] = b
131
+
132
+ for b1 in range(total_bonds):
133
+ x,y = all_bonds[b1]
134
+ for i,b2 in enumerate(in_bonds[x]): #b2 is offseted by total_mess
135
+ if b2 < total_mess or all_bonds[b2-total_mess][0] != y:
136
+ if i == MAX_NB: break
137
+ bgraph[b1,i] = b2
138
+
139
+ return (fatoms, fbonds, agraph, bgraph, scope)
140
+
@@ -0,0 +1,350 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from .mol_tree import Vocab, MolTree, MolTreeNode
5
+ from .nnutils import create_var, GRU
6
+ from .chemutils import enum_assemble, set_atommap
7
+ import copy
8
+
9
+ MAX_NB = 15
10
+ MAX_DECODE_LEN = 100
11
+
12
+ class JTNNDecoder(nn.Module):
13
+
14
+ def __init__(self, vocab, hidden_size, latent_size, embedding):
15
+ super(JTNNDecoder, self).__init__()
16
+ self.hidden_size = hidden_size
17
+ self.vocab_size = vocab.size()
18
+ self.vocab = vocab
19
+ self.embedding = embedding
20
+
21
+ #GRU Weights
22
+ self.W_z = nn.Linear(2 * hidden_size, hidden_size)
23
+ self.U_r = nn.Linear(hidden_size, hidden_size, bias=False)
24
+ self.W_r = nn.Linear(hidden_size, hidden_size)
25
+ self.W_h = nn.Linear(2 * hidden_size, hidden_size)
26
+
27
+ #Word Prediction Weights
28
+ self.W = nn.Linear(hidden_size + latent_size, hidden_size)
29
+
30
+ #Stop Prediction Weights
31
+ self.U = nn.Linear(hidden_size + latent_size, hidden_size)
32
+ self.U_i = nn.Linear(2 * hidden_size, hidden_size)
33
+
34
+ #Output Weights
35
+ self.W_o = nn.Linear(hidden_size, self.vocab_size)
36
+ self.U_o = nn.Linear(hidden_size, 1)
37
+
38
+ #Loss Functions
39
+ self.pred_loss = nn.CrossEntropyLoss(reduction='sum')
40
+ self.stop_loss = nn.BCEWithLogitsLoss(reduction='sum')
41
+
42
+ def aggregate(self, hiddens, contexts, x_tree_vecs, mode):
43
+ if mode == 'word':
44
+ V, V_o = self.W, self.W_o
45
+ elif mode == 'stop':
46
+ V, V_o = self.U, self.U_o
47
+ else:
48
+ raise ValueError('aggregate mode is wrong')
49
+
50
+ tree_contexts = x_tree_vecs.index_select(0, contexts)
51
+ input_vec = torch.cat([hiddens, tree_contexts], dim=-1)
52
+ # print('input_vec: ', input_vec.shape)
53
+ # print('V W', self.W.weight.shape)
54
+ # print('V U', self.U.weight.shape)
55
+ # print('tree_contexts: ', tree_contexts.shape)
56
+ # print('hiddens: ', hiddens.shape)
57
+ output_vec = F.relu( V(input_vec) )
58
+ return V_o(output_vec)
59
+
60
+ def forward(self, mol_batch, x_tree_vecs):
61
+ pred_hiddens,pred_contexts,pred_targets = [],[],[]
62
+ stop_hiddens,stop_contexts,stop_targets = [],[],[]
63
+ traces = []
64
+ for mol_tree in mol_batch:
65
+ s = []
66
+ dfs(s, mol_tree.nodes[0], -1)
67
+ traces.append(s)
68
+ for node in mol_tree.nodes:
69
+ node.neighbors = []
70
+
71
+ #Predict Root
72
+ batch_size = len(mol_batch)
73
+ pred_hiddens.append(create_var(torch.zeros(len(mol_batch),self.hidden_size)))
74
+ pred_targets.extend([mol_tree.nodes[0].wid for mol_tree in mol_batch])
75
+ pred_contexts.append( create_var( torch.LongTensor(range(batch_size)) ) )
76
+
77
+ max_iter = max([len(tr) for tr in traces])
78
+ padding = create_var(torch.zeros(self.hidden_size), False)
79
+ h = {}
80
+
81
+ for t in range(max_iter):
82
+ prop_list = []
83
+ batch_list = []
84
+ for i,plist in enumerate(traces):
85
+ if t < len(plist):
86
+ prop_list.append(plist[t])
87
+ batch_list.append(i)
88
+
89
+ cur_x = []
90
+ cur_h_nei,cur_o_nei = [],[]
91
+
92
+ for node_x, real_y, _ in prop_list:
93
+ #Neighbors for message passing (target not included)
94
+ cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors if node_y.idx != real_y.idx]
95
+ pad_len = MAX_NB - len(cur_nei)
96
+ cur_h_nei.extend(cur_nei)
97
+ cur_h_nei.extend([padding] * pad_len)
98
+
99
+ #Neighbors for stop prediction (all neighbors)
100
+ cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors]
101
+ pad_len = MAX_NB - len(cur_nei)
102
+ cur_o_nei.extend(cur_nei)
103
+ cur_o_nei.extend([padding] * pad_len)
104
+
105
+ #Current clique embedding
106
+ cur_x.append(node_x.wid)
107
+
108
+ #Clique embedding
109
+ cur_x = create_var(torch.LongTensor(cur_x))
110
+ cur_x = self.embedding(cur_x)
111
+
112
+ #Message passing
113
+ cur_h_nei = torch.stack(cur_h_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
114
+ new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
115
+
116
+ #Node Aggregate
117
+ cur_o_nei = torch.stack(cur_o_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
118
+ cur_o = cur_o_nei.sum(dim=1)
119
+
120
+ #Gather targets
121
+ pred_target,pred_list = [],[]
122
+ stop_target = []
123
+ for i,m in enumerate(prop_list):
124
+ node_x,node_y,direction = m
125
+ x,y = node_x.idx,node_y.idx
126
+ h[(x,y)] = new_h[i]
127
+ node_y.neighbors.append(node_x)
128
+ if direction == 1:
129
+ pred_target.append(node_y.wid)
130
+ pred_list.append(i)
131
+ stop_target.append(direction)
132
+
133
+ #Hidden states for stop prediction
134
+ cur_batch = create_var(torch.LongTensor(batch_list))
135
+ stop_hidden = torch.cat([cur_x,cur_o], dim=1)
136
+ stop_hiddens.append( stop_hidden )
137
+ stop_contexts.append( cur_batch )
138
+ stop_targets.extend( stop_target )
139
+
140
+ #Hidden states for clique prediction
141
+ if len(pred_list) > 0:
142
+ batch_list = [batch_list[i] for i in pred_list]
143
+ cur_batch = create_var(torch.LongTensor(batch_list))
144
+ pred_contexts.append( cur_batch )
145
+
146
+ cur_pred = create_var(torch.LongTensor(pred_list))
147
+ pred_hiddens.append( new_h.index_select(0, cur_pred) )
148
+ pred_targets.extend( pred_target )
149
+
150
+ #Last stop at root
151
+ cur_x,cur_o_nei = [],[]
152
+ for mol_tree in mol_batch:
153
+ node_x = mol_tree.nodes[0]
154
+ cur_x.append(node_x.wid)
155
+ cur_nei = [h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors]
156
+ pad_len = MAX_NB - len(cur_nei)
157
+ cur_o_nei.extend(cur_nei)
158
+ cur_o_nei.extend([padding] * pad_len)
159
+
160
+ cur_x = create_var(torch.LongTensor(cur_x))
161
+ cur_x = self.embedding(cur_x)
162
+ cur_o_nei = torch.stack(cur_o_nei, dim=0).view(-1,MAX_NB,self.hidden_size)
163
+ cur_o = cur_o_nei.sum(dim=1)
164
+
165
+ stop_hidden = torch.cat([cur_x,cur_o], dim=1)
166
+ stop_hiddens.append( stop_hidden )
167
+ stop_contexts.append( create_var( torch.LongTensor(range(batch_size)) ) )
168
+ stop_targets.extend( [0] * len(mol_batch) )
169
+
170
+ #Predict next clique
171
+ pred_contexts = torch.cat(pred_contexts, dim=0)
172
+ pred_hiddens = torch.cat(pred_hiddens, dim=0)
173
+ pred_scores = self.aggregate(pred_hiddens, pred_contexts, x_tree_vecs, 'word')
174
+ pred_targets = create_var(torch.LongTensor(pred_targets))
175
+
176
+ pred_loss = self.pred_loss(pred_scores, pred_targets) / len(mol_batch)
177
+ _,preds = torch.max(pred_scores, dim=1)
178
+ pred_acc = torch.eq(preds, pred_targets).float()
179
+ pred_acc = torch.sum(pred_acc) / pred_targets.nelement()
180
+
181
+ #Predict stop
182
+ stop_contexts = torch.cat(stop_contexts, dim=0)
183
+ stop_hiddens = torch.cat(stop_hiddens, dim=0)
184
+ stop_hiddens = F.relu( self.U_i(stop_hiddens) )
185
+ stop_scores = self.aggregate(stop_hiddens, stop_contexts, x_tree_vecs, 'stop')
186
+ stop_scores = stop_scores.squeeze(-1)
187
+ stop_targets = create_var(torch.Tensor(stop_targets))
188
+
189
+ stop_loss = self.stop_loss(stop_scores, stop_targets) / len(mol_batch)
190
+ stops = torch.ge(stop_scores, 0).float()
191
+ stop_acc = torch.eq(stops, stop_targets).float()
192
+ stop_acc = torch.sum(stop_acc) / stop_targets.nelement()
193
+
194
+ return pred_loss, stop_loss, pred_acc.item(), stop_acc.item()
195
+
196
+ def decode(self, x_tree_vecs, prob_decode):
197
+ assert x_tree_vecs.size(0) == 1
198
+
199
+ stack = []
200
+ init_hiddens = create_var( torch.zeros(1, self.hidden_size) )
201
+ zero_pad = create_var(torch.zeros(1,1,self.hidden_size))
202
+ contexts = create_var( torch.LongTensor(1).zero_() )
203
+
204
+ #Root Prediction
205
+ root_score = self.aggregate(init_hiddens, contexts, x_tree_vecs, 'word')
206
+ _,root_wid = torch.max(root_score, dim=1)
207
+ root_wid = root_wid.item()
208
+
209
+ root = MolTreeNode(self.vocab.get_smiles(root_wid))
210
+ root.wid = root_wid
211
+ root.idx = 0
212
+ stack.append( (root, self.vocab.get_slots(root.wid)) )
213
+
214
+ all_nodes = [root]
215
+ h = {}
216
+ for step in range(MAX_DECODE_LEN):
217
+ node_x,fa_slot = stack[-1]
218
+ cur_h_nei = [ h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors ]
219
+ if len(cur_h_nei) > 0:
220
+ cur_h_nei = torch.stack(cur_h_nei, dim=0).view(1,-1,self.hidden_size)
221
+ else:
222
+ cur_h_nei = zero_pad
223
+
224
+ cur_x = create_var(torch.LongTensor([node_x.wid]))
225
+ cur_x = self.embedding(cur_x)
226
+
227
+ #Predict stop
228
+ cur_h = cur_h_nei.sum(dim=1)
229
+ stop_hiddens = torch.cat([cur_x,cur_h], dim=1)
230
+ stop_hiddens = F.relu( self.U_i(stop_hiddens) )
231
+ stop_score = self.aggregate(stop_hiddens, contexts, x_tree_vecs, 'stop')
232
+
233
+ if prob_decode:
234
+ backtrack = (torch.bernoulli( torch.sigmoid(stop_score) ).item() == 0)
235
+ else:
236
+ backtrack = (stop_score.item() < 0)
237
+
238
+ if not backtrack: #Forward: Predict next clique
239
+ new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
240
+ pred_score = self.aggregate(new_h, contexts, x_tree_vecs, 'word')
241
+
242
+ if prob_decode:
243
+ sort_wid = torch.multinomial(F.softmax(pred_score, dim=1).squeeze(), 5)
244
+ else:
245
+ _,sort_wid = torch.sort(pred_score, dim=1, descending=True)
246
+ sort_wid = sort_wid.data.squeeze()
247
+
248
+ next_wid = None
249
+ for wid in sort_wid[:5]:
250
+ slots = self.vocab.get_slots(wid)
251
+ node_y = MolTreeNode(self.vocab.get_smiles(wid))
252
+ if have_slots(fa_slot, slots) and can_assemble(node_x, node_y):
253
+ next_wid = wid
254
+ next_slots = slots
255
+ break
256
+
257
+ if next_wid is None:
258
+ backtrack = True #No more children can be added
259
+ else:
260
+ node_y = MolTreeNode(self.vocab.get_smiles(next_wid))
261
+ node_y.wid = next_wid
262
+ node_y.idx = len(all_nodes)
263
+ node_y.neighbors.append(node_x)
264
+ h[(node_x.idx,node_y.idx)] = new_h[0]
265
+ stack.append( (node_y,next_slots) )
266
+ all_nodes.append(node_y)
267
+
268
+ if backtrack: #Backtrack, use if instead of else
269
+ if len(stack) == 1:
270
+ break #At root, terminate
271
+
272
+ node_fa,_ = stack[-2]
273
+ cur_h_nei = [ h[(node_y.idx,node_x.idx)] for node_y in node_x.neighbors if node_y.idx != node_fa.idx ]
274
+ if len(cur_h_nei) > 0:
275
+ cur_h_nei = torch.stack(cur_h_nei, dim=0).view(1,-1,self.hidden_size)
276
+ else:
277
+ cur_h_nei = zero_pad
278
+
279
+ new_h = GRU(cur_x, cur_h_nei, self.W_z, self.W_r, self.U_r, self.W_h)
280
+ h[(node_x.idx,node_fa.idx)] = new_h[0]
281
+ node_fa.neighbors.append(node_x)
282
+ stack.pop()
283
+
284
+ return root, all_nodes
285
+
286
+ """
287
+ Helper Functions:
288
+ """
289
+ def dfs(stack, x, fa_idx):
290
+ for y in x.neighbors:
291
+ if y.idx == fa_idx: continue
292
+ stack.append( (x,y,1) )
293
+ dfs(stack, y, x.idx)
294
+ stack.append( (y,x,0) )
295
+
296
+ def have_slots(fa_slots, ch_slots):
297
+ if len(fa_slots) > 2 and len(ch_slots) > 2:
298
+ return True
299
+ matches = []
300
+ for i,s1 in enumerate(fa_slots):
301
+ a1,c1,h1 = s1
302
+ for j,s2 in enumerate(ch_slots):
303
+ a2,c2,h2 = s2
304
+ if a1 == a2 and c1 == c2 and (a1 != "C" or h1 + h2 >= 4):
305
+ matches.append( (i,j) )
306
+
307
+ if len(matches) == 0: return False
308
+
309
+ fa_match,ch_match = zip(*matches)
310
+ if len(set(fa_match)) == 1 and 1 < len(fa_slots) <= 2: #never remove atom from ring
311
+ fa_slots.pop(fa_match[0])
312
+ if len(set(ch_match)) == 1 and 1 < len(ch_slots) <= 2: #never remove atom from ring
313
+ ch_slots.pop(ch_match[0])
314
+
315
+ return True
316
+
317
+ def can_assemble(node_x, node_y):
318
+ node_x.nid = 1
319
+ node_x.is_leaf = False
320
+ set_atommap(node_x.mol, node_x.nid)
321
+
322
+ neis = node_x.neighbors + [node_y]
323
+ for i,nei in enumerate(neis):
324
+ nei.nid = i + 2
325
+ nei.is_leaf = (len(nei.neighbors) <= 1)
326
+ if nei.is_leaf:
327
+ set_atommap(nei.mol, 0)
328
+ else:
329
+ set_atommap(nei.mol, nei.nid)
330
+
331
+ neighbors = [nei for nei in neis if nei.mol.GetNumAtoms() > 1]
332
+ neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
333
+ singletons = [nei for nei in neis if nei.mol.GetNumAtoms() == 1]
334
+ neighbors = singletons + neighbors
335
+ cands,aroma_scores = enum_assemble(node_x, neighbors)
336
+ return len(cands) > 0# and sum(aroma_scores) >= 0
337
+
338
+ if __name__ == "__main__":
339
+ 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"]
340
+ for s in smiles:
341
+ print(s)
342
+ tree = MolTree(s)
343
+ for i,node in enumerate(tree.nodes):
344
+ node.idx = i
345
+
346
+ stack = []
347
+ dfs(stack, tree.nodes[0], -1)
348
+ for x,y,d in stack:
349
+ print(x.smiles, y.smiles, d)
350
+ print('------------------------------')
@@ -0,0 +1,131 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from collections import deque
5
+ from .mol_tree import Vocab, MolTree
6
+ from .nnutils import create_var, index_select_ND
7
+
8
+ class JTNNEncoder(nn.Module):
9
+
10
+ def __init__(self, hidden_size, depth, embedding):
11
+ super(JTNNEncoder, self).__init__()
12
+ self.hidden_size = hidden_size
13
+ self.depth = depth
14
+
15
+ self.embedding = embedding
16
+ self.outputNN = nn.Sequential(
17
+ nn.Linear(2 * hidden_size, hidden_size),
18
+ nn.ReLU()
19
+ )
20
+ self.GRU = GraphGRU(hidden_size, hidden_size, depth=depth)
21
+
22
+ def forward(self, fnode, fmess, node_graph, mess_graph, scope):
23
+ fnode = create_var(fnode)
24
+ fmess = create_var(fmess)
25
+ node_graph = create_var(node_graph)
26
+ mess_graph = create_var(mess_graph)
27
+ messages = create_var(torch.zeros(mess_graph.size(0), self.hidden_size))
28
+
29
+ fnode = self.embedding(fnode)
30
+ fmess = index_select_ND(fnode, 0, fmess)
31
+ messages = self.GRU(messages, fmess, mess_graph)
32
+
33
+ mess_nei = index_select_ND(messages, 0, node_graph)
34
+ node_vecs = torch.cat([fnode, mess_nei.sum(dim=1)], dim=-1)
35
+ node_vecs = self.outputNN(node_vecs)
36
+
37
+ max_len = max([x for _,x in scope])
38
+ batch_vecs = []
39
+ for st,le in scope:
40
+ cur_vecs = node_vecs[st] #Root is the first node
41
+ batch_vecs.append( cur_vecs )
42
+
43
+ tree_vecs = torch.stack(batch_vecs, dim=0)
44
+ return tree_vecs, messages
45
+
46
+ @staticmethod
47
+ def tensorize(tree_batch):
48
+ node_batch = []
49
+ scope = []
50
+ for tree in tree_batch:
51
+ scope.append( (len(node_batch), len(tree.nodes)) )
52
+ node_batch.extend(tree.nodes)
53
+
54
+ return JTNNEncoder.tensorize_nodes(node_batch, scope)
55
+
56
+ @staticmethod
57
+ def tensorize_nodes(node_batch, scope):
58
+ messages,mess_dict = [None],{}
59
+ fnode = []
60
+ for x in node_batch:
61
+ fnode.append(x.wid)
62
+ for y in x.neighbors:
63
+ mess_dict[(x.idx,y.idx)] = len(messages)
64
+ messages.append( (x,y) )
65
+
66
+ node_graph = [[] for i in range(len(node_batch))]
67
+ mess_graph = [[] for i in range(len(messages))]
68
+ fmess = [0] * len(messages)
69
+
70
+ for x,y in messages[1:]:
71
+ mid1 = mess_dict[(x.idx,y.idx)]
72
+ fmess[mid1] = x.idx
73
+ node_graph[y.idx].append(mid1)
74
+ for z in y.neighbors:
75
+ if z.idx == x.idx: continue
76
+ mid2 = mess_dict[(y.idx,z.idx)]
77
+ mess_graph[mid2].append(mid1)
78
+
79
+ max_len = max([len(t) for t in node_graph] + [1])
80
+ for t in node_graph:
81
+ pad_len = max_len - len(t)
82
+ t.extend([0] * pad_len)
83
+
84
+ max_len = max([len(t) for t in mess_graph] + [1])
85
+ for t in mess_graph:
86
+ pad_len = max_len - len(t)
87
+ t.extend([0] * pad_len)
88
+
89
+ mess_graph = torch.LongTensor(mess_graph)
90
+ node_graph = torch.LongTensor(node_graph)
91
+ fmess = torch.LongTensor(fmess)
92
+ fnode = torch.LongTensor(fnode)
93
+ return (fnode, fmess, node_graph, mess_graph, scope), mess_dict
94
+
95
+ class GraphGRU(nn.Module):
96
+
97
+ def __init__(self, input_size, hidden_size, depth):
98
+ super(GraphGRU, self).__init__()
99
+ self.hidden_size = hidden_size
100
+ self.input_size = input_size
101
+ self.depth = depth
102
+
103
+ self.W_z = nn.Linear(input_size + hidden_size, hidden_size)
104
+ self.W_r = nn.Linear(input_size, hidden_size, bias=False)
105
+ self.U_r = nn.Linear(hidden_size, hidden_size)
106
+ self.W_h = nn.Linear(input_size + hidden_size, hidden_size)
107
+
108
+ def forward(self, h, x, mess_graph):
109
+ mask = torch.ones(h.size(0), 1)
110
+ mask[0] = 0 #first vector is padding
111
+ mask = create_var(mask)
112
+ for it in range(self.depth):
113
+ h_nei = index_select_ND(h, 0, mess_graph)
114
+ sum_h = h_nei.sum(dim=1)
115
+ z_input = torch.cat([x, sum_h], dim=1)
116
+ z = F.sigmoid(self.W_z(z_input))
117
+
118
+ r_1 = self.W_r(x).view(-1, 1, self.hidden_size)
119
+ r_2 = self.U_r(h_nei)
120
+ r = F.sigmoid(r_1 + r_2)
121
+
122
+ gated_h = r * h_nei
123
+ sum_gated_h = gated_h.sum(dim=1)
124
+ h_input = torch.cat([x, sum_gated_h], dim=1)
125
+ pre_h = F.tanh(self.W_h(h_input))
126
+ h = (1.0 - z) * sum_h + z * pre_h
127
+ h = h * mask
128
+
129
+ return h
130
+
131
+