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,474 @@
1
+ import numpy as np
2
+ from tqdm import tqdm
3
+ from typing import Optional, Union, Dict, Any, Tuple, List, Type
4
+ from dataclasses import dataclass, field
5
+
6
+ import torch
7
+ import torch.nn.functional as F
8
+ from torch_geometric.loader import DataLoader
9
+ from torch_geometric.data import Data
10
+
11
+ from .sde import load_sde
12
+ from .solver import get_pc_sampler
13
+ from .model import GDSSModel, get_sde_loss_fn
14
+ from .utils import compute_dataset_info, to_dense, quantize_mol
15
+
16
+ from ...base import BaseMolecularGenerator
17
+ from ...utils import graph_from_smiles, graph_to_smiles
18
+
19
+ @dataclass
20
+ class GDSSMolecularGenerator(BaseMolecularGenerator):
21
+ """ This generator implements "Score-based Generative Modeling of Graphs via the System of Stochastic Differential Equations"
22
+
23
+ References
24
+ ----------
25
+ - Paper: https://arxiv.org/abs/2202.02514
26
+ - Official Implementation: https://github.com/harryjo97/GDSS
27
+
28
+
29
+ Parameters
30
+ ----------
31
+ num_layer : int, default=3
32
+ Number of layers in the score networks.
33
+ hidden_size_adj : float, default=8
34
+ Hidden dimension size for the adjacency in the adjacency score network.
35
+ hidden_size : int, default=16
36
+ Hidden dimension size latent representation.
37
+ attention_dim : int, default=16
38
+ Dimension of attention layers.
39
+ num_head : int, default=4
40
+ Number of attention heads.
41
+ sde_type_x : str, default='VE'
42
+ SDE type for node features. One of 'VP', 'VE', 'subVP'.
43
+ sde_beta_min_x : float, default=0.1
44
+ Minimum noise level for node features.
45
+ sde_beta_max_x : float, default=1
46
+ Maximum noise level for node features.
47
+ sde_num_scales_x : int, default=1000
48
+ Number of noise scales for node features.
49
+ sde_type_adj : str, default='VE'
50
+ SDE type for adjacency matrix. One of 'VP', 'VE', 'subVP'.
51
+ sde_beta_min_adj : float, default=0.1
52
+ Minimum noise level for adjacency matrix.
53
+ sde_beta_max_adj : float, default=1
54
+ Maximum noise level for adjacency matrix.
55
+ sde_num_scales_adj : int, default=1000
56
+ Number of noise scales for adjacency matrix.
57
+ batch_size : int, default=128
58
+ Batch size for training.
59
+ epochs : int, default=500
60
+ Number of training epochs.
61
+ learning_rate : float, default=0.005
62
+ Learning rate for optimizer.
63
+ grad_clip_value : Optional[float], default=1
64
+ Value for gradient clipping. None means no clipping.
65
+ weight_decay : float, default=1e-4
66
+ Weight decay for optimizer.
67
+ use_loss_reduce_mean : bool, default=False
68
+ Whether to use mean reduction for loss calculation.
69
+ use_lr_scheduler : bool, default=False
70
+ Whether to use learning rate scheduler.
71
+ scheduler_factor : float, default=0.5
72
+ Factor by which to reduce learning rate when using scheduler (only used if use_lr_scheduler is True).
73
+ scheduler_patience : int, default=5
74
+ Number of epochs with no improvement after which learning rate will be reduced (only used if use_lr_scheduler is True).
75
+ sampler_predictor : str, default='Reverse'
76
+ Predictor method for sampling. One of 'Euler', 'Reverse'.
77
+ sampler_corrector : str, default='Langevin'
78
+ Corrector method for sampling. One of 'Langevin', 'None'.
79
+ sampler_snr : float, default=0.2
80
+ Signal-to-noise ratio for corrector.
81
+ sampler_scale_eps : float, default=0.7
82
+ Scale factor for noise level in corrector.
83
+ sampler_n_steps : int, default=1
84
+ Number of corrector steps per predictor step.
85
+ sampler_probability_flow : bool, default=False
86
+ Whether to use probability flow ODE for sampling.
87
+ sampler_noise_removal : bool, default=True
88
+ Whether to remove noise in the final step of sampling.
89
+ verbose : bool, default=False
90
+ Whether to display progress bars and logs.
91
+
92
+ """
93
+
94
+ # Model parameters
95
+ num_layer: int = 3
96
+ hidden_size_adj: float = 8
97
+ hidden_size: int = 16
98
+ attention_dim: int = 16
99
+ num_head: int = 4
100
+
101
+ # Diffusion parameters
102
+ sde_type_x: str = 'VE' # One of 'VP', 'VE', 'subVP'
103
+ sde_beta_min_x: float = 0.1
104
+ sde_beta_max_x: float = 1
105
+ sde_num_scales_x: int = 1000
106
+ sde_type_adj: str = 'VE' # One of 'VP', 'VE', 'subVP'
107
+ sde_beta_min_adj: float = 0.1
108
+ sde_beta_max_adj: float = 1
109
+ sde_num_scales_adj: int = 1000
110
+
111
+ # Training parameters
112
+ batch_size: int = 128
113
+ epochs: int = 500
114
+ learning_rate: float = 0.005
115
+ grad_clip_value: Optional[float] = 1.0
116
+ weight_decay: float = 1e-4
117
+ use_loss_reduce_mean: bool = False
118
+
119
+ # Scheduler parameters
120
+ use_lr_scheduler: bool = False
121
+ scheduler_factor: float = 0.5
122
+ scheduler_patience: int = 5
123
+
124
+ # Sampling parameters
125
+ sampler_predictor: str = 'Reverse' # One of 'Euler', 'Reverse', others will be treated as 'Euler'
126
+ sampler_corrector: str = 'Langevin' # One of 'Langevin', 'None', others will be treated as 'None'
127
+ sampler_snr: float = 0.2
128
+ sampler_scale_eps: float = 0.7
129
+ sampler_n_steps: int = 1
130
+ sampler_probability_flow: bool = False
131
+ sampler_noise_removal: bool = True
132
+
133
+ verbose: bool = False
134
+ # Attributes
135
+ model_name: str = "GDSSMolecularGenerator"
136
+ fitting_loss: List[float] = field(default_factory=list, init=False)
137
+ fitting_epoch: int = field(default=0, init=False)
138
+ model_class: Type[GDSSModel] = field(default=GDSSModel, init=False)
139
+ # dataset_info: Dict[str, Any] = field(default_factory=dict, init=False)
140
+ def __post_init__(self):
141
+ """Initialize the model after dataclass initialization."""
142
+ super().__post_init__()
143
+ self.max_node = None
144
+ self.input_dim_X = None
145
+ self.input_dim_adj = None
146
+ self.conv = 'GCN'
147
+ self.sampler_eps = 1e-4
148
+ self.train_eps = 1e-5
149
+ self.dataset_info = None
150
+ self.loss_fn = None
151
+ self.sampling_fn = None
152
+
153
+ @staticmethod
154
+ def _get_param_names() -> List[str]:
155
+ """Get parameter names for the estimator.
156
+
157
+ Returns
158
+ -------
159
+ List[str]
160
+ List of parameter names that can be used for model configuration.
161
+ """
162
+
163
+ return [
164
+ # Model Hyperparameters
165
+ "max_node", "num_layer", "num_head", "input_dim_X", "input_dim_adj", "hidden_size_adj", "hidden_size", "attention_dim",
166
+ # Diffusion parameters
167
+ "dataset_info", "sde_type_x", "sde_beta_min_x", "sde_beta_max_x", "sde_num_scales_x",
168
+ "sde_type_adj", "sde_beta_min_adj", "sde_beta_max_adj", "sde_num_scales_adj",
169
+ # Training Parameters
170
+ "batch_size", "epochs", "learning_rate", "grad_clip_value",
171
+ "weight_decay", "use_loss_reduce_mean", "train_eps", "conv",
172
+ # Scheduler Parameters
173
+ "use_lr_scheduler", "scheduler_factor", "scheduler_patience",
174
+ # Sampling Parameters
175
+ "sampler_predictor", "sampler_corrector", "sampler_snr", "sampler_scale_eps",
176
+ "sampler_n_steps", "sampler_probability_flow", "sampler_noise_removal", "sampler_eps",
177
+ # Other Parameters
178
+ "fitting_epoch", "fitting_loss", "device", "verbose", "model_name"
179
+ ]
180
+
181
+ def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
182
+ params = ["input_dim_X", "max_node", "hidden_size", "num_layer", "input_dim_adj", "hidden_size_adj", "attention_dim", "num_head"]
183
+
184
+ if checkpoint is not None:
185
+ if "hyperparameters" not in checkpoint:
186
+ raise ValueError("Checkpoint missing 'hyperparameters' key")
187
+ return {k: checkpoint["hyperparameters"][k] for k in params}
188
+
189
+ return {k: getattr(self, k) for k in params}
190
+
191
+ def _convert_to_pytorch_data(self, X, y=None):
192
+ """Convert numpy arrays to PyTorch Geometric data format.
193
+ """
194
+ if self.verbose:
195
+ iterator = tqdm(enumerate(X), desc="Converting molecules to graphs", total=len(X))
196
+ else:
197
+ iterator = enumerate(X)
198
+
199
+ pyg_graph_list = []
200
+ for idx, smiles_or_mol in iterator:
201
+ if y is not None:
202
+ properties = y[idx]
203
+ else:
204
+ properties = None
205
+ graph = graph_from_smiles(smiles_or_mol, properties)
206
+ g = Data()
207
+
208
+ # No H, first heavy atom has type 0
209
+ node_type = torch.from_numpy(graph['node_feat'][:, 0] - 1)
210
+
211
+ # Filter out invalid node types (< 0)
212
+ valid_mask = node_type >= 0
213
+ if not valid_mask.all():
214
+ # Get valid nodes and adjust edge indices
215
+ valid_indices = torch.where(valid_mask)[0]
216
+ index_map = -torch.ones(node_type.size(0), dtype=torch.long)
217
+ index_map[valid_indices] = torch.arange(valid_indices.size(0))
218
+
219
+ # Filter edges that connect to invalid nodes
220
+ edge_index = torch.from_numpy(graph["edge_index"])
221
+ valid_edges_mask = valid_mask[edge_index[0]] & valid_mask[edge_index[1]]
222
+ valid_edge_index = edge_index[:, valid_edges_mask]
223
+
224
+ # Remap edge indices to account for removed nodes
225
+ remapped_edge_index = index_map[valid_edge_index]
226
+
227
+ # Filter edge attributes
228
+ edge_attr = torch.from_numpy(graph["edge_feat"])[:, 0] + 1
229
+ valid_edge_attr = edge_attr[valid_edges_mask]
230
+
231
+ # Update node and edge data
232
+ node_type = node_type[valid_mask]
233
+ g.edge_index = remapped_edge_index
234
+ g.edge_attr = valid_edge_attr.long().squeeze(-1)
235
+ else:
236
+ # No invalid nodes, proceed normally
237
+ g.edge_index = torch.from_numpy(graph["edge_index"])
238
+ edge_attr = torch.from_numpy(graph["edge_feat"])[:, 0] + 1
239
+ g.edge_attr = edge_attr.long().squeeze(-1)
240
+
241
+ # * is encoded as "misc" which is 119 - 1 and should be 117
242
+ node_type[node_type == 118] = 117
243
+ g.x = node_type.long().squeeze(-1)
244
+ del graph["node_feat"]
245
+ del graph["edge_index"]
246
+ del graph["edge_feat"]
247
+
248
+ g.y = torch.from_numpy(graph["y"])
249
+ del graph["y"]
250
+
251
+ pyg_graph_list.append(g)
252
+
253
+ return pyg_graph_list
254
+
255
+ def _setup_diffusion_params(self, X: Union[List, Dict]) -> None:
256
+ # Extract dataset info from X if it's a dict (from checkpoint), otherwise compute it
257
+ if isinstance(X, dict):
258
+ dataset_info = X["hyperparameters"]["dataset_info"]
259
+ max_node = X["hyperparameters"]["max_node"]
260
+ else:
261
+ assert isinstance(X, list)
262
+ dataset_info = compute_dataset_info(X)
263
+ max_node = dataset_info["max_node"]
264
+
265
+ self.input_dim_X = dataset_info["x_margins"].shape[0]
266
+ self.input_dim_adj = dataset_info["e_margins"].shape[0]
267
+ self.dataset_info = dataset_info
268
+ self.max_node = max_node
269
+
270
+ x_sde = load_sde(self.sde_type_x, self.sde_beta_min_x, self.sde_beta_max_x, self.sde_num_scales_x)
271
+ adj_sde = load_sde(self.sde_type_adj, self.sde_beta_min_adj, self.sde_beta_max_adj, self.sde_num_scales_adj)
272
+
273
+ self.loss_fn = get_sde_loss_fn(
274
+ x_sde,
275
+ adj_sde,
276
+ train=True,
277
+ reduce_mean=self.use_loss_reduce_mean,
278
+ continuous=True,
279
+ likelihood_weighting=False,
280
+ eps=self.train_eps,
281
+ )
282
+
283
+ self.sampling_fn = get_pc_sampler(
284
+ sde_x=x_sde,
285
+ sde_adj=adj_sde,
286
+ predictor=self.sampler_predictor,
287
+ corrector=self.sampler_corrector,
288
+ snr=self.sampler_snr,
289
+ scale_eps=self.sampler_scale_eps,
290
+ n_steps=self.sampler_n_steps,
291
+ probability_flow=self.sampler_probability_flow,
292
+ continuous=True,
293
+ denoise=self.sampler_noise_removal,
294
+ eps=self.sampler_eps,
295
+ device=self.device,
296
+ )
297
+
298
+
299
+ def _initialize_model(
300
+ self,
301
+ model_class: Type[torch.nn.Module],
302
+ checkpoint: Optional[Dict] = None
303
+ ) -> torch.nn.Module:
304
+ """Initialize the model with parameters or a checkpoint."""
305
+ model_params = self._get_model_params(checkpoint)
306
+ self.model = model_class(**model_params)
307
+ self.model = self.model.to(self.device)
308
+
309
+ if checkpoint is not None:
310
+ self._setup_diffusion_params(checkpoint)
311
+ self.model.load_state_dict(checkpoint["model_state_dict"])
312
+ return self.model
313
+
314
+ def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
315
+ """Setup optimization components including optimizer and learning rate scheduler.
316
+ """
317
+ optimizer = torch.optim.Adam(
318
+ self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay
319
+ )
320
+ scheduler = None
321
+ if self.use_lr_scheduler:
322
+ scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
323
+ optimizer,
324
+ mode="min",
325
+ factor=self.scheduler_factor,
326
+ patience=self.scheduler_patience,
327
+ min_lr=1e-6,
328
+ cooldown=0,
329
+ eps=1e-8,
330
+ )
331
+
332
+ return optimizer, scheduler
333
+
334
+ def fit(
335
+ self,
336
+ X_train: List[str],
337
+ ) -> "GDSSMolecularGenerator":
338
+ """Fit the model to the training data.
339
+
340
+ Parameters
341
+ ----------
342
+ X_train : List[str]
343
+ List of training data in SMILES format.
344
+
345
+ Returns
346
+ -------
347
+ self : GDSSMolecularGenerator
348
+ The fitted model.
349
+ """
350
+ X_train, _ = self._validate_inputs(X_train)
351
+ self._setup_diffusion_params(X_train)
352
+ self._initialize_model(self.model_class)
353
+ self.model.initialize_parameters()
354
+
355
+ optimizer, scheduler = self._setup_optimizers()
356
+ train_dataset = self._convert_to_pytorch_data(X_train)
357
+ train_loader = DataLoader(
358
+ train_dataset,
359
+ batch_size=self.batch_size,
360
+ shuffle=True,
361
+ num_workers=0
362
+ )
363
+
364
+ self.fitting_loss = []
365
+ self.fitting_epoch = 0
366
+ for epoch in range(self.epochs):
367
+ train_losses = self._train_epoch(train_loader, optimizer, epoch)
368
+ self.fitting_loss.append(np.mean(train_losses).item())
369
+ if scheduler:
370
+ scheduler.step(np.mean(train_losses).item())
371
+
372
+ self.fitting_epoch = epoch
373
+ self.is_fitted_ = True
374
+ return self
375
+
376
+ def _train_epoch(self, train_loader, optimizer, epoch):
377
+ self.model.train()
378
+ losses = []
379
+ iterator = (
380
+ tqdm(train_loader, desc="Training", leave=False)
381
+ if self.verbose
382
+ else train_loader
383
+ )
384
+ active_index = self.dataset_info["active_index"]
385
+ for step, batched_data in enumerate(iterator):
386
+ batched_data = batched_data.to(self.device)
387
+ optimizer.zero_grad()
388
+
389
+ data_x = F.one_hot(batched_data.x, num_classes=118).float()[:, active_index]
390
+ data_edge_attr = batched_data.edge_attr.float()
391
+ X, E, node_mask = to_dense(data_x, batched_data.edge_index, data_edge_attr, batched_data.batch, self.max_node)
392
+
393
+ loss_x, loss_adj = self.model.compute_loss(x=X, adj=E, flags=node_mask, loss_fn=self.loss_fn)
394
+ loss = loss_x + loss_adj
395
+ loss.backward()
396
+ if self.grad_clip_value is not None:
397
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
398
+
399
+ optimizer.step()
400
+ losses.append(loss.item())
401
+
402
+ if self.verbose:
403
+ iterator.set_postfix({"Epoch": epoch, "Loss": f"{loss.item():.4f}", "Loss_x": f"{loss_x.item():.4f}", "Loss_adj": f"{loss_adj.item():.4f}"})
404
+
405
+ return losses
406
+
407
+ @torch.no_grad()
408
+ def generate(self, num_nodes: Optional[Union[List[List], np.ndarray, torch.Tensor]] = None, batch_size: int = 32) -> List[str]:
409
+ """Randomly generate molecules with specified node counts.
410
+
411
+ Parameters
412
+ ----------
413
+ num_nodes : Optional[Union[List[List], np.ndarray, torch.Tensor]], default=None
414
+ Number of nodes for each molecule in the batch. If None, samples from
415
+ the training distribution. Can be provided as:
416
+ - A list of lists
417
+ - A numpy array of shape (batch_size, 1)
418
+ - A torch tensor of shape (batch_size, 1)
419
+
420
+ batch_size : int, default=32
421
+ Number of molecules to generate.
422
+
423
+ Returns
424
+ -------
425
+ List[str]
426
+ List of generated molecules in SMILES format.
427
+ """
428
+ if not self.is_fitted_:
429
+ raise ValueError("Model must be fitted before generating molecules.")
430
+
431
+ if num_nodes is not None:
432
+ batch_size = len(num_nodes)
433
+
434
+ if num_nodes is None:
435
+ num_nodes_dist = self.dataset_info["num_nodes_dist"]
436
+ num_nodes = num_nodes_dist.sample_n(batch_size, self.device)
437
+ elif isinstance(num_nodes, list):
438
+ num_nodes = torch.tensor(num_nodes).to(self.device)
439
+ elif isinstance(num_nodes, np.ndarray):
440
+ num_nodes = torch.from_numpy(num_nodes).to(self.device)
441
+ if num_nodes.dim() == 1:
442
+ num_nodes = num_nodes.unsqueeze(-1)
443
+
444
+ assert num_nodes.size(0) == batch_size
445
+ arange = (
446
+ torch.arange(self.max_node).to(self.device)
447
+ .unsqueeze(0)
448
+ .expand(batch_size, -1)
449
+ )
450
+ node_mask = arange < num_nodes
451
+
452
+ if not hasattr(self, 'dataset_info') or self.dataset_info is None:
453
+ raise ValueError("Dataset info not found. Please call setup_diffusion_params first.")
454
+
455
+ shape_x = (
456
+ batch_size,
457
+ self.max_node,
458
+ self.input_dim_X,
459
+ )
460
+ shape_adj = (batch_size, self.max_node, self.max_node)
461
+
462
+ X, E, _ = self.sampling_fn(self.model.score_network_x, self.model.score_network_a, shape_x, shape_adj, node_mask)
463
+ E = quantize_mol(E)
464
+ X = X.argmax(dim=-1)
465
+
466
+ molecule_list = []
467
+ for i in range(batch_size):
468
+ n = num_nodes[i][0].item()
469
+ atom_types = X[i, :n].cpu()
470
+ edge_types = E[i, :n, :n].cpu()
471
+ molecule_list.append([atom_types, edge_types])
472
+
473
+ smiles_list = graph_to_smiles(molecule_list, self.dataset_info["atom_decoder"])
474
+ return smiles_list