diffbio 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.
- diffbio/__init__.py +39 -0
- diffbio/configs.py +75 -0
- diffbio/constants.py +204 -0
- diffbio/core/__init__.py +127 -0
- diffbio/core/base_operators.py +612 -0
- diffbio/core/data_types.py +260 -0
- diffbio/core/gnn_components.py +629 -0
- diffbio/core/graph_utils.py +149 -0
- diffbio/core/neural_components.py +270 -0
- diffbio/core/optimal_transport.py +133 -0
- diffbio/core/soft_ops/__init__.py +216 -0
- diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
- diffbio/core/soft_ops/_projections_simplex.py +240 -0
- diffbio/core/soft_ops/_projections_transport.py +508 -0
- diffbio/core/soft_ops/_sorting_network.py +204 -0
- diffbio/core/soft_ops/_types.py +15 -0
- diffbio/core/soft_ops/_utils.py +342 -0
- diffbio/core/soft_ops/autograd_safe.py +120 -0
- diffbio/core/soft_ops/comparison.py +235 -0
- diffbio/core/soft_ops/elementwise.py +309 -0
- diffbio/core/soft_ops/logical.py +146 -0
- diffbio/core/soft_ops/quantile.py +376 -0
- diffbio/core/soft_ops/selection.py +236 -0
- diffbio/core/soft_ops/sorting.py +926 -0
- diffbio/core/soft_ops/straight_through.py +261 -0
- diffbio/core/uncertainty.py +279 -0
- diffbio/evaluation/__init__.py +42 -0
- diffbio/evaluation/adapters.py +409 -0
- diffbio/evaluation/graders.py +223 -0
- diffbio/evaluation/problem.py +157 -0
- diffbio/evaluation/runner.py +277 -0
- diffbio/losses/__init__.py +59 -0
- diffbio/losses/alignment_losses.py +222 -0
- diffbio/losses/biological_regularization.py +288 -0
- diffbio/losses/metric_losses.py +139 -0
- diffbio/losses/singlecell_losses.py +387 -0
- diffbio/losses/statistical_losses.py +345 -0
- diffbio/operators/__init__.py +60 -0
- diffbio/operators/_count_vae.py +197 -0
- diffbio/operators/_loss_balancing.py +65 -0
- diffbio/operators/_masked_gene_transformer.py +118 -0
- diffbio/operators/_transformer_validation.py +50 -0
- diffbio/operators/alignment/__init__.py +51 -0
- diffbio/operators/alignment/profile_hmm.py +350 -0
- diffbio/operators/alignment/scoring.py +127 -0
- diffbio/operators/alignment/smith_waterman.py +261 -0
- diffbio/operators/alignment/soft_msa.py +419 -0
- diffbio/operators/assembly/__init__.py +27 -0
- diffbio/operators/assembly/gnn_assembly.py +252 -0
- diffbio/operators/assembly/metagenomic_binning.py +296 -0
- diffbio/operators/crispr/__init__.py +17 -0
- diffbio/operators/crispr/guide_scoring.py +269 -0
- diffbio/operators/drug_discovery/__init__.py +133 -0
- diffbio/operators/drug_discovery/_graph_utils.py +142 -0
- diffbio/operators/drug_discovery/admet_predictor.py +285 -0
- diffbio/operators/drug_discovery/attentive_fp.py +411 -0
- diffbio/operators/drug_discovery/dti.py +261 -0
- diffbio/operators/drug_discovery/fingerprint.py +490 -0
- diffbio/operators/drug_discovery/maccs_keys.py +267 -0
- diffbio/operators/drug_discovery/message_passing.py +200 -0
- diffbio/operators/drug_discovery/primitives.py +242 -0
- diffbio/operators/drug_discovery/property_predictor.py +163 -0
- diffbio/operators/drug_discovery/similarity.py +193 -0
- diffbio/operators/epigenomics/__init__.py +35 -0
- diffbio/operators/epigenomics/chromatin_state.py +491 -0
- diffbio/operators/epigenomics/contextual.py +288 -0
- diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
- diffbio/operators/epigenomics/peak_calling.py +555 -0
- diffbio/operators/foundation_models/__init__.py +119 -0
- diffbio/operators/foundation_models/adapters.py +114 -0
- diffbio/operators/foundation_models/contracts.py +245 -0
- diffbio/operators/foundation_models/embedding_probe.py +83 -0
- diffbio/operators/foundation_models/experimental.py +128 -0
- diffbio/operators/foundation_models/foundation_model.py +332 -0
- diffbio/operators/foundation_models/frozen.py +59 -0
- diffbio/operators/foundation_models/precomputed.py +270 -0
- diffbio/operators/foundation_models/transformer_encoder.py +564 -0
- diffbio/operators/mapping/__init__.py +17 -0
- diffbio/operators/mapping/neural_mapper.py +493 -0
- diffbio/operators/metabolomics/__init__.py +39 -0
- diffbio/operators/metabolomics/spectral_similarity.py +315 -0
- diffbio/operators/molecular_dynamics/__init__.py +51 -0
- diffbio/operators/molecular_dynamics/force_field.py +265 -0
- diffbio/operators/molecular_dynamics/integrator.py +304 -0
- diffbio/operators/molecular_dynamics/primitives.py +115 -0
- diffbio/operators/multiomics/__init__.py +38 -0
- diffbio/operators/multiomics/hic_contact.py +377 -0
- diffbio/operators/multiomics/multiomics_vae.py +325 -0
- diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
- diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
- diffbio/operators/normalization/__init__.py +42 -0
- diffbio/operators/normalization/embedding.py +222 -0
- diffbio/operators/normalization/phate.py +400 -0
- diffbio/operators/normalization/umap.py +261 -0
- diffbio/operators/normalization/vae_normalizer.py +258 -0
- diffbio/operators/population/__init__.py +17 -0
- diffbio/operators/population/ancestry_estimation.py +274 -0
- diffbio/operators/preprocessing/__init__.py +76 -0
- diffbio/operators/preprocessing/adapter_removal.py +311 -0
- diffbio/operators/preprocessing/duplicate_filter.py +317 -0
- diffbio/operators/preprocessing/error_correction.py +287 -0
- diffbio/operators/protein/__init__.py +31 -0
- diffbio/operators/protein/secondary_structure.py +509 -0
- diffbio/operators/quality_filter.py +128 -0
- diffbio/operators/rna_structure/__init__.py +35 -0
- diffbio/operators/rna_structure/rna_folding.py +509 -0
- diffbio/operators/rnaseq/__init__.py +23 -0
- diffbio/operators/rnaseq/motif_discovery.py +251 -0
- diffbio/operators/rnaseq/splicing_psi.py +216 -0
- diffbio/operators/singlecell/__init__.py +193 -0
- diffbio/operators/singlecell/ambient_removal.py +333 -0
- diffbio/operators/singlecell/archetypes.py +191 -0
- diffbio/operators/singlecell/batch_correction.py +288 -0
- diffbio/operators/singlecell/cell_annotation.py +519 -0
- diffbio/operators/singlecell/communication.py +704 -0
- diffbio/operators/singlecell/differential_distribution.py +243 -0
- diffbio/operators/singlecell/doublet_detection.py +657 -0
- diffbio/operators/singlecell/downsampling.py +166 -0
- diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
- diffbio/operators/singlecell/grn_inference.py +336 -0
- diffbio/operators/singlecell/imputation.py +429 -0
- diffbio/operators/singlecell/knockdown_filter.py +176 -0
- diffbio/operators/singlecell/ot_trajectory.py +277 -0
- diffbio/operators/singlecell/simulation.py +444 -0
- diffbio/operators/singlecell/sindy_grn.py +247 -0
- diffbio/operators/singlecell/soft_clustering.py +211 -0
- diffbio/operators/singlecell/spatial_domains.py +677 -0
- diffbio/operators/singlecell/switch_de.py +184 -0
- diffbio/operators/singlecell/trajectory.py +447 -0
- diffbio/operators/singlecell/velocity.py +361 -0
- diffbio/operators/statistical/__init__.py +35 -0
- diffbio/operators/statistical/em_quantification.py +260 -0
- diffbio/operators/statistical/hmm.py +234 -0
- diffbio/operators/statistical/nb_glm.py +272 -0
- diffbio/operators/variant/__init__.py +64 -0
- diffbio/operators/variant/classifier.py +333 -0
- diffbio/operators/variant/cnn_classifier.py +255 -0
- diffbio/operators/variant/cnv_segmentation.py +678 -0
- diffbio/operators/variant/deepvariant_pileup.py +426 -0
- diffbio/operators/variant/pileup.py +240 -0
- diffbio/operators/variant/quality_recalibration.py +274 -0
- diffbio/pipelines/__init__.py +65 -0
- diffbio/pipelines/differential_expression.py +279 -0
- diffbio/pipelines/enhanced_variant_calling.py +326 -0
- diffbio/pipelines/perturbation.py +407 -0
- diffbio/pipelines/preprocessing.py +267 -0
- diffbio/pipelines/single_cell.py +366 -0
- diffbio/pipelines/variant_calling.py +490 -0
- diffbio/samplers/__init__.py +9 -0
- diffbio/samplers/perturbation_sampler.py +142 -0
- diffbio/sequences/__init__.py +34 -0
- diffbio/sequences/dna.py +239 -0
- diffbio/sources/__init__.py +149 -0
- diffbio/sources/_anndata_shared.py +89 -0
- diffbio/sources/_batch_iteration.py +37 -0
- diffbio/sources/_benchmark_source.py +152 -0
- diffbio/sources/_indexed_batch_source.py +38 -0
- diffbio/sources/_utils.py +45 -0
- diffbio/sources/anndata_interop.py +387 -0
- diffbio/sources/anndata_source.py +361 -0
- diffbio/sources/archive_ii.py +174 -0
- diffbio/sources/balifam.py +207 -0
- diffbio/sources/bam.py +265 -0
- diffbio/sources/bengrn_ground_truth.py +306 -0
- diffbio/sources/contextual_epigenomics.py +242 -0
- diffbio/sources/dti.py +359 -0
- diffbio/sources/embeddings.py +203 -0
- diffbio/sources/encode_peaks.py +223 -0
- diffbio/sources/fasta.py +226 -0
- diffbio/sources/immune_human.py +172 -0
- diffbio/sources/indexed_embeddings.py +128 -0
- diffbio/sources/indexed_view.py +191 -0
- diffbio/sources/molnet.py +493 -0
- diffbio/sources/multiomics.py +279 -0
- diffbio/sources/pancreas.py +108 -0
- diffbio/sources/perturbation/__init__.py +69 -0
- diffbio/sources/perturbation/_types.py +51 -0
- diffbio/sources/perturbation/_utils.py +125 -0
- diffbio/sources/perturbation/concat_source.py +115 -0
- diffbio/sources/perturbation/control_mapping.py +215 -0
- diffbio/sources/perturbation/experiment_config.py +261 -0
- diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
- diffbio/sources/perturbation/output_space.py +52 -0
- diffbio/sources/perturbation/perturbation_source.py +513 -0
- diffbio/sources/seqfish.py +145 -0
- diffbio/sources/sequence_foundation.py +68 -0
- diffbio/sources/singlecell_foundation.py +68 -0
- diffbio/splitters/__init__.py +63 -0
- diffbio/splitters/base.py +251 -0
- diffbio/splitters/molecular.py +330 -0
- diffbio/splitters/perturbation.py +199 -0
- diffbio/splitters/random.py +217 -0
- diffbio/splitters/sequence.py +201 -0
- diffbio/utils/__init__.py +55 -0
- diffbio/utils/dependency_runtime.py +115 -0
- diffbio/utils/nn_utils.py +157 -0
- diffbio/utils/quality.py +45 -0
- diffbio/utils/training.py +585 -0
- diffbio-0.1.0.dist-info/METADATA +480 -0
- diffbio-0.1.0.dist-info/RECORD +202 -0
- diffbio-0.1.0.dist-info/WHEEL +4 -0
- diffbio-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Multi-task ADMET property prediction operator.
|
|
2
|
+
|
|
3
|
+
This module implements a ChemProp-style multi-task ADMET predictor
|
|
4
|
+
for predicting Absorption, Distribution, Metabolism, Excretion, and
|
|
5
|
+
Toxicity properties of drug candidates.
|
|
6
|
+
|
|
7
|
+
The implementation follows the TDC ADMET Benchmark with 22 standard endpoints.
|
|
8
|
+
|
|
9
|
+
References:
|
|
10
|
+
- https://tdcommons.ai/benchmark/admet_group/overview/
|
|
11
|
+
- https://github.com/chemprop/chemprop
|
|
12
|
+
- Swanson et al. "ADMET-AI" Bioinformatics 2024
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
from artifex.generative_models.core.base import MLP
|
|
21
|
+
from datarax.core.config import OperatorConfig
|
|
22
|
+
from datarax.core.operator import OperatorModule
|
|
23
|
+
from flax import nnx
|
|
24
|
+
|
|
25
|
+
from diffbio.operators.drug_discovery._graph_utils import (
|
|
26
|
+
build_optional_dropout,
|
|
27
|
+
graph_sum_readout,
|
|
28
|
+
initialize_graph_encoder_from_config,
|
|
29
|
+
)
|
|
30
|
+
from diffbio.utils.nn_utils import ARTIFEX_RELU_MLP_KWARGS
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# Standard TDC ADMET benchmark task names (22 tasks)
|
|
35
|
+
ADMET_TASK_NAMES: list[str] = [
|
|
36
|
+
# Absorption (6)
|
|
37
|
+
"Caco2_Wang",
|
|
38
|
+
"HIA_Hou",
|
|
39
|
+
"Pgp_Broccatelli",
|
|
40
|
+
"Bioavailability_Ma",
|
|
41
|
+
"Lipophilicity_AstraZeneca",
|
|
42
|
+
"Solubility_AqSolDB",
|
|
43
|
+
# Distribution (3)
|
|
44
|
+
"BBB_Martins",
|
|
45
|
+
"PPBR_AZ",
|
|
46
|
+
"VDss_Lombardo",
|
|
47
|
+
# Metabolism (6)
|
|
48
|
+
"CYP2C9_Veith",
|
|
49
|
+
"CYP2D6_Veith",
|
|
50
|
+
"CYP3A4_Veith",
|
|
51
|
+
"CYP2C9_Substrate_CarbonMangels",
|
|
52
|
+
"CYP2D6_Substrate_CarbonMangels",
|
|
53
|
+
"CYP3A4_Substrate_CarbonMangels",
|
|
54
|
+
# Excretion (3)
|
|
55
|
+
"Half_Life_Obach",
|
|
56
|
+
"Clearance_Hepatocyte_AZ",
|
|
57
|
+
"Clearance_Microsome_AZ",
|
|
58
|
+
# Toxicity (4)
|
|
59
|
+
"LD50_Zhu",
|
|
60
|
+
"hERG",
|
|
61
|
+
"AMES",
|
|
62
|
+
"DILI",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Task types: classification or regression
|
|
66
|
+
ADMET_TASK_TYPES: dict[str, str] = {
|
|
67
|
+
# Absorption
|
|
68
|
+
"Caco2_Wang": "regression",
|
|
69
|
+
"HIA_Hou": "classification",
|
|
70
|
+
"Pgp_Broccatelli": "classification",
|
|
71
|
+
"Bioavailability_Ma": "classification",
|
|
72
|
+
"Lipophilicity_AstraZeneca": "regression",
|
|
73
|
+
"Solubility_AqSolDB": "regression",
|
|
74
|
+
# Distribution
|
|
75
|
+
"BBB_Martins": "classification",
|
|
76
|
+
"PPBR_AZ": "regression",
|
|
77
|
+
"VDss_Lombardo": "regression",
|
|
78
|
+
# Metabolism
|
|
79
|
+
"CYP2C9_Veith": "classification",
|
|
80
|
+
"CYP2D6_Veith": "classification",
|
|
81
|
+
"CYP3A4_Veith": "classification",
|
|
82
|
+
"CYP2C9_Substrate_CarbonMangels": "classification",
|
|
83
|
+
"CYP2D6_Substrate_CarbonMangels": "classification",
|
|
84
|
+
"CYP3A4_Substrate_CarbonMangels": "classification",
|
|
85
|
+
# Excretion
|
|
86
|
+
"Half_Life_Obach": "regression",
|
|
87
|
+
"Clearance_Hepatocyte_AZ": "regression",
|
|
88
|
+
"Clearance_Microsome_AZ": "regression",
|
|
89
|
+
# Toxicity
|
|
90
|
+
"LD50_Zhu": "regression",
|
|
91
|
+
"hERG": "classification",
|
|
92
|
+
"AMES": "classification",
|
|
93
|
+
"DILI": "classification",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class ADMETConfig(OperatorConfig):
|
|
99
|
+
# pylint: disable=too-many-instance-attributes
|
|
100
|
+
"""Configuration for ADMET property predictor.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
hidden_dim: Hidden dimension for message passing (default: 300).
|
|
104
|
+
num_message_passing_steps: Number of D-MPNN iterations (default: 3).
|
|
105
|
+
num_tasks: Number of ADMET prediction tasks (default: 22).
|
|
106
|
+
dropout_rate: Dropout rate for regularization (default: 0.0).
|
|
107
|
+
in_features: Number of input node features (default: 4).
|
|
108
|
+
num_edge_features: Number of edge features (default: 4).
|
|
109
|
+
ffn_hidden_dim: FFN hidden dimension (default: same as hidden_dim).
|
|
110
|
+
ffn_num_layers: Number of FFN layers (default: 2).
|
|
111
|
+
apply_task_activations: Apply sigmoid for classification tasks (default: False).
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
hidden_dim: int = 300
|
|
115
|
+
num_message_passing_steps: int = 3
|
|
116
|
+
num_tasks: int = 22
|
|
117
|
+
dropout_rate: float = 0.0
|
|
118
|
+
in_features: int = 4
|
|
119
|
+
num_edge_features: int = 4
|
|
120
|
+
ffn_hidden_dim: int | None = None
|
|
121
|
+
ffn_num_layers: int = 2
|
|
122
|
+
apply_task_activations: bool = False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ADMETPredictor(OperatorModule):
|
|
126
|
+
"""Multi-task ADMET property predictor.
|
|
127
|
+
|
|
128
|
+
Implements a ChemProp-style directed message passing neural network
|
|
129
|
+
for predicting multiple ADMET properties simultaneously. The architecture
|
|
130
|
+
uses a shared molecular encoder with task-specific prediction heads.
|
|
131
|
+
|
|
132
|
+
Architecture:
|
|
133
|
+
1. Message passing encoder (D-MPNN style)
|
|
134
|
+
2. Graph-level readout via sum pooling
|
|
135
|
+
3. Shared feed-forward layers
|
|
136
|
+
4. Task-specific output heads
|
|
137
|
+
|
|
138
|
+
The 22 standard TDC ADMET endpoints cover:
|
|
139
|
+
- Absorption: Caco2, HIA, Pgp, Bioavailability, Lipophilicity, Solubility
|
|
140
|
+
- Distribution: BBB, PPBR, VDss
|
|
141
|
+
- Metabolism: CYP enzymes (2C9, 2D6, 3A4) inhibition and substrate
|
|
142
|
+
- Excretion: Half-life, Hepatocyte clearance, Microsome clearance
|
|
143
|
+
- Toxicity: LD50, hERG, AMES, DILI
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
```python
|
|
147
|
+
config = ADMETConfig(hidden_dim=256, num_tasks=22)
|
|
148
|
+
predictor = ADMETPredictor(config, rngs=nnx.Rngs(42))
|
|
149
|
+
data = {"node_features": nodes, "adjacency": adj, "node_mask": mask}
|
|
150
|
+
result, _, _ = predictor.apply(data, {}, None)
|
|
151
|
+
predictions = result["predictions"] # shape: (22,)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
References:
|
|
155
|
+
- https://tdcommons.ai/benchmark/admet_group/overview/
|
|
156
|
+
- Yang et al. "Analyzing Learned Molecular Representations" JCIM 2019
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
config: ADMETConfig,
|
|
162
|
+
*,
|
|
163
|
+
rngs: nnx.Rngs | None = None,
|
|
164
|
+
name: str | None = None,
|
|
165
|
+
):
|
|
166
|
+
"""Initialize ADMET predictor.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
config: ADMET configuration.
|
|
170
|
+
rngs: Flax NNX random number generators.
|
|
171
|
+
name: Optional name for the operator.
|
|
172
|
+
"""
|
|
173
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
174
|
+
|
|
175
|
+
rngs = initialize_graph_encoder_from_config(self, config, rngs=rngs)
|
|
176
|
+
|
|
177
|
+
# FFN hidden dim defaults to hidden_dim
|
|
178
|
+
ffn_hidden = config.ffn_hidden_dim or config.hidden_dim
|
|
179
|
+
|
|
180
|
+
if config.ffn_num_layers > 1:
|
|
181
|
+
self.ffn_backbone = MLP(
|
|
182
|
+
hidden_dims=[ffn_hidden] * (config.ffn_num_layers - 1),
|
|
183
|
+
in_features=config.hidden_dim,
|
|
184
|
+
dropout_rate=config.dropout_rate,
|
|
185
|
+
rngs=rngs,
|
|
186
|
+
**ARTIFEX_RELU_MLP_KWARGS,
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
self.ffn_backbone = None
|
|
190
|
+
|
|
191
|
+
# Task-specific output heads (one per ADMET task)
|
|
192
|
+
last_hidden = ffn_hidden if config.ffn_num_layers > 1 else config.hidden_dim
|
|
193
|
+
task_heads = [nnx.Linear(last_hidden, 1, rngs=rngs) for _ in range(config.num_tasks)]
|
|
194
|
+
self.task_heads = nnx.List(task_heads)
|
|
195
|
+
|
|
196
|
+
# Dropout
|
|
197
|
+
self.dropout = build_optional_dropout(config.dropout_rate, rngs=rngs)
|
|
198
|
+
|
|
199
|
+
def apply(
|
|
200
|
+
self,
|
|
201
|
+
data: dict[str, Any],
|
|
202
|
+
state: dict[str, Any],
|
|
203
|
+
metadata: dict[str, Any] | None,
|
|
204
|
+
random_params: Any = None, # noqa: ARG002
|
|
205
|
+
stats: dict[str, Any] | None = None, # noqa: ARG002
|
|
206
|
+
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any] | None]:
|
|
207
|
+
"""Predict ADMET properties from molecular graph.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
data: Input data containing:
|
|
211
|
+
- node_features: (num_nodes, num_features) atom features
|
|
212
|
+
- adjacency: (num_nodes, num_nodes) adjacency matrix
|
|
213
|
+
- edge_features: Optional (num_nodes, num_nodes, num_edge_features)
|
|
214
|
+
- node_mask: (num_nodes,) mask for valid nodes
|
|
215
|
+
state: Per-element state (passed through).
|
|
216
|
+
metadata: Optional metadata.
|
|
217
|
+
random_params: Unused random parameters.
|
|
218
|
+
stats: Optional statistics dictionary.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Tuple of:
|
|
222
|
+
- data with added "predictions" and "task_predictions" keys
|
|
223
|
+
- unchanged state
|
|
224
|
+
- unchanged metadata
|
|
225
|
+
"""
|
|
226
|
+
del random_params, stats # Unused
|
|
227
|
+
|
|
228
|
+
graph_repr = graph_sum_readout(data, self.encoder, dropout=self.dropout)
|
|
229
|
+
|
|
230
|
+
h = graph_repr
|
|
231
|
+
if self.ffn_backbone is not None:
|
|
232
|
+
ffn_output = self.ffn_backbone(h)
|
|
233
|
+
if isinstance(ffn_output, tuple):
|
|
234
|
+
raise TypeError("ADMETPredictor shared FFN must return a single tensor output.")
|
|
235
|
+
h = ffn_output
|
|
236
|
+
|
|
237
|
+
# Task-specific predictions
|
|
238
|
+
task_predictions = []
|
|
239
|
+
for i, head in enumerate(self.task_heads):
|
|
240
|
+
pred = head(h).squeeze(-1)
|
|
241
|
+
|
|
242
|
+
# Apply activation for classification tasks if configured
|
|
243
|
+
if self.config.apply_task_activations:
|
|
244
|
+
task_name = ADMET_TASK_NAMES[i] if i < len(ADMET_TASK_NAMES) else f"task_{i}"
|
|
245
|
+
if ADMET_TASK_TYPES.get(task_name) == "classification":
|
|
246
|
+
pred = nnx.sigmoid(pred)
|
|
247
|
+
|
|
248
|
+
task_predictions.append(pred)
|
|
249
|
+
|
|
250
|
+
# Stack all predictions
|
|
251
|
+
predictions = jnp.stack(task_predictions)
|
|
252
|
+
|
|
253
|
+
result = {
|
|
254
|
+
**data,
|
|
255
|
+
"predictions": predictions,
|
|
256
|
+
"task_predictions": task_predictions,
|
|
257
|
+
"graph_representation": graph_repr,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result, state, metadata
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_admet_predictor(
|
|
264
|
+
hidden_dim: int = 300,
|
|
265
|
+
num_layers: int = 3,
|
|
266
|
+
dropout_rate: float = 0.0,
|
|
267
|
+
seed: int = 42,
|
|
268
|
+
) -> ADMETPredictor:
|
|
269
|
+
"""Create an ADMET predictor with standard configuration.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
hidden_dim: Hidden dimension for message passing.
|
|
273
|
+
num_layers: Number of message passing steps.
|
|
274
|
+
dropout_rate: Dropout rate.
|
|
275
|
+
seed: Random seed.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Configured ADMETPredictor.
|
|
279
|
+
"""
|
|
280
|
+
config = ADMETConfig(
|
|
281
|
+
hidden_dim=hidden_dim,
|
|
282
|
+
num_message_passing_steps=num_layers,
|
|
283
|
+
dropout_rate=dropout_rate,
|
|
284
|
+
)
|
|
285
|
+
return ADMETPredictor(config, rngs=nnx.Rngs(seed))
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""AttentiveFP: Attention-based graph fingerprint for molecular property prediction.
|
|
2
|
+
|
|
3
|
+
This module implements the AttentiveFP architecture from Xiong et al. 2019,
|
|
4
|
+
which combines graph attention mechanisms with GRU cells for molecular
|
|
5
|
+
representation learning.
|
|
6
|
+
|
|
7
|
+
The architecture provides:
|
|
8
|
+
|
|
9
|
+
- Interpretable attention weights showing atom importance
|
|
10
|
+
- Two-level aggregation (atom-level and molecule-level)
|
|
11
|
+
- GRU-based state updates for iterative refinement
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
- Xiong et al. "Pushing the Boundaries of Molecular Representation for
|
|
15
|
+
Drug Discovery with the Graph Attention Mechanism" JCIM 2019
|
|
16
|
+
- https://pytorch-geometric.readthedocs.io/en/latest/generated/torch_geometric.nn.models.AttentiveFP.html
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import jax
|
|
24
|
+
import jax.numpy as jnp
|
|
25
|
+
from datarax.core.config import OperatorConfig
|
|
26
|
+
from datarax.core.operator import OperatorModule
|
|
27
|
+
from flax import nnx
|
|
28
|
+
|
|
29
|
+
from diffbio.operators.drug_discovery._graph_utils import (
|
|
30
|
+
build_optional_dropout,
|
|
31
|
+
stabilize_operator_id,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class _AttentiveFPArchitectureConfig:
|
|
39
|
+
"""Architecture configuration for AttentiveFP."""
|
|
40
|
+
|
|
41
|
+
hidden_dim: int = 200
|
|
42
|
+
out_dim: int = 200
|
|
43
|
+
num_layers: int = 2
|
|
44
|
+
num_timesteps: int = 2
|
|
45
|
+
dropout_rate: float = 0.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class _AttentiveFPInputConfig:
|
|
50
|
+
"""Graph input configuration for AttentiveFP."""
|
|
51
|
+
|
|
52
|
+
in_features: int = 39
|
|
53
|
+
edge_dim: int = 10
|
|
54
|
+
negative_slope: float = 0.2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class AttentiveFPConfig(
|
|
59
|
+
_AttentiveFPArchitectureConfig,
|
|
60
|
+
_AttentiveFPInputConfig,
|
|
61
|
+
OperatorConfig,
|
|
62
|
+
):
|
|
63
|
+
"""Configuration for AttentiveFP operator."""
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
"""Validate the AttentiveFP configuration."""
|
|
67
|
+
super().__post_init__()
|
|
68
|
+
|
|
69
|
+
if self.hidden_dim <= 0:
|
|
70
|
+
raise ValueError("hidden_dim must be positive.")
|
|
71
|
+
if self.out_dim <= 0:
|
|
72
|
+
raise ValueError("out_dim must be positive.")
|
|
73
|
+
if self.num_layers <= 0:
|
|
74
|
+
raise ValueError("num_layers must be positive.")
|
|
75
|
+
if self.num_timesteps <= 0:
|
|
76
|
+
raise ValueError("num_timesteps must be positive.")
|
|
77
|
+
if not 0.0 <= self.dropout_rate < 1.0:
|
|
78
|
+
raise ValueError("dropout_rate must be in [0.0, 1.0).")
|
|
79
|
+
if self.in_features <= 0:
|
|
80
|
+
raise ValueError("in_features must be positive.")
|
|
81
|
+
if self.edge_dim < 0:
|
|
82
|
+
raise ValueError("edge_dim must be non-negative.")
|
|
83
|
+
if self.negative_slope < 0.0:
|
|
84
|
+
raise ValueError("negative_slope must be non-negative.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class GATEConv(nnx.Module):
|
|
88
|
+
"""Graph Attention with Edge features (GATE) convolution layer.
|
|
89
|
+
|
|
90
|
+
Combines node features with edge features using attention mechanism.
|
|
91
|
+
This is the core building block for AttentiveFP's atom-level processing.
|
|
92
|
+
|
|
93
|
+
The attention mechanism computes:
|
|
94
|
+
alpha_ij = softmax_j(LeakyReLU(a^T [Wh_i || Wh_j || We_ij]))
|
|
95
|
+
|
|
96
|
+
where || denotes concatenation and e_ij are edge features.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
in_dim: int,
|
|
102
|
+
out_dim: int,
|
|
103
|
+
edge_dim: int,
|
|
104
|
+
*,
|
|
105
|
+
negative_slope: float = 0.2,
|
|
106
|
+
rngs: nnx.Rngs,
|
|
107
|
+
):
|
|
108
|
+
"""Initialize GATE convolution.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
in_dim: Input feature dimension.
|
|
112
|
+
out_dim: Output feature dimension.
|
|
113
|
+
edge_dim: Edge feature dimension.
|
|
114
|
+
negative_slope: LeakyReLU negative slope.
|
|
115
|
+
rngs: Random number generators.
|
|
116
|
+
"""
|
|
117
|
+
super().__init__()
|
|
118
|
+
self.negative_slope = negative_slope
|
|
119
|
+
|
|
120
|
+
# Linear transformations
|
|
121
|
+
self.linear_src = nnx.Linear(in_dim, out_dim, rngs=rngs)
|
|
122
|
+
self.linear_dst = nnx.Linear(in_dim, out_dim, rngs=rngs)
|
|
123
|
+
|
|
124
|
+
# Edge feature projection (if edge_dim > 0)
|
|
125
|
+
self.use_edge_features = edge_dim > 0
|
|
126
|
+
if self.use_edge_features:
|
|
127
|
+
self.linear_edge = nnx.Linear(edge_dim, out_dim, rngs=rngs)
|
|
128
|
+
|
|
129
|
+
# Attention coefficients
|
|
130
|
+
# Attention is computed as: a^T [src || dst || edge]
|
|
131
|
+
attn_dim = out_dim * 3 if self.use_edge_features else out_dim * 2
|
|
132
|
+
self.attn = nnx.Linear(attn_dim, 1, use_bias=False, rngs=rngs)
|
|
133
|
+
|
|
134
|
+
def __call__(
|
|
135
|
+
self,
|
|
136
|
+
node_features: jnp.ndarray,
|
|
137
|
+
adjacency: jnp.ndarray,
|
|
138
|
+
edge_features: jnp.ndarray | None = None,
|
|
139
|
+
) -> tuple[jnp.ndarray, jnp.ndarray]:
|
|
140
|
+
"""Apply GATE convolution.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
node_features: (num_nodes, in_dim) node features
|
|
144
|
+
adjacency: (num_nodes, num_nodes) adjacency matrix
|
|
145
|
+
edge_features: Optional (num_nodes, num_nodes, edge_dim)
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Tuple of (updated_features, attention_weights)
|
|
149
|
+
"""
|
|
150
|
+
num_nodes = node_features.shape[0]
|
|
151
|
+
|
|
152
|
+
# Transform source and destination features
|
|
153
|
+
h_src = self.linear_src(node_features) # (N, out_dim)
|
|
154
|
+
h_dst = self.linear_dst(node_features) # (N, out_dim)
|
|
155
|
+
|
|
156
|
+
# Expand for pairwise computation
|
|
157
|
+
h_src_exp = jnp.expand_dims(h_src, axis=1) # (N, 1, out_dim)
|
|
158
|
+
h_dst_exp = jnp.expand_dims(h_dst, axis=0) # (1, N, out_dim)
|
|
159
|
+
|
|
160
|
+
# Broadcast to (N, N, out_dim)
|
|
161
|
+
h_src_broad = jnp.broadcast_to(h_src_exp, (num_nodes, num_nodes, h_src.shape[-1]))
|
|
162
|
+
h_dst_broad = jnp.broadcast_to(h_dst_exp, (num_nodes, num_nodes, h_dst.shape[-1]))
|
|
163
|
+
|
|
164
|
+
# Concatenate for attention
|
|
165
|
+
if self.use_edge_features and edge_features is not None:
|
|
166
|
+
h_edge = self.linear_edge(edge_features) # (N, N, out_dim)
|
|
167
|
+
attn_input = jnp.concatenate([h_src_broad, h_dst_broad, h_edge], axis=-1)
|
|
168
|
+
else:
|
|
169
|
+
attn_input = jnp.concatenate([h_src_broad, h_dst_broad], axis=-1)
|
|
170
|
+
|
|
171
|
+
# Compute attention logits
|
|
172
|
+
attn_logits = self.attn(attn_input).squeeze(-1) # (N, N)
|
|
173
|
+
|
|
174
|
+
# Apply LeakyReLU
|
|
175
|
+
attn_logits = jnp.where(
|
|
176
|
+
attn_logits >= 0,
|
|
177
|
+
attn_logits,
|
|
178
|
+
self.negative_slope * attn_logits,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Mask non-edges with large negative value
|
|
182
|
+
attn_logits = jnp.where(adjacency > 0, attn_logits, -1e9)
|
|
183
|
+
|
|
184
|
+
# Softmax over neighbors
|
|
185
|
+
attn_weights = jax.nn.softmax(attn_logits, axis=-1) # (N, N)
|
|
186
|
+
|
|
187
|
+
# Mask attention weights for non-edges
|
|
188
|
+
attn_weights = attn_weights * adjacency
|
|
189
|
+
|
|
190
|
+
# Aggregate: weighted sum of transformed features
|
|
191
|
+
out = jnp.einsum("ij,jd->id", attn_weights, h_dst) # (N, out_dim)
|
|
192
|
+
|
|
193
|
+
return out, attn_weights
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class AttentiveFP(OperatorModule):
|
|
197
|
+
"""AttentiveFP: Attention-based molecular fingerprint.
|
|
198
|
+
|
|
199
|
+
Implements the AttentiveFP architecture with:
|
|
200
|
+
1. Atom-level attention layers with GRU refinement
|
|
201
|
+
2. Molecule-level aggregation with attention and GRU
|
|
202
|
+
3. Final projection to fingerprint dimension
|
|
203
|
+
|
|
204
|
+
The model provides interpretable attention weights that indicate
|
|
205
|
+
which atoms contribute most to the molecular representation.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
```python
|
|
209
|
+
config = AttentiveFPConfig(hidden_dim=128, out_dim=256)
|
|
210
|
+
afp = AttentiveFP(config, rngs=nnx.Rngs(42))
|
|
211
|
+
data = {"node_features": nodes, "adjacency": adj, "edge_features": edges}
|
|
212
|
+
result, _, _ = afp.apply(data, {}, None)
|
|
213
|
+
fingerprint = result["fingerprint"] # (256,)
|
|
214
|
+
attn = result["attention_weights"] # interpretability
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
References:
|
|
218
|
+
- Xiong et al. JCIM 2019
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
config: AttentiveFPConfig,
|
|
224
|
+
*,
|
|
225
|
+
rngs: nnx.Rngs | None = None,
|
|
226
|
+
):
|
|
227
|
+
"""Initialize AttentiveFP.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
config: AttentiveFP configuration.
|
|
231
|
+
rngs: Flax NNX random number generators.
|
|
232
|
+
"""
|
|
233
|
+
super().__init__(config, rngs=rngs)
|
|
234
|
+
|
|
235
|
+
stabilize_operator_id(self)
|
|
236
|
+
|
|
237
|
+
if rngs is None:
|
|
238
|
+
rngs = nnx.Rngs(0)
|
|
239
|
+
|
|
240
|
+
# Initial linear projection
|
|
241
|
+
self.input_proj = nnx.Linear(config.in_features, config.hidden_dim, rngs=rngs)
|
|
242
|
+
|
|
243
|
+
# Atom-level: GATE convolutions with GRU
|
|
244
|
+
atom_convs = []
|
|
245
|
+
atom_grus = []
|
|
246
|
+
|
|
247
|
+
for _ in range(config.num_layers):
|
|
248
|
+
in_dim = config.hidden_dim
|
|
249
|
+
atom_convs.append(
|
|
250
|
+
GATEConv(
|
|
251
|
+
in_dim=in_dim,
|
|
252
|
+
out_dim=config.hidden_dim,
|
|
253
|
+
edge_dim=config.edge_dim,
|
|
254
|
+
negative_slope=config.negative_slope,
|
|
255
|
+
rngs=rngs,
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
atom_grus.append(
|
|
259
|
+
nnx.GRUCell(
|
|
260
|
+
in_features=config.hidden_dim,
|
|
261
|
+
hidden_features=config.hidden_dim,
|
|
262
|
+
rngs=rngs,
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
self.atom_convs = nnx.List(atom_convs)
|
|
266
|
+
self.atom_grus = nnx.List(atom_grus)
|
|
267
|
+
|
|
268
|
+
# Molecule-level aggregation
|
|
269
|
+
# Attention for global pooling
|
|
270
|
+
self.mol_attn = nnx.Linear(config.hidden_dim, 1, rngs=rngs)
|
|
271
|
+
|
|
272
|
+
# GRU for molecule-level refinement
|
|
273
|
+
mol_grus = []
|
|
274
|
+
for _ in range(config.num_timesteps):
|
|
275
|
+
mol_grus.append(
|
|
276
|
+
nnx.GRUCell(
|
|
277
|
+
in_features=config.hidden_dim,
|
|
278
|
+
hidden_features=config.hidden_dim,
|
|
279
|
+
rngs=rngs,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
self.mol_grus = nnx.List(mol_grus)
|
|
283
|
+
|
|
284
|
+
# Final projection
|
|
285
|
+
self.output_proj = nnx.Linear(config.hidden_dim, config.out_dim, rngs=rngs)
|
|
286
|
+
|
|
287
|
+
# Dropout
|
|
288
|
+
self.dropout = build_optional_dropout(config.dropout_rate, rngs=rngs)
|
|
289
|
+
|
|
290
|
+
def apply(
|
|
291
|
+
self,
|
|
292
|
+
data: dict[str, Any],
|
|
293
|
+
state: dict[str, Any],
|
|
294
|
+
metadata: dict[str, Any] | None,
|
|
295
|
+
random_params: Any = None, # noqa: ARG002
|
|
296
|
+
stats: dict[str, Any] | None = None, # noqa: ARG002
|
|
297
|
+
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any] | None]:
|
|
298
|
+
"""Compute AttentiveFP molecular fingerprint.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
data: Input data containing:
|
|
302
|
+
- node_features: (num_nodes, in_features) atom features
|
|
303
|
+
- adjacency: (num_nodes, num_nodes) adjacency matrix
|
|
304
|
+
- edge_features: Optional (num_nodes, num_nodes, edge_dim)
|
|
305
|
+
- node_mask: (num_nodes,) optional mask for valid nodes
|
|
306
|
+
state: Per-element state (passed through).
|
|
307
|
+
metadata: Optional metadata.
|
|
308
|
+
random_params: Unused random parameters.
|
|
309
|
+
stats: Optional statistics dictionary.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Tuple of:
|
|
313
|
+
- data with "fingerprint" and "attention_weights" keys
|
|
314
|
+
- unchanged state
|
|
315
|
+
- unchanged metadata
|
|
316
|
+
"""
|
|
317
|
+
del random_params, stats # Unused
|
|
318
|
+
|
|
319
|
+
node_features = data["node_features"]
|
|
320
|
+
adjacency = data["adjacency"]
|
|
321
|
+
edge_features = data.get("edge_features")
|
|
322
|
+
node_mask = data.get("node_mask")
|
|
323
|
+
|
|
324
|
+
# Initial projection
|
|
325
|
+
h = self.input_proj(node_features) # (N, hidden_dim)
|
|
326
|
+
|
|
327
|
+
if self.dropout is not None:
|
|
328
|
+
h = self.dropout(h)
|
|
329
|
+
|
|
330
|
+
# Collect attention weights for interpretability
|
|
331
|
+
all_attention_weights = []
|
|
332
|
+
|
|
333
|
+
# Atom-level message passing with GRU
|
|
334
|
+
for conv, gru in zip(self.atom_convs, self.atom_grus):
|
|
335
|
+
# Graph attention convolution
|
|
336
|
+
h_new, attn_weights = conv(h, adjacency, edge_features)
|
|
337
|
+
all_attention_weights.append(attn_weights)
|
|
338
|
+
|
|
339
|
+
if self.dropout is not None:
|
|
340
|
+
h_new = self.dropout(h_new)
|
|
341
|
+
|
|
342
|
+
# GRU update: h_new is input, h is hidden state
|
|
343
|
+
# GRUCell returns (new_carry, output) tuple - we use new_carry as next h
|
|
344
|
+
h, _ = gru(h, h_new)
|
|
345
|
+
|
|
346
|
+
# Apply node mask
|
|
347
|
+
if node_mask is not None:
|
|
348
|
+
h = h * node_mask[:, None]
|
|
349
|
+
|
|
350
|
+
# Molecule-level aggregation with attention
|
|
351
|
+
# Compute attention scores for global pooling
|
|
352
|
+
attn_scores = self.mol_attn(h).squeeze(-1) # (N,)
|
|
353
|
+
if node_mask is not None:
|
|
354
|
+
attn_scores = jnp.where(node_mask > 0, attn_scores, -1e9)
|
|
355
|
+
attn_probs = jax.nn.softmax(attn_scores) # (N,)
|
|
356
|
+
|
|
357
|
+
# Weighted sum for initial molecule representation
|
|
358
|
+
mol_repr = jnp.einsum("n,nd->d", attn_probs, h) # (hidden_dim,)
|
|
359
|
+
|
|
360
|
+
# GRU refinement at molecule level
|
|
361
|
+
for mol_gru in self.mol_grus:
|
|
362
|
+
# Use atom representations as context
|
|
363
|
+
context = jnp.einsum("n,nd->d", attn_probs, h)
|
|
364
|
+
# GRUCell returns (new_carry, output) tuple
|
|
365
|
+
mol_repr, _ = mol_gru(mol_repr, context)
|
|
366
|
+
|
|
367
|
+
# Final projection
|
|
368
|
+
fingerprint = self.output_proj(mol_repr)
|
|
369
|
+
|
|
370
|
+
if self.dropout is not None:
|
|
371
|
+
fingerprint = self.dropout(fingerprint)
|
|
372
|
+
|
|
373
|
+
result = {
|
|
374
|
+
**data,
|
|
375
|
+
"fingerprint": fingerprint,
|
|
376
|
+
"attention_weights": all_attention_weights,
|
|
377
|
+
"molecule_attention": attn_probs,
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return result, state, metadata
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def create_attentive_fp(
|
|
384
|
+
hidden_dim: int = 200,
|
|
385
|
+
out_dim: int = 200,
|
|
386
|
+
num_layers: int = 2,
|
|
387
|
+
num_timesteps: int = 2,
|
|
388
|
+
dropout_rate: float = 0.0,
|
|
389
|
+
seed: int = 42,
|
|
390
|
+
) -> AttentiveFP:
|
|
391
|
+
"""Create an AttentiveFP operator.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
hidden_dim: Hidden dimension for GNN layers.
|
|
395
|
+
out_dim: Output fingerprint dimension.
|
|
396
|
+
num_layers: Number of atom-level attention layers.
|
|
397
|
+
num_timesteps: Number of molecule-level GRU iterations.
|
|
398
|
+
dropout_rate: Dropout rate.
|
|
399
|
+
seed: Random seed.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Configured AttentiveFP.
|
|
403
|
+
"""
|
|
404
|
+
config = AttentiveFPConfig(
|
|
405
|
+
hidden_dim=hidden_dim,
|
|
406
|
+
out_dim=out_dim,
|
|
407
|
+
num_layers=num_layers,
|
|
408
|
+
num_timesteps=num_timesteps,
|
|
409
|
+
dropout_rate=dropout_rate,
|
|
410
|
+
)
|
|
411
|
+
return AttentiveFP(config, rngs=nnx.Rngs(seed))
|