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,361 @@
1
+ """AnnData (.h5ad) data source for single-cell genomics.
2
+
3
+ This module provides AnnDataSource for loading single-cell RNA-seq data from
4
+ .h5ad files (AnnData format) and converting them to JAX-compatible data dicts
5
+ suitable for DiffBio operators.
6
+
7
+ Follows the datarax eager-loading pattern (same as HFEagerSource): all data is
8
+ loaded to JAX arrays at init, then iteration/batching uses pure JAX operations
9
+ with O(1) memory shuffling via Grain's index_shuffle.
10
+
11
+ Handles both dense and sparse count matrices, cell/gene metadata, and
12
+ optional embeddings (PCA, UMAP, etc.).
13
+
14
+ References:
15
+ - https://anndata.readthedocs.io/
16
+ - Wolf et al. "SCANPY: large-scale single-cell gene expression data analysis"
17
+ Genome Biology, 2018.
18
+ """
19
+
20
+ # Ownership note: DiffBio owns the AnnData biological schema adapter; batching
21
+ # and iteration stay on Datarax eager-source primitives.
22
+
23
+ from __future__ import annotations
24
+
25
+ import logging
26
+ from collections.abc import Callable, Iterator
27
+ from dataclasses import dataclass
28
+ from typing import Any
29
+
30
+ import jax
31
+ import jax.numpy as jnp
32
+ import numpy as np
33
+ from flax import nnx
34
+
35
+ from datarax.core.config import StructuralConfig
36
+ from datarax.core.data_source import DataSourceModule
37
+ from datarax.sources._eager_source_ops import eager_get_batch, eager_iter, eager_reset
38
+
39
+ from diffbio.sources._anndata_shared import (
40
+ build_anndata_data,
41
+ extract_anndata_annotations,
42
+ initialize_eager_source_state,
43
+ read_h5ad,
44
+ to_dense_array,
45
+ )
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class AnnDataSourceConfig(StructuralConfig):
52
+ """Configuration for AnnDataSource.
53
+
54
+ Attributes:
55
+ file_path: Path to the .h5ad file (string or Path object).
56
+ backed: Whether to open in backed mode (memory-mapped).
57
+ shuffle: Whether to shuffle during iteration.
58
+ seed: Integer seed for Grain's index_shuffle.
59
+ split: Optional split name for pipeline integration.
60
+ """
61
+
62
+ file_path: str | None = None
63
+ backed: bool = False
64
+ shuffle: bool = False
65
+ seed: int = 42
66
+ split: str | None = None
67
+
68
+ def __post_init__(self) -> None:
69
+ """Validate configuration after initialization."""
70
+ if self.shuffle:
71
+ object.__setattr__(self, "stochastic", True)
72
+ if self.stream_name is None:
73
+ object.__setattr__(self, "stream_name", "shuffle")
74
+ else:
75
+ object.__setattr__(self, "stochastic", False)
76
+
77
+ super().__post_init__()
78
+
79
+ if self.file_path is None:
80
+ raise ValueError("file_path is required for AnnDataSourceConfig")
81
+
82
+
83
+ class AnnDataSource(DataSourceModule):
84
+ """Eager-loading AnnData source for single-cell RNA-seq data.
85
+
86
+ Loads all data from .h5ad files to JAX arrays at initialization, then
87
+ provides pure JAX iteration, batching, and indexed access. Follows the
88
+ same eager-loading pattern as datarax's HFEagerSource.
89
+
90
+ Provides:
91
+ - Full dataset loading via ``load()``
92
+ - Per-cell indexed access via ``__getitem__``
93
+ - Iteration via ``__iter__`` with optional O(1) memory shuffling
94
+ - Batch retrieval via ``get_batch(batch_size)``
95
+ - Automatic sparse-to-dense conversion
96
+ - JAX array output for count matrices and embeddings
97
+
98
+ Output dictionary keys:
99
+ - ``counts``: Dense JAX array of shape (n_cells, n_genes) from ``.X``
100
+ - ``obs``: Dict of cell metadata columns from ``.obs``
101
+ - ``var``: Dict of gene metadata columns from ``.var``
102
+ - ``obsm``: Dict of embedding JAX arrays from ``.obsm`` (empty if absent)
103
+
104
+ Example:
105
+ ```python
106
+ config = AnnDataSourceConfig(file_path="pbmc3k.h5ad")
107
+ source = AnnDataSource(config)
108
+ print(len(source)) # 2700
109
+ print(source.load()["counts"].shape) # (2700, 32738)
110
+
111
+ for cell in source:
112
+ print(cell["counts"].shape) # (32738,)
113
+ break
114
+
115
+ batch = source.get_batch(32)
116
+ print(batch["counts"].shape) # (32, 32738)
117
+ ```
118
+ """
119
+
120
+ # Annotate data storage for Flax NNX (prevents parameter tracking)
121
+ data: dict[str, Any] = nnx.data()
122
+
123
+ def __init__(
124
+ self,
125
+ config: AnnDataSourceConfig,
126
+ *,
127
+ rngs: nnx.Rngs | None = None,
128
+ name: str | None = None,
129
+ ) -> None:
130
+ """Initialize AnnDataSource from a .h5ad file.
131
+
132
+ Loads all data to JAX arrays at construction time.
133
+
134
+ Args:
135
+ config: AnnDataSourceConfig with file path and options.
136
+ rngs: Optional RNG state for shuffling.
137
+ name: Optional module name.
138
+
139
+ Raises:
140
+ FileNotFoundError: If the file does not exist.
141
+ ImportError: If anndata is not installed.
142
+ """
143
+ if name is None:
144
+ name = f"AnnDataSource({config.file_path})"
145
+ super().__init__(config, rngs=rngs, name=name)
146
+
147
+ adata = read_h5ad(config)
148
+
149
+ # Convert count matrix to JAX array
150
+ counts = jnp.array(to_dense_array(adata.X))
151
+ obs, var, obsm = extract_anndata_annotations(adata)
152
+
153
+ self._initialize_loaded_source(
154
+ config=config,
155
+ adata=adata,
156
+ data=build_anndata_data(counts=counts, obs=obs, var=var, obsm=obsm),
157
+ length=adata.n_obs,
158
+ )
159
+
160
+ # =================================================================
161
+ # Public API: load / info
162
+ # =================================================================
163
+
164
+ def load(self) -> dict[str, Any]:
165
+ """Return the full dataset as a dictionary of JAX arrays and metadata.
166
+
167
+ Returns:
168
+ Dictionary with keys ``counts``, ``obs``, ``var``, ``obsm``.
169
+ """
170
+ return dict(self.data)
171
+
172
+ def get_dataset_info(self) -> dict[str, int]:
173
+ """Return cached dataset metadata.
174
+
175
+ Returns:
176
+ Dict with ``n_genes`` and ``n_cells``.
177
+ """
178
+ return self._dataset_info
179
+
180
+ def _initialize_loaded_source(
181
+ self,
182
+ *,
183
+ config: AnnDataSourceConfig,
184
+ adata: Any,
185
+ data: dict[str, Any],
186
+ length: int,
187
+ ) -> None:
188
+ """Initialize common eager-source state after AnnData loading."""
189
+ initialize_eager_source_state(
190
+ self,
191
+ data=data,
192
+ length=length,
193
+ seed=config.seed,
194
+ shuffle=config.shuffle,
195
+ dataset_name=str(config.file_path),
196
+ split_name=config.split,
197
+ dataset_info={
198
+ "n_genes": adata.n_vars,
199
+ "n_cells": adata.n_obs,
200
+ },
201
+ )
202
+
203
+ def _iter_with_builder(
204
+ self,
205
+ build_element: Callable[[dict[str, Any], int], dict[str, Any]],
206
+ ) -> Iterator[dict[str, Any]]:
207
+ """Iterate using the standard eager-source bookkeeping state."""
208
+ return eager_iter(
209
+ self.data,
210
+ self.length,
211
+ self.index,
212
+ self.epoch,
213
+ self.shuffle,
214
+ self._seed,
215
+ build_element,
216
+ )
217
+
218
+ def _get_batch_with_gather(
219
+ self,
220
+ batch_size: int,
221
+ key: jax.Array | None,
222
+ gather_fn: Callable[[dict[str, Any], Any], dict[str, Any]],
223
+ ) -> dict[str, Any]:
224
+ """Collect a batch using the standard eager-source bookkeeping state."""
225
+ return eager_get_batch(
226
+ self.data,
227
+ self.length,
228
+ self.index,
229
+ self.epoch,
230
+ self.shuffle,
231
+ self._seed,
232
+ batch_size,
233
+ key,
234
+ gather_fn,
235
+ )
236
+
237
+ # =================================================================
238
+ # DataSourceModule protocol: __len__, __iter__, __next__, __getitem__
239
+ # =================================================================
240
+
241
+ def __len__(self) -> int:
242
+ """Return the number of cells in the dataset."""
243
+ return self.length
244
+
245
+ def __iter__(self) -> Iterator[dict[str, Any]]:
246
+ """Iterate over cells with optional O(1) memory shuffling.
247
+
248
+ Yields:
249
+ Per-cell dictionaries with ``counts``, ``obs``, ``obsm`` keys.
250
+ """
251
+ return self._iter_with_builder(_build_cell_element)
252
+
253
+ def __next__(self) -> dict[str, Any]:
254
+ """Get the next cell element (required by DataSourceModule).
255
+
256
+ Raises:
257
+ StopIteration: When iteration is exhausted.
258
+ """
259
+ raise StopIteration
260
+
261
+ def __getitem__(self, idx: int) -> dict[str, Any]:
262
+ """Get data for a single cell by index.
263
+
264
+ Supports negative indexing.
265
+
266
+ Args:
267
+ idx: Cell index (supports negative indexing).
268
+
269
+ Returns:
270
+ Dictionary with ``counts``, ``obs``, ``obsm`` keys.
271
+
272
+ Raises:
273
+ IndexError: If idx is out of bounds.
274
+ """
275
+ if idx < 0:
276
+ idx = self.length + idx
277
+ if idx < 0 or idx >= self.length:
278
+ raise IndexError(f"Cell index {idx} out of range for dataset with {self.length} cells")
279
+ return _build_cell_element(self.data, idx)
280
+
281
+ # =================================================================
282
+ # Batch retrieval
283
+ # =================================================================
284
+
285
+ def get_batch(self, batch_size: int, key: jax.Array | None = None) -> dict[str, Any]:
286
+ """Get a batch of cells.
287
+
288
+ Stateful (advances internal index) when called without ``key``.
289
+ Stateless (random sampling) when called with ``key``.
290
+
291
+ Args:
292
+ batch_size: Number of cells per batch.
293
+ key: Optional RNG key for stateless random sampling.
294
+
295
+ Returns:
296
+ Dictionary with batched arrays.
297
+ """
298
+
299
+ def _gather(data: dict[str, Any], indices: jax.Array) -> dict[str, Any]:
300
+ counts = data["counts"][indices]
301
+ obs = {col: np.asarray(arr)[np.array(indices)] for col, arr in data["obs"].items()}
302
+ obsm: dict[str, jnp.ndarray] = {}
303
+ for emb_name, emb_arr in data["obsm"].items():
304
+ obsm[emb_name] = emb_arr[indices]
305
+ return {"counts": counts, "obs": obs, "obsm": obsm}
306
+
307
+ return self._get_batch_with_gather(batch_size, key, _gather)
308
+
309
+ # =================================================================
310
+ # State management
311
+ # =================================================================
312
+
313
+ def reset(self, seed: int | None = None) -> None:
314
+ """Reset source to the beginning.
315
+
316
+ Args:
317
+ seed: Unused (uses config seed).
318
+ """
319
+ del seed
320
+ eager_reset(self.index, self.epoch, self._cache)
321
+
322
+ def set_shuffle(self, shuffle: bool) -> None:
323
+ """Enable or disable shuffling.
324
+
325
+ Args:
326
+ shuffle: Whether to shuffle data during iteration.
327
+ """
328
+ self.shuffle = shuffle
329
+
330
+ def __repr__(self) -> str:
331
+ """Return string representation."""
332
+ return (
333
+ f"AnnDataSource("
334
+ f"dataset={self.dataset_name}, "
335
+ f"length={self.length}, "
336
+ f"shuffle={self.shuffle}, "
337
+ f"epoch={self.epoch.get_value()})"
338
+ )
339
+
340
+
341
+ # =====================================================================
342
+ # Module-level helpers (kept outside the class)
343
+ # =====================================================================
344
+
345
+
346
+ def _build_cell_element(data: dict[str, Any], idx: int) -> dict[str, Any]:
347
+ """Build a per-cell dictionary from the full dataset at a given index.
348
+
349
+ Args:
350
+ data: The full data dict with ``counts``, ``obs``, ``obsm`` keys.
351
+ idx: Cell index.
352
+
353
+ Returns:
354
+ Per-cell dictionary with scalar obs values and 1D arrays.
355
+ """
356
+ cell_counts = data["counts"][idx]
357
+ cell_obs = {col: arr[idx] for col, arr in data["obs"].items()}
358
+ cell_obsm: dict[str, jnp.ndarray] = {}
359
+ for emb_name, emb_arr in data["obsm"].items():
360
+ cell_obsm[emb_name] = emb_arr[idx]
361
+ return {"counts": cell_counts, "obs": cell_obs, "obsm": cell_obsm}
@@ -0,0 +1,174 @@
1
+ """ArchiveII RNA secondary structure DataSource.
2
+
3
+ Loads RNA sequences with known secondary structures from the ArchiveII
4
+ benchmark dataset (Sloma & Mathews, 2016). Structures are provided in
5
+ dot-bracket notation (DBN) and represent experimentally validated RNA
6
+ secondary structures from crystal structures and NMR.
7
+
8
+ The dataset is read from CSV files produced by RNAFoldAssess, with
9
+ columns: name, sequence, ground_truth_type, ground_truth_data.
10
+
11
+ Only rows with ``ground_truth_type == "DBN"`` are loaded; rows without
12
+ structure annotations are silently skipped.
13
+
14
+ References:
15
+ Sloma, M. F. & Mathews, D. H. (2016). Exact calculation of loop
16
+ formation probability identifies folding motifs in RNA secondary
17
+ structures. RNA 22, 1808-1818.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import csv
23
+ import logging
24
+ from collections.abc import Iterator
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from datarax.core.config import StructuralConfig
30
+ from datarax.core.data_source import DataSourceModule
31
+ from flax import nnx
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ _DEFAULT_DATA_DIR = "/media/mahdi/ssd23/Works/RNAFoldAssess/tutorial/processed_data"
36
+ _STRUCTURE_FILENAME = "example_data_structure.csv"
37
+
38
+
39
+ @dataclass(frozen=True, kw_only=True)
40
+ class ArchiveIIConfig(StructuralConfig):
41
+ """Configuration for ArchiveIISource.
42
+
43
+ Attributes:
44
+ data_dir: Directory containing the ArchiveII CSV file.
45
+ filename: Name of the CSV file with structure annotations.
46
+ max_sequences: Maximum number of sequences to load.
47
+ None means load all available sequences.
48
+ """
49
+
50
+ data_dir: str = _DEFAULT_DATA_DIR
51
+ filename: str = _STRUCTURE_FILENAME
52
+ max_sequences: int | None = None
53
+
54
+ def __post_init__(self) -> None:
55
+ """Validate that the data file exists."""
56
+ super().__post_init__()
57
+ path = Path(self.data_dir) / self.filename
58
+ if not path.exists():
59
+ raise FileNotFoundError(
60
+ f"ArchiveII data not found: {path}. "
61
+ f"Expected a CSV with columns: name, sequence, "
62
+ f"ground_truth_type, ground_truth_data."
63
+ )
64
+
65
+
66
+ def _parse_csv(
67
+ csv_path: Path,
68
+ max_sequences: int | None,
69
+ ) -> list[dict[str, str]]:
70
+ """Parse ArchiveII CSV and return DBN-annotated entries.
71
+
72
+ Args:
73
+ csv_path: Path to the CSV file.
74
+ max_sequences: Maximum entries to return. None for all.
75
+
76
+ Returns:
77
+ List of dicts with keys: name, sequence, structure.
78
+ """
79
+ entries: list[dict[str, str]] = []
80
+ with csv_path.open(encoding="utf-8") as fh:
81
+ reader = csv.DictReader(fh)
82
+ for row in reader:
83
+ gt_type = row.get("ground_truth_type", "").strip()
84
+ gt_data = row.get("ground_truth_data", "").strip()
85
+ if gt_type != "DBN" or not gt_data:
86
+ continue
87
+ sequence = row["sequence"].strip().upper()
88
+ name = row["name"].strip()
89
+ entries.append(
90
+ {
91
+ "name": name,
92
+ "sequence": sequence,
93
+ "structure": gt_data,
94
+ }
95
+ )
96
+ if max_sequences is not None and len(entries) >= max_sequences:
97
+ break
98
+ return entries
99
+
100
+
101
+ class ArchiveIISource(DataSourceModule):
102
+ """DataSource for ArchiveII RNA secondary structures.
103
+
104
+ Loads RNA sequences and their known dot-bracket notation (DBN)
105
+ structures from a CSV file. Only entries with ground_truth_type
106
+ ``"DBN"`` are included.
107
+
108
+ Each entry is a dict with keys:
109
+ - ``name``: Sequence identifier (e.g. ``"1KXK_chain_0"``)
110
+ - ``sequence``: RNA sequence string (e.g. ``"GUCUACC..."``)
111
+ - ``structure``: DBN string (e.g. ``"....(((...)))..."``)
112
+
113
+ Example:
114
+ ```python
115
+ config = ArchiveIIConfig(max_sequences=10)
116
+ source = ArchiveIISource(config)
117
+ data = source.load()
118
+ print(data["n_sequences"])
119
+ print(data["entries"][0]["name"])
120
+ ```
121
+ """
122
+
123
+ data: dict[str, Any] = nnx.data()
124
+
125
+ def __init__(
126
+ self,
127
+ config: ArchiveIIConfig,
128
+ *,
129
+ rngs: nnx.Rngs | None = None,
130
+ name: str | None = None,
131
+ ) -> None:
132
+ """Load ArchiveII RNA structures from CSV.
133
+
134
+ Args:
135
+ config: Configuration with data directory and limits.
136
+ rngs: Optional RNG state (unused, for interface compat).
137
+ name: Optional module name.
138
+ """
139
+ super().__init__(config, rngs=rngs, name=name or "ArchiveIISource")
140
+ csv_path = Path(config.data_dir) / config.filename
141
+ entries = _parse_csv(csv_path, config.max_sequences)
142
+
143
+ if not entries:
144
+ raise ValueError(
145
+ f"No DBN-annotated sequences found in {csv_path}. "
146
+ f"Ensure the CSV has rows with "
147
+ f"ground_truth_type='DBN'."
148
+ )
149
+
150
+ self.data = {
151
+ "entries": entries,
152
+ "n_sequences": len(entries),
153
+ }
154
+ logger.info(
155
+ "Loaded ArchiveII: %d sequences from %s",
156
+ len(entries),
157
+ csv_path,
158
+ )
159
+
160
+ def load(self) -> dict[str, Any]:
161
+ """Return the full dataset as a dictionary.
162
+
163
+ Returns:
164
+ Dict with keys: entries (list of dicts), n_sequences.
165
+ """
166
+ return self.data
167
+
168
+ def __len__(self) -> int:
169
+ """Return the number of loaded sequences."""
170
+ return self.data["n_sequences"]
171
+
172
+ def __iter__(self) -> Iterator[dict[str, str]]:
173
+ """Iterate over individual RNA entries."""
174
+ yield from self.data["entries"]