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,493 @@
1
+ import os
2
+ import numpy as np
3
+ from tqdm import tqdm
4
+ from typing import Optional, Union, Dict, Any, Tuple, List
5
+ import warnings
6
+ import datetime
7
+
8
+ import torch
9
+ from torch_geometric.loader import DataLoader
10
+ from torch_geometric.data import Data
11
+ from .gnn import GREANet
12
+ from .features import smiles_to_graph
13
+
14
+ from ...base import BaseMolecularPredictor
15
+
16
+ class GREAMolecularPredictor(BaseMolecularPredictor):
17
+ """This predictor implements a GNN model based on Graph Rationalization
18
+ for molecular property prediction tasks.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ num_tasks: int = 1,
24
+ task_type: str = "classification",
25
+ gamma: float = 0.4,
26
+ num_layer: int = 5,
27
+ emb_dim: int = 300,
28
+ gnn_type: str = "gin-virtual",
29
+ drop_ratio: float = 0.5,
30
+ norm_layer: str = "batch_norm",
31
+ batch_size: int = 32,
32
+ epochs: int = 100,
33
+ learning_rate: float = 0.001,
34
+ weight_decay: float = 0.0,
35
+ patience: int = 50,
36
+ device: Optional[str] = None,
37
+ use_lr_scheduler: bool = True,
38
+ scheduler_factor: float = 0.5,
39
+ scheduler_patience: int = 5,
40
+ grad_clip_value: Optional[float] = None,
41
+ verbose: bool = False,
42
+ model_name: str = "GREApredictor",
43
+ criterion = None,
44
+ ):
45
+ super().__init__()
46
+
47
+ # Model hyperparameters
48
+ self.num_tasks = num_tasks
49
+ self.task_type = task_type
50
+ self.num_layer = num_layer
51
+ self.gamma = gamma
52
+ self.emb_dim = emb_dim
53
+ self.gnn_type = gnn_type
54
+ self.drop_ratio = drop_ratio
55
+ self.norm_layer = norm_layer
56
+
57
+ # Training parameters
58
+ self.batch_size = batch_size
59
+ self.epochs = epochs
60
+ self.learning_rate = learning_rate
61
+ self.weight_decay = weight_decay
62
+ self.patience = patience
63
+ self.use_lr_scheduler = use_lr_scheduler
64
+ self.scheduler_factor = scheduler_factor
65
+ self.scheduler_patience = scheduler_patience
66
+ self.grad_clip_value = grad_clip_value
67
+ self.criterion = criterion if criterion is not None else self._load_default_criterion()
68
+
69
+ # Set device
70
+ self.device = (
71
+ torch.device(device)
72
+ if device
73
+ else torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
74
+ )
75
+
76
+ # Initialize model
77
+ self.model = GREANet(
78
+ num_tasks=self.num_tasks,
79
+ num_layer=self.num_layer,
80
+ gamma=self.gamma,
81
+ emb_dim=self.emb_dim,
82
+ gnn_type=self.gnn_type,
83
+ drop_ratio=self.drop_ratio,
84
+ norm_layer=self.norm_layer,
85
+ ).to(self.device)
86
+
87
+ self.fitting_loss = []
88
+ self.fitting_loss_mean = float("inf")
89
+ self.fitting_epoch = 0
90
+ self.verbose = verbose
91
+ self.model_name = model_name
92
+
93
+ @staticmethod
94
+ def _get_param_names() -> List[str]:
95
+ """Get parameter names for the estimator.
96
+
97
+ Returns
98
+ -------
99
+ List[str]
100
+ List of parameter names that can be used for model configuration.
101
+ """
102
+ return [
103
+ # Model hyperparameters
104
+ "num_tasks",
105
+ "task_type",
106
+ "num_layer",
107
+ "gamma",
108
+ "emb_dim",
109
+ "gnn_type",
110
+ "drop_ratio",
111
+ "norm_layer",
112
+ # Training parameters
113
+ "batch_size",
114
+ "epochs",
115
+ "learning_rate",
116
+ "weight_decay",
117
+ "patience",
118
+ "device",
119
+ "grad_clip_value",
120
+ "criterion",
121
+ # Scheduler parameters
122
+ "use_lr_scheduler",
123
+ "scheduler_factor",
124
+ "scheduler_patience",
125
+ ]
126
+
127
+ def setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
128
+ """Setup optimization components including optimizer and learning rate scheduler.
129
+
130
+ Returns
131
+ -------
132
+ Tuple[optim.Optimizer, Optional[Any]]
133
+ A tuple containing:
134
+ - The configured optimizer
135
+ - The learning rate scheduler (if enabled, else None)
136
+
137
+ Notes
138
+ -----
139
+ The scheduler returned can be either _LRScheduler or ReduceLROnPlateau,
140
+ hence we use Any as the type hint instead of optim.lr_scheduler._LRScheduler
141
+ """
142
+ optimizer = torch.optim.Adam(
143
+ self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay
144
+ )
145
+ if self.grad_clip_value is not None:
146
+ for group in optimizer.param_groups:
147
+ group.setdefault("max_norm", self.grad_clip_value)
148
+ group.setdefault("norm_type", 2.0)
149
+
150
+ scheduler = None
151
+ if self.use_lr_scheduler:
152
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
153
+ optimizer,
154
+ mode="min",
155
+ factor=self.scheduler_factor,
156
+ patience=self.scheduler_patience,
157
+ min_lr=1e-6,
158
+ cooldown=0,
159
+ eps=1e-8,
160
+ )
161
+
162
+ return optimizer, scheduler
163
+
164
+ def _convert_to_pytorch_data(self, X, y=None):
165
+ """Convert numpy arrays to PyTorch data format.
166
+
167
+ Should be implemented by child classes to handle specific molecular
168
+ representations (e.g., SMILES to graphs, conformers to graphs, etc.)
169
+ """
170
+
171
+ pyg_graph_list = []
172
+ for idx, smiles in enumerate(tqdm(X, desc="Converting molecules to graphs")):
173
+ if y is not None:
174
+ properties = y[idx]
175
+ else:
176
+ properties = None
177
+ graph = smiles_to_graph(smiles, properties)
178
+ g = Data()
179
+ g.num_nodes = graph["num_nodes"]
180
+ g.edge_index = torch.from_numpy(graph["edge_index"])
181
+
182
+ del graph["num_nodes"]
183
+ del graph["edge_index"]
184
+
185
+ if graph["edge_feat"] is not None:
186
+ g.edge_attr = torch.from_numpy(graph["edge_feat"])
187
+ del graph["edge_feat"]
188
+
189
+ if graph["node_feat"] is not None:
190
+ g.x = torch.from_numpy(graph["node_feat"])
191
+ del graph["node_feat"]
192
+
193
+ if graph["y"] is not None:
194
+ g.y = torch.from_numpy(graph["y"])
195
+ del graph["y"]
196
+
197
+ try:
198
+ g.fp = torch.tensor(graph["fp"], dtype=torch.int8).view(1, -1)
199
+ del graph["fp"]
200
+ except:
201
+ pass
202
+
203
+ pyg_graph_list.append(g)
204
+
205
+ return pyg_graph_list
206
+
207
+ def fit(self, X: List[str], y: Optional[Union[List, np.ndarray]]) -> "GREAMolecularPredictor":
208
+ """Fit the model to the data.
209
+
210
+ Parameters
211
+ ----------
212
+ X : list of SMILES strings
213
+ Input molecular structures as SMILES strings
214
+ y : array-like,
215
+ Target values for property prediction
216
+ Returns
217
+ -------
218
+ self : object
219
+ Fitted estimator
220
+ """
221
+ self.model = self.model.to(self.device)
222
+ # Setup training
223
+ optimizer, scheduler = self.setup_optimizers()
224
+ cnt_wait = 0
225
+
226
+ # Convert molecular data to PyTorch Geometric format
227
+ X, y = self._validate_inputs(X, y)
228
+ dataset = self._convert_to_pytorch_data(X, y)
229
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
230
+
231
+ # Initialize best model state
232
+ self.fitting_loss = []
233
+ self.fitting_loss_mean = float("inf")
234
+ self.fitting_epoch = 0
235
+ best_param_vector = None
236
+
237
+ # Training loop
238
+ for epoch in range(self.epochs):
239
+ # Train for one epoch
240
+ losses = self.train_epoch(loader, optimizer)
241
+ loss_mean = np.mean(losses)
242
+ if scheduler:
243
+ scheduler.step()
244
+
245
+ # Update best model
246
+ if loss_mean < self.fitting_loss_mean:
247
+ self.fitting_epoch = epoch
248
+ self.fitting_loss = losses
249
+ self.fitting_loss_mean = loss_mean
250
+ best_param_vector = torch.nn.utils.parameters_to_vector(self.model.parameters())
251
+ cnt_wait = 0
252
+ else:
253
+ cnt_wait += 1
254
+ if cnt_wait > self.patience:
255
+ break
256
+
257
+ if best_param_vector is not None:
258
+ torch.nn.utils.vector_to_parameters(best_param_vector, self.model.parameters())
259
+
260
+ self.is_fitted_ = True
261
+ return self
262
+
263
+ def predict(self, X: List[str]) -> Dict[str, np.ndarray]:
264
+ """Make predictions using the fitted model.
265
+
266
+ Parameters
267
+ ----------
268
+ X : List[str]
269
+ List of SMILES strings to make predictions for
270
+
271
+ Returns
272
+ -------
273
+ Dict[str, np.ndarray]
274
+ Dictionary containing:
275
+ - 'prediction': Model predictions (shape: [n_samples, n_tasks])
276
+ - 'confidence': Prediction confidences (shape: [n_samples, n_tasks])
277
+ - 'variance': Prediction variances (shape: [n_samples, n_tasks])
278
+
279
+ """
280
+ self._check_is_fitted()
281
+
282
+ # Convert to PyTorch Geometric format and create loader
283
+ X, _ = self._validate_inputs(X)
284
+ dataset = self._convert_to_pytorch_data(X)
285
+ loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
286
+
287
+ # Make predictions
288
+ self.model.eval()
289
+ predictions = []
290
+ confidences = []
291
+ variances = []
292
+ with torch.no_grad():
293
+ for batch in loader:
294
+ batch = batch.to(self.device)
295
+ out = self.model(batch)
296
+ predictions.append(out["prediction"].cpu().numpy())
297
+ confidences.append(out["confidence"].cpu().numpy())
298
+ variances.append(out["variance"].cpu().numpy())
299
+
300
+ return {
301
+ "prediction": np.concatenate(predictions, axis=0),
302
+ "confidence": np.concatenate(confidences, axis=0),
303
+ "variance": np.concatenate(variances, axis=0),
304
+ }
305
+
306
+ def train_epoch(self, train_loader, optimizer):
307
+ """Training logic for one epoch.
308
+
309
+ Args:
310
+ train_loader: DataLoader containing training data
311
+ optimizer: Optimizer instance for model parameter updates
312
+
313
+ Returns:
314
+ list: List of loss values for each training step
315
+ """
316
+ self.model.train()
317
+ losses = []
318
+
319
+ iterator = (
320
+ tqdm(train_loader, desc="Training", leave=False)
321
+ if self.verbose
322
+ else train_loader
323
+ )
324
+
325
+ for batch in iterator:
326
+ batch = batch.to(self.device)
327
+ optimizer.zero_grad()
328
+
329
+ # Forward pass and loss computation
330
+ loss = self.model.compute_loss(batch, self.criterion)
331
+
332
+ # Backward pass
333
+ loss.backward()
334
+
335
+ # Compute gradient norm if gradient clipping is enabled
336
+ if self.grad_clip_value is not None:
337
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
338
+
339
+ optimizer.step()
340
+
341
+ losses.append(loss.item())
342
+
343
+ # Update progress bar if using tqdm
344
+ if self.verbose:
345
+ iterator.set_postfix({"loss": f"{loss.item():.4f}"})
346
+
347
+ return losses
348
+
349
+ def save_model(self, path: str) -> None:
350
+ """Save the model to disk.
351
+
352
+ Parameters
353
+ ----------
354
+ path : str
355
+ Path where to save the model
356
+ """
357
+ if not self.is_fitted_:
358
+ raise ValueError("Model must be fitted before saving")
359
+
360
+ if not path.endswith((".pt", ".pth")):
361
+ raise ValueError("Save path should end with '.pt' or '.pth'")
362
+
363
+ # Create directory if it doesn't exist
364
+ os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
365
+
366
+ model_name = os.path.splitext(os.path.basename(path))[0]
367
+ save_dict = {
368
+ "model_state_dict": self.model.state_dict(),
369
+ "hyperparameters": self.get_params(),
370
+ "fitting_epoch": self.fitting_epoch,
371
+ "fitting_loss": self.fitting_loss,
372
+ "fitting_loss_mean": self.fitting_loss_mean,
373
+ "model_name": model_name,
374
+ "date_saved": datetime.datetime.now().isoformat(),
375
+ "version": getattr(self, "__version__", "1.0.0"),
376
+ }
377
+ torch.save(save_dict, path)
378
+
379
+ def load_model(self, path: str, repo: Optional[str] = None) -> "GREAMolecularPredictor":
380
+ """Load a saved model from disk or download from HuggingFace hub.
381
+
382
+ Parameters
383
+ ----------
384
+ path : str
385
+ Path to the saved model file or desired local path for downloaded model
386
+ repo : str, optional
387
+ HuggingFace model repository ID (e.g., 'username/model-name')
388
+ If provided and local path doesn't exist, will attempt to download from hub
389
+
390
+ Returns
391
+ -------
392
+ self : GREAMolecularPredictor
393
+ Updated model instance with loaded weights and parameters
394
+
395
+ Raises
396
+ ------
397
+ FileNotFoundError
398
+ If the model file doesn't exist locally and no repo is provided,
399
+ or if download from repo fails
400
+ ValueError
401
+ If the saved file is corrupted or incompatible
402
+ """
403
+ # First try to load from local path
404
+ if os.path.exists(path):
405
+ try:
406
+ checkpoint = torch.load(path, map_location=self.device)
407
+ except Exception as e:
408
+ raise ValueError(f"Error loading model from {path}: {str(e)}")
409
+
410
+ # If local file doesn't exist and repo is provided, try downloading
411
+ elif repo is not None:
412
+ try:
413
+ from huggingface_hub import hf_hub_download
414
+ # Create directory if it doesn't exist
415
+ os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
416
+
417
+ # Download from HuggingFace using specified parameters
418
+ model_name = os.path.splitext(os.path.basename(path))[0]
419
+ downloaded_path = hf_hub_download(
420
+ repo_id=repo,
421
+ filename=model_name,
422
+ local_dir=os.path.dirname(path),
423
+ local_dir_use_symlinks=False,
424
+ )
425
+
426
+ # Load the downloaded model
427
+ checkpoint = torch.load(downloaded_path, map_location=self.device)
428
+
429
+ except Exception as e:
430
+ if os.path.exists(path):
431
+ os.remove(path) # Clean up partial downloads
432
+ raise FileNotFoundError(
433
+ f"Failed to download model from repository '{repo}': {str(e)}"
434
+ )
435
+
436
+ # If neither local file exists nor repo provided
437
+ else:
438
+ raise FileNotFoundError(f"No model file found at '{path}' and no repository provided")
439
+
440
+ try:
441
+ # Validate checkpoint contents
442
+ required_keys = {
443
+ "model_state_dict",
444
+ "hyperparameters",
445
+ "fitting_loss",
446
+ "fitting_epoch",
447
+ "model_name",
448
+ }
449
+ if not all(key in checkpoint for key in required_keys):
450
+ missing_keys = required_keys - set(checkpoint.keys())
451
+ raise ValueError(f"Checkpoint missing required keys: {missing_keys}")
452
+
453
+ # Validate compatibility of hyperparameters
454
+ for key, value in checkpoint["hyperparameters"].items():
455
+ if hasattr(self, key) and getattr(self, key) != value:
456
+ warnings.warn(
457
+ f"Loaded model has different {key} ({value}) "
458
+ f"than current instance ({getattr(self, key)}). "
459
+ "Updating to match loaded model."
460
+ )
461
+ setattr(self, key, value)
462
+
463
+ # Update model state
464
+ if not hasattr(self, 'model') or self.model is None:
465
+ raise ValueError("Model not initialized. Please initialize model before loading weights.")
466
+
467
+ try:
468
+ self.model.load_state_dict(checkpoint["model_state_dict"])
469
+ except Exception as e:
470
+ raise ValueError(f"Failed to load model state dict: {str(e)}")
471
+
472
+ # Update training state
473
+ self.fitting_epoch = checkpoint.get("fitting_epoch", 0)
474
+ self.fitting_loss = checkpoint.get("fitting_loss", float("inf"))
475
+ self.model_name = checkpoint['model_name']
476
+ self.is_fitted_ = True
477
+
478
+ # Move model to correct device
479
+ self.model = self.model.to(self.device)
480
+
481
+ print(f"Model successfully loaded from {'repository' if repo else 'local path'}")
482
+ return self
483
+
484
+ except Exception as e:
485
+ raise ValueError(f"Error loading model checkpoint: {str(e)}")
486
+
487
+ finally:
488
+ # Clean up any temporary files
489
+ if repo is not None and os.path.exists(downloaded_path) and downloaded_path != path:
490
+ try:
491
+ os.remove(downloaded_path)
492
+ except Exception:
493
+ pass
@@ -0,0 +1,97 @@
1
+ # The code is from torch_scatter: https://github.com/rusty1s/pytorch_scatter/blob/1.3.0/torch_scatter/add.py
2
+
3
+ from itertools import repeat
4
+
5
+ def maybe_dim_size(index, dim_size=None):
6
+ if dim_size is not None:
7
+ return dim_size
8
+ return index.max().item() + 1 if index.numel() > 0 else 0
9
+
10
+ def gen(src, index, dim=-1, out=None, dim_size=None, fill_value=0):
11
+ dim = range(src.dim())[dim] # Get real dim value.
12
+
13
+ # Automatically expand index tensor to the right dimensions.
14
+ if index.dim() == 1:
15
+ index_size = list(repeat(1, src.dim()))
16
+ index_size[dim] = src.size(dim)
17
+ index = index.view(index_size).expand_as(src)
18
+
19
+ # Generate output tensor if not given.
20
+ if out is None:
21
+ out_size = list(src.size())
22
+ dim_size = maybe_dim_size(index, dim_size)
23
+ out_size[dim] = dim_size
24
+ out = src.new_full(out_size, fill_value)
25
+
26
+ return src, out, index, dim
27
+
28
+ def scatter_add(src, index, dim=-1, out=None, dim_size=None, fill_value=0):
29
+ r"""
30
+ |
31
+
32
+ .. image:: https://raw.githubusercontent.com/rusty1s/pytorch_scatter/
33
+ master/docs/source/_figures/add.svg?sanitize=true
34
+ :align: center
35
+ :width: 400px
36
+
37
+ |
38
+
39
+ Sums all values from the :attr:`src` tensor into :attr:`out` at the indices
40
+ specified in the :attr:`index` tensor along a given axis :attr:`dim`. For
41
+ each value in :attr:`src`, its output index is specified by its index in
42
+ :attr:`input` for dimensions outside of :attr:`dim` and by the
43
+ corresponding value in :attr:`index` for dimension :attr:`dim`. If
44
+ multiple indices reference the same location, their **contributions add**.
45
+
46
+ Formally, if :attr:`src` and :attr:`index` are n-dimensional tensors with
47
+ size :math:`(x_0, ..., x_{i-1}, x_i, x_{i+1}, ..., x_{n-1})` and
48
+ :attr:`dim` = `i`, then :attr:`out` must be an n-dimensional tensor with
49
+ size :math:`(x_0, ..., x_{i-1}, y, x_{i+1}, ..., x_{n-1})`. Moreover, the
50
+ values of :attr:`index` must be between `0` and `out.size(dim) - 1`.
51
+
52
+ For one-dimensional tensors, the operation computes
53
+
54
+ .. math::
55
+ \mathrm{out}_i = \mathrm{out}_i + \sum_j \mathrm{src}_j
56
+
57
+ where :math:`\sum_j` is over :math:`j` such that
58
+ :math:`\mathrm{index}_j = i`.
59
+
60
+ Args:
61
+ src (Tensor): The source tensor.
62
+ index (LongTensor): The indices of elements to scatter.
63
+ dim (int, optional): The axis along which to index.
64
+ (default: :obj:`-1`)
65
+ out (Tensor, optional): The destination tensor. (default: :obj:`None`)
66
+ dim_size (int, optional): If :attr:`out` is not given, automatically
67
+ create output with size :attr:`dim_size` at dimension :attr:`dim`.
68
+ If :attr:`dim_size` is not given, a minimal sized output tensor is
69
+ returned. (default: :obj:`None`)
70
+ fill_value (int, optional): If :attr:`out` is not given, automatically
71
+ fill output tensor with :attr:`fill_value`. (default: :obj:`0`)
72
+
73
+ :rtype: :class:`Tensor`
74
+
75
+ .. testsetup::
76
+
77
+ import torch
78
+
79
+ .. testcode::
80
+
81
+ from torch_scatter import scatter_add
82
+
83
+ src = torch.Tensor([[2, 0, 1, 4, 3], [0, 2, 1, 3, 4]])
84
+ index = torch.tensor([[4, 5, 4, 2, 3], [0, 0, 2, 2, 1]])
85
+ out = src.new_zeros((2, 6))
86
+
87
+ out = scatter_add(src, index, out=out)
88
+
89
+ print(out)
90
+
91
+ .. testoutput::
92
+
93
+ tensor([[0., 0., 4., 3., 3., 0.],
94
+ [2., 4., 4., 0., 0., 0.]])
95
+ """
96
+ src, out, index, dim = gen(src, index, dim, out, dim_size, fill_value)
97
+ return out.scatter_add_(dim, index, src)
@@ -0,0 +1,3 @@
1
+ from .modeling_grin import GRINMolecularPredictor
2
+
3
+ __all__ = ['GRINMolecularPredictor']