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,207 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ from .utils import PlaceHolder
6
+ from ...nn import MLP, AttentionWithNodeMask
7
+ from ...nn.embedder import TimestepEmbedder, CategoricalEmbedder, ClusterContinuousEmbedder
8
+
9
+ def modulate(x, shift, scale):
10
+ return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
11
+
12
+ class Transformer(nn.Module):
13
+ def __init__(
14
+ self,
15
+ max_node,
16
+ hidden_size=384,
17
+ num_layer=12,
18
+ num_head=16,
19
+ mlp_ratio=4.0,
20
+ dropout=0.,
21
+ drop_condition=0.1,
22
+ input_dim_X=118,
23
+ input_dim_E=5,
24
+ input_dim_y=None,
25
+ task_type=[], # a list of 'regression' or 'classification'
26
+ ):
27
+ super().__init__()
28
+ self.input_dim_y = input_dim_y
29
+ self.x_embedder = nn.Linear(input_dim_X + max_node * input_dim_E, hidden_size, bias=False)
30
+ self.t_embedder = TimestepEmbedder(hidden_size)
31
+
32
+ if input_dim_y > 0 and len(task_type) > 0:
33
+ self.y_embedder_list = torch.nn.ModuleList()
34
+ for i in range(input_dim_y):
35
+ if task_type[i] == 'regression':
36
+ self.y_embedder_list.append(ClusterContinuousEmbedder(1, hidden_size, drop_condition))
37
+ else:
38
+ self.y_embedder_list.append(CategoricalEmbedder(2, hidden_size, drop_condition))
39
+ else:
40
+ self.y_embedder_list = None
41
+
42
+ self.blocks = nn.ModuleList(
43
+ [
44
+ AttentionBlock(hidden_size, num_head, mlp_ratio=mlp_ratio, dropout=dropout)
45
+ for _ in range(num_layer)
46
+ ]
47
+ )
48
+
49
+ self.final_layer = FinalLayer(
50
+ max_node=max_node,
51
+ hidden_size=hidden_size,
52
+ atom_type=input_dim_X,
53
+ bond_type=input_dim_E,
54
+ mlp_ratio=mlp_ratio,
55
+ num_head=num_head,
56
+ )
57
+
58
+ self.initialize_parameters()
59
+
60
+ def initialize_parameters(self, seed=None):
61
+ if seed is not None:
62
+ torch.manual_seed(seed)
63
+
64
+ # Initialize transformer layers:
65
+ def _basic_init(module):
66
+ if isinstance(module, nn.Linear):
67
+ torch.nn.init.xavier_uniform_(module.weight)
68
+ if module.bias is not None:
69
+ nn.init.constant_(module.bias, 0)
70
+
71
+ def _constant_init(module, i):
72
+ if isinstance(module, nn.Linear):
73
+ nn.init.constant_(module.weight, i)
74
+ if module.bias is not None:
75
+ nn.init.constant_(module.bias, i)
76
+
77
+ self.apply(_basic_init)
78
+
79
+ for block in self.blocks :
80
+ _constant_init(block.adaLN_modulation[0], 0)
81
+ _constant_init(self.final_layer.adaLN_modulation[0], 0)
82
+
83
+ def forward(self, noisy_data, unconditioned):
84
+ X, E = noisy_data['X_t'].float(), noisy_data['E_t'].float()
85
+ X_in, E_in = X, E
86
+ node_mask, t = noisy_data['node_mask'], noisy_data['t']
87
+ y = noisy_data['y_t'] if self.y_embedder_list is not None else None
88
+ assert (y is None) == (self.y_embedder_list is None), "y and y_embedder_list must both be None or both be not None"
89
+
90
+ if y is not None:
91
+ force_drop_id = torch.zeros_like(y.sum(-1))
92
+ force_drop_id[torch.isnan(y.sum(-1))] = 1
93
+ if unconditioned:
94
+ force_drop_id = torch.ones_like(y[:, 0])
95
+
96
+ bs, n, _ = X.size()
97
+ h = torch.cat([X, E.reshape(bs, n, -1)], dim=-1)
98
+ h = self.x_embedder(h)
99
+ c = self.t_embedder(t)
100
+ if self.y_embedder_list is not None:
101
+ for i in range(self.input_dim_y):
102
+ c = c + self.y_embedder_list[i](y[:, i:i+1], self.training, force_drop_id)
103
+
104
+ for i, block in enumerate(self.blocks):
105
+ h = block(h, c, node_mask)
106
+
107
+ X, E, y = self.final_layer(h, X_in, E_in, c, t, node_mask)
108
+ return PlaceHolder(X=X, E=E, y=y).mask(node_mask)
109
+
110
+ def compute_loss(self, noisy_data, true_X, true_E, lw_X, lw_E, unconditioned=False):
111
+ pred = self.forward(noisy_data, unconditioned=unconditioned)
112
+
113
+ # Reshape predictions and targets
114
+ true_X = torch.reshape(true_X, (-1, true_X.size(-1))) # (bs * n, dx)
115
+ true_E = torch.reshape(true_E, (-1, true_E.size(-1))) # (bs * n * n, de)
116
+ masked_pred_X = torch.reshape(pred.X, (-1, pred.X.size(-1))) # (bs * n, dx)
117
+ masked_pred_E = torch.reshape(pred.E, (-1, pred.E.size(-1))) # (bs * n * n, de)
118
+
119
+ # Remove masked rows
120
+ mask_X = (true_X != 0.).any(dim=-1)
121
+ mask_E = (true_E != 0.).any(dim=-1)
122
+
123
+ flat_true_X = true_X[mask_X, :]
124
+ flat_pred_X = masked_pred_X[mask_X, :]
125
+ flat_true_E = true_E[mask_E, :]
126
+ flat_pred_E = masked_pred_E[mask_E, :]
127
+
128
+ # Calculate node and edge losses using cross entropy
129
+ loss_X = F.cross_entropy(flat_pred_X, torch.argmax(flat_true_X, dim=-1)) if true_X.numel() > 0 else 0.0
130
+ loss_E = F.cross_entropy(flat_pred_E, torch.argmax(flat_true_E, dim=-1)) if true_E.numel() > 0 else 0.0
131
+ loss = lw_X * loss_X + lw_E * loss_E
132
+ return loss, loss_X, loss_E
133
+
134
+ class AttentionBlock(nn.Module):
135
+ def __init__(self, hidden_size, num_head, mlp_ratio=4.0, dropout=0.):
136
+ super().__init__()
137
+ self.dropout = dropout
138
+ self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False)
139
+ self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False)
140
+
141
+ self.attn = AttentionWithNodeMask(
142
+ hidden_size, num_head=num_head, qkv_bias=True, qk_norm=True, proj_drop=dropout
143
+ )
144
+ self.mlp = MLP(
145
+ in_features=hidden_size,
146
+ hidden_features=int(hidden_size * mlp_ratio),
147
+ drop=self.dropout,
148
+ use_bn=False,
149
+ )
150
+ self.adaLN_modulation = nn.Sequential(
151
+ nn.Linear(hidden_size, hidden_size, bias=True),
152
+ nn.SiLU(),
153
+ nn.Linear(hidden_size, 6 * hidden_size, bias=True)
154
+ )
155
+
156
+ def forward(self, x, c, node_mask):
157
+ (
158
+ shift_msa,
159
+ scale_msa,
160
+ gate_msa,
161
+ shift_mlp,
162
+ scale_mlp,
163
+ gate_mlp,
164
+ ) = self.adaLN_modulation(c).chunk(6, dim=1)
165
+ x = x + gate_msa.unsqueeze(1) * modulate(self.norm1(self.attn(x, node_mask=node_mask)), shift_msa, scale_msa)
166
+ x = x + gate_mlp.unsqueeze(1) * modulate(self.norm2(self.mlp(x)), shift_mlp, scale_mlp)
167
+ return x
168
+
169
+ class FinalLayer(nn.Module):
170
+ def __init__(self, max_node, hidden_size, atom_type, bond_type, mlp_ratio, num_head=None):
171
+ super().__init__()
172
+ self.atom_type = atom_type
173
+ self.bond_type = bond_type
174
+ final_size = atom_type + max_node * bond_type
175
+ self.XEdecoder = MLP(in_features=hidden_size, out_features=final_size, drop=0., use_bn=False)
176
+
177
+ self.norm_final = nn.LayerNorm(final_size, elementwise_affine=False)
178
+ self.adaLN_modulation = nn.Sequential(
179
+ nn.Linear(hidden_size, hidden_size, bias=True),
180
+ nn.SiLU(),
181
+ nn.Linear(hidden_size, 2 * final_size, bias=True)
182
+ )
183
+
184
+ def forward(self, x, x_in, e_in, c, t, node_mask):
185
+ x_all = self.XEdecoder(x)
186
+ bs, n, _ = x_all.size()
187
+ shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)
188
+ x_all = modulate(self.norm_final(x_all), shift, scale)
189
+
190
+ atom_out = x_all[:, :, :self.atom_type]
191
+ atom_out = x_in + atom_out
192
+
193
+ bond_out = x_all[:, :, self.atom_type:].reshape(bs, n, n, self.bond_type)
194
+ bond_out = e_in + bond_out
195
+
196
+ ##### standardize adj_out
197
+ edge_mask = (~node_mask)[:, :, None] & (~node_mask)[:, None, :]
198
+ diag_mask = (
199
+ torch.eye(n, dtype=torch.bool)
200
+ .unsqueeze(0)
201
+ .expand(bs, -1, -1)
202
+ .type_as(edge_mask)
203
+ )
204
+ bond_out.masked_fill_(edge_mask[:, :, :, None], 0)
205
+ bond_out.masked_fill_(diag_mask[:, :, :, None], 0)
206
+ bond_out = 1 / 2 * (bond_out + torch.transpose(bond_out, 1, 2))
207
+ return atom_out, bond_out, None
@@ -0,0 +1,220 @@
1
+ import torch
2
+ import json
3
+ from rdkit import Chem
4
+ import numpy as np
5
+ from torch_geometric.utils import to_dense_adj, to_dense_batch, remove_self_loops
6
+
7
+ def to_dense(x, edge_index, edge_attr, batch, max_num_nodes=None):
8
+ X, node_mask = to_dense_batch(x=x, batch=batch, max_num_nodes=max_num_nodes)
9
+ edge_index, edge_attr = remove_self_loops(edge_index, edge_attr)
10
+ if max_num_nodes is None:
11
+ max_num_nodes = X.size(1)
12
+ E = to_dense_adj(edge_index=edge_index, batch=batch, edge_attr=edge_attr, max_num_nodes=max_num_nodes)
13
+ E = encode_no_edge(E)
14
+ return PlaceHolder(X=X, E=E, y=None), node_mask
15
+
16
+ def encode_no_edge(E):
17
+ assert len(E.shape) == 4
18
+ if E.shape[-1] == 0:
19
+ return E
20
+ no_edge = torch.sum(E, dim=3) == 0
21
+ first_elt = E[:, :, :, 0]
22
+ first_elt[no_edge] = 1
23
+ E[:, :, :, 0] = first_elt
24
+ diag = torch.eye(E.shape[1], dtype=torch.bool).unsqueeze(0).expand(E.shape[0], -1, -1)
25
+ E[diag] = 0
26
+ return E
27
+
28
+ class PlaceHolder:
29
+ def __init__(self, X, E, y):
30
+ self.X = X
31
+ self.E = E
32
+ self.y = y
33
+
34
+ def type_as(self, x: torch.Tensor, categorical: bool = False):
35
+ """ Changes the device and dtype of X, E, y. """
36
+ self.X = self.X.type_as(x)
37
+ self.E = self.E.type_as(x)
38
+ if categorical:
39
+ self.y = self.y.type_as(x)
40
+ return self
41
+
42
+ def mask(self, node_mask, collapse=False):
43
+ x_mask = node_mask.unsqueeze(-1) # bs, n, 1
44
+ e_mask1 = x_mask.unsqueeze(2) # bs, n, 1, 1
45
+ e_mask2 = x_mask.unsqueeze(1) # bs, 1, n, 1
46
+
47
+ if collapse:
48
+ self.X = torch.argmax(self.X, dim=-1)
49
+ self.E = torch.argmax(self.E, dim=-1)
50
+
51
+ self.X[node_mask == 0] = - 1
52
+ self.E[(e_mask1 * e_mask2).squeeze(-1) == 0] = - 1
53
+ else:
54
+ self.X = self.X * x_mask
55
+ self.E = self.E * e_mask1 * e_mask2
56
+ assert torch.allclose(self.E, torch.transpose(self.E, 1, 2))
57
+ return self
58
+
59
+ def compute_dataset_info(smiles_or_mol_list, cache_path=None):
60
+ pt = Chem.GetPeriodicTable()
61
+ atom_name_list = []
62
+ atom_count_list = []
63
+ for i in range(2, 119):
64
+ atom_name_list.append(pt.GetElementSymbol(i))
65
+ atom_count_list.append(0)
66
+ atom_name_list.append('*')
67
+ atom_count_list.append(0)
68
+
69
+ bond_count_list = [0, 0, 0, 0, 0]
70
+ bond_type_to_index = {Chem.BondType.SINGLE: 1, Chem.BondType.DOUBLE: 2, Chem.BondType.TRIPLE: 3, Chem.BondType.AROMATIC: 4}
71
+ tansition_E = np.zeros((118, 118, 5))
72
+
73
+ n_atom_list = []
74
+ n_bond_list = []
75
+
76
+ n_atoms_per_mol_count = {}
77
+ max_node = 0
78
+ for i, sms_or_mol in enumerate(smiles_or_mol_list):
79
+ if isinstance(sms_or_mol, str):
80
+ mol = Chem.MolFromSmiles(sms_or_mol)
81
+ else:
82
+ mol = sms_or_mol
83
+
84
+ n_atom = mol.GetNumHeavyAtoms()
85
+ n_bond = mol.GetNumBonds()
86
+ n_atom_list.append(n_atom)
87
+ n_bond_list.append(n_bond)
88
+ max_node = max(max_node, n_atom)
89
+
90
+ # Count atoms per molecule size
91
+ if n_atom in n_atoms_per_mol_count:
92
+ n_atoms_per_mol_count[n_atom] += 1
93
+ else:
94
+ n_atoms_per_mol_count[n_atom] = 1
95
+
96
+ cur_atom_count_arr = np.zeros(118)
97
+ for atom in mol.GetAtoms():
98
+ symbol = atom.GetSymbol()
99
+ if symbol == 'H':
100
+ continue
101
+ elif symbol == '*':
102
+ atom_count_list[-1] += 1
103
+ cur_atom_count_arr[-1] += 1
104
+ else:
105
+ atom_count_list[atom.GetAtomicNum()-2] += 1
106
+ cur_atom_count_arr[atom.GetAtomicNum()-2] += 1
107
+
108
+ tansition_E_temp = np.zeros((118, 118, 5))
109
+ for bond in mol.GetBonds():
110
+ start_atom, end_atom = bond.GetBeginAtom(), bond.GetEndAtom()
111
+ if start_atom.GetSymbol() == 'H' or end_atom.GetSymbol() == 'H':
112
+ continue
113
+
114
+ if start_atom.GetSymbol() == '*':
115
+ start_index = 117
116
+ else:
117
+ start_index = start_atom.GetAtomicNum() - 2
118
+ if end_atom.GetSymbol() == '*':
119
+ end_index = 117
120
+ else:
121
+ end_index = end_atom.GetAtomicNum() - 2
122
+
123
+ bond_type = bond.GetBondType()
124
+ bond_index = bond_type_to_index[bond_type]
125
+ bond_count_list[bond_index] += 2
126
+
127
+ tansition_E[start_index, end_index, bond_index] += 2
128
+ tansition_E[end_index, start_index, bond_index] += 2
129
+ tansition_E_temp[start_index, end_index, bond_index] += 2
130
+ tansition_E_temp[end_index, start_index, bond_index] += 2
131
+
132
+ bond_count_list[0] += n_atom * (n_atom - 1) - n_bond * 2
133
+ cur_tot_bond = cur_atom_count_arr.reshape(-1,1) * cur_atom_count_arr.reshape(1,-1) * 2 # 118 * 118
134
+ cur_tot_bond = cur_tot_bond - np.diag(cur_atom_count_arr) * 2 # 118 * 118
135
+ tansition_E[:, :, 0] += cur_tot_bond - tansition_E_temp.sum(axis=-1)
136
+ assert (cur_tot_bond > tansition_E_temp.sum(axis=-1)).sum() >= 0, f'i:{i}, sms:{sms}'
137
+
138
+ # Create n_atoms_per_mol array with proper size
139
+ n_atoms_per_mol = [0] * (max_node + 1)
140
+ for n_atom, count in n_atoms_per_mol_count.items():
141
+ n_atoms_per_mol[n_atom] = count
142
+
143
+ n_atoms_per_mol = np.array(n_atoms_per_mol) / np.sum(n_atoms_per_mol)
144
+ n_atoms_per_mol = n_atoms_per_mol.tolist()
145
+
146
+ atom_count_list = np.array(atom_count_list) / np.sum(atom_count_list)
147
+ active_atoms = np.array(atom_name_list)[atom_count_list > 0]
148
+ active_atoms = active_atoms.tolist()
149
+ atom_count_list = atom_count_list.tolist()
150
+
151
+ bond_count_list = np.array(bond_count_list) / np.sum(bond_count_list)
152
+ bond_count_list = bond_count_list.tolist()
153
+
154
+ no_edge = np.sum(tansition_E, axis=-1) == 0
155
+ first_elt = tansition_E[:, :, 0]
156
+ first_elt[no_edge] = 1
157
+ tansition_E[:, :, 0] = first_elt
158
+ tansition_E = tansition_E / np.sum(tansition_E, axis=-1, keepdims=True)
159
+
160
+ node_types = torch.Tensor(atom_count_list)
161
+ edge_types = torch.Tensor(bond_count_list)
162
+ active_index = (node_types > 0).nonzero().squeeze()
163
+
164
+ x_margins = node_types.float() / torch.sum(node_types)
165
+ x_margins = x_margins[active_index]
166
+ e_margins = edge_types.float() / torch.sum(edge_types)
167
+
168
+ xe_conditions = torch.Tensor(tansition_E)
169
+ xe_conditions = xe_conditions[active_index][:, active_index]
170
+
171
+ xe_conditions = xe_conditions.sum(dim=1)
172
+ ex_conditions = xe_conditions.t()
173
+ xe_conditions = xe_conditions / (xe_conditions.sum(dim=-1, keepdim=True) + 1e-10)
174
+ ex_conditions = ex_conditions / (ex_conditions.sum(dim=-1, keepdim=True) + 1e-10)
175
+ num_nodes_dist = DistributionNodes(torch.Tensor(n_atoms_per_mol))
176
+
177
+ meta_dict = {
178
+ 'active_index': active_index,
179
+ 'x_margins': x_margins,
180
+ 'e_margins': e_margins,
181
+ 'xe_conditions': xe_conditions,
182
+ 'ex_conditions': ex_conditions,
183
+ 'atom_decoder': active_atoms,
184
+ 'num_nodes_dist': num_nodes_dist,
185
+ 'max_node': max_node,
186
+ }
187
+ if cache_path is not None:
188
+ with open(cache_path, "w") as f:
189
+ json.dump(meta_dict, f)
190
+
191
+ return meta_dict
192
+
193
+ class DistributionNodes:
194
+ def __init__(self, histogram):
195
+ """ Compute the distribution of the number of nodes in the dataset, and sample from this distribution.
196
+ historgram: dict. The keys are num_nodes, the values are counts
197
+ """
198
+
199
+ if type(histogram) == dict:
200
+ max_n_nodes = max(histogram.keys())
201
+ prob = torch.zeros(max_n_nodes + 1)
202
+ for num_nodes, count in histogram.items():
203
+ prob[num_nodes] = count
204
+ else:
205
+ prob = histogram
206
+
207
+ self.prob = prob / prob.sum()
208
+ self.m = torch.distributions.Categorical(prob)
209
+
210
+ def sample_n(self, n_samples, device):
211
+ idx = self.m.sample((n_samples,))
212
+ return idx.to(device)
213
+
214
+ def log_prob(self, batch_n_nodes):
215
+ assert len(batch_n_nodes.size()) == 1
216
+ p = self.prob.to(batch_n_nodes.device)
217
+
218
+ probas = p[batch_n_nodes]
219
+ log_p = torch.log(probas + 1e-30)
220
+ return log_p
@@ -0,0 +1,3 @@
1
+ from .modeling_graph_ga import GraphGAMolecularGenerator
2
+
3
+ __all__ = ['GraphGAMolecularGenerator']
@@ -0,0 +1,182 @@
1
+ import random
2
+
3
+ import numpy as np
4
+ from rdkit import Chem, rdBase
5
+ from rdkit.Chem import AllChem
6
+ rdBase.DisableLog('rdApp.error')
7
+
8
+
9
+ def cut(mol):
10
+ if not mol.HasSubstructMatch(Chem.MolFromSmarts('[*]-;!@[*]')):
11
+ return None
12
+
13
+ bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[*]-;!@[*]'))) # single bond not in ring
14
+
15
+ bs = [mol.GetBondBetweenAtoms(bis[0], bis[1]).GetIdx()]
16
+
17
+ fragments_mol = Chem.FragmentOnBonds(mol, bs, addDummies=True, dummyLabels=[(1, 1)])
18
+
19
+ try:
20
+ return Chem.GetMolFrags(fragments_mol, asMols=True, sanitizeFrags=True)
21
+ except ValueError:
22
+ return None
23
+
24
+ return None
25
+
26
+
27
+ def cut_ring(mol):
28
+
29
+ for i in range(10):
30
+ if random.random() < 0.5:
31
+ if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R]@[R]@[R]')):
32
+ return None
33
+ bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R]@[R]@[R]')))
34
+ bis = ((bis[0], bis[1]), (bis[2], bis[3]),)
35
+ else:
36
+ if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R;!D2]@[R]')):
37
+ return None
38
+ bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R;!D2]@[R]')))
39
+ bis = ((bis[0], bis[1]), (bis[1], bis[2]),)
40
+
41
+ bs = [mol.GetBondBetweenAtoms(x, y).GetIdx() for x, y in bis]
42
+
43
+ fragments_mol = Chem.FragmentOnBonds(mol, bs, addDummies=True, dummyLabels=[(1, 1), (1, 1)])
44
+
45
+ try:
46
+ fragments = Chem.GetMolFrags(fragments_mol, asMols=True, sanitizeFrags=True)
47
+ if len(fragments) == 2:
48
+ return fragments
49
+ except ValueError:
50
+ return None
51
+
52
+ return None
53
+
54
+
55
+ def ring_OK(mol):
56
+ if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]')):
57
+ return True
58
+
59
+ ring_allene = mol.HasSubstructMatch(Chem.MolFromSmarts('[R]=[R]=[R]'))
60
+
61
+ cycle_list = mol.GetRingInfo().AtomRings()
62
+ max_cycle_length = max([len(j) for j in cycle_list])
63
+ macro_cycle = max_cycle_length > 6
64
+
65
+ double_bond_in_small_ring = mol.HasSubstructMatch(Chem.MolFromSmarts('[r3,r4]=[r3,r4]'))
66
+
67
+ return not ring_allene and not macro_cycle and not double_bond_in_small_ring
68
+
69
+
70
+ # TODO: set from main? calculate for dataset?
71
+ average_size = 39.15
72
+ size_stdev = 3.50
73
+
74
+
75
+ def mol_ok(mol):
76
+ try:
77
+ Chem.SanitizeMol(mol)
78
+ target_size = size_stdev * np.random.randn() + average_size # parameters set in GA_mol
79
+ if mol.GetNumAtoms() > 5 and mol.GetNumAtoms() < target_size:
80
+ return True
81
+ else:
82
+ return False
83
+ except ValueError:
84
+ return False
85
+
86
+
87
+ def crossover_ring(parent_A, parent_B):
88
+ ring_smarts = Chem.MolFromSmarts('[R]')
89
+ if not parent_A.HasSubstructMatch(ring_smarts) and not parent_B.HasSubstructMatch(ring_smarts):
90
+ return None
91
+
92
+ rxn_smarts1 = ['[*:1]~[1*].[1*]~[*:2]>>[*:1]-[*:2]', '[*:1]~[1*].[1*]~[*:2]>>[*:1]=[*:2]']
93
+ rxn_smarts2 = ['([*:1]~[1*].[1*]~[*:2])>>[*:1]-[*:2]', '([*:1]~[1*].[1*]~[*:2])>>[*:1]=[*:2]']
94
+
95
+ for i in range(10):
96
+ fragments_A = cut_ring(parent_A)
97
+ fragments_B = cut_ring(parent_B)
98
+
99
+ if fragments_A is None or fragments_B is None:
100
+ return None
101
+
102
+ new_mol_trial = []
103
+ for rs in rxn_smarts1:
104
+ rxn1 = AllChem.ReactionFromSmarts(rs)
105
+ new_mol_trial = []
106
+ for fa in fragments_A:
107
+ for fb in fragments_B:
108
+ if len(rxn1.RunReactants((fa, fb))) > 0:
109
+ new_mol_trial.append(rxn1.RunReactants((fa, fb))[0])
110
+
111
+ new_mols = []
112
+ for rs in rxn_smarts2:
113
+ rxn2 = AllChem.ReactionFromSmarts(rs)
114
+ for m in new_mol_trial:
115
+ m = m[0]
116
+ if mol_ok(m):
117
+ new_mols += list(rxn2.RunReactants((m,)))
118
+
119
+ new_mols2 = []
120
+ for m in new_mols:
121
+ m = m[0]
122
+ if mol_ok(m) and ring_OK(m):
123
+ new_mols2.append(m)
124
+
125
+ if len(new_mols2) > 0:
126
+ return random.choice(new_mols2)
127
+
128
+ return None
129
+
130
+
131
+ def crossover_non_ring(parent_A, parent_B):
132
+
133
+ for i in range(10):
134
+ fragments_A = cut(parent_A)
135
+ fragments_B = cut(parent_B)
136
+ if fragments_A is None or fragments_B is None:
137
+ return None
138
+ rxn = AllChem.ReactionFromSmarts('[*:1]-[1*].[1*]-[*:2]>>[*:1]-[*:2]')
139
+ new_mol_trial = []
140
+ for fa in fragments_A:
141
+ for fb in fragments_B:
142
+ if len(rxn.RunReactants((fa, fb))) > 0:
143
+ new_mol_trial.append(rxn.RunReactants((fa, fb))[0])
144
+
145
+ new_mols = []
146
+ for mol in new_mol_trial:
147
+ mol = mol[0]
148
+ if mol_ok(mol):
149
+ new_mols.append(mol)
150
+
151
+ if len(new_mols) > 0:
152
+ return random.choice(new_mols)
153
+
154
+ return None
155
+
156
+
157
+ def crossover(parent_A, parent_B):
158
+ parent_smiles = [Chem.MolToSmiles(parent_A), Chem.MolToSmiles(parent_B)]
159
+ try:
160
+ Chem.Kekulize(parent_A, clearAromaticFlags=True)
161
+ Chem.Kekulize(parent_B, clearAromaticFlags=True)
162
+
163
+ except ValueError:
164
+ pass
165
+
166
+ for i in range(10):
167
+ if random.random() <= 0.5:
168
+ # print 'non-ring crossover'
169
+ new_mol = crossover_non_ring(parent_A, parent_B)
170
+ if new_mol is not None:
171
+ new_smiles = Chem.MolToSmiles(new_mol)
172
+ if new_smiles is not None and new_smiles not in parent_smiles:
173
+ return new_mol
174
+ else:
175
+ # print 'ring crossover'
176
+ new_mol = crossover_ring(parent_A, parent_B)
177
+ if new_mol is not None:
178
+ new_smiles = Chem.MolToSmiles(new_mol)
179
+ if new_smiles is not None and new_smiles not in parent_smiles:
180
+ return new_mol
181
+
182
+ return None