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,217 @@
|
|
|
1
|
+
"""Random and stratified splitters for DiffBio.
|
|
2
|
+
|
|
3
|
+
This module provides random splitting utilities:
|
|
4
|
+
- RandomSplitter: Simple random permutation-based splitting
|
|
5
|
+
- StratifiedSplitter: Stratified splitting preserving class distribution
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
import jax
|
|
12
|
+
import jax.numpy as jnp
|
|
13
|
+
from flax import nnx
|
|
14
|
+
|
|
15
|
+
from datarax.core.data_source import DataSourceModule
|
|
16
|
+
|
|
17
|
+
from diffbio.splitters.base import SplitResult, SplitterConfig, SplitterModule
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RandomSplitterConfig(SplitterConfig):
|
|
24
|
+
"""Configuration for random splitter.
|
|
25
|
+
|
|
26
|
+
Inherits all fields from SplitterConfig:
|
|
27
|
+
- train_frac: Fraction of data for training (default: 0.8)
|
|
28
|
+
- valid_frac: Fraction of data for validation (default: 0.1)
|
|
29
|
+
- test_frac: Fraction of data for testing (default: 0.1)
|
|
30
|
+
- seed: Random seed for reproducibility (optional)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RandomSplitter(SplitterModule):
|
|
37
|
+
"""Simple random splitting using JAX RNG.
|
|
38
|
+
|
|
39
|
+
Uses JAX random permutation for reproducible splits.
|
|
40
|
+
All data points are randomly assigned to train/valid/test sets
|
|
41
|
+
according to the configured fractions.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
config = RandomSplitterConfig(train_frac=0.8, valid_frac=0.1, test_frac=0.1, seed=42)
|
|
46
|
+
splitter = RandomSplitter(config)
|
|
47
|
+
result = splitter.split(data_source)
|
|
48
|
+
print(f"Train size: {result.train_size}")
|
|
49
|
+
```
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
config: RandomSplitterConfig,
|
|
55
|
+
*,
|
|
56
|
+
rngs: nnx.Rngs | None = None,
|
|
57
|
+
name: str | None = None,
|
|
58
|
+
):
|
|
59
|
+
"""Initialize RandomSplitter.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
config: Random splitter configuration
|
|
63
|
+
rngs: Random number generators
|
|
64
|
+
name: Optional module name
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
67
|
+
|
|
68
|
+
def split(self, data_source: DataSourceModule) -> SplitResult:
|
|
69
|
+
"""Split data source randomly.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
data_source: Datarax DataSourceModule to split
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
SplitResult with randomly assigned train/valid/test indices
|
|
76
|
+
"""
|
|
77
|
+
n = len(data_source)
|
|
78
|
+
train_end = int(self.config.train_frac * n)
|
|
79
|
+
valid_end = int((self.config.train_frac + self.config.valid_frac) * n)
|
|
80
|
+
|
|
81
|
+
# Use JAX RNG for reproducibility
|
|
82
|
+
if self.config.seed is not None:
|
|
83
|
+
key = jax.random.key(self.config.seed)
|
|
84
|
+
elif self.rngs is not None and "split" in self.rngs:
|
|
85
|
+
key = self.rngs.split()
|
|
86
|
+
else:
|
|
87
|
+
key = jax.random.key(0)
|
|
88
|
+
|
|
89
|
+
indices = jax.random.permutation(key, jnp.arange(n))
|
|
90
|
+
|
|
91
|
+
return SplitResult(
|
|
92
|
+
train_indices=indices[:train_end],
|
|
93
|
+
valid_indices=indices[train_end:valid_end],
|
|
94
|
+
test_indices=indices[valid_end:],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def k_fold_split(
|
|
98
|
+
self, data_source: DataSourceModule, k: int = 5
|
|
99
|
+
) -> list[tuple[jnp.ndarray, jnp.ndarray]]:
|
|
100
|
+
"""K-fold cross-validation split.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
data_source: Datarax DataSourceModule to split
|
|
104
|
+
k: Number of folds
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of (train_indices, val_indices) tuples for each fold
|
|
108
|
+
"""
|
|
109
|
+
n = len(data_source)
|
|
110
|
+
|
|
111
|
+
if self.config.seed is not None:
|
|
112
|
+
key = jax.random.key(self.config.seed)
|
|
113
|
+
else:
|
|
114
|
+
key = jax.random.key(0)
|
|
115
|
+
|
|
116
|
+
indices = jax.random.permutation(key, jnp.arange(n))
|
|
117
|
+
fold_size = n // k
|
|
118
|
+
|
|
119
|
+
folds = []
|
|
120
|
+
for i in range(k):
|
|
121
|
+
val_start = i * fold_size
|
|
122
|
+
val_end = (i + 1) * fold_size if i < k - 1 else n
|
|
123
|
+
|
|
124
|
+
val_indices = indices[val_start:val_end]
|
|
125
|
+
train_indices = jnp.concatenate([indices[:val_start], indices[val_end:]])
|
|
126
|
+
folds.append((train_indices, val_indices))
|
|
127
|
+
|
|
128
|
+
return folds
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class StratifiedSplitterConfig(SplitterConfig):
|
|
133
|
+
"""Configuration for stratified splitter.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
label_key: Key in data element containing labels (default: "y")
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
label_key: str = "y"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class StratifiedSplitter(SplitterModule):
|
|
143
|
+
"""Stratified splitting that preserves class distribution.
|
|
144
|
+
|
|
145
|
+
Ensures each split has approximately the same class distribution
|
|
146
|
+
as the original dataset. Useful for imbalanced classification tasks.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
```python
|
|
150
|
+
config = StratifiedSplitterConfig(seed=42, label_key="target")
|
|
151
|
+
splitter = StratifiedSplitter(config)
|
|
152
|
+
result = splitter.split(data_source)
|
|
153
|
+
```
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
config: StratifiedSplitterConfig,
|
|
159
|
+
*,
|
|
160
|
+
rngs: nnx.Rngs | None = None,
|
|
161
|
+
name: str | None = None,
|
|
162
|
+
):
|
|
163
|
+
"""Initialize StratifiedSplitter.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
config: Stratified splitter configuration
|
|
167
|
+
rngs: Random number generators
|
|
168
|
+
name: Optional module name
|
|
169
|
+
"""
|
|
170
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
171
|
+
|
|
172
|
+
def split(self, data_source: DataSourceModule) -> SplitResult:
|
|
173
|
+
"""Split preserving class distribution.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
data_source: Datarax DataSourceModule to split
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
SplitResult with stratified train/valid/test indices
|
|
180
|
+
"""
|
|
181
|
+
# Extract labels from data source
|
|
182
|
+
labels = jnp.array(
|
|
183
|
+
[data_source[i].data[self.config.label_key] for i in range(len(data_source))]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Group indices by class
|
|
187
|
+
unique_labels = jnp.unique(labels)
|
|
188
|
+
class_indices = {int(label): jnp.where(labels == label)[0] for label in unique_labels}
|
|
189
|
+
|
|
190
|
+
# Use JAX RNG
|
|
191
|
+
if self.config.seed is not None:
|
|
192
|
+
key = jax.random.key(self.config.seed)
|
|
193
|
+
else:
|
|
194
|
+
key = jax.random.key(0)
|
|
195
|
+
|
|
196
|
+
train_inds: list[jnp.ndarray] = []
|
|
197
|
+
valid_inds: list[jnp.ndarray] = []
|
|
198
|
+
test_inds: list[jnp.ndarray] = []
|
|
199
|
+
|
|
200
|
+
for _label, indices in class_indices.items():
|
|
201
|
+
key, subkey = jax.random.split(key)
|
|
202
|
+
shuffled = jax.random.permutation(subkey, indices)
|
|
203
|
+
|
|
204
|
+
n_class = len(shuffled)
|
|
205
|
+
train_end = int(self.config.train_frac * n_class)
|
|
206
|
+
valid_end = int((self.config.train_frac + self.config.valid_frac) * n_class)
|
|
207
|
+
|
|
208
|
+
train_inds.append(shuffled[:train_end])
|
|
209
|
+
valid_inds.append(shuffled[train_end:valid_end])
|
|
210
|
+
test_inds.append(shuffled[valid_end:])
|
|
211
|
+
|
|
212
|
+
empty = jnp.array([], dtype=jnp.int32)
|
|
213
|
+
return SplitResult(
|
|
214
|
+
train_indices=jnp.concatenate(train_inds) if train_inds else empty,
|
|
215
|
+
valid_indices=jnp.concatenate(valid_inds) if valid_inds else empty,
|
|
216
|
+
test_indices=jnp.concatenate(test_inds) if test_inds else empty,
|
|
217
|
+
)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Sequence identity splitter for bioinformatics applications.
|
|
2
|
+
|
|
3
|
+
This module provides sequence-aware splitting utilities:
|
|
4
|
+
- SequenceIdentitySplitter: Split by sequence identity clustering
|
|
5
|
+
|
|
6
|
+
For genomics/proteomics applications where similar sequences
|
|
7
|
+
should not appear in both train and test sets.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Sequence
|
|
13
|
+
|
|
14
|
+
from flax import nnx
|
|
15
|
+
|
|
16
|
+
from datarax.core.data_source import DataSourceModule
|
|
17
|
+
|
|
18
|
+
from diffbio.splitters.base import SplitResult, SplitterConfig, SplitterModule
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SequenceIdentitySplitterConfig(SplitterConfig):
|
|
25
|
+
"""Configuration for sequence identity splitter.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
sequence_key: Key in data element containing sequence string (default: "sequence")
|
|
29
|
+
identity_threshold: Identity threshold for clustering (default: 0.3)
|
|
30
|
+
Sequences with identity > threshold are clustered together.
|
|
31
|
+
alignment_method: Method for identity computation ("simple" or "mmseqs2")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
sequence_key: str = "sequence"
|
|
35
|
+
identity_threshold: float = 0.3
|
|
36
|
+
alignment_method: str = "simple"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SequenceIdentitySplitter(SplitterModule):
|
|
40
|
+
"""Split sequences by identity threshold.
|
|
41
|
+
|
|
42
|
+
Groups similar sequences together using identity clustering,
|
|
43
|
+
then assigns clusters to train/valid/test to ensure structural
|
|
44
|
+
diversity between splits. This prevents data leakage from
|
|
45
|
+
similar sequences appearing in different splits.
|
|
46
|
+
|
|
47
|
+
Inherits from SplitterModule (StructuralModule) because:
|
|
48
|
+
|
|
49
|
+
- Non-parametric: clustering is deterministic
|
|
50
|
+
- Frozen config: splitting strategy doesn't change
|
|
51
|
+
- Domain-specific: requires sequence comparison
|
|
52
|
+
|
|
53
|
+
Similar to CD-HIT or MMseqs2 clustering approach.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
```python
|
|
57
|
+
config = SequenceIdentitySplitterConfig(identity_threshold=0.3)
|
|
58
|
+
splitter = SequenceIdentitySplitter(config)
|
|
59
|
+
result = splitter.split(sequence_source)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
References:
|
|
63
|
+
Li, Weizhong, and Adam Godzik. "Cd-hit: a fast program for clustering
|
|
64
|
+
and comparing large sets of protein or nucleotide sequences."
|
|
65
|
+
Bioinformatics 22.13 (2006): 1658-1659.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
config: SequenceIdentitySplitterConfig,
|
|
71
|
+
*,
|
|
72
|
+
rngs: nnx.Rngs | None = None,
|
|
73
|
+
name: str | None = None,
|
|
74
|
+
):
|
|
75
|
+
"""Initialize SequenceIdentitySplitter.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config: Sequence identity splitter configuration
|
|
79
|
+
rngs: Random number generators (unused for identity splitting)
|
|
80
|
+
name: Optional module name
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
83
|
+
|
|
84
|
+
def _compute_identity(self, seq1: str, seq2: str) -> float:
|
|
85
|
+
"""Compute sequence identity between two sequences.
|
|
86
|
+
|
|
87
|
+
Uses simple character matching. For unequal lengths,
|
|
88
|
+
compares up to the length of the shorter sequence.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
seq1: First sequence
|
|
92
|
+
seq2: Second sequence
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Identity fraction between 0.0 and 1.0
|
|
96
|
+
"""
|
|
97
|
+
if not seq1 or not seq2:
|
|
98
|
+
return 0.0
|
|
99
|
+
|
|
100
|
+
# Use shorter sequence for comparison
|
|
101
|
+
min_len = min(len(seq1), len(seq2))
|
|
102
|
+
seq1 = seq1[:min_len]
|
|
103
|
+
seq2 = seq2[:min_len]
|
|
104
|
+
|
|
105
|
+
if not seq1:
|
|
106
|
+
return 0.0
|
|
107
|
+
|
|
108
|
+
matches = sum(c1 == c2 for c1, c2 in zip(seq1, seq2))
|
|
109
|
+
return matches / len(seq1)
|
|
110
|
+
|
|
111
|
+
def _cluster_by_identity(self, sequences: Sequence[str]) -> list[list[int]]:
|
|
112
|
+
"""Cluster sequences by identity threshold.
|
|
113
|
+
|
|
114
|
+
Uses greedy clustering: each sequence joins the first cluster
|
|
115
|
+
where it has identity > threshold with the representative.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
sequences: List of sequence strings
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of clusters, each cluster is a list of sequence indices
|
|
122
|
+
"""
|
|
123
|
+
if self.config.alignment_method == "simple":
|
|
124
|
+
return self._simple_clustering(sequences)
|
|
125
|
+
elif self.config.alignment_method == "mmseqs2":
|
|
126
|
+
return self._mmseqs2_clustering(sequences)
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f"Unknown alignment method: {self.config.alignment_method}")
|
|
129
|
+
|
|
130
|
+
def _simple_clustering(self, sequences: Sequence[str]) -> list[list[int]]:
|
|
131
|
+
"""Simple greedy clustering by identity.
|
|
132
|
+
|
|
133
|
+
Each sequence joins the first cluster where it has
|
|
134
|
+
identity > threshold with the representative.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
sequences: List of sequence strings
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of clusters
|
|
141
|
+
"""
|
|
142
|
+
clusters: list[list[int]] = []
|
|
143
|
+
representatives: list[str] = []
|
|
144
|
+
|
|
145
|
+
for idx, seq in enumerate(sequences):
|
|
146
|
+
assigned = False
|
|
147
|
+
|
|
148
|
+
for cluster_idx, rep in enumerate(representatives):
|
|
149
|
+
identity = self._compute_identity(seq, rep)
|
|
150
|
+
if identity > self.config.identity_threshold:
|
|
151
|
+
clusters[cluster_idx].append(idx)
|
|
152
|
+
assigned = True
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
if not assigned:
|
|
156
|
+
clusters.append([idx])
|
|
157
|
+
representatives.append(seq)
|
|
158
|
+
|
|
159
|
+
return clusters
|
|
160
|
+
|
|
161
|
+
def _mmseqs2_clustering(self, sequences: Sequence[str]) -> list[list[int]]:
|
|
162
|
+
"""Use MMseqs2 for clustering.
|
|
163
|
+
|
|
164
|
+
Requires MMseqs2 installation.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
sequences: List of sequence strings
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of clusters
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
NotImplementedError: MMseqs2 integration not yet implemented
|
|
174
|
+
"""
|
|
175
|
+
raise NotImplementedError(
|
|
176
|
+
"MMseqs2 clustering requires external tool installation. "
|
|
177
|
+
"Use alignment_method='simple' for built-in clustering."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def split(self, data_source: DataSourceModule) -> SplitResult:
|
|
181
|
+
"""Split by sequence identity clustering.
|
|
182
|
+
|
|
183
|
+
Clusters sequences by identity, then assigns clusters
|
|
184
|
+
to train/valid/test splits. Largest clusters go to
|
|
185
|
+
train first.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
data_source: Datarax DataSourceModule to split
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
SplitResult with identity-based train/valid/test indices
|
|
192
|
+
"""
|
|
193
|
+
# Extract sequences from data source
|
|
194
|
+
sequences = [data_source[i].data[self.config.sequence_key] for i in range(len(data_source))]
|
|
195
|
+
|
|
196
|
+
# Cluster by identity
|
|
197
|
+
clusters = self._cluster_by_identity(sequences)
|
|
198
|
+
|
|
199
|
+
# Sort clusters by size (largest first)
|
|
200
|
+
sorted_clusters = sorted(clusters, key=len, reverse=True)
|
|
201
|
+
return self.assign_groups_to_splits(sorted_clusters, len(data_source))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Utility functions for DiffBio.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for I/O, encoding, training,
|
|
4
|
+
neural network building, and other common operations in bioinformatics pipelines.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from diffbio.utils.dependency_runtime import (
|
|
8
|
+
ECOSYSTEM_PACKAGES,
|
|
9
|
+
DependencyRuntimeRecord,
|
|
10
|
+
FNOConstructorContract,
|
|
11
|
+
collect_dependency_runtime,
|
|
12
|
+
inspect_fno_constructor,
|
|
13
|
+
verify_canonical_dependency_runtime,
|
|
14
|
+
)
|
|
15
|
+
from diffbio.utils.quality import apply_quality_filter
|
|
16
|
+
from diffbio.utils.nn_utils import (
|
|
17
|
+
ensure_rngs,
|
|
18
|
+
extract_windows_1d,
|
|
19
|
+
get_rng_key,
|
|
20
|
+
init_learnable_param,
|
|
21
|
+
)
|
|
22
|
+
from diffbio.utils.training import (
|
|
23
|
+
Trainer,
|
|
24
|
+
TrainingConfig,
|
|
25
|
+
TrainingState,
|
|
26
|
+
create_optax_optimizer,
|
|
27
|
+
create_synthetic_training_data,
|
|
28
|
+
cross_entropy_loss,
|
|
29
|
+
data_iterator,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Dependency runtime utilities
|
|
34
|
+
"ECOSYSTEM_PACKAGES",
|
|
35
|
+
"DependencyRuntimeRecord",
|
|
36
|
+
"FNOConstructorContract",
|
|
37
|
+
"collect_dependency_runtime",
|
|
38
|
+
"inspect_fno_constructor",
|
|
39
|
+
"verify_canonical_dependency_runtime",
|
|
40
|
+
# Training utilities
|
|
41
|
+
"Trainer",
|
|
42
|
+
"TrainingConfig",
|
|
43
|
+
"TrainingState",
|
|
44
|
+
"create_optax_optimizer",
|
|
45
|
+
"create_synthetic_training_data",
|
|
46
|
+
"cross_entropy_loss",
|
|
47
|
+
"data_iterator",
|
|
48
|
+
# Quality utilities
|
|
49
|
+
"apply_quality_filter",
|
|
50
|
+
# Neural network utilities
|
|
51
|
+
"ensure_rngs",
|
|
52
|
+
"extract_windows_1d",
|
|
53
|
+
"get_rng_key",
|
|
54
|
+
"init_learnable_param",
|
|
55
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Helpers for verifying the installed ecosystem runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import importlib
|
|
8
|
+
import inspect
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import site
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
|
|
13
|
+
ECOSYSTEM_PACKAGES: tuple[str, ...] = ("datarax", "artifex", "opifex", "calibrax")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class DependencyRuntimeRecord:
|
|
18
|
+
"""Resolved runtime provenance for one ecosystem package."""
|
|
19
|
+
|
|
20
|
+
package: str
|
|
21
|
+
module_file: str
|
|
22
|
+
installed_from_site_packages: bool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class FNOConstructorContract:
|
|
27
|
+
"""Observed constructor contract for the live Opifex FNO surface."""
|
|
28
|
+
|
|
29
|
+
import_path: str
|
|
30
|
+
constructor_signature: str
|
|
31
|
+
supports_spatial_dims: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _site_packages_roots() -> tuple[Path, ...]:
|
|
35
|
+
"""Return normalized site-packages roots for the active interpreter."""
|
|
36
|
+
return tuple(Path(root).resolve() for root in site.getsitepackages())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _resolve_module_file(module: ModuleType) -> Path:
|
|
40
|
+
"""Return the concrete module file for an imported package.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
RuntimeError: If the imported module does not expose a file path.
|
|
44
|
+
"""
|
|
45
|
+
module_file = getattr(module, "__file__", None)
|
|
46
|
+
if module_file is None:
|
|
47
|
+
msg = f"Imported module {module.__name__!r} does not expose __file__"
|
|
48
|
+
raise RuntimeError(msg)
|
|
49
|
+
return Path(module_file).resolve()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_in_site_packages(module_file: Path, site_roots: Sequence[Path]) -> bool:
|
|
53
|
+
"""Return whether a module file resolves under one of the site-packages roots."""
|
|
54
|
+
return any(module_file.is_relative_to(root) for root in site_roots)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def collect_dependency_runtime(
|
|
58
|
+
package_names: Sequence[str] = ECOSYSTEM_PACKAGES,
|
|
59
|
+
) -> dict[str, DependencyRuntimeRecord]:
|
|
60
|
+
"""Collect import provenance for the configured ecosystem packages."""
|
|
61
|
+
site_roots = _site_packages_roots()
|
|
62
|
+
runtime: dict[str, DependencyRuntimeRecord] = {}
|
|
63
|
+
|
|
64
|
+
for package_name in package_names:
|
|
65
|
+
module = importlib.import_module(package_name)
|
|
66
|
+
module_file = _resolve_module_file(module)
|
|
67
|
+
runtime[package_name] = DependencyRuntimeRecord(
|
|
68
|
+
package=package_name,
|
|
69
|
+
module_file=str(module_file),
|
|
70
|
+
installed_from_site_packages=_is_in_site_packages(module_file, site_roots),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return runtime
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def inspect_fno_constructor(
|
|
77
|
+
import_path: str = "opifex.neural.operators",
|
|
78
|
+
) -> FNOConstructorContract:
|
|
79
|
+
"""Inspect the live FourierNeuralOperator constructor contract."""
|
|
80
|
+
module = importlib.import_module(import_path)
|
|
81
|
+
constructor_signature = str(inspect.signature(module.FourierNeuralOperator.__init__))
|
|
82
|
+
return FNOConstructorContract(
|
|
83
|
+
import_path=import_path,
|
|
84
|
+
constructor_signature=constructor_signature,
|
|
85
|
+
supports_spatial_dims="spatial_dims" in constructor_signature,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def verify_canonical_dependency_runtime(
|
|
90
|
+
package_names: Sequence[str] = ECOSYSTEM_PACKAGES,
|
|
91
|
+
) -> tuple[dict[str, DependencyRuntimeRecord], FNOConstructorContract]:
|
|
92
|
+
"""Validate the canonical installed runtime contract for ecosystem dependencies.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
RuntimeError: If any ecosystem package resolves outside site-packages or
|
|
96
|
+
if the live Opifex FNO constructor lacks ``spatial_dims`` support.
|
|
97
|
+
"""
|
|
98
|
+
runtime = collect_dependency_runtime(package_names)
|
|
99
|
+
non_installed_packages = sorted(
|
|
100
|
+
package for package, record in runtime.items() if not record.installed_from_site_packages
|
|
101
|
+
)
|
|
102
|
+
if non_installed_packages:
|
|
103
|
+
package_list = ", ".join(non_installed_packages)
|
|
104
|
+
msg = (
|
|
105
|
+
"Canonical runtime must resolve ecosystem dependencies from installed "
|
|
106
|
+
f"site-packages; found non-installed imports for: {package_list}"
|
|
107
|
+
)
|
|
108
|
+
raise RuntimeError(msg)
|
|
109
|
+
|
|
110
|
+
fno_contract = inspect_fno_constructor()
|
|
111
|
+
if not fno_contract.supports_spatial_dims:
|
|
112
|
+
msg = "Live Opifex FourierNeuralOperator constructor does not expose spatial_dims"
|
|
113
|
+
raise RuntimeError(msg)
|
|
114
|
+
|
|
115
|
+
return runtime, fno_contract
|