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,111 @@
1
+ import rdkit
2
+ import rdkit.Chem as Chem
3
+ from .chemutils import get_clique_mol, tree_decomp, get_mol, get_smiles, set_atommap, enum_assemble, decode_stereo
4
+ from .vocab import *
5
+
6
+ class MolTreeNode(object):
7
+
8
+ def __init__(self, smiles, clique=[]):
9
+ self.smiles = smiles
10
+ self.mol = get_mol(self.smiles)
11
+
12
+ self.clique = [x for x in clique] #copy
13
+ self.neighbors = []
14
+
15
+ def add_neighbor(self, nei_node):
16
+ self.neighbors.append(nei_node)
17
+
18
+ def recover(self, original_mol):
19
+ clique = []
20
+ clique.extend(self.clique)
21
+ if not self.is_leaf:
22
+ for cidx in self.clique:
23
+ original_mol.GetAtomWithIdx(cidx).SetAtomMapNum(self.nid)
24
+
25
+ for nei_node in self.neighbors:
26
+ clique.extend(nei_node.clique)
27
+ if nei_node.is_leaf: #Leaf node, no need to mark
28
+ continue
29
+ for cidx in nei_node.clique:
30
+ #allow singleton node override the atom mapping
31
+ if cidx not in self.clique or len(nei_node.clique) == 1:
32
+ atom = original_mol.GetAtomWithIdx(cidx)
33
+ atom.SetAtomMapNum(nei_node.nid)
34
+
35
+ clique = list(set(clique))
36
+ label_mol = get_clique_mol(original_mol, clique)
37
+ self.label = Chem.MolToSmiles(Chem.MolFromSmiles(get_smiles(label_mol)))
38
+
39
+ for cidx in clique:
40
+ original_mol.GetAtomWithIdx(cidx).SetAtomMapNum(0)
41
+
42
+ return self.label
43
+
44
+ def assemble(self):
45
+ neighbors = [nei for nei in self.neighbors if nei.mol.GetNumAtoms() > 1]
46
+ neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
47
+ singletons = [nei for nei in self.neighbors if nei.mol.GetNumAtoms() == 1]
48
+ neighbors = singletons + neighbors
49
+
50
+ cands,aroma = enum_assemble(self, neighbors)
51
+ new_cands = [cand for i,cand in enumerate(cands) if aroma[i] >= 0]
52
+ if len(new_cands) > 0: cands = new_cands
53
+
54
+ if len(cands) > 0:
55
+ self.cands, _ = zip(*cands)
56
+ self.cands = list(self.cands)
57
+ else:
58
+ self.cands = []
59
+
60
+ class MolTree(object):
61
+
62
+ def __init__(self, smiles, voc=None):
63
+ self.smiles = smiles
64
+ self.mol = get_mol(smiles)
65
+ self.voc = voc
66
+
67
+ #Stereo Generation (currently disabled)
68
+ #mol = Chem.MolFromSmiles(smiles)
69
+ #self.smiles3D = Chem.MolToSmiles(mol, isomericSmiles=True)
70
+ #self.smiles2D = Chem.MolToSmiles(mol)
71
+ #self.stereo_cands = decode_stereo(self.smiles2D)
72
+
73
+ cliques, edges = tree_decomp(self.mol)
74
+ self.nodes = []
75
+ root = 0
76
+ for i,c in enumerate(cliques):
77
+ cmol = get_clique_mol(self.mol, c)
78
+ node = MolTreeNode(get_smiles(cmol), c)
79
+ self.nodes.append(node)
80
+ if min(c) == 0: root = i
81
+
82
+ for x,y in edges:
83
+ self.nodes[x].add_neighbor(self.nodes[y])
84
+ self.nodes[y].add_neighbor(self.nodes[x])
85
+
86
+ if root > 0:
87
+ self.nodes[0],self.nodes[root] = self.nodes[root],self.nodes[0]
88
+
89
+ for i,node in enumerate(self.nodes):
90
+ node.nid = i + 1
91
+ if len(node.neighbors) > 1: #Leaf node mol is not marked
92
+ set_atommap(node.mol, node.nid)
93
+ node.is_leaf = (len(node.neighbors) == 1)
94
+
95
+ def size(self):
96
+ return len(self.nodes)
97
+
98
+ def recover(self):
99
+ for node in self.nodes:
100
+ node.recover(self.mol)
101
+
102
+ def assemble(self):
103
+ for node in self.nodes:
104
+ node.assemble()
105
+
106
+ def dfs(node, fa_idx):
107
+ max_depth = 0
108
+ for child in node.neighbors:
109
+ if child.idx == fa_idx: continue
110
+ max_depth = max(max_depth, dfs(child, node.idx))
111
+ return max_depth + 1
@@ -0,0 +1,129 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import rdkit.Chem as Chem
4
+ import torch.nn.functional as F
5
+
6
+
7
+ from .nnutils import *
8
+ from .chemutils import get_mol
9
+
10
+ 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']
11
+
12
+ ATOM_FDIM = len(ELEM_LIST) + 6 + 5 + 4 + 1
13
+ BOND_FDIM = 5 + 6
14
+ MAX_NB = 6
15
+
16
+ def onek_encoding_unk(x, allowable_set):
17
+ if x not in allowable_set:
18
+ x = allowable_set[-1]
19
+ return list(map(lambda s: x == s, allowable_set))
20
+
21
+ def atom_features(atom):
22
+ return torch.Tensor(onek_encoding_unk(atom.GetSymbol(), ELEM_LIST)
23
+ + onek_encoding_unk(atom.GetDegree(), [0,1,2,3,4,5])
24
+ + onek_encoding_unk(atom.GetFormalCharge(), [-1,-2,1,2,0])
25
+ + onek_encoding_unk(int(atom.GetChiralTag()), [0,1,2,3])
26
+ + [atom.GetIsAromatic()])
27
+
28
+ def bond_features(bond):
29
+ bt = bond.GetBondType()
30
+ stereo = int(bond.GetStereo())
31
+ fbond = [bt == Chem.rdchem.BondType.SINGLE, bt == Chem.rdchem.BondType.DOUBLE, bt == Chem.rdchem.BondType.TRIPLE, bt == Chem.rdchem.BondType.AROMATIC, bond.IsInRing()]
32
+ fstereo = onek_encoding_unk(stereo, [0,1,2,3,4,5])
33
+ return torch.Tensor(fbond + fstereo)
34
+
35
+ class MPN(nn.Module):
36
+
37
+ def __init__(self, hidden_size, depth):
38
+ super(MPN, self).__init__()
39
+ self.hidden_size = hidden_size
40
+ self.depth = depth
41
+
42
+ self.W_i = nn.Linear(ATOM_FDIM + BOND_FDIM, hidden_size, bias=False)
43
+ self.W_h = nn.Linear(hidden_size, hidden_size, bias=False)
44
+ self.W_o = nn.Linear(ATOM_FDIM + hidden_size, hidden_size)
45
+
46
+ def forward(self, fatoms, fbonds, agraph, bgraph, scope):
47
+ fatoms = create_var(fatoms)
48
+ fbonds = create_var(fbonds)
49
+ agraph = create_var(agraph)
50
+ bgraph = create_var(bgraph)
51
+
52
+ binput = self.W_i(fbonds)
53
+ message = F.relu(binput)
54
+
55
+ for i in range(self.depth - 1):
56
+ nei_message = index_select_ND(message, 0, bgraph)
57
+ nei_message = nei_message.sum(dim=1)
58
+ nei_message = self.W_h(nei_message)
59
+ message = F.relu(binput + nei_message)
60
+
61
+ nei_message = index_select_ND(message, 0, agraph)
62
+ nei_message = nei_message.sum(dim=1)
63
+ ainput = torch.cat([fatoms, nei_message], dim=1)
64
+ atom_hiddens = F.relu(self.W_o(ainput))
65
+
66
+ max_len = max([x for _,x in scope])
67
+ batch_vecs = []
68
+ for st,le in scope:
69
+ cur_vecs = atom_hiddens[st : st + le].mean(dim=0)
70
+ batch_vecs.append( cur_vecs )
71
+
72
+ mol_vecs = torch.stack(batch_vecs, dim=0)
73
+ return mol_vecs
74
+
75
+ @staticmethod
76
+ def tensorize(mol_batch):
77
+ padding = torch.zeros(ATOM_FDIM + BOND_FDIM)
78
+ fatoms,fbonds = [],[padding] #Ensure bond is 1-indexed
79
+ in_bonds,all_bonds = [],[(-1,-1)] #Ensure bond is 1-indexed
80
+ scope = []
81
+ total_atoms = 0
82
+
83
+ for smiles in mol_batch:
84
+ mol = get_mol(smiles)
85
+ #mol = Chem.MolFromSmiles(smiles)
86
+ n_atoms = mol.GetNumAtoms()
87
+ for atom in mol.GetAtoms():
88
+ fatoms.append( atom_features(atom) )
89
+ in_bonds.append([])
90
+
91
+ for bond in mol.GetBonds():
92
+ a1 = bond.GetBeginAtom()
93
+ a2 = bond.GetEndAtom()
94
+ x = a1.GetIdx() + total_atoms
95
+ y = a2.GetIdx() + total_atoms
96
+
97
+ b = len(all_bonds)
98
+ all_bonds.append((x,y))
99
+ fbonds.append( torch.cat([fatoms[x], bond_features(bond)], 0) )
100
+ in_bonds[y].append(b)
101
+
102
+ b = len(all_bonds)
103
+ all_bonds.append((y,x))
104
+ fbonds.append( torch.cat([fatoms[y], bond_features(bond)], 0) )
105
+ in_bonds[x].append(b)
106
+
107
+ scope.append((total_atoms,n_atoms))
108
+ total_atoms += n_atoms
109
+
110
+ total_bonds = len(all_bonds)
111
+ fatoms = torch.stack(fatoms, 0)
112
+ fbonds = torch.stack(fbonds, 0)
113
+ agraph = torch.zeros(total_atoms,MAX_NB).long()
114
+ bgraph = torch.zeros(total_bonds,MAX_NB).long()
115
+
116
+ for a in range(total_atoms):
117
+ for i,b in enumerate(in_bonds[a]):
118
+ if i >= MAX_NB: break
119
+ agraph[a,i] = b
120
+
121
+ for b1 in range(1, total_bonds):
122
+ x,y = all_bonds[b1]
123
+ for i,b2 in enumerate(in_bonds[x]):
124
+ if all_bonds[b2][0] != y:
125
+ if i >= MAX_NB: break
126
+ bgraph[b1,i] = b2
127
+
128
+ return (fatoms, fbonds, agraph, bgraph, scope)
129
+
@@ -0,0 +1,66 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from torch.autograd import Variable
5
+
6
+ def create_var(tensor, requires_grad=None):
7
+ if requires_grad is None:
8
+ return Variable(tensor).cuda()
9
+ else:
10
+ return Variable(tensor, requires_grad=requires_grad).cuda()
11
+
12
+ def index_select_ND(source, dim, index):
13
+ index_size = index.size()
14
+ suffix_dim = source.size()[1:]
15
+ final_size = index_size + suffix_dim
16
+ target = source.index_select(dim, index.view(-1))
17
+ return target.view(final_size)
18
+
19
+ def avg_pool(all_vecs, scope, dim):
20
+ size = create_var(torch.Tensor([le for _,le in scope]))
21
+ return all_vecs.sum(dim=dim) / size.unsqueeze(-1)
22
+
23
+ def stack_pad_tensor(tensor_list):
24
+ max_len = max([t.size(0) for t in tensor_list])
25
+ for i,tensor in enumerate(tensor_list):
26
+ pad_len = max_len - tensor.size(0)
27
+ tensor_list[i] = F.pad( tensor, (0,0,0,pad_len) )
28
+ return torch.stack(tensor_list, dim=0)
29
+
30
+ #3D padded tensor to 2D matrix, with padded zeros removed
31
+ def flatten_tensor(tensor, scope):
32
+ assert tensor.size(0) == len(scope)
33
+ tlist = []
34
+ for i,tup in enumerate(scope):
35
+ le = tup[1]
36
+ tlist.append( tensor[i, 0:le] )
37
+ return torch.cat(tlist, dim=0)
38
+
39
+ #2D matrix to 3D padded tensor
40
+ def inflate_tensor(tensor, scope):
41
+ max_len = max([le for _,le in scope])
42
+ batch_vecs = []
43
+ for st,le in scope:
44
+ cur_vecs = tensor[st : st + le]
45
+ cur_vecs = F.pad( cur_vecs, (0,0,0,max_len-le) )
46
+ batch_vecs.append( cur_vecs )
47
+
48
+ return torch.stack(batch_vecs, dim=0)
49
+
50
+ def GRU(x, h_nei, W_z, W_r, U_r, W_h):
51
+ hidden_size = x.size()[-1]
52
+ sum_h = h_nei.sum(dim=1)
53
+ z_input = torch.cat([x,sum_h], dim=1)
54
+ z = F.sigmoid(W_z(z_input))
55
+
56
+ r_1 = W_r(x).view(-1,1,hidden_size)
57
+ r_2 = U_r(h_nei)
58
+ r = F.sigmoid(r_1 + r_2)
59
+
60
+ gated_h = r * h_nei
61
+ sum_gated_h = gated_h.sum(dim=1)
62
+ h_input = torch.cat([x,sum_gated_h], dim=1)
63
+ pre_h = F.tanh(W_h(h_input))
64
+ new_h = (1.0 - z) * sum_h + z * pre_h
65
+ return new_h
66
+
@@ -0,0 +1,31 @@
1
+ import rdkit
2
+ import rdkit.Chem as Chem
3
+ import copy
4
+
5
+ def get_slots(smiles):
6
+ mol = Chem.MolFromSmiles(smiles)
7
+ return [(atom.GetSymbol(), atom.GetFormalCharge(), atom.GetTotalNumHs()) for atom in mol.GetAtoms()]
8
+
9
+ class Vocab(object):
10
+ benzynes = ['C1=CC=CC=C1', 'C1=CC=NC=C1', 'C1=CC=NN=C1', 'C1=CN=CC=N1', 'C1=CN=CN=C1', 'C1=CN=NC=N1', 'C1=CN=NN=C1', 'C1=NC=NC=N1', 'C1=NN=CN=N1']
11
+ penzynes = ['C1=C[NH]C=C1', 'C1=C[NH]C=N1', 'C1=C[NH]N=C1', 'C1=C[NH]N=N1', 'C1=COC=C1', 'C1=COC=N1', 'C1=CON=C1', 'C1=CSC=C1', 'C1=CSC=N1', 'C1=CSN=C1', 'C1=CSN=N1', 'C1=NN=C[NH]1', 'C1=NN=CO1', 'C1=NN=CS1', 'C1=N[NH]C=N1', 'C1=N[NH]N=C1', 'C1=N[NH]N=N1', 'C1=NN=N[NH]1', 'C1=NN=NS1', 'C1=NOC=N1', 'C1=NON=C1', 'C1=NSC=N1', 'C1=NSN=C1']
12
+
13
+ def __init__(self, smiles_list):
14
+ self.vocab = smiles_list
15
+ self.vmap = {x:i for i,x in enumerate(self.vocab)}
16
+ self.slots = [get_slots(smiles) for smiles in self.vocab]
17
+ Vocab.benzynes = [s for s in smiles_list if s.count('=') >= 2 and Chem.MolFromSmiles(s).GetNumAtoms() == 6] + ['C1=CCNCC1']
18
+ Vocab.penzynes = [s for s in smiles_list if s.count('=') >= 2 and Chem.MolFromSmiles(s).GetNumAtoms() == 5] + ['C1=NCCN1','C1=NNCC1']
19
+
20
+ def get_index(self, smiles):
21
+ return self.vmap[smiles]
22
+
23
+ def get_smiles(self, idx):
24
+ return self.vocab[idx]
25
+
26
+ def get_slots(self, idx):
27
+ return copy.deepcopy(self.slots[idx])
28
+
29
+ def size(self):
30
+ return len(self.vocab)
31
+
@@ -0,0 +1,246 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import copy
5
+ import rdkit
6
+ import rdkit.Chem as Chem
7
+
8
+ from .jtnn.mol_tree import Vocab, MolTree
9
+ from .jtnn.nnutils import create_var, flatten_tensor, avg_pool
10
+ from .jtnn.jtnn_enc import JTNNEncoder
11
+ from .jtnn.jtnn_dec import JTNNDecoder
12
+ from .jtnn.mpn import MPN
13
+ from .jtnn.jtmpn import JTMPN
14
+ from .jtnn.datautils import tensorize
15
+ from .jtnn.chemutils import enum_assemble, set_atommap, copy_edit_mol, attach_mols
16
+
17
+ class JTNNVAE(nn.Module):
18
+ def __init__(self, vocab, hidden_size, latent_size, depthT, depthG):
19
+ super(JTNNVAE, self).__init__()
20
+ self.hidden_size = hidden_size
21
+ assert latent_size % 2 == 0, 'latent_size must be even'
22
+ self.latent_size = latent_size = int(latent_size / 2) #Tree and Mol has two vectors
23
+
24
+ if vocab is None:
25
+ raise ValueError('JTNNVAE is not properly initialized. vocab is required')
26
+
27
+ self.vocab = vocab
28
+ self.jtnn = JTNNEncoder(hidden_size, depthT, nn.Embedding(vocab.size(), hidden_size))
29
+ self.decoder = JTNNDecoder(vocab, hidden_size, latent_size, nn.Embedding(vocab.size(), hidden_size))
30
+
31
+ self.jtmpn = JTMPN(hidden_size, depthG)
32
+ self.mpn = MPN(hidden_size, depthG)
33
+
34
+ self.A_assm = nn.Linear(latent_size, hidden_size, bias=False)
35
+ self.assm_loss = nn.CrossEntropyLoss(reduction='sum')
36
+
37
+ self.T_mean = nn.Linear(hidden_size, latent_size)
38
+ self.T_var = nn.Linear(hidden_size, latent_size)
39
+ self.G_mean = nn.Linear(hidden_size, latent_size)
40
+ self.G_var = nn.Linear(hidden_size, latent_size)
41
+
42
+ self.initialize_parameters()
43
+
44
+ def initialize_parameters(self, seed=None):
45
+ if seed is not None:
46
+ torch.manual_seed(seed)
47
+
48
+ def _basic_init(module):
49
+ if isinstance(module, nn.Linear):
50
+ torch.nn.init.xavier_uniform_(module.weight)
51
+ if module.bias is not None:
52
+ nn.init.constant_(module.bias, 0)
53
+
54
+ self.apply(_basic_init)
55
+
56
+ def compute_loss(self, x_batch):
57
+ word_loss, topo_loss, assm_loss, kl_div, _, _, _ = self.forward(x_batch)
58
+ return word_loss, topo_loss, assm_loss, kl_div
59
+
60
+ def encode(self, jtenc_holder, mpn_holder):
61
+ tree_vecs, tree_mess = self.jtnn(*jtenc_holder)
62
+ mol_vecs = self.mpn(*mpn_holder)
63
+ return tree_vecs, tree_mess, mol_vecs
64
+
65
+ def encode_from_smiles(self, smiles_list):
66
+ tree_batch = [MolTree(s) for s in smiles_list]
67
+ _, jtenc_holder, mpn_holder = tensorize(tree_batch, self.vocab, assm=False)
68
+ tree_vecs, _, mol_vecs = self.encode(jtenc_holder, mpn_holder)
69
+ return torch.cat([tree_vecs, mol_vecs], dim=-1)
70
+
71
+ def encode_latent(self, jtenc_holder, mpn_holder):
72
+ tree_vecs, _ = self.jtnn(*jtenc_holder)
73
+ mol_vecs = self.mpn(*mpn_holder)
74
+ tree_mean = self.T_mean(tree_vecs)
75
+ mol_mean = self.G_mean(mol_vecs)
76
+ tree_var = -torch.abs(self.T_var(tree_vecs))
77
+ mol_var = -torch.abs(self.G_var(mol_vecs))
78
+ return torch.cat([tree_mean, mol_mean], dim=1), torch.cat([tree_var, mol_var], dim=1)
79
+
80
+ def rsample(self, z_vecs, W_mean, W_var):
81
+ batch_size = z_vecs.size(0)
82
+ z_mean = W_mean(z_vecs)
83
+ z_log_var = -torch.abs(W_var(z_vecs)) # Following Mueller et al.
84
+ kl_loss = -0.5 * torch.sum(1.0 + z_log_var - z_mean * z_mean - torch.exp(z_log_var)) / batch_size
85
+ epsilon = create_var(torch.randn_like(z_mean))
86
+ z_vecs = z_mean + torch.exp(z_log_var / 2) * epsilon
87
+ return z_vecs, kl_loss
88
+
89
+ def sample_prior(self, device, prob_decode=False):
90
+ z_tree = torch.randn(1, self.latent_size).to(device)
91
+ z_mol = torch.randn(1, self.latent_size).to(device)
92
+ return self.decode(z_tree, z_mol, prob_decode, device)
93
+
94
+ def forward(self, x_batch):
95
+ x_batch, x_jtenc_holder, x_mpn_holder, x_jtmpn_holder = x_batch
96
+ x_tree_vecs, x_tree_mess, x_mol_vecs = self.encode(x_jtenc_holder, x_mpn_holder)
97
+ z_tree_vecs, tree_kl = self.rsample(x_tree_vecs, self.T_mean, self.T_var)
98
+ z_mol_vecs, mol_kl = self.rsample(x_mol_vecs, self.G_mean, self.G_var)
99
+
100
+ kl_div = tree_kl + mol_kl
101
+ word_loss, topo_loss, word_acc, topo_acc = self.decoder(x_batch, z_tree_vecs)
102
+ assm_loss, assm_acc = self.assm(x_batch, x_jtmpn_holder, z_mol_vecs, x_tree_mess)
103
+
104
+ # return word_loss + topo_loss + assm_loss + beta * kl_div, kl_div.item(), word_acc, topo_acc, assm_acc
105
+ return word_loss, topo_loss, assm_loss, kl_div, word_acc, topo_acc, assm_acc
106
+
107
+ def assm(self, mol_batch, jtmpn_holder, x_mol_vecs, x_tree_mess):
108
+ jtmpn_holder,batch_idx = jtmpn_holder
109
+ fatoms,fbonds,agraph,bgraph,scope = jtmpn_holder
110
+ batch_idx = create_var(batch_idx)
111
+
112
+ cand_vecs = self.jtmpn(fatoms, fbonds, agraph, bgraph, scope, x_tree_mess)
113
+
114
+ x_mol_vecs = x_mol_vecs.index_select(0, batch_idx)
115
+ x_mol_vecs = self.A_assm(x_mol_vecs) #bilinear
116
+ scores = torch.bmm(
117
+ x_mol_vecs.unsqueeze(1),
118
+ cand_vecs.unsqueeze(-1)
119
+ ).squeeze()
120
+
121
+ cnt,tot,acc = 0,0,0
122
+ all_loss = []
123
+ for i,mol_tree in enumerate(mol_batch):
124
+ comp_nodes = [node for node in mol_tree.nodes if len(node.cands) > 1 and not node.is_leaf]
125
+ cnt += len(comp_nodes)
126
+ for node in comp_nodes:
127
+ label = node.cands.index(node.label)
128
+ ncand = len(node.cands)
129
+ cur_score = scores.narrow(0, tot, ncand)
130
+ tot += ncand
131
+
132
+ if cur_score.data[label] >= cur_score.max().item():
133
+ acc += 1
134
+
135
+ label = create_var(torch.LongTensor([label]))
136
+ all_loss.append( self.assm_loss(cur_score.view(1,-1), label) )
137
+
138
+ all_loss = sum(all_loss) / len(mol_batch)
139
+ return all_loss, acc * 1.0 / cnt
140
+
141
+ def decode(self, x_tree_vecs, x_mol_vecs, prob_decode, device):
142
+ #currently do not support batch decoding
143
+ assert x_tree_vecs.size(0) == 1 and x_mol_vecs.size(0) == 1
144
+
145
+ pred_root, pred_nodes = self.decoder.decode(x_tree_vecs, prob_decode)
146
+ if len(pred_nodes) == 0: return None
147
+ elif len(pred_nodes) == 1: return pred_root.smiles
148
+
149
+ #Mark nid & is_leaf & atommap
150
+ for i, node in enumerate(pred_nodes):
151
+ node.nid = i + 1
152
+ node.is_leaf = (len(node.neighbors) == 1)
153
+ if len(node.neighbors) > 1:
154
+ set_atommap(node.mol, node.nid)
155
+
156
+ scope = [(0, len(pred_nodes))]
157
+ jtenc_holder,mess_dict = JTNNEncoder.tensorize_nodes(pred_nodes, scope)
158
+ _, tree_mess = self.jtnn(*jtenc_holder)
159
+ tree_mess = (tree_mess, mess_dict) #Important: tree_mess is a matrix, mess_dict is a python dict
160
+
161
+ x_mol_vecs = self.A_assm(x_mol_vecs).squeeze() #bilinear
162
+
163
+ cur_mol = copy_edit_mol(pred_root.mol)
164
+ global_amap = [{}] + [{} for node in pred_nodes]
165
+ global_amap[1] = {atom.GetIdx():atom.GetIdx() for atom in cur_mol.GetAtoms()}
166
+
167
+ cur_mol,_ = self.dfs_assemble(tree_mess, x_mol_vecs, pred_nodes, cur_mol, global_amap, [], pred_root, None, prob_decode, check_aroma=True, device=device)
168
+ if cur_mol is None:
169
+ cur_mol = copy_edit_mol(pred_root.mol)
170
+ global_amap = [{}] + [{} for node in pred_nodes]
171
+ global_amap[1] = {atom.GetIdx():atom.GetIdx() for atom in cur_mol.GetAtoms()}
172
+ cur_mol, pre_mol = self.dfs_assemble(tree_mess, x_mol_vecs, pred_nodes, cur_mol, global_amap, [], pred_root, None, prob_decode, check_aroma=False, device=device)
173
+ if cur_mol is None: cur_mol = pre_mol
174
+
175
+ if cur_mol is None:
176
+ return None
177
+
178
+ cur_mol = cur_mol.GetMol()
179
+ set_atommap(cur_mol)
180
+ cur_mol = Chem.MolFromSmiles(Chem.MolToSmiles(cur_mol))
181
+ return Chem.MolToSmiles(cur_mol) if cur_mol is not None else None
182
+
183
+ def dfs_assemble(self, y_tree_mess, x_mol_vecs, all_nodes, cur_mol, global_amap, fa_amap, cur_node, fa_node, prob_decode, check_aroma, device):
184
+ fa_nid = fa_node.nid if fa_node is not None else -1
185
+ prev_nodes = [fa_node] if fa_node is not None else []
186
+
187
+ children = [nei for nei in cur_node.neighbors if nei.nid != fa_nid]
188
+ neighbors = [nei for nei in children if nei.mol.GetNumAtoms() > 1]
189
+ neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
190
+ singletons = [nei for nei in children if nei.mol.GetNumAtoms() == 1]
191
+ neighbors = singletons + neighbors
192
+
193
+ cur_amap = [(fa_nid,a2,a1) for nid,a1,a2 in fa_amap if nid == cur_node.nid]
194
+ cands,aroma_score = enum_assemble(cur_node, neighbors, prev_nodes, cur_amap)
195
+ if len(cands) == 0 or (sum(aroma_score) < 0 and check_aroma):
196
+ return None, cur_mol
197
+
198
+ cand_smiles,cand_amap = list(zip(*cands))
199
+ aroma_score = torch.Tensor(aroma_score).to(device)
200
+ cands = [(smiles, all_nodes, cur_node) for smiles in cand_smiles]
201
+
202
+ if len(cands) > 1:
203
+ jtmpn_holder = JTMPN.tensorize(cands, y_tree_mess[1])
204
+ fatoms,fbonds,agraph,bgraph,scope = jtmpn_holder
205
+ cand_vecs = self.jtmpn(fatoms, fbonds, agraph, bgraph, scope, y_tree_mess[0])
206
+ scores = torch.mv(cand_vecs, x_mol_vecs) + aroma_score
207
+ else:
208
+ scores = torch.Tensor([1.0])
209
+
210
+ if prob_decode:
211
+ probs = F.softmax(scores.view(1,-1), dim=1).squeeze() + 1e-7 #prevent prob = 0
212
+ cand_idx = torch.multinomial(probs, probs.numel())
213
+ else:
214
+ _, cand_idx = torch.sort(scores, descending=True)
215
+
216
+ backup_mol = Chem.RWMol(cur_mol)
217
+ pre_mol = cur_mol
218
+ for i in range(cand_idx.numel()):
219
+ cur_mol = Chem.RWMol(backup_mol)
220
+ pred_amap = cand_amap[cand_idx[i].item()]
221
+ new_global_amap = copy.deepcopy(global_amap)
222
+
223
+ for nei_id,ctr_atom,nei_atom in pred_amap:
224
+ if nei_id == fa_nid:
225
+ continue
226
+ new_global_amap[nei_id][nei_atom] = new_global_amap[cur_node.nid][ctr_atom]
227
+
228
+ cur_mol = attach_mols(cur_mol, children, [], new_global_amap) #father is already attached
229
+ new_mol = cur_mol.GetMol()
230
+ new_mol = Chem.MolFromSmiles(Chem.MolToSmiles(new_mol))
231
+
232
+ if new_mol is None: continue
233
+
234
+ has_error = False
235
+ for nei_node in children:
236
+ if nei_node.is_leaf: continue
237
+ tmp_mol, tmp_mol2 = self.dfs_assemble(y_tree_mess, x_mol_vecs, all_nodes, cur_mol, new_global_amap, pred_amap, nei_node, cur_node, prob_decode, check_aroma, device)
238
+ if tmp_mol is None:
239
+ has_error = True
240
+ if i == 0: pre_mol = tmp_mol2
241
+ break
242
+ cur_mol = tmp_mol
243
+
244
+ if not has_error: return cur_mol, cur_mol
245
+
246
+ return None, pre_mol