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,285 @@
1
+ import os
2
+ import numpy as np
3
+ from tqdm import tqdm
4
+ from typing import Optional, Union, Dict, Any, Tuple, List, Callable, Literal, Type
5
+ import warnings
6
+ from dataclasses import dataclass, field
7
+
8
+ import torch
9
+ from torch_geometric.loader import DataLoader
10
+ from torch_geometric.data import Data
11
+
12
+ from .model import GNN
13
+ from ..constant import GNN_ENCODER_MODELS, GNN_ENCODER_READOUTS, GNN_ENCODER_PARAMS
14
+ from ...base import BaseMolecularEncoder
15
+ from ...utils import graph_from_smiles
16
+
17
+ ALLOWABLE_ENCODER_MODELS = GNN_ENCODER_MODELS
18
+ ALLOWABLE_ENCODER_READOUTS = GNN_ENCODER_READOUTS
19
+
20
+ @dataclass
21
+ class EdgePredMolecularEncoder(BaseMolecularEncoder):
22
+ """This encoder implements a GNN-based model for molecular representation learning
23
+ using the edge prediction.
24
+
25
+ References
26
+ ----------
27
+ - Paper: Strategies for Pre-training Graph Neural Networks (ICLR 2020) https://arxiv.org/abs/1905.12265
28
+ - Code: https://github.com/snap-stanford/pretrain-gnns/tree/master/chem
29
+
30
+ Parameters
31
+ ----------
32
+ num_layer : int, default=5
33
+ Number of GNN layers.
34
+ hidden_size : int, default=300
35
+ Dimension of hidden node features.
36
+ drop_ratio : float, default=0.5
37
+ Dropout probability.
38
+ norm_layer : str, default="batch_norm"
39
+ Type of normalization layer to use. One of ["batch_norm", "layer_norm", "instance_norm", "graph_norm", "size_norm", "pair_norm"].
40
+ encoder_type : str, default="gin-virtual"
41
+ Type of GNN architecture to use.
42
+ readout : str, default="sum"
43
+ Method for aggregating node features to obtain graph-level representations.
44
+ batch_size : int, default=128
45
+ Number of samples per batch for training.
46
+ epochs : int, default=500
47
+ Maximum number of training epochs.
48
+ learning_rate : float, default=0.001
49
+ Learning rate for optimizer.
50
+ grad_clip_value : float, optional
51
+ Maximum norm of gradients for gradient clipping.
52
+ weight_decay : float, default=0.0
53
+ L2 regularization strength.
54
+ use_lr_scheduler : bool, default=False
55
+ Whether to use learning rate scheduler.
56
+ scheduler_factor : float, default=0.5
57
+ Factor by which to reduce learning rate when plateau is reached.
58
+ scheduler_patience : int, default=5
59
+ Number of epochs with no improvement after which learning rate will be reduced.
60
+ verbose : bool, default=False
61
+ Whether to print progress information during training.
62
+ """
63
+ # Model parameters
64
+ num_layer: int = 5
65
+ hidden_size: int = 300
66
+ drop_ratio: float = 0.5
67
+ norm_layer: str = "batch_norm"
68
+
69
+ encoder_type: str = "gin-virtual"
70
+ readout: str = "sum"
71
+
72
+ # Training parameters
73
+ batch_size: int = 128
74
+ epochs: int = 500
75
+ learning_rate: float = 0.001
76
+ grad_clip_value: Optional[float] = None
77
+ weight_decay: float = 0.0
78
+
79
+ # Scheduler parameters
80
+ use_lr_scheduler: bool = False
81
+ scheduler_factor: float = 0.5
82
+ scheduler_patience: int = 5
83
+
84
+ # Other parameters
85
+ verbose: bool = False
86
+ model_name: str = "EdgePredMolecularEncoder"
87
+
88
+ # Non-init fields
89
+ fitting_loss: List[float] = field(default_factory=list, init=False)
90
+ fitting_epoch: int = field(default=0, init=False)
91
+ model_class: Type[GNN] = field(default=GNN, init=False)
92
+
93
+ def __post_init__(self):
94
+ """Initialize the model after dataclass initialization."""
95
+ super().__post_init__()
96
+ if self.encoder_type not in ALLOWABLE_ENCODER_MODELS:
97
+ raise ValueError(f"Invalid encoder_model: {self.encoder_type}. Currently only {ALLOWABLE_ENCODER_MODELS} are supported.")
98
+ if self.readout not in ALLOWABLE_ENCODER_READOUTS:
99
+ raise ValueError(f"Invalid encoder_readout: {self.readout}. Currently only {ALLOWABLE_ENCODER_READOUTS} are supported.")
100
+
101
+ @staticmethod
102
+ def _get_param_names() -> List[str]:
103
+ return GNN_ENCODER_PARAMS.copy()
104
+
105
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
106
+ params = {
107
+ "num_layer": self.num_layer,
108
+ "hidden_size": self.hidden_size,
109
+ "encoder_type": self.encoder_type,
110
+ "drop_ratio": self.drop_ratio,
111
+ "norm_layer": self.norm_layer,
112
+ "readout": self.readout,
113
+ }
114
+
115
+ if checkpoint is not None:
116
+ if "hyperparameters" not in checkpoint:
117
+ raise ValueError("Checkpoint missing 'hyperparameters' key")
118
+ params = {k: checkpoint["hyperparameters"].get(k, v) for k, v in params.items()}
119
+
120
+ return params
121
+
122
+ def _convert_to_pytorch_data(self, X):
123
+ """Convert numpy arrays to PyTorch Geometric data format.
124
+ """
125
+ if self.verbose:
126
+ iterator = tqdm(enumerate(X), desc="Converting molecules to graphs", total=len(X))
127
+ else:
128
+ iterator = enumerate(X)
129
+
130
+ pyg_graph_list = []
131
+ for idx, smiles_or_mol in iterator:
132
+ graph = graph_from_smiles(smiles_or_mol, None)
133
+ g = Data()
134
+ g.num_nodes = graph["num_nodes"]
135
+ g.edge_index = torch.from_numpy(graph["edge_index"])
136
+
137
+ del graph["num_nodes"]
138
+ del graph["edge_index"]
139
+
140
+ if graph["edge_feat"] is not None:
141
+ g.edge_attr = torch.from_numpy(graph["edge_feat"])
142
+ del graph["edge_feat"]
143
+
144
+ if graph["node_feat"] is not None:
145
+ g.x = torch.from_numpy(graph["node_feat"])
146
+ del graph["node_feat"]
147
+
148
+ pyg_graph_list.append(g)
149
+
150
+ return pyg_graph_list
151
+
152
+ def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
153
+ """Setup optimization components including optimizer and learning rate scheduler.
154
+ """
155
+
156
+ optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay)
157
+ scheduler = None
158
+ if self.use_lr_scheduler:
159
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
160
+ optimizer,
161
+ mode="min",
162
+ factor=self.scheduler_factor,
163
+ patience=self.scheduler_patience,
164
+ min_lr=1e-6,
165
+ cooldown=0,
166
+ eps=1e-8,
167
+ )
168
+
169
+ return optimizer, scheduler
170
+
171
+ def fit(
172
+ self,
173
+ X_train: List[str],
174
+ ) -> "EdgePredMolecularEncoder":
175
+ """Fit the model to the training data with optional validation set.
176
+
177
+ Parameters
178
+ ----------
179
+ X_train : List[str]
180
+ Training set input molecular structures as SMILES strings
181
+ Returns
182
+ -------
183
+ self : EdgePredMolecularEncoder
184
+ Fitted estimator
185
+ """
186
+
187
+ self._initialize_model(self.model_class)
188
+ self.model.initialize_parameters()
189
+ optimizer, scheduler = self._setup_optimizers()
190
+
191
+ # Prepare datasets and loaders
192
+ X_train, _ = self._validate_inputs(X_train, return_rdkit_mol=True)
193
+ train_dataset = self._convert_to_pytorch_data(X_train)
194
+ train_loader = DataLoader(
195
+ train_dataset,
196
+ batch_size=self.batch_size,
197
+ shuffle=True,
198
+ num_workers=0
199
+ )
200
+ self.fitting_loss = []
201
+
202
+ for epoch in range(self.epochs):
203
+ # Training phase
204
+ train_losses = self._train_epoch(train_loader, optimizer, epoch)
205
+ self.fitting_loss.append(np.mean(train_losses))
206
+ if scheduler:
207
+ scheduler.step(np.mean(train_losses))
208
+
209
+ self.fitting_epoch = epoch
210
+ self.is_fitted_ = True
211
+ return self
212
+
213
+ def _train_epoch(self, train_loader, optimizer, epoch):
214
+ """Training logic for one epoch.
215
+
216
+ Args:
217
+ train_loader: DataLoader containing training data
218
+ optimizer: Optimizer instance for model parameter updates
219
+
220
+ Returns:
221
+ list: List of loss values for each training step
222
+ """
223
+ self.model.train()
224
+ losses = []
225
+
226
+ iterator = (
227
+ tqdm(train_loader, desc="Training", leave=False)
228
+ if self.verbose
229
+ else train_loader
230
+ )
231
+
232
+ for batch in iterator:
233
+ batch = batch.to(self.device)
234
+ optimizer.zero_grad()
235
+ loss = self.model.compute_loss(batch)
236
+ loss.backward()
237
+ if self.grad_clip_value is not None:
238
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
239
+ optimizer.step()
240
+ losses.append(loss.item())
241
+
242
+ # Update progress bar if using tqdm
243
+ if self.verbose:
244
+ iterator.set_postfix({"Epoch": f"{epoch}", "Loss": f"{loss.item():.4f}"})
245
+
246
+ return losses
247
+
248
+ def encode(self, X: List[str], return_type: Literal["np", "pt"] = "pt") -> Union[np.ndarray, torch.Tensor]:
249
+ """Encode molecules into vector representations.
250
+
251
+ Parameters
252
+ ----------
253
+ X : List[str]
254
+ List of SMILES strings
255
+ return_type : Literal["np", "pt"], default="pt"
256
+ Return type of the representations
257
+
258
+ Returns
259
+ -------
260
+ representations : ndarray or torch.Tensor
261
+ Molecular representations
262
+ """
263
+ self._check_is_fitted()
264
+
265
+ # Convert to PyTorch Geometric format and create loader
266
+ X, _ = self._validate_inputs(X, return_rdkit_mol=True)
267
+ dataset = self._convert_to_pytorch_data(X)
268
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
269
+
270
+ if self.model is None:
271
+ raise RuntimeError("Model not initialized")
272
+
273
+ # Generate encodings
274
+ self.model = self.model.to(self.device)
275
+ self.model.eval()
276
+ encodings = []
277
+ with torch.no_grad():
278
+ for batch in tqdm(loader, disable=not self.verbose):
279
+ batch = batch.to(self.device)
280
+ out = self.model(batch)
281
+ encodings.append(out["graph"].cpu())
282
+
283
+ # Concatenate and convert to requested format
284
+ encodings = torch.cat(encodings, dim=0)
285
+ return encodings if return_type == "pt" else encodings.numpy()
@@ -0,0 +1,3 @@
1
+ from .modeling_graphmae import GraphMAEMolecularEncoder
2
+
3
+ __all__ = ['GraphMAEMolecularEncoder']
@@ -0,0 +1,206 @@
1
+ import random
2
+ import copy
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from torch_geometric.data import Data
6
+
7
+ class DataLoaderMaskingPred(torch.utils.data.DataLoader):
8
+ r"""Data loader which merges data objects from a
9
+ :class:`torch_geometric.data.dataset` to a mini-batch.
10
+ Args:
11
+ dataset (Dataset): The dataset from which to load the data.
12
+ batch_size (int, optional): How may samples per batch to load.
13
+ (default: :obj:`1`)
14
+ shuffle (bool, optional): If set to :obj:`True`, the data will be
15
+ reshuffled at every epoch (default: :obj:`True`)
16
+ """
17
+
18
+ def __init__(self, dataset, batch_size=1, shuffle=True, mask_rate=0.0, mask_edge=False, **kwargs):
19
+ self._transform = MaskAtom(num_atom_type = 119, num_edge_type = 5, mask_rate = mask_rate, mask_edge=mask_edge)
20
+ super(DataLoaderMaskingPred, self).__init__(
21
+ dataset,
22
+ batch_size,
23
+ shuffle,
24
+ collate_fn=self.collate_fn,
25
+ **kwargs)
26
+
27
+ def collate_fn(self, batches):
28
+ batchs = [self._transform(x) for x in batches]
29
+ return BatchMasking.from_data_list(batchs)
30
+
31
+ class MaskAtom:
32
+ def __init__(self, num_atom_type, num_edge_type, mask_rate, mask_edge=True):
33
+ """
34
+ Randomly masks an atom, and optionally masks edges connecting to it.
35
+ The mask atom type index is num_possible_atom_type
36
+ The mask edge type index in num_possible_edge_type
37
+ :param num_atom_type:
38
+ :param num_edge_type:
39
+ :param mask_rate: % of atoms to be masked
40
+ :param mask_edge: If True, also mask the edges that connect to the
41
+ masked atoms
42
+ """
43
+ self.num_atom_type = num_atom_type
44
+ self.num_edge_type = num_edge_type
45
+ self.mask_rate = mask_rate
46
+ self.mask_edge = mask_edge
47
+ # self.num_chirality_tag = 3
48
+ # self.num_bond_direction = 3
49
+
50
+ def __call__(self, input_data, masked_atom_indices=None):
51
+ """
52
+
53
+ :param data: pytorch geometric data object. Assume that the edge
54
+ ordering is the default pytorch geometric ordering, where the two
55
+ directions of a single edge occur in pairs.
56
+ Eg. data.edge_index = tensor([[0, 1, 1, 2, 2, 3],
57
+ [1, 0, 2, 1, 3, 2]])
58
+ :param masked_atom_indices: If None, then randomly samples num_atoms
59
+ * mask rate number of atom indices
60
+ Otherwise a list of atom idx that sets the atoms to be masked (for
61
+ debugging only)
62
+ :return: None, Creates new attributes in original data object:
63
+ data.mask_node_idx
64
+ data.mask_node_label
65
+ data.mask_edge_idx
66
+ data.mask_edge_label
67
+ """
68
+ data = copy.deepcopy(input_data)
69
+ if masked_atom_indices == None:
70
+ # sample x distinct atoms to be masked, based on mask rate. But
71
+ # will sample at least 1 atom
72
+ num_atoms = data.x.size()[0]
73
+ sample_size = int(num_atoms * self.mask_rate + 1)
74
+ masked_atom_indices = random.sample(range(num_atoms), sample_size)
75
+
76
+ # create mask node label by copying atom feature of mask atom
77
+ mask_node_labels_list = []
78
+ for atom_idx in masked_atom_indices:
79
+ mask_node_labels_list.append(data.x[atom_idx].view(1, -1))
80
+ data.mask_node_label = torch.cat(mask_node_labels_list, dim=0)
81
+ data.masked_atom_indices = torch.tensor(masked_atom_indices)
82
+
83
+ # ----------- graphMAE -----------
84
+ atom_type = F.one_hot(data.mask_node_label[:, 0], num_classes=self.num_atom_type).float()
85
+ # atom_chirality = F.one_hot(data.mask_node_label[:, 1], num_classes=self.num_chirality_tag).float()
86
+ # data.node_attr_label = torch.cat((atom_type,atom_chirality), dim=1)
87
+ data.node_attr_label = atom_type
88
+
89
+ # modify the original node feature of the masked node
90
+ for atom_idx in masked_atom_indices:
91
+ feat_dim = data.x[atom_idx].shape[0]
92
+ data.x[atom_idx] = torch.zeros(feat_dim)
93
+ data.x[atom_idx][0] = self.num_atom_type - 1
94
+
95
+ if self.mask_edge:
96
+ # create mask edge labels by copying edge features of edges that are bonded to
97
+ # mask atoms
98
+ connected_edge_indices = []
99
+ for bond_idx, (u, v) in enumerate(data.edge_index.cpu().numpy().T):
100
+ for atom_idx in masked_atom_indices:
101
+ if atom_idx in set((u, v)) and \
102
+ bond_idx not in connected_edge_indices:
103
+ connected_edge_indices.append(bond_idx)
104
+
105
+ if len(connected_edge_indices) > 0:
106
+ # create mask edge labels by copying bond features of the bonds connected to
107
+ # the mask atoms
108
+ mask_edge_labels_list = []
109
+ for bond_idx in connected_edge_indices[::2]: # because the
110
+ # edge ordering is such that two directions of a single
111
+ # edge occur in pairs, so to get the unique undirected
112
+ # edge indices, we take every 2nd edge index from list
113
+ mask_edge_labels_list.append(
114
+ data.edge_attr[bond_idx].view(1, -1))
115
+
116
+ data.mask_edge_label = torch.cat(mask_edge_labels_list, dim=0)
117
+ # modify the original bond features of the bonds connected to the mask atoms
118
+ for bond_idx in connected_edge_indices:
119
+ feat_dim = data.edge_attr[bond_idx].shape[0]
120
+ data.edge_attr[bond_idx] = torch.zeros(feat_dim)
121
+ data.edge_attr[bond_idx][0] = self.num_edge_type - 1
122
+ # print('feat_dim', feat_dim, 'data.edge_attr[bond_idx].shape', data.edge_attr[bond_idx].shape)
123
+
124
+ data.connected_edge_indices = torch.tensor(
125
+ connected_edge_indices[::2])
126
+ else:
127
+ data.mask_edge_label = torch.empty((0, 2)).to(torch.int64)
128
+ data.connected_edge_indices = torch.tensor(
129
+ connected_edge_indices).to(torch.int64)
130
+
131
+ edge_type = F.one_hot(data.mask_edge_label[:, 0], num_classes=self.num_edge_type).float()
132
+ # bond_direction = F.one_hot(data.mask_edge_label[:, 1], num_classes=self.num_bond_direction).float()
133
+ data.edge_attr_label = edge_type
134
+ # data.edge_attr_label = edge_type
135
+
136
+ return data
137
+
138
+ def __repr__(self):
139
+ return '{}(num_atom_type={}, num_edge_type={}, mask_rate={}, mask_edge={})'.format(
140
+ self.__class__.__name__, self.num_atom_type, self.num_edge_type,
141
+ self.mask_rate, self.mask_edge)
142
+
143
+ class BatchMasking(Data):
144
+ r"""A plain old python object modeling a batch of graphs as one big
145
+ (dicconnected) graph. With :class:`torch_geometric.data.Data` being the
146
+ base class, all its methods can also be used here.
147
+ In addition, single graphs can be reconstructed via the assignment vector
148
+ :obj:`batch`, which maps each node to its respective graph identifier.
149
+ """
150
+
151
+ def __init__(self, batch=None, **kwargs):
152
+ super(BatchMasking, self).__init__(**kwargs)
153
+ self.batch = batch
154
+
155
+ @staticmethod
156
+ def from_data_list(data_list):
157
+ r"""Constructs a batch object from a python list holding
158
+ :class:`torch_geometric.data.Data` objects.
159
+ The assignment vector :obj:`batch` is created on the fly."""
160
+ keys = [set(data.keys()) for data in data_list]
161
+ keys = list(set.union(*keys))
162
+ assert 'batch' not in keys
163
+
164
+ batch = BatchMasking()
165
+
166
+ for key in keys:
167
+ batch[key] = []
168
+ batch.batch = []
169
+
170
+ cumsum_node = 0
171
+ cumsum_edge = 0
172
+
173
+ for i, data in enumerate(data_list):
174
+ num_nodes = data.num_nodes
175
+ batch.batch.append(torch.full((num_nodes, ), i, dtype=torch.long))
176
+ for key in data.keys():
177
+ item = data[key]
178
+ if key in ['edge_index', 'masked_atom_indices']:
179
+ item = item + cumsum_node
180
+ elif key == 'connected_edge_indices':
181
+ item = item + cumsum_edge
182
+ batch[key].append(item)
183
+
184
+ cumsum_node += num_nodes
185
+ cumsum_edge += data.edge_index.shape[1]
186
+
187
+ for key in keys:
188
+ batch[key] = torch.cat(
189
+ batch[key], dim=data_list[0].__cat_dim__(key, batch[key][0]))
190
+ batch.batch = torch.cat(batch.batch, dim=-1)
191
+ return batch.contiguous()
192
+
193
+ def cumsum(self, key, item):
194
+ r"""If :obj:`True`, the attribute :obj:`key` with content :obj:`item`
195
+ should be added up cumulatively before concatenated together.
196
+ .. note::
197
+ This method is for internal use only, and should only be overridden
198
+ if the batch concatenation process is corrupted for a specific data
199
+ attribute.
200
+ """
201
+ return key in ['edge_index', 'face', 'masked_atom_indices', 'connected_edge_indices']
202
+
203
+ @property
204
+ def num_graphs(self):
205
+ """Returns the number of graphs in the batch."""
206
+ return self.batch[-1].item() + 1
@@ -0,0 +1,137 @@
1
+ from functools import partial
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ from torch_geometric.nn import global_add_pool, global_mean_pool, global_max_pool
6
+ import torch.nn.functional as F
7
+
8
+ from ...nn import GNN_node, GNN_node_Virtualnode
9
+ from ...nn.gnn import GINConv, GCNConv
10
+ from ...utils import init_weights
11
+
12
+ def sce_loss(x, y, alpha=1):
13
+ x = F.normalize(x, p=2, dim=-1)
14
+ y = F.normalize(y, p=2, dim=-1)
15
+ loss = (1 - (x * y).sum(dim=-1)).pow_(alpha)
16
+ loss = loss.mean()
17
+ return loss
18
+
19
+ class GNN(nn.Module):
20
+ def __init__(
21
+ self,
22
+ num_layer,
23
+ hidden_size,
24
+ drop_ratio=0.5,
25
+ norm_layer="batch_norm",
26
+ encoder_type="gin-virtual",
27
+ readout="sum",
28
+ mask_edge=False,
29
+ predictor_type="gin",
30
+ ):
31
+ super(GNN, self).__init__()
32
+ gnn_name = encoder_type.split("-")[0]
33
+ self.num_atom_type = 119
34
+ self.num_edge_type = 5
35
+ self.hidden_size = hidden_size
36
+
37
+ encoder_params = {
38
+ "num_layer": num_layer,
39
+ "hidden_size": hidden_size,
40
+ "JK": "last",
41
+ "drop_ratio": drop_ratio,
42
+ "residual": True,
43
+ "gnn_name": gnn_name,
44
+ "norm_layer": norm_layer
45
+ }
46
+
47
+ # Choose encoder type based on encoder_model
48
+ encoder_class = GNN_node_Virtualnode if "virtual" in encoder_type else GNN_node
49
+ self.graph_encoder = encoder_class(**encoder_params)
50
+ pooling_funcs = {
51
+ "sum": global_add_pool,
52
+ "mean": global_mean_pool,
53
+ "max": global_max_pool
54
+ }
55
+ self.pool = pooling_funcs.get(readout)
56
+ if self.pool is None:
57
+ raise ValueError(f"Invalid graph pooling type {readout}.")
58
+
59
+ self.atom_predictor = Predictor(hidden_size, self.num_atom_type, nn_type=predictor_type)
60
+ self.mask_edge = mask_edge
61
+ if mask_edge:
62
+ self.bond_predictor = Predictor(hidden_size, self.num_edge_type, nn_type="linear")
63
+
64
+ def initialize_parameters(self, seed=None):
65
+ """
66
+ Randomly initialize all model parameters using the init_weights function.
67
+
68
+ Args:
69
+ seed (int, optional): Random seed for reproducibility. Defaults to None.
70
+ """
71
+ if seed is not None:
72
+ torch.manual_seed(seed)
73
+
74
+ # Initialize the main components
75
+ init_weights(self.graph_encoder)
76
+ init_weights(self.atom_predictor)
77
+ if self.mask_edge:
78
+ init_weights(self.bond_predictor)
79
+
80
+ # Reset all parameters using PyTorch Geometric's reset function
81
+ def reset_parameters(module):
82
+ if hasattr(module, 'reset_parameters'):
83
+ module.reset_parameters()
84
+ elif hasattr(module, 'weight') and hasattr(module.weight, 'data'):
85
+ init_weights(module)
86
+
87
+ self.apply(reset_parameters)
88
+
89
+ def compute_loss(self, batched_data, alpha_l=1):
90
+ criterion = partial(sce_loss, alpha=alpha_l)
91
+
92
+ h_node, _ = self.graph_encoder(batched_data)
93
+
94
+ node_attr_label = batched_data.node_attr_label
95
+ masked_node_indices = batched_data.masked_atom_indices
96
+ pred_node = self.atom_predictor(h_node, batched_data.edge_index, batched_data.edge_attr, masked_node_indices)
97
+ loss_atom = criterion(pred_node[masked_node_indices], node_attr_label.to(pred_node.dtype))
98
+ if self.mask_edge:
99
+ masked_edge_index = batched_data.edge_index[:, batched_data.connected_edge_indices]
100
+ edge_rep = h_node[masked_edge_index[0]] + h_node[masked_edge_index[1]]
101
+ pred_edge = self.bond_predictor(edge_rep)
102
+ loss_edge = criterion(pred_edge, batched_data.edge_attr_label.to(pred_edge.dtype))
103
+ else:
104
+ loss_edge = torch.tensor(0.0)
105
+ return loss_atom, loss_edge
106
+
107
+ def forward(self, batched_data):
108
+ h_node, _ = self.graph_encoder(batched_data)
109
+ h_rep = self.pool(h_node, batched_data.batch)
110
+ return {"graph": h_rep, "node": h_node}
111
+
112
+ class Predictor(torch.nn.Module):
113
+ def __init__(self, hidden_size, output_size, nn_type = "gin"):
114
+ super().__init__()
115
+ self._dec_type = nn_type
116
+ if "gin" in nn_type:
117
+ self.conv = GINConv(hidden_size, output_size)
118
+ elif "gcn" in nn_type:
119
+ self.conv = GCNConv(hidden_size, output_size)
120
+ elif "linear" in nn_type:
121
+ self.dec = torch.nn.Linear(hidden_size, output_size)
122
+ else:
123
+ raise NotImplementedError(f"{nn_type}")
124
+ if "linear" not in nn_type:
125
+ self.activation = torch.nn.PReLU()
126
+ self.enc_to_dec = torch.nn.Linear(hidden_size, hidden_size, bias=False)
127
+
128
+ def forward(self, x, edge_index=None, edge_attr=None, mask_node_indices=None):
129
+ if self._dec_type == "linear":
130
+ out = self.dec(x)
131
+ else:
132
+ assert edge_index is not None and edge_attr is not None and mask_node_indices is not None
133
+ x = self.activation(x)
134
+ x = self.enc_to_dec(x)
135
+ x[mask_node_indices] = 0
136
+ out = self.conv(x, edge_index, edge_attr)
137
+ return out