torch-molecule 0.1.0__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.dist-info/METADATA +240 -0
- torch_molecule-0.1.0.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dist-info/licenses/LICENSE +21 -0
- torch_molecule-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
predictor module
|
|
5
|
+
"""
|
|
6
|
+
from .predictor.grea import GREAMolecularPredictor
|
|
7
|
+
from .predictor.gnn import GNNMolecularPredictor
|
|
8
|
+
from .predictor.sgir import SGIRMolecularPredictor
|
|
9
|
+
from .predictor.irm import IRMMolecularPredictor
|
|
10
|
+
from .predictor.ssr import SSRMolecularPredictor
|
|
11
|
+
from .predictor.dir import DIRMolecularPredictor
|
|
12
|
+
from .predictor.rpgnn import RPGNNMolecularPredictor
|
|
13
|
+
from .predictor.bfgnn import BFGNNMolecularPredictor
|
|
14
|
+
from .predictor.grin import GRINMolecularPredictor
|
|
15
|
+
from .predictor.lstm import LSTMMolecularPredictor
|
|
16
|
+
from .predictor.smiles_transformer import SMILESTransformerMolecularPredictor
|
|
17
|
+
"""
|
|
18
|
+
encoder module
|
|
19
|
+
"""
|
|
20
|
+
from .encoder.attrmask import AttrMaskMolecularEncoder
|
|
21
|
+
from .encoder.moama import MoamaMolecularEncoder
|
|
22
|
+
from .encoder.graphmae import GraphMAEMolecularEncoder
|
|
23
|
+
from .encoder.supervised import SupervisedMolecularEncoder
|
|
24
|
+
from .encoder.contextpred import ContextPredMolecularEncoder
|
|
25
|
+
from .encoder.edgepred import EdgePredMolecularEncoder
|
|
26
|
+
from .encoder.infograph import InfoGraphMolecularEncoder
|
|
27
|
+
from .encoder.pretrained import HFPretrainedMolecularEncoder
|
|
28
|
+
"""
|
|
29
|
+
generator module
|
|
30
|
+
"""
|
|
31
|
+
from .generator.graph_dit import GraphDITMolecularGenerator
|
|
32
|
+
from .generator.digress import DigressMolecularGenerator
|
|
33
|
+
from .generator.gdss import GDSSMolecularGenerator
|
|
34
|
+
from .generator.graph_ga import GraphGAMolecularGenerator
|
|
35
|
+
from .generator.jtvae import JTVAEMolecularGenerator
|
|
36
|
+
from .generator.lstm import LSTMMolecularGenerator
|
|
37
|
+
from .generator.molgpt import MolGPTMolecularGenerator
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# 'BaseMolecularPredictor',
|
|
41
|
+
# predictors
|
|
42
|
+
'SGIRMolecularPredictor',
|
|
43
|
+
'GREAMolecularPredictor',
|
|
44
|
+
'GNNMolecularPredictor',
|
|
45
|
+
'IRMMolecularPredictor',
|
|
46
|
+
'SSRMolecularPredictor',
|
|
47
|
+
'DIRMolecularPredictor',
|
|
48
|
+
'RPGNNMolecularPredictor',
|
|
49
|
+
'BFGNNMolecularPredictor',
|
|
50
|
+
'GRINMolecularPredictor',
|
|
51
|
+
'LSTMMolecularPredictor',
|
|
52
|
+
'SMILESTransformerMolecularPredictor',
|
|
53
|
+
# encoders
|
|
54
|
+
'SupervisedMolecularEncoder',
|
|
55
|
+
'AttrMaskMolecularEncoder',
|
|
56
|
+
'MoamaMolecularEncoder',
|
|
57
|
+
'GraphMAEMolecularEncoder',
|
|
58
|
+
'ContextPredMolecularEncoder',
|
|
59
|
+
'EdgePredMolecularEncoder',
|
|
60
|
+
'InfoGraphMolecularEncoder',
|
|
61
|
+
'HFPretrainedMolecularEncoder',
|
|
62
|
+
# generators
|
|
63
|
+
'GraphDITMolecularGenerator',
|
|
64
|
+
'DigressMolecularGenerator',
|
|
65
|
+
'GDSSMolecularGenerator',
|
|
66
|
+
'GraphGAMolecularGenerator',
|
|
67
|
+
'JTVAEMolecularGenerator',
|
|
68
|
+
'MolGPTMolecularGenerator',
|
|
69
|
+
'LSTMMolecularGenerator',
|
|
70
|
+
]
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Optional, Dict, List, Type, Any, ClassVar, Union, Tuple, Callable, Literal
|
|
4
|
+
import torch
|
|
5
|
+
import os
|
|
6
|
+
import numpy as np
|
|
7
|
+
from ..utils.checkpoint import LocalCheckpointManager, HuggingFaceCheckpointManager
|
|
8
|
+
from ..utils.checker import MolecularInputChecker
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class BaseModel(ABC):
|
|
12
|
+
"""Base class for molecular models with shared functionality.
|
|
13
|
+
|
|
14
|
+
This abstract class provides common methods and utilities for molecular models,
|
|
15
|
+
including model initialization, saving/loading, and parameter management.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
device: Optional[torch.device] = field(default=None)
|
|
19
|
+
model_name: str = field(default="BaseModel")
|
|
20
|
+
model_class: Optional[Type[torch.nn.Module]] = field(default=None, init=False) # used for model initialization
|
|
21
|
+
model: Optional[torch.nn.Module] = field(default=None, init=False) # initialized model
|
|
22
|
+
is_fitted_: bool = field(default=False, init=False)
|
|
23
|
+
|
|
24
|
+
def __post_init__(self):
|
|
25
|
+
"""Initialize common device settings after instance creation.
|
|
26
|
+
|
|
27
|
+
Sets the device to CUDA if available, otherwise CPU, when no device is specified.
|
|
28
|
+
Converts string device specifications to torch.device objects.
|
|
29
|
+
"""
|
|
30
|
+
if self.device is None:
|
|
31
|
+
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
|
32
|
+
elif isinstance(self.device, str):
|
|
33
|
+
self.device = torch.device(self.device)
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def _setup_optimizers(self) -> Tuple[torch.optim.Optimizer, Optional[Any]]:
|
|
37
|
+
"""Set up optimizers for model training.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Tuple[torch.optim.Optimizer, Optional[Any]]
|
|
42
|
+
Tuple containing the primary optimizer and an optional secondary optimizer or scheduler.
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def _train_epoch(self, train_loader, optimizer):
|
|
48
|
+
"""Train the model for one epoch.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
train_loader : torch.utils.data.DataLoader
|
|
53
|
+
DataLoader containing training batches
|
|
54
|
+
optimizer : torch.optim.Optimizer
|
|
55
|
+
Optimizer to use for parameter updates
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
dict
|
|
60
|
+
Training metrics for the epoch
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def _get_model_params(self, checkpoint: Optional[Dict] = None) -> Dict[str, Any]:
|
|
66
|
+
"""Get model parameters used for model initialization.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
checkpoint : Optional[Dict], default=None
|
|
71
|
+
Optional dictionary containing model checkpoint data
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
Dict[str, Any]
|
|
76
|
+
Dictionary of parameters to initialize the model
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _get_param_names(self) -> List[str]:
|
|
82
|
+
"""Get parameter names in the modeling class.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
List[str]
|
|
87
|
+
List of parameter names that can be configured
|
|
88
|
+
"""
|
|
89
|
+
# return ["model_name", "model_class", "is_fitted_"]
|
|
90
|
+
return ["model_name", "is_fitted_"]
|
|
91
|
+
|
|
92
|
+
def get_params(self, deep: bool = True) -> Dict[str, Any]:
|
|
93
|
+
"""Get parameters for this estimator.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
deep : bool, default=True
|
|
98
|
+
If True, will return the parameters for this estimator and
|
|
99
|
+
contained subobjects that are estimators.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
Dict[str, Any]
|
|
104
|
+
Dictionary of parameter names mapped to their values
|
|
105
|
+
"""
|
|
106
|
+
out = {}
|
|
107
|
+
for key in self._get_param_names():
|
|
108
|
+
value = getattr(self, key)
|
|
109
|
+
if deep and hasattr(value, "get_params"):
|
|
110
|
+
deep_items = value.get_params().items()
|
|
111
|
+
out.update((key + "__" + k, val) for k, val in deep_items)
|
|
112
|
+
out[key] = value
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
def set_params(self, **params) -> "BaseModel":
|
|
116
|
+
"""Set parameters for this estimator.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
**params
|
|
121
|
+
Parameter names mapped to their values
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
BaseModel
|
|
126
|
+
Self instance for method chaining
|
|
127
|
+
|
|
128
|
+
Raises
|
|
129
|
+
------
|
|
130
|
+
ValueError
|
|
131
|
+
If an invalid parameter is provided
|
|
132
|
+
"""
|
|
133
|
+
valid_params = self.get_params(deep=True)
|
|
134
|
+
for key, value in params.items():
|
|
135
|
+
if key not in valid_params:
|
|
136
|
+
raise ValueError(f"Invalid parameter {key} for model {self}")
|
|
137
|
+
setattr(self, key, value)
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def _initialize_model(
|
|
141
|
+
self,
|
|
142
|
+
model_class: Type[torch.nn.Module],
|
|
143
|
+
checkpoint: Optional[Dict] = None
|
|
144
|
+
) -> torch.nn.Module:
|
|
145
|
+
"""Initialize the model with parameters or a checkpoint.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
model_class : Type[torch.nn.Module]
|
|
150
|
+
PyTorch module class to instantiate
|
|
151
|
+
checkpoint : Optional[Dict], default=None
|
|
152
|
+
Optional dictionary containing model checkpoint data
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
torch.nn.Module
|
|
157
|
+
Initialized PyTorch model
|
|
158
|
+
"""
|
|
159
|
+
model_params = self._get_model_params(checkpoint)
|
|
160
|
+
self.model = model_class(**model_params)
|
|
161
|
+
self.model = self.model.to(self.device)
|
|
162
|
+
|
|
163
|
+
if checkpoint is not None:
|
|
164
|
+
self.model.load_state_dict(checkpoint["model_state_dict"])
|
|
165
|
+
return self.model
|
|
166
|
+
|
|
167
|
+
def _validate_inputs(
|
|
168
|
+
self, X: List[str], y: Optional[Union[List, np.ndarray]] = None, num_task: int = 0, num_pretask: int = 0, return_rdkit_mol: bool = True
|
|
169
|
+
) -> Tuple[Union[List[str], List["Chem.Mol"]], Optional[np.ndarray]]:
|
|
170
|
+
"""Validate molecular inputs and targets.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
X : List[str]
|
|
175
|
+
List of SMILES strings representing molecules
|
|
176
|
+
y : Optional[Union[List, np.ndarray]], default=None
|
|
177
|
+
Optional target values for supervised learning
|
|
178
|
+
num_task : int, default=0
|
|
179
|
+
Number of prediction tasks
|
|
180
|
+
num_pretask : int, default=0
|
|
181
|
+
Number of pre-training tasks
|
|
182
|
+
return_rdkit_mol : bool, default=True
|
|
183
|
+
Whether to return RDKit Mol objects instead of SMILES
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
Tuple[Union[List[str], List["Chem.Mol"]], Optional[np.ndarray]]
|
|
188
|
+
Tuple of validated inputs and targets
|
|
189
|
+
"""
|
|
190
|
+
return MolecularInputChecker.validate_inputs(X, y, num_task, num_pretask, return_rdkit_mol)
|
|
191
|
+
|
|
192
|
+
def save_to_local(self, path: str) -> None:
|
|
193
|
+
"""Save model to local disk.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
path : str
|
|
198
|
+
File path to save the model
|
|
199
|
+
|
|
200
|
+
Raises
|
|
201
|
+
------
|
|
202
|
+
ValueError
|
|
203
|
+
If the model is not fitted
|
|
204
|
+
"""
|
|
205
|
+
if not self.is_fitted_:
|
|
206
|
+
raise ValueError("Model must be fitted before saving to local disk.")
|
|
207
|
+
LocalCheckpointManager.save_model_to_local(self, path)
|
|
208
|
+
|
|
209
|
+
def load_from_local(self, path: str) -> None:
|
|
210
|
+
"""Load model from local disk.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
path : str
|
|
215
|
+
File path to load the model from
|
|
216
|
+
"""
|
|
217
|
+
LocalCheckpointManager.load_model_from_local(self, path)
|
|
218
|
+
|
|
219
|
+
def save_to_hf(
|
|
220
|
+
self,
|
|
221
|
+
repo_id: str,
|
|
222
|
+
task_id: str = "default",
|
|
223
|
+
metadata_dict: Optional[Dict[str, Any]] = None,
|
|
224
|
+
metrics: Optional[Dict[str, float]] = None,
|
|
225
|
+
commit_message: str = "Update model",
|
|
226
|
+
hf_token: Optional[str] = None,
|
|
227
|
+
private: bool = False,
|
|
228
|
+
config_filename: Optional[str] = 'config.json',
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Save model to Hugging Face Hub.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
repo_id : str
|
|
235
|
+
Hugging Face repository ID
|
|
236
|
+
task_id : str, default="default"
|
|
237
|
+
Task identifier for the model
|
|
238
|
+
metadata_dict : Optional[Dict[str, Any]], default=None
|
|
239
|
+
Optional metadata to store with the model
|
|
240
|
+
metrics : Optional[Dict[str, float]], default=None
|
|
241
|
+
Optional performance metrics to store with the model
|
|
242
|
+
commit_message : str, default="Update model"
|
|
243
|
+
Git commit message
|
|
244
|
+
hf_token : Optional[str], default=None
|
|
245
|
+
Hugging Face authentication token
|
|
246
|
+
private : bool, default=False
|
|
247
|
+
Whether the repository should be private
|
|
248
|
+
config_filename : Optional[str], default='config.json'
|
|
249
|
+
Name of the configuration file to save to the repository
|
|
250
|
+
Raises
|
|
251
|
+
------
|
|
252
|
+
ValueError
|
|
253
|
+
If the model is not fitted
|
|
254
|
+
"""
|
|
255
|
+
if not self.is_fitted_:
|
|
256
|
+
raise ValueError("Model must be fitted before saving to Hugging Face Hub.")
|
|
257
|
+
HuggingFaceCheckpointManager.push_to_huggingface(
|
|
258
|
+
model_instance=self,
|
|
259
|
+
repo_id=repo_id,
|
|
260
|
+
task_id=task_id,
|
|
261
|
+
metadata_dict=metadata_dict,
|
|
262
|
+
metrics=metrics,
|
|
263
|
+
commit_message=commit_message,
|
|
264
|
+
token=hf_token,
|
|
265
|
+
private=private,
|
|
266
|
+
config_filename=config_filename,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def load_from_hf(self, repo_id: str, local_cache: Optional[str] = None, config_filename: Optional[str] = 'config.json') -> None:
|
|
270
|
+
"""Load model from Hugging Face Hub.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
repo_id : str
|
|
275
|
+
Hugging Face repository ID
|
|
276
|
+
local_cache : str, default=None
|
|
277
|
+
Local path to save the model
|
|
278
|
+
config_filename : str, default='config.json'
|
|
279
|
+
Name of the configuration file to load from the repository
|
|
280
|
+
"""
|
|
281
|
+
if local_cache is None:
|
|
282
|
+
local_cache = 'model.pt'
|
|
283
|
+
HuggingFaceCheckpointManager.load_model_from_hf(self, repo_id, local_cache, config_filename=config_filename)
|
|
284
|
+
|
|
285
|
+
def save(self, path: Optional[str] = None, repo_id: Optional[str] = None, **kwargs) -> None:
|
|
286
|
+
"""Automatic save to either local disk or Hugging Face Hub.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
path : Optional[str], default=None
|
|
291
|
+
File path for local saving (required if repo_id is None)
|
|
292
|
+
repo_id : Optional[str], default=None
|
|
293
|
+
Hugging Face repository ID for remote saving
|
|
294
|
+
**kwargs
|
|
295
|
+
Additional arguments passed to save_to_hf
|
|
296
|
+
|
|
297
|
+
Raises
|
|
298
|
+
------
|
|
299
|
+
ValueError
|
|
300
|
+
If path is None when repo_id is None
|
|
301
|
+
"""
|
|
302
|
+
# if both path and repo_id are None, raise an error
|
|
303
|
+
if path is None and repo_id is None:
|
|
304
|
+
raise ValueError("path must be provided if repo_id is not given.")
|
|
305
|
+
|
|
306
|
+
if repo_id is not None:
|
|
307
|
+
self.save_to_hf(repo_id=repo_id, **kwargs)
|
|
308
|
+
|
|
309
|
+
if path is not None:
|
|
310
|
+
self.save_to_local(path)
|
|
311
|
+
|
|
312
|
+
def load(self, path: Optional[str] = None, repo_id: Optional[str] = None, **kwargs) -> None:
|
|
313
|
+
"""Automatic load from either local disk or Hugging Face Hub.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
path : Optional[str], default=None
|
|
318
|
+
File path for local loading.
|
|
319
|
+
repo_id : Optional[str], default=None
|
|
320
|
+
Hugging Face repository ID for remote loading. If path is provided, repo_id is ignored.
|
|
321
|
+
**kwargs
|
|
322
|
+
Additional arguments passed to load_from_hf
|
|
323
|
+
|
|
324
|
+
Raises
|
|
325
|
+
------
|
|
326
|
+
FileNotFoundError
|
|
327
|
+
If no local file is found and no repo_id is provided
|
|
328
|
+
"""
|
|
329
|
+
if path is not None:
|
|
330
|
+
if os.path.exists(path):
|
|
331
|
+
self.load_from_local(path)
|
|
332
|
+
else:
|
|
333
|
+
raise FileNotFoundError(f"No local file found at '{path}'.")
|
|
334
|
+
else:
|
|
335
|
+
if repo_id is None:
|
|
336
|
+
raise ValueError("repo_id must be provided if path is not given.")
|
|
337
|
+
self.load_from_hf(repo_id, **kwargs)
|
|
338
|
+
|
|
339
|
+
def _check_is_fitted(self) -> None:
|
|
340
|
+
"""Check if the model is fitted.
|
|
341
|
+
|
|
342
|
+
Raises
|
|
343
|
+
------
|
|
344
|
+
AttributeError
|
|
345
|
+
If the model is not fitted
|
|
346
|
+
"""
|
|
347
|
+
if not self.is_fitted_:
|
|
348
|
+
raise AttributeError("This model is not fitted yet. Call 'fit' before using it.")
|
|
349
|
+
|
|
350
|
+
def __str__(self, N_CHAR_MAX: int = 700) -> str:
|
|
351
|
+
"""Return a string representation of the model.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
N_CHAR_MAX : int, default=700
|
|
356
|
+
Maximum number of characters in the string representation
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
str
|
|
361
|
+
String representation of the model
|
|
362
|
+
"""
|
|
363
|
+
attributes = {
|
|
364
|
+
name: value
|
|
365
|
+
for name, value in sorted(self.__dict__.items())
|
|
366
|
+
if not name.startswith("_") and not callable(value)
|
|
367
|
+
}
|
|
368
|
+
attributes = {k: v for k, v in attributes.items() if k != "fitting_loss"}
|
|
369
|
+
|
|
370
|
+
def format_value(v):
|
|
371
|
+
"""Helper to format values for representation."""
|
|
372
|
+
if isinstance(v, (float, np.float32, np.float64)):
|
|
373
|
+
return f"{v:.3g}"
|
|
374
|
+
elif isinstance(v, (list, tuple, np.ndarray)) and len(v) > 6:
|
|
375
|
+
return f"{v[:3]}...{v[-3:]}"
|
|
376
|
+
elif isinstance(v, str) and len(v) > 50:
|
|
377
|
+
return f"'{v[:25]}...{v[-22:]}'"
|
|
378
|
+
elif isinstance(v, dict) and len(v) > 6:
|
|
379
|
+
return f"{{{', '.join(f'{k}: {v}' for k, v in list(v.items())[:3])}...}}"
|
|
380
|
+
elif isinstance(v, torch.nn.Module):
|
|
381
|
+
return f"{v.__class__.__name__}(...)"
|
|
382
|
+
return repr(v)
|
|
383
|
+
|
|
384
|
+
class_name = self.__class__.__name__
|
|
385
|
+
important_attrs = ["model_name", "is_fitted_", "task_type", "num_task"]
|
|
386
|
+
attributes_str = [f"{attr}={format_value(attributes.pop(attr))}" for attr in important_attrs if attr in attributes]
|
|
387
|
+
attributes_str += [f"{k}={format_value(v)}" for k, v in sorted(attributes.items())]
|
|
388
|
+
|
|
389
|
+
content = ",\n ".join(attributes_str)
|
|
390
|
+
repr_str = f"{class_name}(\n {content}\n)"
|
|
391
|
+
|
|
392
|
+
if len(repr_str) > N_CHAR_MAX:
|
|
393
|
+
repr_str = "\n".join([repr_str[:N_CHAR_MAX//2], "...", repr_str[-N_CHAR_MAX//2:]])
|
|
394
|
+
|
|
395
|
+
return repr_str
|
|
396
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional, ClassVar, Union, List, Dict, Any, Tuple, Callable, Type, Literal
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
import numpy as np
|
|
7
|
+
from .base import BaseModel
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BaseMolecularEncoder(BaseModel, ABC):
|
|
11
|
+
"""Base class for molecular representation learning."""
|
|
12
|
+
|
|
13
|
+
model_name: str = field(default="BaseMolecularEncoder")
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def encode(self, X: List[str], return_type: Literal["np", "pt"] = "pt") -> Union[np.ndarray, torch.Tensor]:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def fit(self, X: List[str], y: Optional[np.ndarray] = None) -> "BaseMolecularEncoder":
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def _inspect_task_types(self, y: Union[np.ndarray, torch.Tensor], return_type: Literal["pt", "np"] = "pt") -> Union[np.ndarray, torch.Tensor]:
|
|
24
|
+
"""Inspect the task types of the target values.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
y : Union[np.ndarray, torch.Tensor]
|
|
29
|
+
Target values: 2D array for multiple tasks
|
|
30
|
+
return_type : Literal["pt", "np"], default="pt"
|
|
31
|
+
Return type of the result
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
result : Union[np.ndarray, torch.Tensor]
|
|
36
|
+
Result of the task types inspection
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(y, np.ndarray):
|
|
39
|
+
y = torch.from_numpy(y)
|
|
40
|
+
# 0/1/nan -> binary classification (True), otherwise -> regression (False)
|
|
41
|
+
result = torch.tensor([torch.all(torch.isnan(y[:, i]) | (y[:, i] == 0) | (y[:, i] == 1)) for i in range(y.shape[1])], dtype=torch.bool)
|
|
42
|
+
if return_type == "np":
|
|
43
|
+
return result.numpy()
|
|
44
|
+
return result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional, ClassVar, Union, List, Dict, Any, Tuple, Callable, Type, Literal
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
import numpy as np
|
|
7
|
+
from .base import BaseModel
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BaseMolecularGenerator(BaseModel, ABC):
|
|
11
|
+
"""Base class for molecular generation."""
|
|
12
|
+
|
|
13
|
+
model_name: str = field(default="BaseMolecularGenerator")
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def fit(self, X: List[str], y: Optional[np.ndarray] = None) -> "BaseMolecularGenerator":
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def generate(self, n_samples: int, **kwargs) -> List[str]:
|
|
21
|
+
"""Generate molecular structures.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|