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,239 @@
1
+ """DNA sequence data types and encoding utilities for DiffBio.
2
+
3
+ This module provides functions and utilities for working with DNA sequences
4
+ in a JAX-compatible, differentiable manner.
5
+ """
6
+
7
+ from typing import Literal
8
+
9
+ import jax
10
+ import jax.numpy as jnp
11
+ from jaxtyping import Array, Float
12
+
13
+
14
+ # DNA nucleotide alphabet
15
+ DNA_ALPHABET = "ACGT"
16
+ DNA_ALPHABET_SIZE = 4
17
+
18
+ # Mapping from nucleotide to index
19
+ _NUC_TO_IDX = {"A": 0, "C": 1, "G": 2, "T": 3, "N": -1}
20
+ _IDX_TO_NUC = {0: "A", 1: "C", 2: "G", 3: "T"}
21
+
22
+
23
+ def encode_dna_string(sequence: str, handle_n: Literal["uniform", "zero"] = "uniform") -> Array:
24
+ """Encode a DNA string as a one-hot JAX array.
25
+
26
+ Args:
27
+ sequence: DNA string containing only A, C, G, T, N characters.
28
+ handle_n: How to handle N (unknown) nucleotides:
29
+ - "uniform": Encode as uniform distribution [0.25, 0.25, 0.25, 0.25]
30
+ - "zero": Encode as zeros [0, 0, 0, 0]
31
+
32
+ Returns:
33
+ One-hot encoded array of shape (len(sequence), 4).
34
+ Columns represent A, C, G, T in that order.
35
+
36
+ Example:
37
+ ```python
38
+ encode_dna_string("ACGT")
39
+ ```
40
+ Array([[1, 0, 0, 0],
41
+ [0, 1, 0, 0],
42
+ [0, 0, 1, 0],
43
+ [0, 0, 0, 1]], dtype=float32)
44
+ """
45
+ sequence = sequence.upper()
46
+
47
+ # Convert to indices
48
+ indices = []
49
+ for nuc in sequence:
50
+ if nuc in _NUC_TO_IDX:
51
+ indices.append(_NUC_TO_IDX[nuc])
52
+ else:
53
+ raise ValueError(f"Invalid nucleotide: {nuc}. Expected A, C, G, T, or N.")
54
+
55
+ indices_array = jnp.array(indices, dtype=jnp.int32)
56
+
57
+ # One-hot encode valid nucleotides (indices 0-3)
58
+ # N nucleotides have index -1 and will be handled separately
59
+ valid_mask = indices_array >= 0
60
+ safe_indices = jnp.where(valid_mask, indices_array, 0) # Use 0 for safe indexing
61
+ one_hot = jax.nn.one_hot(safe_indices, DNA_ALPHABET_SIZE)
62
+
63
+ # Handle N nucleotides
64
+ if handle_n == "uniform":
65
+ n_encoding = jnp.ones(DNA_ALPHABET_SIZE) / DNA_ALPHABET_SIZE
66
+ else: # "zero"
67
+ n_encoding = jnp.zeros(DNA_ALPHABET_SIZE)
68
+
69
+ # Apply N encoding where needed
70
+ n_mask = ~valid_mask
71
+ one_hot = jnp.where(n_mask[:, None], n_encoding, one_hot)
72
+
73
+ return one_hot
74
+
75
+
76
+ def decode_dna_onehot(encoded: Array, threshold: float = 0.5) -> str:
77
+ """Decode a one-hot encoded DNA array back to a string.
78
+
79
+ Args:
80
+ encoded: One-hot encoded array of shape (length, 4).
81
+ threshold: Minimum confidence to assign a nucleotide.
82
+ Below threshold, returns 'N'.
83
+
84
+ Returns:
85
+ DNA string.
86
+ """
87
+ # Get argmax for each position
88
+ indices = jnp.argmax(encoded, axis=-1)
89
+ max_vals = jnp.max(encoded, axis=-1)
90
+
91
+ # Build string
92
+ result = []
93
+ for i in range(len(indices)):
94
+ idx = int(indices[i])
95
+ max_val = float(max_vals[i])
96
+ if max_val >= threshold and idx in _IDX_TO_NUC:
97
+ result.append(_IDX_TO_NUC[idx])
98
+ else:
99
+ result.append("N")
100
+
101
+ return "".join(result)
102
+
103
+
104
+ def phred_to_probability(phred_scores: Array) -> Array:
105
+ """Convert Phred quality scores to error probabilities.
106
+
107
+ Args:
108
+ phred_scores: Array of Phred scores (typically 0-40).
109
+
110
+ Returns:
111
+ Array of error probabilities in range [0, 1].
112
+
113
+ Note:
114
+ Error probability = 10^(-Q/10) where Q is Phred score.
115
+ """
116
+ return jnp.power(10.0, -phred_scores / 10.0)
117
+
118
+
119
+ def probability_to_phred(error_prob: Array, max_phred: float = 60.0) -> Array:
120
+ """Convert error probabilities to Phred quality scores.
121
+
122
+ Args:
123
+ error_prob: Array of error probabilities in range (0, 1].
124
+ max_phred: Maximum Phred score to return (for numerical stability).
125
+
126
+ Returns:
127
+ Array of Phred scores.
128
+ """
129
+ # Clip to avoid log(0)
130
+ safe_prob = jnp.clip(error_prob, 1e-10, 1.0)
131
+ phred = -10.0 * jnp.log10(safe_prob)
132
+ return jnp.clip(phred, 0.0, max_phred)
133
+
134
+
135
+ def soft_encode_dna(
136
+ sequence: Array,
137
+ quality_scores: Array,
138
+ temperature: float = 1.0,
139
+ ) -> Array:
140
+ """Create soft one-hot encoding weighted by quality scores.
141
+
142
+ This creates a differentiable encoding where positions with low quality
143
+ have more uniform distributions (higher entropy).
144
+
145
+ Args:
146
+ sequence: One-hot encoded sequence of shape (length, 4).
147
+ quality_scores: Phred quality scores of shape (length,).
148
+ temperature: Temperature parameter controlling softness.
149
+ Lower = sharper (more like hard one-hot)
150
+ Higher = softer (more uniform)
151
+
152
+ Returns:
153
+ Soft-encoded array of shape (length, 4) where each row
154
+ sums to 1 but may not be exactly one-hot.
155
+ """
156
+ # Convert quality to confidence (higher quality = higher confidence)
157
+ confidence = 1.0 - phred_to_probability(quality_scores)
158
+
159
+ # Scale one-hot by confidence and add uniform noise for uncertainty
160
+ uniform = jnp.ones(DNA_ALPHABET_SIZE) / DNA_ALPHABET_SIZE
161
+
162
+ # Weighted combination: high confidence -> one-hot, low confidence -> uniform
163
+ soft_encoded = confidence[:, None] * sequence + (1 - confidence[:, None]) * uniform
164
+
165
+ # Apply temperature scaling and softmax for normalization
166
+ logits = jnp.log(soft_encoded + 1e-10) / temperature
167
+ return jax.nn.softmax(logits, axis=-1)
168
+
169
+
170
+ def complement_dna(encoded: Array) -> Array:
171
+ """Get the complement of a DNA sequence.
172
+
173
+ Complement mapping: A<->T, C<->G
174
+
175
+ Args:
176
+ encoded: One-hot encoded DNA of shape (..., 4).
177
+
178
+ Returns:
179
+ Complemented sequence of same shape.
180
+ """
181
+ # Complement swaps A<->T (indices 0<->3) and C<->G (indices 1<->2)
182
+ # Permutation: [0,1,2,3] -> [3,2,1,0]
183
+ return encoded[..., ::-1]
184
+
185
+
186
+ def reverse_complement_dna(encoded: Array) -> Array:
187
+ """Get the reverse complement of a DNA sequence.
188
+
189
+ Args:
190
+ encoded: One-hot encoded DNA of shape (length, 4).
191
+
192
+ Returns:
193
+ Reverse complemented sequence of same shape.
194
+ """
195
+ return complement_dna(encoded[::-1])
196
+
197
+
198
+ def gc_content(encoded: Array) -> Float[Array, ""]:
199
+ """Calculate GC content of a DNA sequence.
200
+
201
+ Args:
202
+ encoded: One-hot encoded DNA of shape (length, 4).
203
+
204
+ Returns:
205
+ Scalar GC content as fraction in [0, 1].
206
+ """
207
+ # G is index 2, C is index 1
208
+ gc_sum = jnp.sum(encoded[:, 1] + encoded[:, 2])
209
+ total = jnp.sum(encoded)
210
+ return gc_sum / (total + 1e-10)
211
+
212
+
213
+ def create_dna_element_data(
214
+ sequence: str | Array,
215
+ quality_scores: Array | None = None,
216
+ ) -> dict:
217
+ """Create data dictionary for a DNA sequence Element.
218
+
219
+ This creates a data structure compatible with Datarax's Element.
220
+
221
+ Args:
222
+ sequence: DNA string or pre-encoded one-hot array.
223
+ quality_scores: Optional Phred quality scores.
224
+
225
+ Returns:
226
+ Dictionary suitable for Element(data=...).
227
+ """
228
+ # Encode sequence if string
229
+ if isinstance(sequence, str):
230
+ encoded_seq = encode_dna_string(sequence)
231
+ else:
232
+ encoded_seq = sequence
233
+
234
+ data = {"sequence": encoded_seq}
235
+
236
+ if quality_scores is not None:
237
+ data["quality_scores"] = quality_scores
238
+
239
+ return data
@@ -0,0 +1,149 @@
1
+ """DiffBio data sources module.
2
+
3
+ This module provides data source implementations extending Datarax's DataSourceModule
4
+ for bioinformatics and drug discovery applications.
5
+
6
+ Sources:
7
+ AnnDataSource: AnnData (.h5ad) file reading for single-cell data
8
+ ENCODEPeakSource: ENCODE narrowPeak BED file reading for ChIP-seq peaks
9
+ IndexedViewSource: Lazy-loading view into a data source using index mapping
10
+ MolNetSource: MoleculeNet benchmark datasets for drug discovery
11
+ BAMSource: BAM/CRAM file reading for aligned sequencing reads
12
+ BioSNAPDTISource: Deterministic binary DTI benchmark source
13
+ DavisDTISource: Deterministic affinity DTI benchmark source
14
+ FastaSource: FASTA file reading for DNA/RNA sequences
15
+
16
+ Interop:
17
+ to_anndata: Convert DiffBio data dict to AnnData object
18
+ from_anndata: Convert AnnData object to DiffBio data dict
19
+ """
20
+
21
+ from diffbio.sources.anndata_interop import from_anndata, to_anndata
22
+ from diffbio.sources.anndata_source import AnnDataSource, AnnDataSourceConfig
23
+ from diffbio.sources.bam import BAMSource, BAMSourceConfig
24
+ from diffbio.sources.contextual_epigenomics import (
25
+ CONTEXTUAL_EPIGENOMICS_DATASET_CONTRACT_KEYS,
26
+ CONTEXTUAL_TARGET_SEMANTICS,
27
+ build_synthetic_contextual_epigenomics_dataset,
28
+ validate_contextual_epigenomics_dataset,
29
+ )
30
+ from diffbio.sources.dti import (
31
+ DTI_DATASET_CONTRACT_KEYS,
32
+ BioSNAPDTISource,
33
+ DTISourceConfig,
34
+ DavisDTISource,
35
+ build_paired_dti_batch,
36
+ deterministic_dti_split,
37
+ validate_dti_dataset,
38
+ )
39
+ from diffbio.sources.embeddings import (
40
+ EmbeddingArtifactSource,
41
+ EmbeddingArtifactSourceConfig,
42
+ )
43
+ from diffbio.sources.encode_peaks import ENCODEPeakConfig, ENCODEPeakSource
44
+ from diffbio.sources.fasta import FastaSource, FastaSourceConfig
45
+ from diffbio.sources.indexed_view import IndexedViewSource, IndexedViewSourceConfig
46
+ from diffbio.sources.molnet import MolNetSource, MolNetSourceConfig
47
+ from diffbio.sources.multiomics import (
48
+ MULTIOMICS_ARTIFACT_METADATA_KEYS,
49
+ MULTIOMICS_DATASET_PROVENANCE_KEYS,
50
+ MetabolomicsEmbeddingSource,
51
+ MetabolomicsEmbeddingSourceConfig,
52
+ MultiOmicsEmbeddingSource,
53
+ MultiOmicsEmbeddingSourceConfig,
54
+ align_metabolomics_embeddings,
55
+ align_multiomics_embeddings,
56
+ build_multiomics_artifact_metadata,
57
+ build_multiomics_dataset_provenance,
58
+ load_metabolomics_embedding_source,
59
+ load_multiomics_embedding_source,
60
+ validate_multiomics_artifact_metadata,
61
+ validate_multiomics_dataset_provenance,
62
+ )
63
+ from diffbio.sources.perturbation import (
64
+ BatchControlMapping,
65
+ ControlMappingConfig,
66
+ ExperimentConfig,
67
+ GlobalH5MetadataCache,
68
+ H5MetadataCache,
69
+ PerturbationAnnDataSource,
70
+ PerturbationConcatSource,
71
+ PerturbationSourceConfig,
72
+ RandomControlMapping,
73
+ load_experiment_config,
74
+ )
75
+ from diffbio.sources.singlecell_foundation import (
76
+ SingleCellEmbeddingSource,
77
+ SingleCellEmbeddingSourceConfig,
78
+ align_singlecell_embeddings,
79
+ load_singlecell_embedding_source,
80
+ )
81
+ from diffbio.sources.sequence_foundation import (
82
+ SequenceEmbeddingSource,
83
+ SequenceEmbeddingSourceConfig,
84
+ align_sequence_embeddings,
85
+ load_sequence_embedding_source,
86
+ )
87
+
88
+ __all__ = [
89
+ "AnnDataSource",
90
+ "AnnDataSourceConfig",
91
+ "BAMSource",
92
+ "BAMSourceConfig",
93
+ "BioSNAPDTISource",
94
+ "CONTEXTUAL_EPIGENOMICS_DATASET_CONTRACT_KEYS",
95
+ "CONTEXTUAL_TARGET_SEMANTICS",
96
+ "DTI_DATASET_CONTRACT_KEYS",
97
+ "DTISourceConfig",
98
+ "DavisDTISource",
99
+ "EmbeddingArtifactSource",
100
+ "EmbeddingArtifactSourceConfig",
101
+ "ENCODEPeakConfig",
102
+ "ENCODEPeakSource",
103
+ "FastaSource",
104
+ "FastaSourceConfig",
105
+ "IndexedViewSource",
106
+ "IndexedViewSourceConfig",
107
+ "MolNetSource",
108
+ "MolNetSourceConfig",
109
+ "MULTIOMICS_ARTIFACT_METADATA_KEYS",
110
+ "MULTIOMICS_DATASET_PROVENANCE_KEYS",
111
+ "MetabolomicsEmbeddingSource",
112
+ "MetabolomicsEmbeddingSourceConfig",
113
+ "MultiOmicsEmbeddingSource",
114
+ "MultiOmicsEmbeddingSourceConfig",
115
+ "from_anndata",
116
+ "to_anndata",
117
+ # Perturbation
118
+ "BatchControlMapping",
119
+ "ControlMappingConfig",
120
+ "ExperimentConfig",
121
+ "GlobalH5MetadataCache",
122
+ "H5MetadataCache",
123
+ "PerturbationAnnDataSource",
124
+ "PerturbationConcatSource",
125
+ "PerturbationSourceConfig",
126
+ "RandomControlMapping",
127
+ "build_synthetic_contextual_epigenomics_dataset",
128
+ "build_paired_dti_batch",
129
+ "deterministic_dti_split",
130
+ "validate_dti_dataset",
131
+ "validate_contextual_epigenomics_dataset",
132
+ "load_experiment_config",
133
+ "SequenceEmbeddingSource",
134
+ "SequenceEmbeddingSourceConfig",
135
+ "align_sequence_embeddings",
136
+ "align_metabolomics_embeddings",
137
+ "align_multiomics_embeddings",
138
+ "build_multiomics_artifact_metadata",
139
+ "build_multiomics_dataset_provenance",
140
+ "load_sequence_embedding_source",
141
+ "load_metabolomics_embedding_source",
142
+ "load_multiomics_embedding_source",
143
+ "SingleCellEmbeddingSource",
144
+ "SingleCellEmbeddingSourceConfig",
145
+ "align_singlecell_embeddings",
146
+ "load_singlecell_embedding_source",
147
+ "validate_multiomics_artifact_metadata",
148
+ "validate_multiomics_dataset_provenance",
149
+ ]
@@ -0,0 +1,89 @@
1
+ """Shared helpers for AnnData-backed DiffBio sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import jax.numpy as jnp
9
+ import numpy as np
10
+ from flax import nnx
11
+
12
+ from diffbio.sources._utils import _require_anndata
13
+
14
+
15
+ def to_dense_array(matrix: Any) -> np.ndarray:
16
+ """Convert a dense or sparse matrix into a float32 NumPy array."""
17
+ import scipy.sparse # noqa: PLC0415
18
+
19
+ if scipy.sparse.issparse(matrix):
20
+ return np.asarray(matrix.toarray(), dtype=np.float32)
21
+ return np.asarray(matrix, dtype=np.float32)
22
+
23
+
24
+ def read_h5ad(config: Any) -> Any:
25
+ """Load an AnnData object after validating the configured file path."""
26
+ anndata_mod = _require_anndata()
27
+ file_path = Path(str(config.file_path))
28
+ if not file_path.exists():
29
+ raise FileNotFoundError(f"AnnData file not found: {file_path}")
30
+ return anndata_mod.read_h5ad(file_path, backed="r" if config.backed else None)
31
+
32
+
33
+ def load_obsm(adata: Any) -> dict[str, jnp.ndarray]:
34
+ """Load AnnData embedding matrices as float32 JAX arrays."""
35
+ if adata.obsm is None or len(adata.obsm) == 0:
36
+ return {}
37
+
38
+ return {
39
+ key: jnp.array(np.asarray(adata.obsm[key], dtype=np.float32)) for key in adata.obsm.keys()
40
+ }
41
+
42
+
43
+ def extract_anndata_annotations(
44
+ adata: Any,
45
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, jnp.ndarray]]:
46
+ """Extract obs, var, and obsm tables into the standard in-memory layout."""
47
+ obs = {col: np.asarray(adata.obs[col]) for col in adata.obs.columns}
48
+ var = {col: np.asarray(adata.var[col]) for col in adata.var.columns}
49
+ obsm = load_obsm(adata)
50
+ return obs, var, obsm
51
+
52
+
53
+ def build_anndata_data(
54
+ *,
55
+ counts: jnp.ndarray,
56
+ obs: dict[str, Any],
57
+ var: dict[str, Any],
58
+ obsm: dict[str, jnp.ndarray],
59
+ ) -> dict[str, Any]:
60
+ """Assemble the canonical in-memory AnnData payload for DiffBio sources."""
61
+ return {
62
+ "counts": counts,
63
+ "obs": obs,
64
+ "var": var,
65
+ "obsm": obsm,
66
+ }
67
+
68
+
69
+ def initialize_eager_source_state(
70
+ source: Any,
71
+ *,
72
+ data: dict[str, Any],
73
+ length: int,
74
+ seed: int,
75
+ shuffle: bool,
76
+ dataset_name: str | None,
77
+ split_name: str | None,
78
+ dataset_info: dict[str, int],
79
+ ) -> None:
80
+ """Populate the common eager-source bookkeeping fields."""
81
+ source.data = data
82
+ source.length = length
83
+ source.index = nnx.Variable(0)
84
+ source.epoch = nnx.Variable(0)
85
+ source._seed = seed
86
+ source.shuffle = shuffle
87
+ source.dataset_name = dataset_name
88
+ source.split_name = split_name
89
+ source._dataset_info = dataset_info
@@ -0,0 +1,37 @@
1
+ """Utilities for simple stateful batch iteration in data sources."""
2
+
3
+ import logging
4
+ from collections.abc import Callable
5
+
6
+ import jax
7
+
8
+ from datarax.typing import Element
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def reset_iteration_state(source: object, seed: int | None = None) -> None:
14
+ """Reset a source object exposing `_current_idx` iteration state."""
15
+ del seed # API compatibility with DataSourceModule reset signature
16
+ source._current_idx = 0
17
+
18
+
19
+ def next_batch(
20
+ *,
21
+ batch_size: int,
22
+ key: jax.Array | None,
23
+ current_idx: int,
24
+ total_size: int,
25
+ get_element: Callable[[int], Element],
26
+ ) -> tuple[list[Element], int]:
27
+ """Collect up to `batch_size` elements starting from `current_idx`."""
28
+ del key # API compatibility with DataSourceModule.get_batch signature
29
+
30
+ batch: list[Element] = []
31
+ idx = current_idx
32
+ for _ in range(batch_size):
33
+ if idx >= total_size:
34
+ break
35
+ batch.append(get_element(idx))
36
+ idx += 1
37
+ return batch, idx
@@ -0,0 +1,152 @@
1
+ """Shared substrate for benchmark-oriented eager data sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any, Callable, Literal, overload
9
+
10
+ import jax.numpy as jnp
11
+ import numpy as np
12
+ from flax import nnx
13
+
14
+ from datarax.core.data_source import DataSourceModule
15
+
16
+ from diffbio.sources._utils import _require_anndata
17
+
18
+
19
+ def load_benchmark_adata(
20
+ *,
21
+ data_dir: str,
22
+ filename: str,
23
+ subsample: int | None = None,
24
+ seed: int = 42,
25
+ ) -> Any:
26
+ """Load a benchmark h5ad file with optional deterministic subsampling."""
27
+ anndata_mod = _require_anndata()
28
+ path = Path(data_dir) / filename
29
+ adata = anndata_mod.read_h5ad(path)
30
+
31
+ if subsample is not None and subsample < adata.n_obs:
32
+ rng = np.random.default_rng(seed)
33
+ indices = rng.choice(adata.n_obs, size=subsample, replace=False)
34
+ indices.sort()
35
+ adata = adata[indices].copy()
36
+
37
+ return adata
38
+
39
+
40
+ def load_benchmark_counts(
41
+ *,
42
+ data_dir: str,
43
+ filename: str,
44
+ to_dense: Callable[[Any], np.ndarray],
45
+ subsample: int | None = None,
46
+ seed: int = 42,
47
+ ) -> tuple[Any, jnp.ndarray]:
48
+ """Load a benchmark h5ad file and convert its count matrix to a JAX array."""
49
+ adata = load_benchmark_adata(
50
+ data_dir=data_dir,
51
+ filename=filename,
52
+ subsample=subsample,
53
+ seed=seed,
54
+ )
55
+ counts = jnp.array(to_dense(adata.X))
56
+ return adata, counts
57
+
58
+
59
+ @overload
60
+ def encode_label_column(
61
+ column: Any,
62
+ *,
63
+ include_names: Literal[False] = False,
64
+ ) -> np.ndarray: ...
65
+
66
+
67
+ @overload
68
+ def encode_label_column(
69
+ column: Any,
70
+ *,
71
+ include_names: Literal[True],
72
+ ) -> tuple[np.ndarray, list[str]]: ...
73
+
74
+
75
+ def encode_label_column(
76
+ column: Any,
77
+ *,
78
+ include_names: bool = False,
79
+ ) -> np.ndarray | tuple[np.ndarray, list[str]]:
80
+ """Encode an observation column into stable int32 label codes."""
81
+ if hasattr(column, "cat"):
82
+ codes = np.asarray(column.cat.codes, dtype=np.int32)
83
+ names = [str(value) for value in column.cat.categories]
84
+ else:
85
+ unique_labels, codes = np.unique(np.asarray(column), return_inverse=True)
86
+ codes = codes.astype(np.int32)
87
+ names = [str(value) for value in unique_labels]
88
+
89
+ if include_names:
90
+ return codes, names
91
+ return codes
92
+
93
+
94
+ def iter_loaded_rows(
95
+ data: dict[str, Any],
96
+ *,
97
+ static_keys: tuple[str, ...] = ("gene_names",),
98
+ ) -> Iterator[dict[str, Any]]:
99
+ """Iterate row-wise over an eager benchmark payload."""
100
+ static_key_set = set(static_keys)
101
+ for i in range(int(data["n_cells"])):
102
+ yield {
103
+ key: value[i] if hasattr(value, "__getitem__") and key not in static_key_set else value
104
+ for key, value in data.items()
105
+ }
106
+
107
+
108
+ class BenchmarkDataSource(DataSourceModule):
109
+ """Shared base class for eager benchmark data sources backed by a data dict."""
110
+
111
+ data: dict[str, Any] = nnx.data()
112
+ iter_static_keys: tuple[str, ...] = ("gene_names",)
113
+
114
+ def load(self) -> dict[str, Any]:
115
+ """Return the eagerly loaded dataset payload."""
116
+ return self.data
117
+
118
+ def __len__(self) -> int:
119
+ """Return the number of rows in the eagerly loaded dataset."""
120
+ return int(self.data["n_cells"])
121
+
122
+ def __iter__(self) -> Iterator[dict[str, Any]]:
123
+ """Iterate row-wise through the loaded dataset payload."""
124
+ return iter_loaded_rows(self.data, static_keys=self.iter_static_keys)
125
+
126
+ def _load_benchmark_counts(
127
+ self,
128
+ config: Any,
129
+ filename: str,
130
+ to_dense: Callable[[Any], np.ndarray],
131
+ ) -> tuple[Any, jnp.ndarray]:
132
+ """Load benchmark AnnData and convert its count matrix to a JAX array."""
133
+ return load_benchmark_counts(
134
+ data_dir=config.data_dir,
135
+ filename=filename,
136
+ to_dense=to_dense,
137
+ subsample=getattr(config, "subsample", None),
138
+ )
139
+
140
+ def _log_loaded_summary(
141
+ self,
142
+ logger: logging.Logger,
143
+ dataset_name: str,
144
+ metric_keys: tuple[str, ...],
145
+ ) -> None:
146
+ """Log a standard loaded-dataset summary using the requested metric keys."""
147
+ labels = [key[2:] if key.startswith("n_") else key for key in metric_keys]
148
+ metric_summary = ", ".join(f"%d {label}" for label in labels)
149
+ logger.info(
150
+ f"Loaded {dataset_name}: {metric_summary}",
151
+ *(int(self.data[key]) for key in metric_keys),
152
+ )
@@ -0,0 +1,38 @@
1
+ """Mixin for stateful index-based batching in data sources."""
2
+
3
+ import logging
4
+
5
+ import jax
6
+
7
+ from datarax.typing import Element
8
+
9
+ from diffbio.sources._batch_iteration import next_batch, reset_iteration_state
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class IndexedBatchSourceMixin:
15
+ """Reusable `reset` and `get_batch` logic for indexable data sources."""
16
+
17
+ def _batch_total_size(self) -> int:
18
+ """Return the total number of elements available for batching."""
19
+ raise NotImplementedError
20
+
21
+ def _batch_element(self, idx: int) -> Element:
22
+ """Return the element at the given index."""
23
+ raise NotImplementedError
24
+
25
+ def reset(self, seed: int | None = None) -> None:
26
+ """Reset iteration state, optionally with a new seed."""
27
+ reset_iteration_state(self, seed)
28
+
29
+ def get_batch(self, batch_size: int, key: jax.Array | None = None) -> list[Element]:
30
+ """Return the next batch of elements, advancing the internal index."""
31
+ batch, self._current_idx = next_batch(
32
+ batch_size=batch_size,
33
+ key=key,
34
+ current_idx=self._current_idx,
35
+ total_size=self._batch_total_size(),
36
+ get_element=self._batch_element,
37
+ )
38
+ return batch