molcraft 0.1.0rc9__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.

Potentially problematic release.


This version of molcraft might be problematic. Click here for more details.

molcraft/trainers.py ADDED
@@ -0,0 +1,212 @@
1
+ import keras
2
+ import warnings
3
+
4
+ from molcraft import layers
5
+ from molcraft import models
6
+ from molcraft import tensors
7
+
8
+
9
+ @keras.saving.register_keras_serializable(package='molcraft')
10
+ class Trainer(models.GraphModel):
11
+
12
+ '''Base trainer.
13
+
14
+ Wraps and (pre)trains a graph neural network for a certain task.
15
+
16
+ Args:
17
+ model:
18
+ A `models.GraphModel` to be (pre)trained.
19
+ '''
20
+
21
+ def __init__(self, model: models.GraphModel, **kwargs) -> None:
22
+ super().__init__(**kwargs)
23
+ self.model = model
24
+
25
+ def get_config(self) -> dict:
26
+ config = super().get_config()
27
+ config['model'] = keras.saving.serialize_keras_object(self.model)
28
+ return config
29
+
30
+ @classmethod
31
+ def from_config(cls, config: dict) -> 'Trainer':
32
+ config['model'] = keras.saving.deserialize_keras_object(config['model'])
33
+ return super().from_config(config)
34
+
35
+
36
+ @keras.saving.register_keras_serializable(package='molcraft')
37
+ class NodePredictionTrainer(Trainer):
38
+
39
+ '''Node prediction trainer.
40
+
41
+ Wraps and (pre)trains a graph neural network to perform node predictions.
42
+
43
+ Ignores super nodes and edges, if they exist.
44
+
45
+ Args:
46
+ model:
47
+ A `models.GraphModel` to be (pre)trained.
48
+ decoder:
49
+ An optional decoder for converting updated node features to node predictions.
50
+ If None, a `keras.layers.Dense` layer is used with `units` set to `label` dim.
51
+ select_rate:
52
+ The rate of which nodes will be selected for prediction. If None, all nodes are predicted.
53
+ mask_selected:
54
+ Whether to mask the selected nodes. Only relevant if `select_rate` is specified.
55
+ edge_masking_rate:
56
+ The rate of which edges will be masked. If None, edges will not be masked.
57
+ Only relevant if `select_rate` is specified.
58
+
59
+ Example:
60
+
61
+ >>> import molcraft
62
+ >>> import keras
63
+ >>>
64
+ >>> featurizer = molcraft.featurizers.MolGraphFeaturizer(
65
+ ... atom_features=[
66
+ ... molcraft.features.AtomType(['C', 'N', 'O', 'P', 'S']),
67
+ ... ]
68
+ ... )
69
+ >>> graph = featurizer(['N[C@@H](C)C(=O)O', 'N[C@@H](CS)C(=O)O'])
70
+ >>> # Label nodes with the one-hot encoded atom types for illustration
71
+ >>> graph = graph.update({'node': {'label': graph.node['feature']}})
72
+ >>>
73
+ >>> inputs = molcraft.layers.Input(graph.spec)
74
+ >>> x = molcraft.layers.NodeEmbedding(128)(inputs)
75
+ >>> x = molcraft.layers.EdgeEmbedding(128)(x)
76
+ >>> x = molcraft.layers.GraphConv(128)(x)
77
+ >>> outputs = molcraft.layers.GraphConv(128)(x)
78
+ >>> model = molcraft.models.GraphModel(inputs, outputs)
79
+ >>>
80
+ >>> pretrainer = molcraft.trainers.NodePredictionTrainer(
81
+ ... model,
82
+ ... decoder=None, # Dense(units=node_label_dim)
83
+ ... select_rate=0.5,
84
+ ... mask_selected=True,
85
+ ... )
86
+ >>> pretrainer.compile(
87
+ ... optimizer=keras.optimizers.Adam(1e-4),
88
+ ... loss=keras.losses.CategoricalCrossentropy(from_logits=True),
89
+ ... )
90
+ >>> pretrainer.fit(graph, epochs=10)
91
+ >>> # pretrainer.model.save('/tmp/model.keras')
92
+ '''
93
+
94
+ def __init__(
95
+ self,
96
+ model: models.GraphModel,
97
+ decoder: keras.layers.Layer | None = None,
98
+ select_rate: float | None = None,
99
+ mask_selected: bool = False,
100
+ edge_masking_rate: float | None = None,
101
+ **kwargs
102
+ ) -> None:
103
+ super().__init__(model=model, **kwargs)
104
+
105
+ for layer in self.model.layers:
106
+ if isinstance(layer, layers.NodeEmbedding):
107
+ break
108
+ else:
109
+ raise ValueError('Could not find `NodeEmbedding` layer.')
110
+
111
+ self._embedder = models.GraphModel(
112
+ self.model.input, layer._symbolic_output
113
+ )
114
+ self._model = models.GraphModel(
115
+ layer._symbolic_output, self.model.output
116
+ )
117
+ self._decoder = decoder
118
+ self._select_rate = select_rate
119
+ self._mask_selected = mask_selected
120
+ if edge_masking_rate and not mask_selected:
121
+ warnings.warn(
122
+ 'Setting `edge_masking_rate` to `None`, '
123
+ 'as `mask_selected` is set to `False`.'
124
+ )
125
+ edge_masking_rate = None
126
+ self._edge_masking_rate = edge_masking_rate
127
+
128
+ def build(self, spec: tensors.GraphTensor.Spec) -> None:
129
+
130
+ self._has_super = ('super' in spec.node)
131
+ self._has_edge_feature = ('feature' in spec.edge)
132
+
133
+ if self._mask_selected:
134
+ node_feature_dim = self._embedder._symbolic_output['node']['feature'].shape[-1]
135
+ self._node_mask_feature = self.get_weight(shape=[node_feature_dim])
136
+
137
+ if self._mask_selected and self._has_edge_feature and self._edge_masking_rate:
138
+ edge_feature_dim = self._embedder._symbolic_output['edge']['feature'].shape[-1]
139
+ self._edge_mask_feature = self.get_weight(shape=[edge_feature_dim])
140
+ elif self._edge_masking_rate and not self._has_edge_feature:
141
+ warnings.warn(
142
+ 'Setting `edge_masking_rate` to `None`, '
143
+ 'as no edge features exist.'
144
+ )
145
+ self._edge_masking_rate = None
146
+
147
+ if self._decoder is None:
148
+ label_dim = spec.node['label'].shape[-1]
149
+ self._decoder = keras.layers.Dense(units=label_dim)
150
+
151
+ def propagate(
152
+ self,
153
+ tensor: tensors.GraphTensor,
154
+ training: bool | None = None,
155
+ ) -> tensors.GraphTensor:
156
+ sample_weight = tensor.node.get('sample_weight')
157
+ if sample_weight is None:
158
+ sample_weight = keras.ops.ones([tensor.num_nodes])
159
+
160
+ tensor = self._embedder(tensor)
161
+
162
+ if self._select_rate is not None and training:
163
+ # Select nodes to be predicted
164
+ is_not_super = (
165
+ True if not self._has_super else keras.ops.logical_not(tensor.node['super'])
166
+ )
167
+ r = keras.random.uniform(shape=[tensor.num_nodes])
168
+ node_mask = keras.ops.logical_and(is_not_super, self._select_rate > r)
169
+ sample_weight = keras.ops.where(node_mask, sample_weight, 0.0)
170
+
171
+ if self._mask_selected:
172
+ # Mask selected node features
173
+ node_feature_masked = keras.ops.where(
174
+ node_mask[:, None], self._node_mask_feature, tensor.node['feature']
175
+ )
176
+ tensor = tensor.update({'node': {'feature': node_feature_masked}})
177
+
178
+ if self._edge_masking_rate:
179
+ # Mask edge features
180
+ is_not_super = (
181
+ True if not self._has_super else keras.ops.logical_not(tensor.edge['super'])
182
+ )
183
+ r = keras.random.uniform(shape=[tensor.num_edges])
184
+ edge_mask = keras.ops.logical_and(is_not_super, self._edge_masking_rate > r)
185
+ edge_feature_masked = keras.ops.where(
186
+ edge_mask[:, None], self._edge_mask_feature, tensor.edge['feature']
187
+ )
188
+ tensor = tensor.update({'edge': {'feature': edge_feature_masked}})
189
+
190
+ node_feature = self._model(tensor).node['feature']
191
+ node_prediction = self._decoder(node_feature)
192
+ return tensor.update({
193
+ 'node': {
194
+ 'prediction': node_prediction,
195
+ 'sample_weight': sample_weight
196
+ }
197
+ })
198
+
199
+ def get_config(self) -> dict:
200
+ config = super().get_config()
201
+ config.update({
202
+ 'decoder': keras.saving.serialize_keras_object(self._decoder),
203
+ 'select_rate': self._select_rate,
204
+ 'mask_selected': self._mask_selected,
205
+ 'edge_masking_rate': self._edge_masking_rate,
206
+ })
207
+ return config
208
+
209
+ @classmethod
210
+ def from_config(cls, config: dict) -> 'NodePredictionTrainer':
211
+ config['decoder'] = keras.saving.deserialize_keras_object(config['decoder'])
212
+ return super().from_config(config)
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: molcraft
3
+ Version: 0.1.0rc9
4
+ Summary: Graph Neural Networks for Molecular Machine Learning
5
+ Author-email: Alexander Kensert <alexander.kensert@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Alexander Kensert
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/compomics/molcraft
29
+ Keywords: python,machine-learning,deep-learning,graph-neural-networks,molecular-machine-learning,molecular-graphs,computational-chemistry,computational-biology
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Intended Audience :: Science/Research
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: POSIX :: Linux
34
+ Requires-Python: >=3.10
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: tensorflow>=2.16
38
+ Requires-Dist: rdkit>=2023.9.5
39
+ Requires-Dist: pandas>=1.0.3
40
+ Requires-Dist: ipython>=8.12.0
41
+ Provides-Extra: gpu
42
+ Requires-Dist: tensorflow[and-cuda]>=2.16; extra == "gpu"
43
+ Dynamic: license-file
44
+
45
+ <img src="https://github.com/akensert/molcraft/blob/main/docs/_static/molcraft-logo.png" alt="molcraft-logo" width="90%">
46
+
47
+ **Deep Learning on Molecules**: A Minimalistic GNN package for Molecular ML.
48
+
49
+ > [!NOTE]
50
+ > In progress.
51
+
52
+ ## Installation
53
+
54
+ For CPU users:
55
+
56
+ ```bash
57
+ pip install molcraft
58
+ ```
59
+
60
+ For GPU users:
61
+ ```bash
62
+ pip install molcraft[gpu]
63
+ ```
64
+
65
+ ## Examples
66
+
67
+ ```python
68
+ from molcraft import features
69
+ from molcraft import descriptors
70
+ from molcraft import featurizers
71
+ from molcraft import layers
72
+ from molcraft import models
73
+ import keras
74
+
75
+ featurizer = featurizers.MolGraphFeaturizer(
76
+ atom_features=[
77
+ features.AtomType(),
78
+ features.NumHydrogens(),
79
+ features.Degree(),
80
+ ],
81
+ bond_features=[
82
+ features.BondType(),
83
+ features.IsRotatable(),
84
+ ],
85
+ super_node=True,
86
+ self_loops=True,
87
+ include_hydrogens=False,
88
+ )
89
+
90
+ graph = featurizer([('N[C@@H](C)C(=O)O', 2.5), ('N[C@@H](CS)C(=O)O', 1.5)])
91
+ print(graph)
92
+
93
+ model = models.GraphModel.from_layers(
94
+ [
95
+ layers.Input(graph.spec),
96
+ layers.NodeEmbedding(dim=128),
97
+ layers.EdgeEmbedding(dim=128),
98
+ layers.GraphConv(units=128),
99
+ layers.GraphConv(units=128),
100
+ layers.GraphConv(units=128),
101
+ layers.GraphConv(units=128),
102
+ layers.Readout(),
103
+ keras.layers.Dense(units=1024, activation='elu'),
104
+ keras.layers.Dense(units=1024, activation='elu'),
105
+ keras.layers.Dense(1)
106
+ ]
107
+ )
108
+
109
+ pred = model(graph)
110
+ print(pred)
111
+
112
+ # featurizers.save_featurizer(featurizer, '/tmp/featurizer.json')
113
+ # models.save_model(model, '/tmp/model.keras')
114
+
115
+ # loaded_featurizer = featurizers.load_featurizer('/tmp/featurizer.json')
116
+ # loaded_model = models.load_model('/tmp/model.keras')
117
+ ```
118
+
@@ -0,0 +1,19 @@
1
+ molcraft/__init__.py,sha256=_1FKaZ23km7d3YPnOGSx3EMeXxjEwSuC30YhWjqKOXI,461
2
+ molcraft/callbacks.py,sha256=B4gGWjVW_1ORrt38jfk1ZFI9c0rOpN5sgjGWVqs3Ess,3571
3
+ molcraft/chem.py,sha256=dPRB4aLk5U6nkzfHCTHosh6f7Cph16UH3Ri4JNCE6fw,22488
4
+ molcraft/datasets.py,sha256=1rHccqra5chIBwo2pz9vduyv0i07uY3CABzmAqWiFBU,4161
5
+ molcraft/descriptors.py,sha256=FI15LYcb0KXqvurCkXUkg_h7rOqRZnRPK5LaWdM7Q8M,4876
6
+ molcraft/features.py,sha256=q-wuRP9YjPu_v5czipsh00VEXEjgFaeuLk6dbgyD_VM,13505
7
+ molcraft/featurizers.py,sha256=_1xq6336s9E2Ho5f3v_dIGLop1dhKf1lscygpeIlVQY,20964
8
+ molcraft/layers.py,sha256=4E8JdI3xLjrI6hvIbHjE23O9PsB7J3H7NIcaaLsUDZM,69879
9
+ molcraft/losses.py,sha256=piu4XYAgjnK7k9LqA4Vkh-SooYZ31sWwRfG1cacCwyA,1081
10
+ molcraft/models.py,sha256=4_658wqfB1rz4QS_68aNr1RWB2cyqwcpNr_k0rvmuj0,23756
11
+ molcraft/ops.py,sha256=cI373Bg51CXXKnn7vhH1GiZ2GrvkgzaG38fhlPmfIs0,6280
12
+ molcraft/records.py,sha256=b9i1azDM_OkY_E4Rl6etwbzOthekVM-Z7oTSxYg5aNM,5888
13
+ molcraft/tensors.py,sha256=KSXAPKhu4kXn6KUmu2ZNE1-CSfmoFDdAPiaxYyIAHUA,22693
14
+ molcraft/trainers.py,sha256=-s3SdBUI2Hfvf3D3JLRAGpPSy0umUlhI5MPIg3eA9Jc,8018
15
+ molcraft-0.1.0rc9.dist-info/licenses/LICENSE,sha256=sbVeqlrtZ0V63uYhZGL5dCxUm8rBAOqe2avyA1zIQNk,1074
16
+ molcraft-0.1.0rc9.dist-info/METADATA,sha256=3CDa5m0sVaWpnmo5CQYyKPbaCqXuv3RTD9AexLT4rFI,3880
17
+ molcraft-0.1.0rc9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ molcraft-0.1.0rc9.dist-info/top_level.txt,sha256=dENV6MfOceshM6MQCgJlcN1ojZkiCL9B4F7XyUge3QM,9
19
+ molcraft-0.1.0rc9.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alexander Kensert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ molcraft