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,3 @@
1
+ from .modeling_grea import GREAMolecularPredictor
2
+
3
+ __all__ = ['GREAMolecularPredictor']
@@ -0,0 +1,190 @@
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ # from torch_scatter import scatter_add
5
+ from .utils import scatter_add
6
+
7
+ from ...nn import GNN_node, GNN_node_Virtualnode, MLP
8
+ from ...utils import init_weights
9
+
10
+ class GREA(nn.Module):
11
+ def __init__(
12
+ self,
13
+ num_task,
14
+ num_layer,
15
+ gamma=0.4,
16
+ hidden_size=300,
17
+ gnn_type="gin-virtual",
18
+ drop_ratio=0.5,
19
+ norm_layer="batch_norm",
20
+ augmented_feature=['maccs', 'morgan']
21
+ ):
22
+ super(GREA, self).__init__()
23
+ gnn_name = gnn_type.split("-")[0]
24
+ self.num_task = num_task
25
+ self.gamma = gamma
26
+ self.hidden_size = hidden_size
27
+
28
+ if "virtual" in gnn_type:
29
+ rationale_encoder = GNN_node_Virtualnode(
30
+ 2, hidden_size, JK="last", drop_ratio=drop_ratio, residual=True, gnn_name=gnn_name
31
+ )
32
+ self.graph_encoder = GNN_node_Virtualnode(
33
+ num_layer,
34
+ hidden_size,
35
+ JK="last",
36
+ drop_ratio=drop_ratio,
37
+ residual=True,
38
+ gnn_name=gnn_name,
39
+ norm_layer=norm_layer,
40
+ )
41
+ else:
42
+ rationale_encoder = GNN_node(
43
+ 2, hidden_size, JK="last", drop_ratio=drop_ratio, residual=True, gnn_name=gnn_name
44
+ )
45
+ self.graph_encoder = GNN_node(
46
+ num_layer,
47
+ hidden_size,
48
+ JK="last",
49
+ drop_ratio=drop_ratio,
50
+ residual=True,
51
+ gnn_name=gnn_name,
52
+ norm_layer=norm_layer,
53
+ )
54
+
55
+ self.separator = Separator(
56
+ rationale_encoder=rationale_encoder,
57
+ gate_nn=torch.nn.Sequential(
58
+ torch.nn.Linear(hidden_size, 2 * hidden_size),
59
+ torch.nn.BatchNorm1d(2 * hidden_size),
60
+ torch.nn.ReLU(),
61
+ torch.nn.Dropout(),
62
+ torch.nn.Linear(2 * hidden_size, 1),
63
+ ),
64
+ )
65
+
66
+ graph_dim = hidden_size
67
+ self.augmented_feature = augmented_feature
68
+ if augmented_feature:
69
+ if "morgan" in augmented_feature:
70
+ graph_dim += 1024
71
+ if "maccs" in augmented_feature:
72
+ graph_dim += 167
73
+ self.predictor = MLP(graph_dim, hidden_features=2 * hidden_size, out_features=num_task)
74
+
75
+
76
+ def initialize_parameters(self, seed=None):
77
+ """
78
+ Randomly initialize all model parameters using the init_weights function.
79
+
80
+ Args:
81
+ seed (int, optional): Random seed for reproducibility. Defaults to None.
82
+ """
83
+ if seed is not None:
84
+ torch.manual_seed(seed)
85
+
86
+ # Initialize the main components
87
+ init_weights(self.graph_encoder)
88
+ init_weights(self.separator)
89
+ init_weights(self.predictor)
90
+
91
+ # Reset all parameters using PyTorch Geometric's reset function
92
+ def reset_parameters(module):
93
+ if hasattr(module, 'reset_parameters'):
94
+ module.reset_parameters()
95
+ elif hasattr(module, 'weight') and hasattr(module.weight, 'data'):
96
+ init_weights(module)
97
+
98
+ self.apply(reset_parameters)
99
+
100
+ def _augment_graph_features(self, batched_data, h_r, h_rep):
101
+ if self.augmented_feature:
102
+ if 'morgan' in self.augmented_feature:
103
+ morgan = batched_data.morgan.type_as(h_r)
104
+ h_r = torch.cat((h_r, morgan), dim=1)
105
+ morgans = morgan.repeat_interleave(batched_data.batch[-1]+1, dim=0)
106
+ h_rep = torch.cat((h_rep, morgans), dim=1)
107
+ if 'maccs' in self.augmented_feature:
108
+ maccs = batched_data.maccs.type_as(h_r)
109
+ h_r = torch.cat((h_r, maccs), dim=1)
110
+ maccses = maccs.repeat_interleave(batched_data.batch[-1]+1, dim=0)
111
+ h_rep = torch.cat((h_rep, maccses), dim=1)
112
+ return h_r, h_rep
113
+
114
+ def compute_loss(self, batched_data, criterion):
115
+ h_node, _ = self.graph_encoder(batched_data)
116
+ h_r, h_env, rationale_size, envir_size, _ = self.separator(batched_data, h_node)
117
+ h_rep = (h_r.unsqueeze(1) + h_env.unsqueeze(0)).view(-1, self.hidden_size)
118
+ h_r, h_rep = self._augment_graph_features(batched_data, h_r, h_rep)
119
+ pred_rem = self.predictor(h_r)
120
+ pred_rep = self.predictor(h_rep)
121
+ loss = torch.abs(
122
+ rationale_size / (rationale_size + envir_size)
123
+ - self.gamma * torch.ones_like(rationale_size)
124
+ ).mean()
125
+ target = batched_data.y.to(torch.float32)
126
+ is_labeled = batched_data.y == batched_data.y
127
+ loss += criterion(pred_rem.to(torch.float32)[is_labeled], target[is_labeled]).mean()
128
+ target_rep = batched_data.y.to(torch.float32).repeat_interleave(
129
+ batched_data.batch[-1] + 1, dim=0
130
+ )
131
+ is_labeled_rep = target_rep == target_rep
132
+ loss += criterion(pred_rep.to(torch.float32)[is_labeled_rep], target_rep[is_labeled_rep]).mean()
133
+ return loss
134
+
135
+ def forward(self, batched_data):
136
+ h_node, _ = self.graph_encoder(batched_data)
137
+ h_r, h_env, _, _, node_score = self.separator(batched_data, h_node)
138
+ h_rep = (h_r.unsqueeze(1) + h_env.unsqueeze(0)).view(-1, self.hidden_size)
139
+ h_r, h_rep = self._augment_graph_features(batched_data, h_r, h_rep)
140
+ prediction = self.predictor(h_r)
141
+ pred_rep = self.predictor(h_rep).view(h_r.size(0), -1)
142
+ if pred_rep.size(1) > 1:
143
+ variance = pred_rep.var(dim=-1, keepdim=True)
144
+ else:
145
+ variance = torch.zeros_like(pred_rep)
146
+ num_graphs = batched_data.batch.max().item() + 1
147
+ score_by_graph = [node_score[batched_data.batch == i].view(-1).tolist() for i in range(num_graphs)]
148
+ return {"prediction": prediction, "variance": variance, "score": score_by_graph, "representation": h_r}
149
+
150
+ @staticmethod
151
+ def _disable_batchnorm_tracking(model):
152
+ def fn(module):
153
+ if isinstance(module, torch.nn.modules.batchnorm._BatchNorm):
154
+ module.track_running_stats = False
155
+
156
+ model.apply(fn)
157
+
158
+ @staticmethod
159
+ def _enable_batchnorm_tracking(model):
160
+ def fn(module):
161
+ if isinstance(module, torch.nn.modules.batchnorm._BatchNorm):
162
+ module.track_running_stats = True
163
+
164
+ model.apply(fn)
165
+
166
+ class Separator(torch.nn.Module):
167
+ def __init__(self, rationale_encoder, gate_nn, nn=None):
168
+ super(Separator, self).__init__()
169
+ self.rationale_encoder = rationale_encoder
170
+ self.gate_nn = gate_nn
171
+ self.nn = nn
172
+
173
+ def forward(self, batched_data, h_node, size=None):
174
+ x, _ = self.rationale_encoder(batched_data)
175
+ batch = batched_data.batch
176
+ x = x.unsqueeze(-1) if x.dim() == 1 else x
177
+ size = batch[-1].item() + 1 if size is None else size
178
+
179
+ gate = self.gate_nn(x).view(-1, 1)
180
+ h_node = self.nn(h_node) if self.nn is not None else h_node
181
+ assert gate.dim() == h_node.dim() and gate.size(0) == h_node.size(0)
182
+ gate = torch.sigmoid(gate)
183
+
184
+ h_out = scatter_add(gate * h_node, batch, dim=0, dim_size=size)
185
+ c_out = scatter_add((1 - gate) * h_node, batch, dim=0, dim_size=size)
186
+
187
+ rationale_size = scatter_add(gate, batch, dim=0, dim_size=size)
188
+ envir_size = scatter_add((1 - gate), batch, dim=0, dim_size=size)
189
+
190
+ return h_out, c_out, rationale_size + 1e-8, envir_size + 1e-8, gate
@@ -0,0 +1,107 @@
1
+ import numpy as np
2
+ import warnings
3
+ from tqdm import tqdm
4
+ from typing import Optional, Union, Dict, Any, List, Type
5
+ from dataclasses import dataclass, field
6
+
7
+ import torch
8
+ from torch_geometric.loader import DataLoader
9
+
10
+ from .model import GREA
11
+ from ..gnn.modeling_gnn import GNNMolecularPredictor
12
+ from ...utils.search import (
13
+ ParameterSpec,
14
+ ParameterType,
15
+ )
16
+
17
+ @dataclass
18
+ class GREAMolecularPredictor(GNNMolecularPredictor):
19
+ """This predictor implements a Graph Rationalization model called GREA.
20
+
21
+ The full name of GREA is Graph Rationalization with Environment-based Augmentations. During model training, it learns the rationales (explainable subgraphs) and use them for molecular property prediction tasks.
22
+
23
+ References
24
+ ----------
25
+ - Graph Rationalization with Environment-based Augmentations.
26
+ https://dl.acm.org/doi/10.1145/3534678.3539347
27
+ - Code: https://github.com/liugangcode/GREA
28
+
29
+ :param gamma: GREA-specific parameter. Default is 0.4.
30
+ :type gamma: float
31
+ """
32
+
33
+ # GREA-specific parameter
34
+ gamma: float = 0.4
35
+ # Override parent defaults
36
+ model_name: str = "GREAMolecularPredictor"
37
+ model_class: Type[GREA] = field(default=GREA, init=False)
38
+
39
+ def __post_init__(self):
40
+ super().__post_init__()
41
+
42
+ @staticmethod
43
+ def _get_param_names() -> List[str]:
44
+ return ["gamma"] + GNNMolecularPredictor._get_param_names()
45
+
46
+ def _get_default_search_space(self):
47
+ search_space = super()._get_default_search_space().copy()
48
+ search_space["gamma"] = ParameterSpec(ParameterType.FLOAT, (0.1, 0.9))
49
+ return search_space
50
+
51
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
52
+ base_params = super()._get_model_params(checkpoint)
53
+ if checkpoint and "hyperparameters" in checkpoint:
54
+ base_params["gamma"] = checkpoint["hyperparameters"].get("gamma", self.gamma)
55
+ else:
56
+ base_params["gamma"] = self.gamma
57
+ base_params.pop("graph_pooling", None)
58
+ return base_params
59
+
60
+ def predict(self, X: List[str]) -> Dict[str, Union[np.ndarray, List[List]]]:
61
+ """Make predictions using the fitted model.
62
+
63
+ Parameters
64
+ ----------
65
+ X : List[str]
66
+ List of SMILES strings to make predictions for
67
+
68
+ Returns
69
+ -------
70
+ Dict[str, np.ndarray]
71
+ Dictionary containing:
72
+ - 'prediction': Model predictions (shape: [n_samples, n_tasks])
73
+ - 'variance': Prediction variances (shape: [n_samples, n_tasks])
74
+ - 'node_importance': A nested list where the outer list has length n_samples and each inner list has length n_nodes for that molecule
75
+ """
76
+ self._check_is_fitted()
77
+
78
+ # Convert to PyTorch Geometric format and create loader
79
+ X, _ = self._validate_inputs(X)
80
+ dataset = self._convert_to_pytorch_data(X)
81
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
82
+
83
+ # Make predictions
84
+ self.model = self.model.to(self.device)
85
+ self.model.eval()
86
+ predictions = []
87
+ variances = []
88
+ node_scores = []
89
+ with torch.no_grad():
90
+ for batch in tqdm(loader, disable=not self.verbose):
91
+ batch = batch.to(self.device)
92
+ out = self.model(batch)
93
+ predictions.append(out["prediction"].cpu().numpy())
94
+ variances.append(out["variance"].cpu().numpy())
95
+ node_scores.extend(out["score"])
96
+
97
+ if predictions and variances:
98
+ return {
99
+ "prediction": np.concatenate(predictions, axis=0),
100
+ "variance": np.concatenate(variances, axis=0),
101
+ "node_importance": node_scores,
102
+ }
103
+ else:
104
+ warnings.warn(
105
+ "No valid predictions could be made from the input data. Returning empty results."
106
+ )
107
+ return {"prediction": np.array([]), "variance": np.array([]), "node_importance": np.array([])}
@@ -0,0 +1,229 @@
1
+ # allowable multiple choice node and edge features
2
+ allowable_features = {
3
+ 'possible_atomic_num_list' : list(range(1, 119)) + ['misc'],
4
+ 'possible_chirality_list' : [
5
+ 'CHI_UNSPECIFIED',
6
+ 'CHI_TETRAHEDRAL_CW',
7
+ 'CHI_TETRAHEDRAL_CCW',
8
+ 'CHI_OTHER',
9
+ 'misc'
10
+ ],
11
+ 'possible_degree_list' : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'misc'],
12
+ 'possible_formal_charge_list' : [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 'misc'],
13
+ 'possible_numH_list' : [0, 1, 2, 3, 4, 5, 6, 7, 8, 'misc'],
14
+ 'possible_number_radical_e_list': [0, 1, 2, 3, 4, 'misc'],
15
+ 'possible_hybridization_list' : [
16
+ 'SP', 'SP2', 'SP3', 'SP3D', 'SP3D2', 'misc'
17
+ ],
18
+ 'possible_is_aromatic_list': [False, True],
19
+ 'possible_is_in_ring_list': [False, True],
20
+ 'possible_bond_type_list' : [
21
+ 'SINGLE',
22
+ 'DOUBLE',
23
+ 'TRIPLE',
24
+ 'AROMATIC',
25
+ 'misc'
26
+ ],
27
+ 'possible_bond_stereo_list': [
28
+ 'STEREONONE',
29
+ 'STEREOZ',
30
+ 'STEREOE',
31
+ 'STEREOCIS',
32
+ 'STEREOTRANS',
33
+ 'STEREOANY',
34
+ ],
35
+ 'possible_is_conjugated_list': [False, True],
36
+ }
37
+
38
+ def safe_index(l, e):
39
+ """
40
+ Return index of element e in list l. If e is not present, return the last index
41
+ """
42
+ try:
43
+ return l.index(e)
44
+ except:
45
+ return len(l) - 1
46
+ # # miscellaneous case
47
+ # i = safe_index(allowable_features['possible_atomic_num_list'], 'asdf')
48
+ # assert allowable_features['possible_atomic_num_list'][i] == 'misc'
49
+ # # normal case
50
+ # i = safe_index(allowable_features['possible_atomic_num_list'], 2)
51
+ # assert allowable_features['possible_atomic_num_list'][i] == 2
52
+
53
+ def atom_to_feature_vector(atom):
54
+ """
55
+ Converts rdkit atom object to feature list of indices
56
+ :param mol: rdkit atom object
57
+ :return: list
58
+ """
59
+ atom_feature = [
60
+ safe_index(allowable_features['possible_atomic_num_list'], atom.GetAtomicNum()),
61
+ safe_index(allowable_features['possible_chirality_list'], str(atom.GetChiralTag())),
62
+ safe_index(allowable_features['possible_degree_list'], atom.GetTotalDegree()),
63
+ safe_index(allowable_features['possible_formal_charge_list'], atom.GetFormalCharge()),
64
+ safe_index(allowable_features['possible_numH_list'], atom.GetTotalNumHs()),
65
+ safe_index(allowable_features['possible_number_radical_e_list'], atom.GetNumRadicalElectrons()),
66
+ safe_index(allowable_features['possible_hybridization_list'], str(atom.GetHybridization())),
67
+ allowable_features['possible_is_aromatic_list'].index(atom.GetIsAromatic()),
68
+ allowable_features['possible_is_in_ring_list'].index(atom.IsInRing()),
69
+ ]
70
+ return atom_feature
71
+ # from rdkit import Chem
72
+ # mol = Chem.MolFromSmiles('Cl[C@H](/C=C/C)Br')
73
+ # atom = mol.GetAtomWithIdx(1) # chiral carbon
74
+ # atom_feature = atom_to_feature_vector(atom)
75
+ # assert atom_feature == [5, 2, 4, 5, 1, 0, 2, 0, 0]
76
+
77
+
78
+ def get_atom_feature_dims():
79
+ return list(map(len, [
80
+ allowable_features['possible_atomic_num_list'],
81
+ allowable_features['possible_chirality_list'],
82
+ allowable_features['possible_degree_list'],
83
+ allowable_features['possible_formal_charge_list'],
84
+ allowable_features['possible_numH_list'],
85
+ allowable_features['possible_number_radical_e_list'],
86
+ allowable_features['possible_hybridization_list'],
87
+ allowable_features['possible_is_aromatic_list'],
88
+ allowable_features['possible_is_in_ring_list']
89
+ ]))
90
+
91
+ def bond_to_feature_vector(bond):
92
+ """
93
+ Converts rdkit bond object to feature list of indices
94
+ :param mol: rdkit bond object
95
+ :return: list
96
+ """
97
+ bond_feature = [
98
+ safe_index(allowable_features['possible_bond_type_list'], str(bond.GetBondType())),
99
+ allowable_features['possible_bond_stereo_list'].index(str(bond.GetStereo())),
100
+ allowable_features['possible_is_conjugated_list'].index(bond.GetIsConjugated()),
101
+ ]
102
+ return bond_feature
103
+ # uses same molecule as atom_to_feature_vector test
104
+ # bond = mol.GetBondWithIdx(2) # double bond with stereochem
105
+ # bond_feature = bond_to_feature_vector(bond)
106
+ # assert bond_feature == [1, 2, 0]
107
+
108
+ def get_bond_feature_dims():
109
+ return list(map(len, [
110
+ allowable_features['possible_bond_type_list'],
111
+ allowable_features['possible_bond_stereo_list'],
112
+ allowable_features['possible_is_conjugated_list']
113
+ ]))
114
+
115
+ def atom_feature_vector_to_dict(atom_feature):
116
+ [atomic_num_idx,
117
+ chirality_idx,
118
+ degree_idx,
119
+ formal_charge_idx,
120
+ num_h_idx,
121
+ number_radical_e_idx,
122
+ hybridization_idx,
123
+ is_aromatic_idx,
124
+ is_in_ring_idx] = atom_feature
125
+
126
+ feature_dict = {
127
+ 'atomic_num': allowable_features['possible_atomic_num_list'][atomic_num_idx],
128
+ 'chirality': allowable_features['possible_chirality_list'][chirality_idx],
129
+ 'degree': allowable_features['possible_degree_list'][degree_idx],
130
+ 'formal_charge': allowable_features['possible_formal_charge_list'][formal_charge_idx],
131
+ 'num_h': allowable_features['possible_numH_list'][num_h_idx],
132
+ 'num_rad_e': allowable_features['possible_number_radical_e_list'][number_radical_e_idx],
133
+ 'hybridization': allowable_features['possible_hybridization_list'][hybridization_idx],
134
+ 'is_aromatic': allowable_features['possible_is_aromatic_list'][is_aromatic_idx],
135
+ 'is_in_ring': allowable_features['possible_is_in_ring_list'][is_in_ring_idx]
136
+ }
137
+
138
+ return feature_dict
139
+ # # uses same atom_feature as atom_to_feature_vector test
140
+ # atom_feature_dict = atom_feature_vector_to_dict(atom_feature)
141
+ # assert atom_feature_dict['atomic_num'] == 6
142
+ # assert atom_feature_dict['chirality'] == 'CHI_TETRAHEDRAL_CCW'
143
+ # assert atom_feature_dict['degree'] == 4
144
+ # assert atom_feature_dict['formal_charge'] == 0
145
+ # assert atom_feature_dict['num_h'] == 1
146
+ # assert atom_feature_dict['num_rad_e'] == 0
147
+ # assert atom_feature_dict['hybridization'] == 'SP3'
148
+ # assert atom_feature_dict['is_aromatic'] == False
149
+ # assert atom_feature_dict['is_in_ring'] == False
150
+
151
+ def bond_feature_vector_to_dict(bond_feature):
152
+ [bond_type_idx,
153
+ bond_stereo_idx,
154
+ is_conjugated_idx] = bond_feature
155
+
156
+ feature_dict = {
157
+ 'bond_type': allowable_features['possible_bond_type_list'][bond_type_idx],
158
+ 'bond_stereo': allowable_features['possible_bond_stereo_list'][bond_stereo_idx],
159
+ 'is_conjugated': allowable_features['possible_is_conjugated_list'][is_conjugated_idx]
160
+ }
161
+
162
+ return feature_dict
163
+ # # uses same bond as bond_to_feature_vector test
164
+ # bond_feature_dict = bond_feature_vector_to_dict(bond_feature)
165
+ # assert bond_feature_dict['bond_type'] == 'DOUBLE'
166
+ # assert bond_feature_dict['bond_stereo'] == 'STEREOE'
167
+ # assert bond_feature_dict['is_conjugated'] == False
168
+
169
+
170
+ import numpy as np
171
+ from rdkit import Chem
172
+
173
+ def smiles_to_graph(smiles_string, properties):
174
+ """
175
+ Converts SMILES string to graph Data object
176
+ :input: SMILES string (str)
177
+ :return: graph object
178
+ """
179
+ try:
180
+ mol = Chem.MolFromSmiles(smiles_string)
181
+ # atoms
182
+ atom_features_list = []
183
+ # atom_label = []
184
+ for atom in mol.GetAtoms():
185
+ atom_features_list.append(atom_to_feature_vector(atom))
186
+ # atom_label.append(atom.GetSymbol())
187
+
188
+ x = np.array(atom_features_list, dtype=np.int64)
189
+
190
+ # bonds
191
+ num_bond_features = 3 # bond type, bond stereo, is_conjugated
192
+ if len(mol.GetBonds()) > 0: # mol has bonds
193
+ edges_list = []
194
+ edge_features_list = []
195
+ for bond in mol.GetBonds():
196
+ i = bond.GetBeginAtomIdx()
197
+ j = bond.GetEndAtomIdx()
198
+
199
+ edge_feature = bond_to_feature_vector(bond)
200
+ # add edges in both directions
201
+ edges_list.append((i, j))
202
+ edge_features_list.append(edge_feature)
203
+ edges_list.append((j, i))
204
+ edge_features_list.append(edge_feature)
205
+
206
+ # data.edge_index: Graph connectivity in COO format with shape [2, num_edges]
207
+ edge_index = np.array(edges_list, dtype=np.int64).T
208
+
209
+ # data.edge_attr: Edge feature matrix with shape [num_edges, num_edge_features]
210
+ edge_attr = np.array(edge_features_list, dtype=np.int64)
211
+
212
+ else: # mol has no bonds
213
+ edge_index = np.empty((2, 0), dtype=np.int64)
214
+ edge_attr = np.empty((0, num_bond_features), dtype=np.int64)
215
+
216
+ graph = dict()
217
+ graph["edge_index"] = edge_index
218
+ graph["edge_feat"] = edge_attr
219
+ graph["node_feat"] = x
220
+ graph["num_nodes"] = len(x)
221
+ if properties is None:
222
+ y = np.full(1, np.nan, dtype=np.float32)
223
+ graph['y'] = y.reshape(1, -1)
224
+ else:
225
+ graph['y'] = np.array(properties, dtype=np.float32).reshape(1, -1)
226
+
227
+ return graph
228
+ except:
229
+ return None