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,436 @@
1
+ import warnings
2
+ import torch
3
+ import numpy as np
4
+ from ..utils import (
5
+ roc_auc_score,
6
+ accuracy_score,
7
+ mean_absolute_error,
8
+ mean_squared_error,
9
+ root_mean_squared_error,
10
+ r2_score,
11
+ )
12
+ from dataclasses import dataclass, field
13
+ from abc import ABC, abstractmethod
14
+ from typing import Optional, ClassVar, Union, List, Dict, Any, Tuple, Callable, Type
15
+ from ..base.base import BaseModel
16
+
17
+ @dataclass
18
+ class BaseMolecularPredictor(BaseModel, ABC):
19
+ """Base class for molecular discovery estimators."""
20
+
21
+ num_task: int
22
+ task_type: str
23
+ model_name: str = field(default="BaseMolecularPredictor")
24
+
25
+ DEFAULT_METRICS: ClassVar[Dict] = {
26
+ "classification": {"default": ("roc_auc", roc_auc_score, True)},
27
+ "regression": {"default": ("mae", mean_absolute_error, False)},
28
+ }
29
+
30
+ def __post_init__(self):
31
+ super().__post_init__()
32
+ if self.task_type not in ["classification", "regression"]:
33
+ raise ValueError(f"Invalid task_type: {self.task_type}")
34
+ if self.num_task <= 0:
35
+ raise ValueError(f"num_task must be positive, got {self.num_task}")
36
+
37
+ def _get_param_names(self) -> List[str]:
38
+ return super()._get_param_names() + ["num_task", "task_type"]
39
+
40
+ @abstractmethod
41
+ def fit(self, X_train, y_train, X_val=None, y_val=None, X_unlbl=None) -> "BaseMolecularPredictor":
42
+ pass
43
+
44
+ @abstractmethod
45
+ def autofit(self, X_train, y_train, X_val=None, y_val=None, search_parameters: Optional[dict] = None, n_trials: int = 10) -> "BaseMolecularPredictor":
46
+ pass
47
+
48
+ @abstractmethod
49
+ def predict(self, X: np.ndarray) -> np.ndarray:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def _train_epoch(self, train_loader, optimizer):
58
+ pass
59
+
60
+ @abstractmethod
61
+ def _evaluation_epoch(self, evaluate_loader):
62
+ pass
63
+
64
+ def _setup_evaluation(
65
+ self,
66
+ evaluate_criterion: Optional[Union[str, Callable]],
67
+ evaluate_higher_better: Optional[bool],
68
+ ) -> None:
69
+ if evaluate_criterion is None:
70
+ default_metric = self.DEFAULT_METRICS[self.task_type]["default"]
71
+ self.evaluate_name = default_metric[0]
72
+ self.evaluate_criterion = default_metric[1]
73
+ self.evaluate_higher_better = default_metric[2]
74
+ else:
75
+ if isinstance(evaluate_criterion, str):
76
+ metric_map = {
77
+ "accuracy": (accuracy_score, True),
78
+ "roc_auc": (roc_auc_score, True),
79
+ "rmse": (root_mean_squared_error, False),
80
+ "mse": (mean_squared_error, False),
81
+ "mae": (mean_absolute_error, False),
82
+ "r2": (r2_score, True),
83
+ }
84
+ if evaluate_criterion not in metric_map:
85
+ raise ValueError(
86
+ f"Unknown metric: {evaluate_criterion}. "
87
+ f"Available metrics: {list(metric_map.keys())}"
88
+ )
89
+ self.evaluate_name = evaluate_criterion
90
+ self.evaluate_criterion = metric_map[evaluate_criterion][0]
91
+ self.evaluate_higher_better = (
92
+ metric_map[evaluate_criterion][1]
93
+ if evaluate_higher_better is None
94
+ else evaluate_higher_better
95
+ )
96
+ else:
97
+ if evaluate_higher_better is None:
98
+ raise ValueError(
99
+ "evaluate_higher_better must be specified for a custom function."
100
+ )
101
+ self.evaluate_name = "custom"
102
+ self.evaluate_criterion = evaluate_criterion
103
+ self.evaluate_higher_better = evaluate_higher_better
104
+
105
+ def _load_default_criterion(self):
106
+ if self.task_type == "regression":
107
+ return torch.nn.L1Loss()
108
+ elif self.task_type == "classification":
109
+ return torch.nn.BCEWithLogitsLoss()
110
+ else:
111
+ warnings.warn(
112
+ "Unknown task type. Using L1 Loss as default. "
113
+ "Please specify 'regression' or 'classification' for better results."
114
+ )
115
+ return torch.nn.L1Loss()
116
+
117
+ # import os
118
+ # import json
119
+ # import datetime
120
+ # import tempfile
121
+ # import warnings
122
+ # import torch
123
+ # import numpy as np
124
+ # from rdkit import Chem
125
+ # from ..utils import (
126
+ # roc_auc_score,
127
+ # accuracy_score,
128
+ # mean_absolute_error,
129
+ # mean_squared_error,
130
+ # root_mean_squared_error,
131
+ # r2_score,
132
+ # )
133
+ # from ..utils.checkpoint import LocalCheckpointManager, HuggingFaceCheckpointManager
134
+ # from ..utils.checker import MolecularInputChecker
135
+ # from dataclasses import dataclass, field
136
+ # from abc import ABC, abstractmethod
137
+ # from typing import Optional, ClassVar, Union, List, Dict, Any, Tuple, Callable, Type
138
+
139
+ # @dataclass
140
+ # class BaseMolecularPredictor(ABC):
141
+ # """Base class for molecular discovery estimators.
142
+
143
+ # This class provides a structure for implementing custom molecular discovery
144
+ # models. It includes basic functionality for parameter management, fitting,
145
+ # prediction, and validation.
146
+ # """
147
+
148
+ # num_task: int
149
+ # task_type: str
150
+ # model_name: str = field(default="BaseMolecularPredictor")
151
+ # device: Optional[torch.device] = field(default=None)
152
+ # model: Optional[torch.nn.Module] = field(default=None, init=False)
153
+
154
+ # DEFAULT_METRICS: ClassVar[Dict] = {
155
+ # "classification": {
156
+ # "default": ("roc_auc", roc_auc_score, True),
157
+ # },
158
+ # "regression": {"default": ("mae", mean_absolute_error, False)},
159
+ # }
160
+
161
+ # is_fitted_: bool = field(default=False, init=False)
162
+ # model_class: Optional[Type[torch.nn.Module]] = field(default=None, init=False)
163
+
164
+ # def __post_init__(self):
165
+ # if self.task_type not in ["classification", "regression"]:
166
+ # raise ValueError(
167
+ # f"task_type must be one of ['classification', 'regression'], got {self.task_type}"
168
+ # )
169
+
170
+ # if not isinstance(self.num_task, (int, np.integer)) or self.num_task <= 0:
171
+ # raise ValueError(f"num_task must be a positive integer, got {self.num_task}")
172
+
173
+ # if self.device is None:
174
+ # self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
175
+ # elif isinstance(self.device, str):
176
+ # self.device = torch.device(self.device)
177
+
178
+ # def get_params(self, deep: bool = True) -> Dict[str, Any]:
179
+ # out = dict()
180
+ # for key in self._get_param_names():
181
+ # value = getattr(self, key)
182
+ # if deep and hasattr(value, "get_params"):
183
+ # deep_items = value.get_params().items()
184
+ # out.update((key + "__" + k, val) for k, val in deep_items)
185
+ # out[key] = value
186
+ # return out
187
+
188
+ # def set_params(self, **params) -> "BaseMolecularPredictor":
189
+ # if not params:
190
+ # return self
191
+
192
+ # valid_params = self.get_params(deep=True)
193
+ # for key, value in params.items():
194
+ # if key not in valid_params:
195
+ # raise ValueError(f"Invalid parameter {key} for predictor {self}")
196
+ # setattr(self, key, value)
197
+ # return self
198
+
199
+ # @abstractmethod
200
+ # def fit(self, X_train, y_train, X_val=None, y_val=None, X_unlbl=None) -> "BaseMolecularPredictor":
201
+ # pass
202
+
203
+ # @abstractmethod
204
+ # def autofit(
205
+ # self,
206
+ # X_train,
207
+ # y_train,
208
+ # X_val=None,
209
+ # y_val=None,
210
+ # search_parameters: Optional[dict] = None,
211
+ # n_trials: int = 10,
212
+ # ) -> "BaseMolecularPredictor":
213
+ # pass
214
+
215
+ # @abstractmethod
216
+ # def predict(self, X: np.ndarray) -> np.ndarray:
217
+ # pass
218
+
219
+ # def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
220
+ # raise NotImplementedError("Child class must implement optimizer and scheduler setup.")
221
+
222
+ # def _setup_evaluation(
223
+ # self,
224
+ # evaluate_criterion: Optional[Union[str, Callable]],
225
+ # evaluate_higher_better: Optional[bool],
226
+ # ) -> None:
227
+ # if evaluate_criterion is None:
228
+ # default_metric = self.DEFAULT_METRICS[self.task_type]["default"]
229
+ # self.evaluate_name = default_metric[0]
230
+ # self.evaluate_criterion = default_metric[1]
231
+ # self.evaluate_higher_better = default_metric[2]
232
+ # else:
233
+ # if isinstance(evaluate_criterion, str):
234
+ # metric_map = {
235
+ # "accuracy": (accuracy_score, True),
236
+ # "roc_auc": (roc_auc_score, True),
237
+ # "rmse": (root_mean_squared_error, False),
238
+ # "mse": (mean_squared_error, False),
239
+ # "mae": (mean_absolute_error, False),
240
+ # "r2": (r2_score, True),
241
+ # }
242
+ # if evaluate_criterion not in metric_map:
243
+ # raise ValueError(
244
+ # f"Unknown metric: {evaluate_criterion}. "
245
+ # f"Available metrics: {list(metric_map.keys())}"
246
+ # )
247
+ # self.evaluate_name = evaluate_criterion
248
+ # self.evaluate_criterion = metric_map[evaluate_criterion][0]
249
+ # self.evaluate_higher_better = (
250
+ # metric_map[evaluate_criterion][1]
251
+ # if evaluate_higher_better is None
252
+ # else evaluate_higher_better
253
+ # )
254
+ # else:
255
+ # if evaluate_higher_better is None:
256
+ # raise ValueError(
257
+ # "evaluate_higher_better must be specified for a custom function."
258
+ # )
259
+ # self.evaluate_name = "custom"
260
+ # self.evaluate_criterion = evaluate_criterion
261
+ # self.evaluate_higher_better = evaluate_higher_better
262
+
263
+ # def _train_epoch(self, train_loader, optimizer):
264
+ # raise NotImplementedError("Child class must implement training logic.")
265
+
266
+ # def _evaluation_epoch(self, evaluate_loader):
267
+ # raise NotImplementedError("Child class must implement evaluation logic.")
268
+
269
+ # def _check_is_fitted(self) -> None:
270
+ # if not self.is_fitted_:
271
+ # raise AttributeError(
272
+ # "This instance is not fitted yet. "
273
+ # "Call 'fit' before using this estimator."
274
+ # )
275
+
276
+ # def __repr__(self, N_CHAR_MAX=700):
277
+ # attributes = {
278
+ # name: value
279
+ # for name, value in sorted(self.__dict__.items())
280
+ # if not name.startswith("_") and not callable(value)
281
+ # }
282
+
283
+ # def format_value(v):
284
+ # if isinstance(v, (float, np.float32, np.float64)):
285
+ # return f"{v:.3g}"
286
+ # elif isinstance(v, (list, tuple, np.ndarray)) and len(v) > 6:
287
+ # return f"{str(v[:3])[:-1]}, ..., {str(v[-3:])[1:]}"
288
+ # elif isinstance(v, str) and len(v) > 50:
289
+ # return f"'{v[:25]}...{v[-22:]}'"
290
+ # elif isinstance(v, dict) and len(v) > 6:
291
+ # items = list(v.items())
292
+ # return f"{dict(items[:3])}...{dict(items[-3:])}"
293
+ # elif isinstance(v, torch.nn.Module):
294
+ # return f"{v.__class__.__name__}(...)"
295
+ # elif v is None:
296
+ # return "None"
297
+ # else:
298
+ # return repr(v)
299
+
300
+ # class_name = self.__class__.__name__
301
+ # attributes_str = []
302
+
303
+ # important_attrs = ["num_task", "task_type", "model_name", "is_fitted_"]
304
+ # for attr in important_attrs:
305
+ # if attr in attributes:
306
+ # value = attributes.pop(attr)
307
+ # attributes_str.append(f"{attr}={format_value(value)}")
308
+
309
+ # sorted_attrs = sorted(attributes.items())
310
+ # attributes_str.extend(f"{name}={format_value(value)}" for name, value in sorted_attrs)
311
+
312
+ # content = ",\n ".join(attributes_str)
313
+ # repr_ = f"{class_name}(\n {content}\n)"
314
+
315
+ # if len(repr_) > N_CHAR_MAX:
316
+ # lines = repr_.split("\n")
317
+ # result = [lines[0]]
318
+ # current_length = len(lines[0])
319
+ # for line in lines[1:-1]:
320
+ # if current_length + len(line) + 5 > N_CHAR_MAX:
321
+ # result.append(" ...")
322
+ # break
323
+ # result.append(line)
324
+ # current_length += len(line) + 1
325
+ # result.append(lines[-1])
326
+ # repr_ = "\n".join(result)
327
+
328
+ # return repr_
329
+
330
+ # @abstractmethod
331
+ # def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
332
+ # raise NotImplementedError("Child class must implement _get_model_params.")
333
+
334
+ # def _initialize_model(
335
+ # self, model_class: Type[torch.nn.Module], device, checkpoint: Optional[Dict] = None
336
+ # ) -> None:
337
+ # """Initialize the model with parameters or a checkpoint."""
338
+ # try:
339
+ # model_params = self._get_model_params(checkpoint)
340
+ # self.model = model_class(**model_params)
341
+ # try:
342
+ # self.model = self.model.to(device)
343
+ # except Exception as e:
344
+ # raise RuntimeError(f"Failed to move model to device {device}: {str(e)}")
345
+
346
+ # if checkpoint is not None:
347
+ # try:
348
+ # self.model.load_state_dict(checkpoint["model_state_dict"])
349
+ # except Exception as e:
350
+ # raise ValueError(f"Failed to load model state dictionary: {str(e)}")
351
+
352
+ # except Exception as e:
353
+ # raise RuntimeError(f"Model initialization failed: {str(e)}")
354
+
355
+ # def _load_default_criterion(self):
356
+ # if self.task_type == "regression":
357
+ # return torch.nn.L1Loss()
358
+ # elif self.task_type == "classification":
359
+ # return torch.nn.BCEWithLogitsLoss()
360
+ # else:
361
+ # warnings.warn(
362
+ # "Unknown task type. Using L1 Loss as default. "
363
+ # "Please specify 'regression' or 'classification' for better results."
364
+ # )
365
+ # return torch.nn.L1Loss()
366
+
367
+ # def _validate_inputs(
368
+ # self, X: List[str], y: Optional[Union[List, np.ndarray]] = None, return_rdkit_mol: bool = True
369
+ # ) -> Tuple[Union[List[str], List["Chem.Mol"]], Optional[np.ndarray]]:
370
+ # return MolecularInputChecker.validate_inputs(X, y, self.num_task, 0, return_rdkit_mol)
371
+
372
+ # # def _validate_smiles(self, smiles: str, idx: int) -> Tuple[bool, Optional[str], Optional[Chem.Mol]]:
373
+ # # return MolecularInputChecker.validate_smiles(smiles, idx)
374
+
375
+ # @staticmethod
376
+ # def _get_param_names() -> List[str]:
377
+ # return ["num_task", "task_type", "is_fitted_"]
378
+
379
+ # def save_to_local(self, path: str) -> None:
380
+ # """Save to local disk using LocalCheckpointManager."""
381
+ # LocalCheckpointManager.save_model_to_local(self, path)
382
+
383
+ # def load_from_local(self, path: str) -> None:
384
+ # """Load from local disk using LocalCheckpointManager."""
385
+ # LocalCheckpointManager.load_model_from_local(self, path)
386
+
387
+ # def save_to_hf(
388
+ # self,
389
+ # repo_id: str,
390
+ # task_id: str = "default",
391
+ # metadata_dict: Optional[Dict[str, Any]] = None,
392
+ # metrics: Optional[Dict[str, float]] = None,
393
+ # commit_message: str = "Update model",
394
+ # token: Optional[str] = None,
395
+ # private: bool = False,
396
+ # ) -> None:
397
+ # HuggingFaceCheckpointManager.push_to_huggingface(
398
+ # model_instance=self,
399
+ # repo_id=repo_id,
400
+ # task_id=task_id,
401
+ # metadata_dict=metadata_dict,
402
+ # metrics=metrics,
403
+ # commit_message=commit_message,
404
+ # token=token,
405
+ # private=private,
406
+ # )
407
+
408
+ # def load_from_hf(self, repo_id: str, path: str) -> None:
409
+ # """Download and load model from Hugging Face Hub."""
410
+ # HuggingFaceCheckpointManager.load_model_from_hf(self, repo_id, path)
411
+
412
+ # def save(self, path: Optional[str] = None, repo_id: Optional[str] = None, **kwargs) -> None:
413
+ # """
414
+ # Automatic save. If `repo_id` is given, push to Hugging Face Hub.
415
+ # Otherwise, save to local path.
416
+ # """
417
+ # if repo_id is not None:
418
+ # self.save_to_hf(repo_id=repo_id, **kwargs)
419
+ # else:
420
+ # if path is None:
421
+ # raise ValueError("path must be provided if repo_id is not given.")
422
+ # self.save_to_local(path)
423
+
424
+ # def load(self, path: str, repo_id: Optional[str] = None) -> None:
425
+ # """
426
+ # Automatic load. If local `path` exists, load from there.
427
+ # Otherwise, try loading from Hugging Face Hub if `repo_id` is given.
428
+ # """
429
+ # if os.path.exists(path):
430
+ # self.load_from_local(path)
431
+ # else:
432
+ # if repo_id is None:
433
+ # raise FileNotFoundError(
434
+ # f"No local file found at '{path}' and no repo_id provided."
435
+ # )
436
+ # self.load_from_hf(repo_id, path)
@@ -0,0 +1,112 @@
1
+ import warnings
2
+ import torch
3
+ import numpy as np
4
+ from ..utils import (
5
+ roc_auc_score,
6
+ accuracy_score,
7
+ mean_absolute_error,
8
+ mean_squared_error,
9
+ root_mean_squared_error,
10
+ r2_score,
11
+ )
12
+ from dataclasses import dataclass, field
13
+ from abc import ABC, abstractmethod
14
+ from typing import Optional, ClassVar, Union, List, Dict, Any, Tuple, Callable, Type
15
+ from ..base.base import BaseModel
16
+
17
+ @dataclass
18
+ class BaseMolecularPredictor(BaseModel, ABC):
19
+ """Base class for molecular discovery estimators."""
20
+
21
+ model_name: str = field(default="BaseMolecularPredictor")
22
+ num_task: int = field(default=0)
23
+ task_type: str = field(default=None)
24
+ DEFAULT_METRICS: ClassVar[Dict] = {
25
+ "classification": {"default": ("roc_auc", roc_auc_score, True)},
26
+ "regression": {"default": ("mae", mean_absolute_error, False)},
27
+ }
28
+
29
+ def __post_init__(self):
30
+ super().__post_init__()
31
+ if self.task_type not in ["classification", "regression"]:
32
+ raise ValueError(f"Invalid task_type: {self.task_type}")
33
+ if self.num_task <= 0:
34
+ raise ValueError(f"num_task must be positive, got {self.num_task}")
35
+
36
+ @staticmethod
37
+ def _get_param_names(self) -> List[str]:
38
+ return super()._get_param_names() + ["num_task", "task_type"]
39
+
40
+ @abstractmethod
41
+ def autofit(self, X_train, y_train, X_val=None, y_val=None, search_parameters: Optional[dict] = None, n_trials: int = 10) -> "BaseMolecularPredictor":
42
+ pass
43
+
44
+ @abstractmethod
45
+ def fit(self, X_train, y_train, X_val=None, y_val=None, search_parameters: Optional[dict] = None, n_trials: int = 10) -> "BaseMolecularPredictor":
46
+ pass
47
+
48
+ @abstractmethod
49
+ def predict(self, X: np.ndarray) -> np.ndarray:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def _evaluation_epoch(self, evaluate_loader):
54
+ pass
55
+
56
+ def _setup_evaluation(
57
+ self,
58
+ evaluate_criterion: Optional[Union[str, Callable]],
59
+ evaluate_higher_better: Optional[bool],
60
+ ) -> None:
61
+ if evaluate_criterion is None:
62
+ default_metric = self.DEFAULT_METRICS[self.task_type]["default"]
63
+ self.evaluate_name = default_metric[0]
64
+ self.evaluate_criterion = default_metric[1]
65
+ self.evaluate_higher_better = default_metric[2]
66
+ else:
67
+ if isinstance(evaluate_criterion, str):
68
+ metric_map = {
69
+ "accuracy": (accuracy_score, True),
70
+ "roc_auc": (roc_auc_score, True),
71
+ "rmse": (root_mean_squared_error, False),
72
+ "mse": (mean_squared_error, False),
73
+ "mae": (mean_absolute_error, False),
74
+ "r2": (r2_score, True),
75
+ }
76
+ if evaluate_criterion not in metric_map:
77
+ raise ValueError(
78
+ f"Unknown metric: {evaluate_criterion}. "
79
+ f"Available metrics: {list(metric_map.keys())}"
80
+ )
81
+ self.evaluate_name = evaluate_criterion
82
+ self.evaluate_criterion = metric_map[evaluate_criterion][0]
83
+ self.evaluate_higher_better = (
84
+ metric_map[evaluate_criterion][1]
85
+ if evaluate_higher_better is None
86
+ else evaluate_higher_better
87
+ )
88
+ else:
89
+ if evaluate_higher_better is None:
90
+ raise ValueError(
91
+ "evaluate_higher_better must be specified for a custom function."
92
+ )
93
+ self.evaluate_name = "custom"
94
+ self.evaluate_criterion = evaluate_criterion
95
+ self.evaluate_higher_better = evaluate_higher_better
96
+
97
+ def _load_default_criterion(self):
98
+ if self.task_type == "regression":
99
+ return torch.nn.L1Loss(reduction='none')
100
+ elif self.task_type == "classification":
101
+ return torch.nn.BCEWithLogitsLoss(reduction='none')
102
+ else:
103
+ warnings.warn(
104
+ "Unknown task type. Using L1 Loss as default. "
105
+ "Please specify 'regression' or 'classification' for better results."
106
+ )
107
+ return torch.nn.L1Loss(reduction='none')
108
+
109
+ def _validate_inputs(
110
+ self, X: List[str], y: Optional[Union[List, np.ndarray]] = None, num_task: int = 0, num_pretask: int = 0, return_rdkit_mol: bool = True
111
+ ) -> Tuple[Union[List[str], List["Chem.Mol"]], Optional[np.ndarray]]:
112
+ return super()._validate_inputs(X, y, self.num_task, 0, return_rdkit_mol)
@@ -0,0 +1,3 @@
1
+ from .modeling_attrmask import AttrMaskMolecularEncoder
2
+
3
+ __all__ = ['AttrMaskMolecularEncoder']
@@ -0,0 +1,113 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch_geometric.nn import global_add_pool, global_mean_pool, global_max_pool
4
+
5
+ from ...nn import GNN_node, GNN_node_Virtualnode, MLP
6
+ from ...utils import init_weights
7
+
8
+ import random
9
+
10
+ class_criterion = torch.nn.CrossEntropyLoss()
11
+
12
+ class GNN(nn.Module):
13
+ def __init__(
14
+ self,
15
+ num_layer,
16
+ hidden_size,
17
+ drop_ratio=0.5,
18
+ norm_layer="batch_norm",
19
+ encoder_type="gin-virtual",
20
+ readout="max",
21
+ mask_num=0,
22
+ mask_rate=0.15
23
+ ):
24
+ super(GNN, self).__init__()
25
+ gnn_name = encoder_type.split("-")[0]
26
+ self.num_atom_type = 119
27
+ self.hidden_size = hidden_size
28
+ self.mask_num = mask_num
29
+ self.mask_rate = mask_rate
30
+
31
+ encoder_params = {
32
+ "num_layer": num_layer,
33
+ "hidden_size": hidden_size,
34
+ "JK": "last",
35
+ "drop_ratio": drop_ratio,
36
+ "residual": True,
37
+ "gnn_name": gnn_name,
38
+ "norm_layer": norm_layer
39
+ }
40
+
41
+ # Choose encoder type based on encoder_model
42
+ encoder_class = GNN_node_Virtualnode if "virtual" in encoder_type else GNN_node
43
+ self.graph_encoder = encoder_class(**encoder_params)
44
+ pooling_funcs = {
45
+ "sum": global_add_pool,
46
+ "mean": global_mean_pool,
47
+ "max": global_max_pool
48
+ }
49
+ self.pool = pooling_funcs.get(readout)
50
+ if self.pool is None:
51
+ raise ValueError(f"Invalid graph pooling type {readout}.")
52
+
53
+ self.predictor = MLP(hidden_size, hidden_features=2 * hidden_size, out_features=self.num_atom_type)
54
+
55
+ def initialize_parameters(self, seed=None):
56
+ """
57
+ Randomly initialize all model parameters using the init_weights function.
58
+
59
+ Args:
60
+ seed (int, optional): Random seed for reproducibility. Defaults to None.
61
+ """
62
+ if seed is not None:
63
+ torch.manual_seed(seed)
64
+
65
+ # Initialize the main components
66
+ init_weights(self.graph_encoder)
67
+ init_weights(self.predictor)
68
+
69
+ # Reset all parameters using PyTorch Geometric's reset function
70
+ def reset_parameters(module):
71
+ if hasattr(module, 'reset_parameters'):
72
+ module.reset_parameters()
73
+ elif hasattr(module, 'weight') and hasattr(module.weight, 'data'):
74
+ init_weights(module)
75
+
76
+ self.apply(reset_parameters)
77
+
78
+ def compute_loss(self, batched_data):
79
+ masked_node_indices = list()
80
+
81
+ # select indices of masked nodes
82
+ for i in range(batched_data.batch[-1] + 1):
83
+ idx = torch.nonzero((batched_data.batch == i).float()).squeeze(-1)
84
+ num_node = idx.shape[0]
85
+ if self.mask_num == 0:
86
+ sample_size = int(num_node * self.mask_rate + 1)
87
+ else:
88
+ sample_size = min(self.mask_num, int(num_node * 0.5))
89
+ masked_node_idx = random.sample(idx.tolist(), sample_size)
90
+ masked_node_idx.sort()
91
+ masked_node_indices += masked_node_idx
92
+
93
+ batched_data.masked_node_indices = torch.tensor(masked_node_indices)
94
+
95
+ batched_data.y = batched_data.x[masked_node_indices][:, 0]
96
+
97
+ # mask nodes' features
98
+ for node_idx in masked_node_indices:
99
+ batched_data.x[node_idx] = torch.tensor([self.num_atom_type - 1] + [0] * (batched_data.x.shape[1] - 1))
100
+
101
+ # generate predictions
102
+ h_node, _ = self.graph_encoder(batched_data)
103
+ prediction_class = self.predictor(h_node[masked_node_indices])
104
+
105
+ target_class = batched_data.y.to(torch.float32)
106
+ loss_class = class_criterion(prediction_class.to(torch.float32), target_class.long())
107
+
108
+ return loss_class
109
+
110
+ def forward(self, batched_data):
111
+ h_node, _ = self.graph_encoder(batched_data)
112
+ h_rep = self.pool(h_node, batched_data.batch)
113
+ return {"graph": h_rep, "node": h_node}