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.
Files changed (202) hide show
  1. diffbio/__init__.py +39 -0
  2. diffbio/configs.py +75 -0
  3. diffbio/constants.py +204 -0
  4. diffbio/core/__init__.py +127 -0
  5. diffbio/core/base_operators.py +612 -0
  6. diffbio/core/data_types.py +260 -0
  7. diffbio/core/gnn_components.py +629 -0
  8. diffbio/core/graph_utils.py +149 -0
  9. diffbio/core/neural_components.py +270 -0
  10. diffbio/core/optimal_transport.py +133 -0
  11. diffbio/core/soft_ops/__init__.py +216 -0
  12. diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
  13. diffbio/core/soft_ops/_projections_simplex.py +240 -0
  14. diffbio/core/soft_ops/_projections_transport.py +508 -0
  15. diffbio/core/soft_ops/_sorting_network.py +204 -0
  16. diffbio/core/soft_ops/_types.py +15 -0
  17. diffbio/core/soft_ops/_utils.py +342 -0
  18. diffbio/core/soft_ops/autograd_safe.py +120 -0
  19. diffbio/core/soft_ops/comparison.py +235 -0
  20. diffbio/core/soft_ops/elementwise.py +309 -0
  21. diffbio/core/soft_ops/logical.py +146 -0
  22. diffbio/core/soft_ops/quantile.py +376 -0
  23. diffbio/core/soft_ops/selection.py +236 -0
  24. diffbio/core/soft_ops/sorting.py +926 -0
  25. diffbio/core/soft_ops/straight_through.py +261 -0
  26. diffbio/core/uncertainty.py +279 -0
  27. diffbio/evaluation/__init__.py +42 -0
  28. diffbio/evaluation/adapters.py +409 -0
  29. diffbio/evaluation/graders.py +223 -0
  30. diffbio/evaluation/problem.py +157 -0
  31. diffbio/evaluation/runner.py +277 -0
  32. diffbio/losses/__init__.py +59 -0
  33. diffbio/losses/alignment_losses.py +222 -0
  34. diffbio/losses/biological_regularization.py +288 -0
  35. diffbio/losses/metric_losses.py +139 -0
  36. diffbio/losses/singlecell_losses.py +387 -0
  37. diffbio/losses/statistical_losses.py +345 -0
  38. diffbio/operators/__init__.py +60 -0
  39. diffbio/operators/_count_vae.py +197 -0
  40. diffbio/operators/_loss_balancing.py +65 -0
  41. diffbio/operators/_masked_gene_transformer.py +118 -0
  42. diffbio/operators/_transformer_validation.py +50 -0
  43. diffbio/operators/alignment/__init__.py +51 -0
  44. diffbio/operators/alignment/profile_hmm.py +350 -0
  45. diffbio/operators/alignment/scoring.py +127 -0
  46. diffbio/operators/alignment/smith_waterman.py +261 -0
  47. diffbio/operators/alignment/soft_msa.py +419 -0
  48. diffbio/operators/assembly/__init__.py +27 -0
  49. diffbio/operators/assembly/gnn_assembly.py +252 -0
  50. diffbio/operators/assembly/metagenomic_binning.py +296 -0
  51. diffbio/operators/crispr/__init__.py +17 -0
  52. diffbio/operators/crispr/guide_scoring.py +269 -0
  53. diffbio/operators/drug_discovery/__init__.py +133 -0
  54. diffbio/operators/drug_discovery/_graph_utils.py +142 -0
  55. diffbio/operators/drug_discovery/admet_predictor.py +285 -0
  56. diffbio/operators/drug_discovery/attentive_fp.py +411 -0
  57. diffbio/operators/drug_discovery/dti.py +261 -0
  58. diffbio/operators/drug_discovery/fingerprint.py +490 -0
  59. diffbio/operators/drug_discovery/maccs_keys.py +267 -0
  60. diffbio/operators/drug_discovery/message_passing.py +200 -0
  61. diffbio/operators/drug_discovery/primitives.py +242 -0
  62. diffbio/operators/drug_discovery/property_predictor.py +163 -0
  63. diffbio/operators/drug_discovery/similarity.py +193 -0
  64. diffbio/operators/epigenomics/__init__.py +35 -0
  65. diffbio/operators/epigenomics/chromatin_state.py +491 -0
  66. diffbio/operators/epigenomics/contextual.py +288 -0
  67. diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
  68. diffbio/operators/epigenomics/peak_calling.py +555 -0
  69. diffbio/operators/foundation_models/__init__.py +119 -0
  70. diffbio/operators/foundation_models/adapters.py +114 -0
  71. diffbio/operators/foundation_models/contracts.py +245 -0
  72. diffbio/operators/foundation_models/embedding_probe.py +83 -0
  73. diffbio/operators/foundation_models/experimental.py +128 -0
  74. diffbio/operators/foundation_models/foundation_model.py +332 -0
  75. diffbio/operators/foundation_models/frozen.py +59 -0
  76. diffbio/operators/foundation_models/precomputed.py +270 -0
  77. diffbio/operators/foundation_models/transformer_encoder.py +564 -0
  78. diffbio/operators/mapping/__init__.py +17 -0
  79. diffbio/operators/mapping/neural_mapper.py +493 -0
  80. diffbio/operators/metabolomics/__init__.py +39 -0
  81. diffbio/operators/metabolomics/spectral_similarity.py +315 -0
  82. diffbio/operators/molecular_dynamics/__init__.py +51 -0
  83. diffbio/operators/molecular_dynamics/force_field.py +265 -0
  84. diffbio/operators/molecular_dynamics/integrator.py +304 -0
  85. diffbio/operators/molecular_dynamics/primitives.py +115 -0
  86. diffbio/operators/multiomics/__init__.py +38 -0
  87. diffbio/operators/multiomics/hic_contact.py +377 -0
  88. diffbio/operators/multiomics/multiomics_vae.py +325 -0
  89. diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
  90. diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
  91. diffbio/operators/normalization/__init__.py +42 -0
  92. diffbio/operators/normalization/embedding.py +222 -0
  93. diffbio/operators/normalization/phate.py +400 -0
  94. diffbio/operators/normalization/umap.py +261 -0
  95. diffbio/operators/normalization/vae_normalizer.py +258 -0
  96. diffbio/operators/population/__init__.py +17 -0
  97. diffbio/operators/population/ancestry_estimation.py +274 -0
  98. diffbio/operators/preprocessing/__init__.py +76 -0
  99. diffbio/operators/preprocessing/adapter_removal.py +311 -0
  100. diffbio/operators/preprocessing/duplicate_filter.py +317 -0
  101. diffbio/operators/preprocessing/error_correction.py +287 -0
  102. diffbio/operators/protein/__init__.py +31 -0
  103. diffbio/operators/protein/secondary_structure.py +509 -0
  104. diffbio/operators/quality_filter.py +128 -0
  105. diffbio/operators/rna_structure/__init__.py +35 -0
  106. diffbio/operators/rna_structure/rna_folding.py +509 -0
  107. diffbio/operators/rnaseq/__init__.py +23 -0
  108. diffbio/operators/rnaseq/motif_discovery.py +251 -0
  109. diffbio/operators/rnaseq/splicing_psi.py +216 -0
  110. diffbio/operators/singlecell/__init__.py +193 -0
  111. diffbio/operators/singlecell/ambient_removal.py +333 -0
  112. diffbio/operators/singlecell/archetypes.py +191 -0
  113. diffbio/operators/singlecell/batch_correction.py +288 -0
  114. diffbio/operators/singlecell/cell_annotation.py +519 -0
  115. diffbio/operators/singlecell/communication.py +704 -0
  116. diffbio/operators/singlecell/differential_distribution.py +243 -0
  117. diffbio/operators/singlecell/doublet_detection.py +657 -0
  118. diffbio/operators/singlecell/downsampling.py +166 -0
  119. diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
  120. diffbio/operators/singlecell/grn_inference.py +336 -0
  121. diffbio/operators/singlecell/imputation.py +429 -0
  122. diffbio/operators/singlecell/knockdown_filter.py +176 -0
  123. diffbio/operators/singlecell/ot_trajectory.py +277 -0
  124. diffbio/operators/singlecell/simulation.py +444 -0
  125. diffbio/operators/singlecell/sindy_grn.py +247 -0
  126. diffbio/operators/singlecell/soft_clustering.py +211 -0
  127. diffbio/operators/singlecell/spatial_domains.py +677 -0
  128. diffbio/operators/singlecell/switch_de.py +184 -0
  129. diffbio/operators/singlecell/trajectory.py +447 -0
  130. diffbio/operators/singlecell/velocity.py +361 -0
  131. diffbio/operators/statistical/__init__.py +35 -0
  132. diffbio/operators/statistical/em_quantification.py +260 -0
  133. diffbio/operators/statistical/hmm.py +234 -0
  134. diffbio/operators/statistical/nb_glm.py +272 -0
  135. diffbio/operators/variant/__init__.py +64 -0
  136. diffbio/operators/variant/classifier.py +333 -0
  137. diffbio/operators/variant/cnn_classifier.py +255 -0
  138. diffbio/operators/variant/cnv_segmentation.py +678 -0
  139. diffbio/operators/variant/deepvariant_pileup.py +426 -0
  140. diffbio/operators/variant/pileup.py +240 -0
  141. diffbio/operators/variant/quality_recalibration.py +274 -0
  142. diffbio/pipelines/__init__.py +65 -0
  143. diffbio/pipelines/differential_expression.py +279 -0
  144. diffbio/pipelines/enhanced_variant_calling.py +326 -0
  145. diffbio/pipelines/perturbation.py +407 -0
  146. diffbio/pipelines/preprocessing.py +267 -0
  147. diffbio/pipelines/single_cell.py +366 -0
  148. diffbio/pipelines/variant_calling.py +490 -0
  149. diffbio/samplers/__init__.py +9 -0
  150. diffbio/samplers/perturbation_sampler.py +142 -0
  151. diffbio/sequences/__init__.py +34 -0
  152. diffbio/sequences/dna.py +239 -0
  153. diffbio/sources/__init__.py +149 -0
  154. diffbio/sources/_anndata_shared.py +89 -0
  155. diffbio/sources/_batch_iteration.py +37 -0
  156. diffbio/sources/_benchmark_source.py +152 -0
  157. diffbio/sources/_indexed_batch_source.py +38 -0
  158. diffbio/sources/_utils.py +45 -0
  159. diffbio/sources/anndata_interop.py +387 -0
  160. diffbio/sources/anndata_source.py +361 -0
  161. diffbio/sources/archive_ii.py +174 -0
  162. diffbio/sources/balifam.py +207 -0
  163. diffbio/sources/bam.py +265 -0
  164. diffbio/sources/bengrn_ground_truth.py +306 -0
  165. diffbio/sources/contextual_epigenomics.py +242 -0
  166. diffbio/sources/dti.py +359 -0
  167. diffbio/sources/embeddings.py +203 -0
  168. diffbio/sources/encode_peaks.py +223 -0
  169. diffbio/sources/fasta.py +226 -0
  170. diffbio/sources/immune_human.py +172 -0
  171. diffbio/sources/indexed_embeddings.py +128 -0
  172. diffbio/sources/indexed_view.py +191 -0
  173. diffbio/sources/molnet.py +493 -0
  174. diffbio/sources/multiomics.py +279 -0
  175. diffbio/sources/pancreas.py +108 -0
  176. diffbio/sources/perturbation/__init__.py +69 -0
  177. diffbio/sources/perturbation/_types.py +51 -0
  178. diffbio/sources/perturbation/_utils.py +125 -0
  179. diffbio/sources/perturbation/concat_source.py +115 -0
  180. diffbio/sources/perturbation/control_mapping.py +215 -0
  181. diffbio/sources/perturbation/experiment_config.py +261 -0
  182. diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
  183. diffbio/sources/perturbation/output_space.py +52 -0
  184. diffbio/sources/perturbation/perturbation_source.py +513 -0
  185. diffbio/sources/seqfish.py +145 -0
  186. diffbio/sources/sequence_foundation.py +68 -0
  187. diffbio/sources/singlecell_foundation.py +68 -0
  188. diffbio/splitters/__init__.py +63 -0
  189. diffbio/splitters/base.py +251 -0
  190. diffbio/splitters/molecular.py +330 -0
  191. diffbio/splitters/perturbation.py +199 -0
  192. diffbio/splitters/random.py +217 -0
  193. diffbio/splitters/sequence.py +201 -0
  194. diffbio/utils/__init__.py +55 -0
  195. diffbio/utils/dependency_runtime.py +115 -0
  196. diffbio/utils/nn_utils.py +157 -0
  197. diffbio/utils/quality.py +45 -0
  198. diffbio/utils/training.py +585 -0
  199. diffbio-0.1.0.dist-info/METADATA +480 -0
  200. diffbio-0.1.0.dist-info/RECORD +202 -0
  201. diffbio-0.1.0.dist-info/WHEEL +4 -0
  202. 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