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,493 @@
|
|
|
1
|
+
"""MolNet benchmark data source for drug discovery.
|
|
2
|
+
|
|
3
|
+
This module provides MolNetSource for loading MoleculeNet benchmark datasets:
|
|
4
|
+
- BBBP (Blood-Brain Barrier Penetration)
|
|
5
|
+
- Tox21 (Toxicity)
|
|
6
|
+
- ESOL (Solubility)
|
|
7
|
+
- FreeSolv (Solvation Energy)
|
|
8
|
+
- Lipophilicity
|
|
9
|
+
- And more...
|
|
10
|
+
|
|
11
|
+
Reference:
|
|
12
|
+
Wu et al. "MoleculeNet: A Benchmark for Molecular Machine Learning"
|
|
13
|
+
Chemical Science, 2018.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import csv
|
|
17
|
+
import gzip
|
|
18
|
+
import logging
|
|
19
|
+
import shutil
|
|
20
|
+
import urllib.error
|
|
21
|
+
import urllib.request
|
|
22
|
+
import warnings
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Literal
|
|
27
|
+
|
|
28
|
+
import jax.numpy as jnp
|
|
29
|
+
from flax import nnx
|
|
30
|
+
|
|
31
|
+
from datarax.core.config import StructuralConfig
|
|
32
|
+
from datarax.core.data_source import DataSourceModule
|
|
33
|
+
from datarax.typing import Element
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# MolNet dataset catalog with download URLs and metadata
|
|
38
|
+
# URLs point to DeepChem's hosted data files
|
|
39
|
+
MOLNET_DATASETS: dict[str, dict] = {
|
|
40
|
+
# ADMET datasets
|
|
41
|
+
"bbbp": {
|
|
42
|
+
"task_type": "classification",
|
|
43
|
+
"n_tasks": 1,
|
|
44
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/BBBP.csv",
|
|
45
|
+
"smiles_col": "smiles",
|
|
46
|
+
"label_cols": ["p_np"],
|
|
47
|
+
},
|
|
48
|
+
"tox21": {
|
|
49
|
+
"task_type": "classification",
|
|
50
|
+
"n_tasks": 12,
|
|
51
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/tox21.csv.gz",
|
|
52
|
+
"smiles_col": "smiles",
|
|
53
|
+
"label_cols": [
|
|
54
|
+
"NR-AR",
|
|
55
|
+
"NR-AR-LBD",
|
|
56
|
+
"NR-AhR",
|
|
57
|
+
"NR-Aromatase",
|
|
58
|
+
"NR-ER",
|
|
59
|
+
"NR-ER-LBD",
|
|
60
|
+
"NR-PPAR-gamma",
|
|
61
|
+
"SR-ARE",
|
|
62
|
+
"SR-ATAD5",
|
|
63
|
+
"SR-HSE",
|
|
64
|
+
"SR-MMP",
|
|
65
|
+
"SR-p53",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
# Physiology datasets
|
|
69
|
+
"esol": {
|
|
70
|
+
"task_type": "regression",
|
|
71
|
+
"n_tasks": 1,
|
|
72
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/delaney-processed.csv",
|
|
73
|
+
"smiles_col": "smiles",
|
|
74
|
+
"label_cols": ["measured log solubility in mols per litre"],
|
|
75
|
+
},
|
|
76
|
+
"freesolv": {
|
|
77
|
+
"task_type": "regression",
|
|
78
|
+
"n_tasks": 1,
|
|
79
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/SAMPL.csv",
|
|
80
|
+
"smiles_col": "smiles",
|
|
81
|
+
"label_cols": ["expt"],
|
|
82
|
+
},
|
|
83
|
+
"lipophilicity": {
|
|
84
|
+
"task_type": "regression",
|
|
85
|
+
"n_tasks": 1,
|
|
86
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/Lipophilicity.csv",
|
|
87
|
+
"smiles_col": "smiles",
|
|
88
|
+
"label_cols": ["exp"],
|
|
89
|
+
},
|
|
90
|
+
# HIV dataset
|
|
91
|
+
"hiv": {
|
|
92
|
+
"task_type": "classification",
|
|
93
|
+
"n_tasks": 1,
|
|
94
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/HIV.csv",
|
|
95
|
+
"smiles_col": "smiles",
|
|
96
|
+
"label_cols": ["HIV_active"],
|
|
97
|
+
},
|
|
98
|
+
# BACE dataset
|
|
99
|
+
"bace": {
|
|
100
|
+
"task_type": "classification",
|
|
101
|
+
"n_tasks": 1,
|
|
102
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/bace.csv",
|
|
103
|
+
"smiles_col": "mol",
|
|
104
|
+
"label_cols": ["Class"],
|
|
105
|
+
},
|
|
106
|
+
# ClinTox dataset
|
|
107
|
+
"clintox": {
|
|
108
|
+
"task_type": "classification",
|
|
109
|
+
"n_tasks": 2,
|
|
110
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/clintox.csv.gz",
|
|
111
|
+
"smiles_col": "smiles",
|
|
112
|
+
"label_cols": ["FDA_APPROVED", "CT_TOX"],
|
|
113
|
+
},
|
|
114
|
+
# SIDER dataset
|
|
115
|
+
"sider": {
|
|
116
|
+
"task_type": "classification",
|
|
117
|
+
"n_tasks": 27,
|
|
118
|
+
"url": "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/sider.csv.gz",
|
|
119
|
+
"smiles_col": "smiles",
|
|
120
|
+
"label_cols": None, # All columns except smiles are labels
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Compact synthetic fallback used when network is unavailable and no cache exists.
|
|
125
|
+
_FALLBACK_MOLNET_SMILES: tuple[str, ...] = (
|
|
126
|
+
"CCO",
|
|
127
|
+
"CCN",
|
|
128
|
+
"CCC",
|
|
129
|
+
"CCCl",
|
|
130
|
+
"CCBr",
|
|
131
|
+
"CC(C)O",
|
|
132
|
+
"CC(C)N",
|
|
133
|
+
"c1ccccc1",
|
|
134
|
+
"c1ccncc1",
|
|
135
|
+
"CCOC(=O)C",
|
|
136
|
+
"CC(=O)O",
|
|
137
|
+
"CC(=O)N",
|
|
138
|
+
"CCS",
|
|
139
|
+
"CCP",
|
|
140
|
+
"COC",
|
|
141
|
+
"CN(C)C",
|
|
142
|
+
"CC(C)C",
|
|
143
|
+
"CC(C)(C)O",
|
|
144
|
+
"O=C(O)C",
|
|
145
|
+
"NCCO",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class MolNetSourceConfig(StructuralConfig):
|
|
151
|
+
"""Configuration for MolNet benchmark data source.
|
|
152
|
+
|
|
153
|
+
Attributes:
|
|
154
|
+
dataset_name: Name of the MolNet dataset (e.g., "bbbp", "tox21", "esol")
|
|
155
|
+
split: Which split to load ("train", "valid", or "test")
|
|
156
|
+
data_dir: Directory to store downloaded data (default: ~/.diffbio/molnet)
|
|
157
|
+
download: Whether to download if data not found (default: True)
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
dataset_name: str = ""
|
|
161
|
+
split: Literal["train", "valid", "test"] = "train"
|
|
162
|
+
data_dir: Path | None = None
|
|
163
|
+
download: bool = True
|
|
164
|
+
|
|
165
|
+
def __post_init__(self) -> None:
|
|
166
|
+
"""Validate configuration after initialization."""
|
|
167
|
+
super().__post_init__()
|
|
168
|
+
if not self.dataset_name:
|
|
169
|
+
raise ValueError("dataset_name is required")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class MolNetSource(DataSourceModule):
|
|
173
|
+
"""MolNet benchmark data source extending Datarax DataSourceModule.
|
|
174
|
+
|
|
175
|
+
Provides standardized access to MoleculeNet benchmark datasets with proper
|
|
176
|
+
train/valid/test splits. Supports automatic downloading and caching.
|
|
177
|
+
|
|
178
|
+
Inherits from DataSourceModule (StructuralModule) because:
|
|
179
|
+
|
|
180
|
+
- Non-parametric: data loading is deterministic
|
|
181
|
+
- Frozen config: dataset parameters don't change
|
|
182
|
+
- Domain-specific: requires molecular data handling
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
```python
|
|
186
|
+
config = MolNetSourceConfig(dataset_name="bbbp", split="train")
|
|
187
|
+
source = MolNetSource(config)
|
|
188
|
+
for element in source:
|
|
189
|
+
print(element.data["smiles"], element.data["y"])
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
References:
|
|
193
|
+
Wu et al. "MoleculeNet: A Benchmark for Molecular Machine Learning"
|
|
194
|
+
Chemical Science, 2018.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
# Annotate data storage for Flax NNX
|
|
198
|
+
_data: list = nnx.data()
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
config: MolNetSourceConfig,
|
|
203
|
+
*,
|
|
204
|
+
rngs: nnx.Rngs | None = None,
|
|
205
|
+
name: str | None = None,
|
|
206
|
+
):
|
|
207
|
+
"""Initialize MolNetSource.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
config: MolNet source configuration
|
|
211
|
+
rngs: Random number generators (unused for data loading)
|
|
212
|
+
name: Optional module name
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
ValueError: If dataset_name is unknown
|
|
216
|
+
FileNotFoundError: If data not found and download=False
|
|
217
|
+
"""
|
|
218
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
219
|
+
|
|
220
|
+
# Validate dataset name
|
|
221
|
+
if config.dataset_name not in MOLNET_DATASETS:
|
|
222
|
+
available = ", ".join(sorted(MOLNET_DATASETS.keys()))
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Unknown dataset: '{config.dataset_name}'. Available datasets: {available}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Set up data directory
|
|
228
|
+
if config.data_dir is None:
|
|
229
|
+
self._data_dir = Path.home() / ".diffbio" / "molnet"
|
|
230
|
+
else:
|
|
231
|
+
self._data_dir = Path(config.data_dir)
|
|
232
|
+
|
|
233
|
+
# Load the dataset
|
|
234
|
+
self._data = self._load_dataset()
|
|
235
|
+
self._current_idx = 0
|
|
236
|
+
|
|
237
|
+
def _load_dataset(self) -> list[Element]:
|
|
238
|
+
"""Load the MolNet dataset.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
List of Element objects containing SMILES and labels
|
|
242
|
+
"""
|
|
243
|
+
dataset_info = MOLNET_DATASETS[self.config.dataset_name]
|
|
244
|
+
data_path = self._get_data_path()
|
|
245
|
+
|
|
246
|
+
self._ensure_dataset_file(data_path, dataset_info)
|
|
247
|
+
|
|
248
|
+
# Parse CSV file
|
|
249
|
+
return self._parse_csv(data_path, dataset_info)
|
|
250
|
+
|
|
251
|
+
def _ensure_dataset_file(self, data_path: Path, dataset_info: dict) -> None:
|
|
252
|
+
"""Ensure the dataset file exists locally."""
|
|
253
|
+
if data_path.exists():
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
if not self.config.download:
|
|
257
|
+
raise FileNotFoundError(
|
|
258
|
+
f"Dataset file not found: {data_path}. Set download=True to download automatically."
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if self._copy_from_default_cache(data_path):
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
self._download_dataset(dataset_info["url"], data_path)
|
|
266
|
+
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
|
267
|
+
self._write_builtin_fallback_dataset(data_path, dataset_info)
|
|
268
|
+
warnings.warn(
|
|
269
|
+
"Unable to download MolNet dataset "
|
|
270
|
+
f"'{self.config.dataset_name}' ({exc!r}); using a built-in fallback sample.",
|
|
271
|
+
RuntimeWarning,
|
|
272
|
+
stacklevel=2,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _get_data_path(self) -> Path:
|
|
276
|
+
"""Get the path to the dataset file."""
|
|
277
|
+
url = MOLNET_DATASETS[self.config.dataset_name]["url"]
|
|
278
|
+
filename = url.split("/")[-1]
|
|
279
|
+
return self._data_dir / self.config.dataset_name / filename
|
|
280
|
+
|
|
281
|
+
def _copy_from_default_cache(self, data_path: Path) -> bool:
|
|
282
|
+
"""Copy from shared ~/.diffbio cache when using a custom data_dir."""
|
|
283
|
+
default_cache_path = (
|
|
284
|
+
Path.home() / ".diffbio" / "molnet" / self.config.dataset_name / data_path.name
|
|
285
|
+
)
|
|
286
|
+
if default_cache_path == data_path or not default_cache_path.exists():
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
shutil.copy2(default_cache_path, data_path)
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
def _download_dataset(self, url: str, data_path: Path) -> None:
|
|
294
|
+
"""Download dataset from URL.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
url: URL to download from
|
|
298
|
+
data_path: Local path to save to
|
|
299
|
+
"""
|
|
300
|
+
from urllib.parse import urlparse
|
|
301
|
+
|
|
302
|
+
# Validate URL scheme for security
|
|
303
|
+
parsed = urlparse(url)
|
|
304
|
+
if parsed.scheme not in ("http", "https"):
|
|
305
|
+
raise ValueError(f"Invalid URL scheme: {parsed.scheme}. Only http/https allowed.")
|
|
306
|
+
|
|
307
|
+
# Create directory
|
|
308
|
+
data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
|
|
310
|
+
# Download file
|
|
311
|
+
urllib.request.urlretrieve(url, data_path) # nosec B310
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _fallback_label_values(row_idx: int, n_labels: int, task_type: str) -> list[str]:
|
|
315
|
+
"""Generate deterministic fallback labels by task type."""
|
|
316
|
+
if task_type == "classification":
|
|
317
|
+
return [str((row_idx + col_idx) % 2) for col_idx in range(n_labels)]
|
|
318
|
+
|
|
319
|
+
base = -2.0 + (0.15 * row_idx)
|
|
320
|
+
return [f"{base + (0.01 * col_idx):.3f}" for col_idx in range(n_labels)]
|
|
321
|
+
|
|
322
|
+
def _write_builtin_fallback_dataset(self, data_path: Path, dataset_info: dict) -> None:
|
|
323
|
+
"""Write a small synthetic dataset to support offline execution."""
|
|
324
|
+
label_cols = dataset_info["label_cols"]
|
|
325
|
+
resolved_label_cols = (
|
|
326
|
+
[f"task_{idx}" for idx in range(dataset_info["n_tasks"])]
|
|
327
|
+
if label_cols is None
|
|
328
|
+
else list(label_cols)
|
|
329
|
+
)
|
|
330
|
+
header = [dataset_info["smiles_col"], *resolved_label_cols]
|
|
331
|
+
|
|
332
|
+
rows: list[list[str]] = []
|
|
333
|
+
for row_idx, smiles in enumerate(_FALLBACK_MOLNET_SMILES):
|
|
334
|
+
labels = self._fallback_label_values(
|
|
335
|
+
row_idx, len(resolved_label_cols), dataset_info["task_type"]
|
|
336
|
+
)
|
|
337
|
+
rows.append([smiles, *labels])
|
|
338
|
+
|
|
339
|
+
data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
340
|
+
if str(data_path).endswith(".gz"):
|
|
341
|
+
with gzip.open(data_path, "wt", newline="", encoding="utf-8") as file_handle:
|
|
342
|
+
writer = csv.writer(file_handle)
|
|
343
|
+
writer.writerow(header)
|
|
344
|
+
writer.writerows(rows)
|
|
345
|
+
else:
|
|
346
|
+
with open(data_path, "w", newline="", encoding="utf-8") as file_handle:
|
|
347
|
+
writer = csv.writer(file_handle)
|
|
348
|
+
writer.writerow(header)
|
|
349
|
+
writer.writerows(rows)
|
|
350
|
+
|
|
351
|
+
def _read_rows_and_labels(
|
|
352
|
+
self,
|
|
353
|
+
data_path: Path,
|
|
354
|
+
smiles_col: str,
|
|
355
|
+
label_cols: list[str] | None,
|
|
356
|
+
) -> tuple[list[dict[str, str]], list[str]]:
|
|
357
|
+
"""Read CSV rows and resolve label columns.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
data_path: Path to CSV file (optionally gzipped).
|
|
361
|
+
smiles_col: Name of the SMILES column.
|
|
362
|
+
label_cols: Explicit label columns, or None to infer.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Tuple of (all_rows, resolved_label_columns).
|
|
366
|
+
"""
|
|
367
|
+
import contextlib
|
|
368
|
+
import gzip
|
|
369
|
+
|
|
370
|
+
with contextlib.ExitStack() as stack:
|
|
371
|
+
if str(data_path).endswith(".gz"):
|
|
372
|
+
file_handle = stack.enter_context(
|
|
373
|
+
gzip.open(data_path, "rt", newline="", encoding="utf-8")
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
file_handle = stack.enter_context(
|
|
377
|
+
open(data_path, newline="", encoding="utf-8") # noqa: SIM115
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
reader = csv.DictReader(file_handle)
|
|
381
|
+
all_rows: list[dict[str, str]] = list(reader)
|
|
382
|
+
|
|
383
|
+
resolved_labels = (
|
|
384
|
+
[c for c in all_rows[0].keys() if c != smiles_col]
|
|
385
|
+
if label_cols is None and all_rows
|
|
386
|
+
else label_cols
|
|
387
|
+
)
|
|
388
|
+
return all_rows, ([] if resolved_labels is None else list(resolved_labels))
|
|
389
|
+
|
|
390
|
+
def _rows_for_split(self, all_rows: list[dict[str, str]]) -> list[dict[str, str]]:
|
|
391
|
+
"""Select rows for the configured split."""
|
|
392
|
+
n_total = len(all_rows)
|
|
393
|
+
n_train = int(0.8 * n_total)
|
|
394
|
+
n_valid = int(0.1 * n_total)
|
|
395
|
+
|
|
396
|
+
if self.config.split == "train":
|
|
397
|
+
return all_rows[:n_train]
|
|
398
|
+
if self.config.split == "valid":
|
|
399
|
+
return all_rows[n_train : n_train + n_valid]
|
|
400
|
+
return all_rows[n_train + n_valid :]
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def _parse_float_or_nan(value: str) -> float:
|
|
404
|
+
"""Parse a float value, falling back to NaN for empty/invalid values."""
|
|
405
|
+
try:
|
|
406
|
+
return float(value) if value else float("nan")
|
|
407
|
+
except ValueError:
|
|
408
|
+
return float("nan")
|
|
409
|
+
|
|
410
|
+
def _parse_labels(self, row: dict[str, str], label_cols: list[str]) -> float | jnp.ndarray:
|
|
411
|
+
"""Parse one or multiple label columns from a CSV row."""
|
|
412
|
+
if len(label_cols) == 1:
|
|
413
|
+
return self._parse_float_or_nan(row.get(label_cols[0], ""))
|
|
414
|
+
values = [self._parse_float_or_nan(row.get(col, "")) for col in label_cols]
|
|
415
|
+
return jnp.array(values)
|
|
416
|
+
|
|
417
|
+
def _parse_csv(self, data_path: Path, dataset_info: dict) -> list[Element]:
|
|
418
|
+
"""Parse CSV file into Elements.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
data_path: Path to CSV file
|
|
422
|
+
dataset_info: Dataset metadata
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
List of Element objects
|
|
426
|
+
"""
|
|
427
|
+
smiles_col = dataset_info["smiles_col"]
|
|
428
|
+
label_cols = dataset_info["label_cols"]
|
|
429
|
+
|
|
430
|
+
all_rows, resolved_label_cols = self._read_rows_and_labels(
|
|
431
|
+
data_path, smiles_col, label_cols
|
|
432
|
+
)
|
|
433
|
+
rows = self._rows_for_split(all_rows)
|
|
434
|
+
elements: list[Element] = []
|
|
435
|
+
|
|
436
|
+
for idx, row in enumerate(rows):
|
|
437
|
+
smiles = row.get(smiles_col, "")
|
|
438
|
+
if not smiles:
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
y = self._parse_labels(row, resolved_label_cols)
|
|
442
|
+
|
|
443
|
+
element = Element(
|
|
444
|
+
data={"smiles": smiles, "y": y},
|
|
445
|
+
state={},
|
|
446
|
+
metadata={ # pyright: ignore[reportArgumentType]
|
|
447
|
+
"idx": idx,
|
|
448
|
+
"dataset": self.config.dataset_name,
|
|
449
|
+
},
|
|
450
|
+
)
|
|
451
|
+
elements.append(element)
|
|
452
|
+
|
|
453
|
+
return elements
|
|
454
|
+
|
|
455
|
+
def __len__(self) -> int:
|
|
456
|
+
"""Return the number of elements in the source."""
|
|
457
|
+
return len(self._data)
|
|
458
|
+
|
|
459
|
+
def __getitem__(self, idx: int) -> Element | None:
|
|
460
|
+
"""Get element by index.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
idx: Index of the element
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Element at the given index, or None if out of bounds
|
|
467
|
+
"""
|
|
468
|
+
if idx < 0 or idx >= len(self._data):
|
|
469
|
+
return None
|
|
470
|
+
return self._data[idx]
|
|
471
|
+
|
|
472
|
+
def __iter__(self) -> Iterator[Element]: # type: ignore[override]
|
|
473
|
+
"""Return iterator over elements."""
|
|
474
|
+
self._current_idx = 0
|
|
475
|
+
return self
|
|
476
|
+
|
|
477
|
+
def __next__(self) -> Element:
|
|
478
|
+
"""Get next element in iteration."""
|
|
479
|
+
if self._current_idx >= len(self._data):
|
|
480
|
+
raise StopIteration
|
|
481
|
+
elem = self._data[self._current_idx]
|
|
482
|
+
self._current_idx += 1
|
|
483
|
+
return elem
|
|
484
|
+
|
|
485
|
+
@property
|
|
486
|
+
def task_type(self) -> str:
|
|
487
|
+
"""Get the task type for this dataset."""
|
|
488
|
+
return MOLNET_DATASETS[self.config.dataset_name]["task_type"]
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def n_tasks(self) -> int:
|
|
492
|
+
"""Get the number of tasks for this dataset."""
|
|
493
|
+
return MOLNET_DATASETS[self.config.dataset_name]["n_tasks"]
|