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.
- torch_molecule/__init__.py +70 -0
- torch_molecule/base/__init__.py +8 -0
- torch_molecule/base/base.py +396 -0
- torch_molecule/base/encoder.py +44 -0
- torch_molecule/base/generator.py +24 -0
- torch_molecule/base/old_encoder.py +322 -0
- torch_molecule/base/old_predictor.py +904 -0
- torch_molecule/base/old_predictor2.py +436 -0
- torch_molecule/base/predictor.py +112 -0
- torch_molecule/encoder/attrmask/__init__.py +3 -0
- torch_molecule/encoder/attrmask/model.py +113 -0
- torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
- torch_molecule/encoder/constant.py +13 -0
- torch_molecule/encoder/contextpred/__init__.py +3 -0
- torch_molecule/encoder/contextpred/model.py +207 -0
- torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
- torch_molecule/encoder/contextpred/utils.py +206 -0
- torch_molecule/encoder/edgepred/__init__.py +3 -0
- torch_molecule/encoder/edgepred/model.py +104 -0
- torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
- torch_molecule/encoder/graphmae/__init__.py +3 -0
- torch_molecule/encoder/graphmae/dataloader.py +206 -0
- torch_molecule/encoder/graphmae/model.py +137 -0
- torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
- torch_molecule/encoder/infograph/__init__.py +3 -0
- torch_molecule/encoder/infograph/loss_function.py +128 -0
- torch_molecule/encoder/infograph/model.py +92 -0
- torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
- torch_molecule/encoder/infograph/module.py +31 -0
- torch_molecule/encoder/moama/__init__.py +3 -0
- torch_molecule/encoder/moama/model.py +131 -0
- torch_molecule/encoder/moama/modeling_moama.py +307 -0
- torch_molecule/encoder/moama/utils.py +143 -0
- torch_molecule/encoder/pretrained/__init__.py +3 -0
- torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
- torch_molecule/encoder/supervised/__init__.py +3 -0
- torch_molecule/encoder/supervised/model.py +97 -0
- torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
- torch_molecule/generator/digress/__init__.py +3 -0
- torch_molecule/generator/digress/diffusion.py +194 -0
- torch_molecule/generator/digress/modeling_digress.py +503 -0
- torch_molecule/generator/digress/transformer.py +360 -0
- torch_molecule/generator/digress/utils.py +196 -0
- torch_molecule/generator/gdss/__init__.py +3 -0
- torch_molecule/generator/gdss/layers.py +257 -0
- torch_molecule/generator/gdss/model.py +204 -0
- torch_molecule/generator/gdss/modeling_gdss.py +474 -0
- torch_molecule/generator/gdss/sde.py +334 -0
- torch_molecule/generator/gdss/solver.py +227 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
- torch_molecule/generator/gdss/utils.py +240 -0
- torch_molecule/generator/graph_dit/__init__.py +3 -0
- torch_molecule/generator/graph_dit/diffusion.py +188 -0
- torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
- torch_molecule/generator/graph_dit/transformer.py +207 -0
- torch_molecule/generator/graph_dit/utils.py +220 -0
- torch_molecule/generator/graph_ga/__init__.py +3 -0
- torch_molecule/generator/graph_ga/crossover.py +182 -0
- torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
- torch_molecule/generator/graph_ga/mutate.py +136 -0
- torch_molecule/generator/graph_ga/oracle.py +95 -0
- torch_molecule/generator/jtvae/__init__.py +3 -0
- torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
- torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
- torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
- torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
- torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
- torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
- torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
- torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
- torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
- torch_molecule/generator/lstm/__init__.py +3 -0
- torch_molecule/generator/lstm/action_sampler.py +91 -0
- torch_molecule/generator/lstm/lstm.py +59 -0
- torch_molecule/generator/lstm/modeling_lstm.py +263 -0
- torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
- torch_molecule/generator/lstm/utils.py +62 -0
- torch_molecule/generator/molgpt/__init__.py +3 -0
- torch_molecule/generator/molgpt/dataset.py +87 -0
- torch_molecule/generator/molgpt/gpt.py +251 -0
- torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
- torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
- torch_molecule/nn/__init__.py +16 -0
- torch_molecule/nn/attention.py +63 -0
- torch_molecule/nn/embedder.py +183 -0
- torch_molecule/nn/gnn.py +631 -0
- torch_molecule/nn/mlp.py +34 -0
- torch_molecule/predictor/bfgnn/__init__.py +3 -0
- torch_molecule/predictor/bfgnn/model.py +116 -0
- torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
- torch_molecule/predictor/dir/__init__.py +3 -0
- torch_molecule/predictor/dir/model.py +235 -0
- torch_molecule/predictor/dir/modeling_dir.py +161 -0
- torch_molecule/predictor/dir/utils.py +40 -0
- torch_molecule/predictor/gnn/__init__.py +3 -0
- torch_molecule/predictor/gnn/model.py +114 -0
- torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
- torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
- torch_molecule/predictor/grea/__init__.py +3 -0
- torch_molecule/predictor/grea/model.py +190 -0
- torch_molecule/predictor/grea/modeling_grea.py +107 -0
- torch_molecule/predictor/grea/old1_features.py +229 -0
- torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
- torch_molecule/predictor/grea/utils.py +97 -0
- torch_molecule/predictor/grin/__init__.py +3 -0
- torch_molecule/predictor/grin/model.py +116 -0
- torch_molecule/predictor/grin/modeling_grin.py +91 -0
- torch_molecule/predictor/irm/__init__.py +3 -0
- torch_molecule/predictor/irm/model.py +127 -0
- torch_molecule/predictor/irm/modeling_irm.py +179 -0
- torch_molecule/predictor/lstm/__init__.py +3 -0
- torch_molecule/predictor/lstm/model.py +145 -0
- torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
- torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
- torch_molecule/predictor/old_modeling_graph.py +650 -0
- torch_molecule/predictor/rpgnn/__init__.py +3 -0
- torch_molecule/predictor/rpgnn/model.py +185 -0
- torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
- torch_molecule/predictor/sgir/__init__.py +3 -0
- torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
- torch_molecule/predictor/sgir/strategy.py +266 -0
- torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
- torch_molecule/predictor/smiles_transformer/model.py +424 -0
- torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
- torch_molecule/predictor/ssr/__init__.py +3 -0
- torch_molecule/predictor/ssr/model.py +283 -0
- torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
- torch_molecule/predictor/ssr/utils.py +185 -0
- torch_molecule/utils/__init__.py +31 -0
- torch_molecule/utils/checker.py +158 -0
- torch_molecule/utils/checkpoint.py +294 -0
- torch_molecule/utils/format.py +140 -0
- torch_molecule/utils/generic/metrics.py +530 -0
- torch_molecule/utils/generic/pseudo_tasks.py +11 -0
- torch_molecule/utils/generic/weights.py +30 -0
- torch_molecule/utils/graph/features.py +178 -0
- torch_molecule/utils/graph/graph_from_smiles.py +134 -0
- torch_molecule/utils/graph/graph_to_smiles.py +312 -0
- torch_molecule/utils/hf.py +232 -0
- torch_molecule/utils/search.py +64 -0
- torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
- torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
- 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
|