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,307 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from tqdm import tqdm
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Tuple, List, Callable, Literal, Type
|
|
5
|
+
import warnings
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
from torch_geometric.loader import DataLoader
|
|
10
|
+
from torch_geometric.data import Data
|
|
11
|
+
|
|
12
|
+
from .model import GNN
|
|
13
|
+
from ..constant import GNN_ENCODER_MODELS, GNN_ENCODER_READOUTS, GNN_ENCODER_PARAMS
|
|
14
|
+
from ...base import BaseMolecularEncoder
|
|
15
|
+
from ...utils import graph_from_smiles
|
|
16
|
+
|
|
17
|
+
ALLOWABLE_ENCODER_MODELS = GNN_ENCODER_MODELS
|
|
18
|
+
ALLOWABLE_ENCODER_READOUTS = GNN_ENCODER_READOUTS
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class MoamaMolecularEncoder(BaseMolecularEncoder):
|
|
22
|
+
"""This encoder implements Motif-aware Attribute Masking for Molecular Graph Pre-training.
|
|
23
|
+
|
|
24
|
+
References
|
|
25
|
+
----------
|
|
26
|
+
- Paper: https://openreview.net/forum?id=uqPnesiGGi
|
|
27
|
+
- Code: https://github.com/einae-nd/MoAMa-dev
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
mask_rate : float, default=0.15
|
|
32
|
+
Fraction of nodes to mask in each graph
|
|
33
|
+
lw_rec : float, default=0.5
|
|
34
|
+
Weight balancing between reconstruction loss and fingerprint loss.
|
|
35
|
+
Higher values emphasize reconstruction, lower values emphasize fingerprint matching.
|
|
36
|
+
num_layer : int, default=5
|
|
37
|
+
Number of GNN layers.
|
|
38
|
+
hidden_size : int, default=300
|
|
39
|
+
Dimension of hidden node features.
|
|
40
|
+
drop_ratio : float, default=0.5
|
|
41
|
+
Dropout probability.
|
|
42
|
+
norm_layer : str, default="batch_norm"
|
|
43
|
+
Type of normalization layer to use. One of ["batch_norm", "layer_norm", "instance_norm", "graph_norm", "size_norm", "pair_norm"].
|
|
44
|
+
encoder_type : str, default="gin-virtual"
|
|
45
|
+
Type of GNN architecture to use. One of ["gin-virtual", "gcn-virtual", "gin", "gcn"].
|
|
46
|
+
readout : str, default="sum"
|
|
47
|
+
Method for aggregating node features to obtain graph-level representations. One of ["sum", "mean", "max"].
|
|
48
|
+
batch_size : int, default=128
|
|
49
|
+
Number of samples per batch for training.
|
|
50
|
+
epochs : int, default=500
|
|
51
|
+
Maximum number of training epochs.
|
|
52
|
+
learning_rate : float, default=0.001
|
|
53
|
+
Learning rate for optimizer.
|
|
54
|
+
grad_clip_value : float, optional
|
|
55
|
+
Maximum norm of gradients for gradient clipping.
|
|
56
|
+
weight_decay : float, default=0.0
|
|
57
|
+
L2 regularization strength.
|
|
58
|
+
use_lr_scheduler : bool, default=False
|
|
59
|
+
Whether to use a learning rate scheduler.
|
|
60
|
+
scheduler_factor : float, default=0.5
|
|
61
|
+
Factor by which to reduce the learning rate when plateau is detected.
|
|
62
|
+
scheduler_patience : int, default=5
|
|
63
|
+
Number of epochs with no improvement after which learning rate will be reduced.
|
|
64
|
+
verbose : bool, default=False
|
|
65
|
+
Whether to print progress information during training.
|
|
66
|
+
model_name : str, default="MoamaMolecularEncoder"
|
|
67
|
+
Name of the encoder model.
|
|
68
|
+
"""
|
|
69
|
+
# Pretraining task parameters
|
|
70
|
+
mask_rate: float = 0.15
|
|
71
|
+
lw_rec: float = 0.5
|
|
72
|
+
|
|
73
|
+
# Model parameters
|
|
74
|
+
num_layer: int = 5
|
|
75
|
+
hidden_size: int = 300
|
|
76
|
+
drop_ratio: float = 0.5
|
|
77
|
+
norm_layer: str = "batch_norm"
|
|
78
|
+
|
|
79
|
+
encoder_type: str = "gin-virtual"
|
|
80
|
+
readout: str = "sum"
|
|
81
|
+
|
|
82
|
+
# Training parameters
|
|
83
|
+
batch_size: int = 128
|
|
84
|
+
epochs: int = 500
|
|
85
|
+
learning_rate: float = 0.001
|
|
86
|
+
grad_clip_value: Optional[float] = None
|
|
87
|
+
weight_decay: float = 0.0
|
|
88
|
+
|
|
89
|
+
# Scheduler parameters
|
|
90
|
+
use_lr_scheduler: bool = False
|
|
91
|
+
scheduler_factor: float = 0.5
|
|
92
|
+
scheduler_patience: int = 5
|
|
93
|
+
|
|
94
|
+
# Other parameters
|
|
95
|
+
verbose: bool = False
|
|
96
|
+
model_name: str = "MoamaMolecularEncoder"
|
|
97
|
+
|
|
98
|
+
# Non-init fields
|
|
99
|
+
fitting_loss: List[float] = field(default_factory=list, init=False)
|
|
100
|
+
fitting_epoch: int = field(default=0, init=False)
|
|
101
|
+
model_class: Type[GNN] = field(default=GNN, init=False)
|
|
102
|
+
|
|
103
|
+
def __post_init__(self):
|
|
104
|
+
"""Initialize the model after dataclass initialization."""
|
|
105
|
+
super().__post_init__()
|
|
106
|
+
if self.encoder_type not in ALLOWABLE_ENCODER_MODELS:
|
|
107
|
+
raise ValueError(f"Invalid encoder_model: {self.encoder_type}. Currently only {ALLOWABLE_ENCODER_MODELS} are supported.")
|
|
108
|
+
if self.readout not in ALLOWABLE_ENCODER_READOUTS:
|
|
109
|
+
raise ValueError(f"Invalid encoder_readout: {self.readout}. Currently only {ALLOWABLE_ENCODER_READOUTS} are supported.")
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _get_param_names() -> List[str]:
|
|
113
|
+
"""Get parameter names for the estimator.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
List[str]
|
|
118
|
+
List of parameter names that can be used for model configuration.
|
|
119
|
+
"""
|
|
120
|
+
return ["mask_rate", "lw_rec"] + GNN_ENCODER_PARAMS.copy()
|
|
121
|
+
|
|
122
|
+
def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
|
|
123
|
+
params = {
|
|
124
|
+
"num_layer": self.num_layer,
|
|
125
|
+
"hidden_size": self.hidden_size,
|
|
126
|
+
"encoder_type": self.encoder_type,
|
|
127
|
+
"drop_ratio": self.drop_ratio,
|
|
128
|
+
"norm_layer": self.norm_layer,
|
|
129
|
+
"readout": self.readout,
|
|
130
|
+
"mask_rate": self.mask_rate,
|
|
131
|
+
"lw_rec": self.lw_rec
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if checkpoint is not None:
|
|
135
|
+
if "hyperparameters" not in checkpoint:
|
|
136
|
+
raise ValueError("Checkpoint missing 'hyperparameters' key")
|
|
137
|
+
params = {k: checkpoint["hyperparameters"].get(k, v) for k, v in params.items()}
|
|
138
|
+
|
|
139
|
+
return params
|
|
140
|
+
|
|
141
|
+
def _convert_to_pytorch_data(self, X):
|
|
142
|
+
"""Convert numpy arrays to PyTorch Geometric data format.
|
|
143
|
+
"""
|
|
144
|
+
if self.verbose:
|
|
145
|
+
iterator = tqdm(enumerate(X), desc="Converting molecules to graphs", total=len(X))
|
|
146
|
+
else:
|
|
147
|
+
iterator = enumerate(X)
|
|
148
|
+
|
|
149
|
+
pyg_graph_list = []
|
|
150
|
+
for idx, smiles_or_mol in iterator:
|
|
151
|
+
graph = graph_from_smiles(smiles_or_mol, None)
|
|
152
|
+
g = Data()
|
|
153
|
+
g.num_nodes = graph["num_nodes"]
|
|
154
|
+
g.edge_index = torch.from_numpy(graph["edge_index"])
|
|
155
|
+
|
|
156
|
+
del graph["num_nodes"]
|
|
157
|
+
del graph["edge_index"]
|
|
158
|
+
|
|
159
|
+
if graph["edge_feat"] is not None:
|
|
160
|
+
g.edge_attr = torch.from_numpy(graph["edge_feat"])
|
|
161
|
+
del graph["edge_feat"]
|
|
162
|
+
|
|
163
|
+
if graph["node_feat"] is not None:
|
|
164
|
+
g.x = torch.from_numpy(graph["node_feat"])
|
|
165
|
+
del graph["node_feat"]
|
|
166
|
+
|
|
167
|
+
pyg_graph_list.append(g)
|
|
168
|
+
|
|
169
|
+
return pyg_graph_list
|
|
170
|
+
|
|
171
|
+
def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
|
|
172
|
+
"""Setup optimization components including optimizer and learning rate scheduler.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate, weight_decay=self.weight_decay)
|
|
176
|
+
scheduler = None
|
|
177
|
+
if self.use_lr_scheduler:
|
|
178
|
+
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
|
|
179
|
+
optimizer,
|
|
180
|
+
mode="min",
|
|
181
|
+
factor=self.scheduler_factor,
|
|
182
|
+
patience=self.scheduler_patience,
|
|
183
|
+
min_lr=1e-6,
|
|
184
|
+
cooldown=0,
|
|
185
|
+
eps=1e-8,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return optimizer, scheduler
|
|
189
|
+
|
|
190
|
+
def fit(
|
|
191
|
+
self,
|
|
192
|
+
X_train: List[str],
|
|
193
|
+
) -> "MoamaMolecularEncoder":
|
|
194
|
+
"""Fit the model to the training data with optional validation set.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
X_train : List[str]
|
|
199
|
+
Training set input molecular structures as SMILES strings
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
self : MoamaMolecularEncoder
|
|
203
|
+
Fitted estimator
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
self._initialize_model(self.model_class)
|
|
207
|
+
self.model.initialize_parameters()
|
|
208
|
+
optimizer, scheduler = self._setup_optimizers()
|
|
209
|
+
|
|
210
|
+
# Prepare datasets and loaders
|
|
211
|
+
smiles = X_train
|
|
212
|
+
X_train, _ = self._validate_inputs(X_train, return_rdkit_mol=True)
|
|
213
|
+
train_dataset = self._convert_to_pytorch_data(X_train)
|
|
214
|
+
for i in range(len(X_train)):
|
|
215
|
+
train_dataset[i].smiles = smiles[i]
|
|
216
|
+
train_loader = DataLoader(
|
|
217
|
+
train_dataset,
|
|
218
|
+
batch_size=self.batch_size,
|
|
219
|
+
shuffle=True,
|
|
220
|
+
num_workers=0
|
|
221
|
+
)
|
|
222
|
+
self.fitting_loss = []
|
|
223
|
+
|
|
224
|
+
for epoch in range(self.epochs):
|
|
225
|
+
# Training phase
|
|
226
|
+
train_losses = self._train_epoch(train_loader, optimizer, epoch)
|
|
227
|
+
self.fitting_loss.append(np.mean(train_losses))
|
|
228
|
+
if scheduler:
|
|
229
|
+
scheduler.step(np.mean(train_losses))
|
|
230
|
+
|
|
231
|
+
self.fitting_epoch = epoch
|
|
232
|
+
self.is_fitted_ = True
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def _train_epoch(self, train_loader, optimizer, epoch):
|
|
236
|
+
"""Training logic for one epoch.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
train_loader: DataLoader containing training data
|
|
240
|
+
optimizer: Optimizer instance for model parameter updates
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
list: List of loss values for each training step
|
|
244
|
+
"""
|
|
245
|
+
self.model.train()
|
|
246
|
+
losses = []
|
|
247
|
+
|
|
248
|
+
iterator = (
|
|
249
|
+
tqdm(train_loader, desc="Training", leave=False)
|
|
250
|
+
if self.verbose
|
|
251
|
+
else train_loader
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
for batch in iterator:
|
|
255
|
+
batch = batch.to(self.device)
|
|
256
|
+
optimizer.zero_grad()
|
|
257
|
+
loss = self.model.compute_loss(batch)
|
|
258
|
+
loss.backward()
|
|
259
|
+
if self.grad_clip_value is not None:
|
|
260
|
+
torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_value)
|
|
261
|
+
optimizer.step()
|
|
262
|
+
losses.append(loss.item())
|
|
263
|
+
|
|
264
|
+
# Update progress bar if using tqdm
|
|
265
|
+
if self.verbose:
|
|
266
|
+
iterator.set_postfix({"Epoch": f"{epoch}", "Loss": f"{loss.item():.4f}"})
|
|
267
|
+
|
|
268
|
+
return losses
|
|
269
|
+
|
|
270
|
+
def encode(self, X: List[str], return_type: Literal["np", "pt"] = "pt") -> Union[np.ndarray, torch.Tensor]:
|
|
271
|
+
"""Encode molecules into vector representations.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
X : List[str]
|
|
276
|
+
List of SMILES strings
|
|
277
|
+
return_type : Literal["np", "pt"], default="pt"
|
|
278
|
+
Return type of the representations
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
representations : ndarray or torch.Tensor
|
|
283
|
+
Molecular representations
|
|
284
|
+
"""
|
|
285
|
+
self._check_is_fitted()
|
|
286
|
+
|
|
287
|
+
# Convert to PyTorch Geometric format and create loader
|
|
288
|
+
X, _ = self._validate_inputs(X, return_rdkit_mol=True)
|
|
289
|
+
dataset = self._convert_to_pytorch_data(X)
|
|
290
|
+
loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=False)
|
|
291
|
+
|
|
292
|
+
if self.model is None:
|
|
293
|
+
raise RuntimeError("Model not initialized")
|
|
294
|
+
|
|
295
|
+
# Generate encodings
|
|
296
|
+
self.model = self.model.to(self.device)
|
|
297
|
+
self.model.eval()
|
|
298
|
+
encodings = []
|
|
299
|
+
with torch.no_grad():
|
|
300
|
+
for batch in tqdm(loader, disable=not self.verbose):
|
|
301
|
+
batch = batch.to(self.device)
|
|
302
|
+
out = self.model(batch)
|
|
303
|
+
encodings.append(out["graph"].cpu())
|
|
304
|
+
|
|
305
|
+
# Concatenate and convert to requested format
|
|
306
|
+
encodings = torch.cat(encodings, dim=0)
|
|
307
|
+
return encodings if return_type == "pt" else encodings.numpy()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import random
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from rdkit import Chem, DataStructs
|
|
5
|
+
from rdkit.Chem import BRICS
|
|
6
|
+
|
|
7
|
+
def get_fingerprint_loss(smiles, h_rep):
|
|
8
|
+
cos = nn.CosineSimilarity(dim=0, eps=1e-6)
|
|
9
|
+
fingerprint_list = [Chem.RDKFingerprint(Chem.MolFromSmiles(x)) for x in smiles]
|
|
10
|
+
fingerprint_loss = 0
|
|
11
|
+
|
|
12
|
+
for i in range(len(h_rep)):
|
|
13
|
+
for j in range(len(h_rep)-i-1):
|
|
14
|
+
finger_sim = DataStructs.FingerprintSimilarity(fingerprint_list[i], fingerprint_list[i+j+1]) #* 10
|
|
15
|
+
emb_sim = (cos(h_rep[i], h_rep[i+j+1]) + 1)/2
|
|
16
|
+
fingerprint_loss += (finger_sim - emb_sim)**2
|
|
17
|
+
|
|
18
|
+
return fingerprint_loss
|
|
19
|
+
|
|
20
|
+
def get_mask_indices(batched_data, mask_rate=0.15):
|
|
21
|
+
masked_node_indices_full = list()
|
|
22
|
+
offset = 0
|
|
23
|
+
|
|
24
|
+
for i in range(len(batched_data)):
|
|
25
|
+
masked_node_indices = list()
|
|
26
|
+
data = batched_data[i]
|
|
27
|
+
mol = Chem.MolFromSmiles(data.smiles)
|
|
28
|
+
motifs_list, edges = get_motifs_edges(mol)
|
|
29
|
+
random.shuffle(motifs_list)
|
|
30
|
+
num_atoms = mol.GetNumAtoms()
|
|
31
|
+
sample_size = int(num_atoms * mask_rate + 1)
|
|
32
|
+
|
|
33
|
+
for motif in motifs_list:
|
|
34
|
+
edge_index_mask = sum(data.edge_index[0]==x for x in motif).bool()
|
|
35
|
+
neigh = torch.masked_select(data.edge_index[1], edge_index_mask)
|
|
36
|
+
|
|
37
|
+
if any(x in neigh for x in masked_node_indices):
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if len(masked_node_indices) + len(motif) > sample_size:
|
|
41
|
+
break
|
|
42
|
+
if len(motif) > 9:
|
|
43
|
+
del motif[5::6]
|
|
44
|
+
for atom in motif:
|
|
45
|
+
masked_node_indices.append(atom + offset)
|
|
46
|
+
|
|
47
|
+
atom_candidates = [x for x in range(0, num_atoms) if x not in masked_node_indices]
|
|
48
|
+
random.shuffle(atom_candidates)
|
|
49
|
+
if len(masked_node_indices) < sample_size:
|
|
50
|
+
for atom in atom_candidates[:sample_size-len(masked_node_indices)]:
|
|
51
|
+
masked_node_indices.append(atom + offset)
|
|
52
|
+
|
|
53
|
+
offset += num_atoms
|
|
54
|
+
|
|
55
|
+
masked_node_indices_full += masked_node_indices
|
|
56
|
+
|
|
57
|
+
return masked_node_indices_full
|
|
58
|
+
|
|
59
|
+
def get_motifs_edges(data):
|
|
60
|
+
Chem.SanitizeMol(data)
|
|
61
|
+
motifs, edges = brics_decomp(data)
|
|
62
|
+
return motifs, edges
|
|
63
|
+
|
|
64
|
+
def brics_decomp(mol):
|
|
65
|
+
n_atoms = mol.GetNumAtoms()
|
|
66
|
+
if n_atoms == 1:
|
|
67
|
+
return [[0]], []
|
|
68
|
+
|
|
69
|
+
cliques = []
|
|
70
|
+
breaks = []
|
|
71
|
+
for bond in mol.GetBonds():
|
|
72
|
+
a1 = bond.GetBeginAtom().GetIdx()
|
|
73
|
+
a2 = bond.GetEndAtom().GetIdx()
|
|
74
|
+
cliques.append([a1, a2])
|
|
75
|
+
|
|
76
|
+
res = list(BRICS.FindBRICSBonds(mol))
|
|
77
|
+
if len(res) == 0:
|
|
78
|
+
return [list(range(n_atoms))], []
|
|
79
|
+
else:
|
|
80
|
+
for bond in res:
|
|
81
|
+
if [bond[0][0], bond[0][1]] in cliques:
|
|
82
|
+
cliques.remove([bond[0][0], bond[0][1]])
|
|
83
|
+
else:
|
|
84
|
+
cliques.remove([bond[0][1], bond[0][0]])
|
|
85
|
+
cliques.append([bond[0][0]])
|
|
86
|
+
cliques.append([bond[0][1]])
|
|
87
|
+
|
|
88
|
+
# break bonds between rings and non-ring atoms
|
|
89
|
+
for c in cliques:
|
|
90
|
+
if len(c) > 1:
|
|
91
|
+
if mol.GetAtomWithIdx(c[0]).IsInRing() and not mol.GetAtomWithIdx(c[1]).IsInRing():
|
|
92
|
+
cliques.remove(c)
|
|
93
|
+
cliques.append([c[1]])
|
|
94
|
+
breaks.append(c)
|
|
95
|
+
if mol.GetAtomWithIdx(c[1]).IsInRing() and not mol.GetAtomWithIdx(c[0]).IsInRing():
|
|
96
|
+
cliques.remove(c)
|
|
97
|
+
cliques.append([c[0]])
|
|
98
|
+
breaks.append(c)
|
|
99
|
+
|
|
100
|
+
# select atoms at intersections as motif
|
|
101
|
+
for atom in mol.GetAtoms():
|
|
102
|
+
if len(atom.GetNeighbors()) > 2 and not atom.IsInRing():
|
|
103
|
+
cliques.append([atom.GetIdx()])
|
|
104
|
+
for nei in atom.GetNeighbors():
|
|
105
|
+
if [nei.GetIdx(), atom.GetIdx()] in cliques:
|
|
106
|
+
cliques.remove([nei.GetIdx(), atom.GetIdx()])
|
|
107
|
+
breaks.append([nei.GetIdx(), atom.GetIdx()])
|
|
108
|
+
elif [atom.GetIdx(), nei.GetIdx()] in cliques:
|
|
109
|
+
cliques.remove([atom.GetIdx(), nei.GetIdx()])
|
|
110
|
+
breaks.append([atom.GetIdx(), nei.GetIdx()])
|
|
111
|
+
cliques.append([nei.GetIdx()])
|
|
112
|
+
|
|
113
|
+
# merge cliques
|
|
114
|
+
for c in range(len(cliques) - 1):
|
|
115
|
+
if c >= len(cliques):
|
|
116
|
+
break
|
|
117
|
+
for k in range(c + 1, len(cliques)):
|
|
118
|
+
if k >= len(cliques):
|
|
119
|
+
break
|
|
120
|
+
if len(set(cliques[c]) & set(cliques[k])) > 0:
|
|
121
|
+
cliques[c] = list(set(cliques[c]) | set(cliques[k]))
|
|
122
|
+
cliques[k] = []
|
|
123
|
+
cliques = [c for c in cliques if len(c) > 0]
|
|
124
|
+
cliques = [c for c in cliques if len(c) > 0]
|
|
125
|
+
|
|
126
|
+
# edges
|
|
127
|
+
edges = []
|
|
128
|
+
for bond in res:
|
|
129
|
+
for c in range(len(cliques)):
|
|
130
|
+
if bond[0][0] in cliques[c]:
|
|
131
|
+
c1 = c
|
|
132
|
+
if bond[0][1] in cliques[c]:
|
|
133
|
+
c2 = c
|
|
134
|
+
edges.append((c1, c2))
|
|
135
|
+
for bond in breaks:
|
|
136
|
+
for c in range(len(cliques)):
|
|
137
|
+
if bond[0] in cliques[c]:
|
|
138
|
+
c1 = c
|
|
139
|
+
if bond[1] in cliques[c]:
|
|
140
|
+
c2 = c
|
|
141
|
+
edges.append((c1, c2))
|
|
142
|
+
|
|
143
|
+
return cliques, edges
|