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,128 @@
1
+ """Indexed embedding-artifact sources with strict row-identity alignment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, cast
7
+
8
+ import jax.numpy as jnp
9
+ import numpy as np
10
+ from flax import nnx
11
+
12
+ from diffbio.sources.embeddings import EmbeddingArtifactSource, EmbeddingArtifactSourceConfig
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class IndexedEmbeddingSourceConfig(EmbeddingArtifactSourceConfig):
17
+ """Configuration for embedding sources that persist row identities."""
18
+
19
+ row_id_key: str | None = None
20
+
21
+ def __post_init__(self) -> None:
22
+ """Validate the required row-identity field name."""
23
+ super().__post_init__()
24
+ if self.row_id_key is None:
25
+ raise ValueError("row_id_key is required for IndexedEmbeddingSourceConfig")
26
+ if not self.row_id_key.strip():
27
+ raise ValueError("row_id_key must be a non-empty string")
28
+
29
+
30
+ class IndexedEmbeddingSource(EmbeddingArtifactSource):
31
+ """Eager Datarax-style source for embeddings with optional row identities."""
32
+
33
+ config: IndexedEmbeddingSourceConfig # pyright: ignore[reportIncompatibleVariableOverride]
34
+
35
+ def __init__(
36
+ self,
37
+ config: IndexedEmbeddingSourceConfig,
38
+ *,
39
+ rngs: nnx.Rngs | None = None,
40
+ name: str | None = None,
41
+ ) -> None:
42
+ """Load the embedding artifact and persist the configured row IDs."""
43
+ super().__init__(config, rngs=rngs, name=name)
44
+
45
+ row_id_key = config.row_id_key
46
+ if row_id_key is None:
47
+ raise ValueError("row_id_key is required for IndexedEmbeddingSource")
48
+
49
+ row_ids_array = self.artifact_metadata.get(row_id_key)
50
+ row_ids: tuple[str, ...] | None = None
51
+ if row_ids_array is not None:
52
+ if int(np.asarray(row_ids_array).shape[0]) != int(self.embeddings.shape[0]):
53
+ raise ValueError(
54
+ f"{row_id_key} must contain one value per embedding row "
55
+ f"({np.asarray(row_ids_array).shape[0]} vs {self.embeddings.shape[0]})."
56
+ )
57
+ row_ids = tuple(str(item) for item in np.asarray(row_ids_array))
58
+ cast(dict[str, Any], self.data)[row_id_key] = row_ids
59
+
60
+ object.__setattr__(self, "_row_ids", row_ids)
61
+
62
+ @property
63
+ def row_id_key(self) -> str:
64
+ """Configured metadata field name that identifies artifact rows."""
65
+ return cast(str, self.config.row_id_key)
66
+
67
+ @property
68
+ def row_ids(self) -> tuple[str, ...] | None:
69
+ """Tuple of persisted row identifiers, if present."""
70
+ return self._row_ids
71
+
72
+ def load(self) -> dict[str, Any]:
73
+ """Return the eager in-memory payload exposed by the source."""
74
+ payload = super().load()
75
+ if self.row_ids is not None:
76
+ payload[self.row_id_key] = self.row_ids
77
+ return payload
78
+
79
+ def align_to_reference_ids(
80
+ self,
81
+ *,
82
+ reference_ids: list[str] | tuple[str, ...],
83
+ require_row_ids: bool = True,
84
+ artifact_label: str,
85
+ id_display_name: str,
86
+ ) -> jnp.ndarray:
87
+ """Align embedding rows to a reference dataset order."""
88
+ canonical_reference_ids = tuple(str(item) for item in reference_ids)
89
+
90
+ if self.embeddings.ndim != 2:
91
+ raise ValueError(
92
+ f"{artifact_label} embedding artifacts must be rank-2 matrices "
93
+ f"(received shape {self.embeddings.shape})."
94
+ )
95
+
96
+ if self.row_ids is None:
97
+ if require_row_ids:
98
+ raise ValueError(
99
+ f"{artifact_label} embedding artifacts must include {self.row_id_key} "
100
+ "for strict alignment."
101
+ )
102
+ if self.embeddings.shape[0] != len(canonical_reference_ids):
103
+ raise ValueError(
104
+ "Positional embedding alignment requires the same number of rows "
105
+ f"as reference items ({self.embeddings.shape[0]} vs "
106
+ f"{len(canonical_reference_ids)})."
107
+ )
108
+ return self.embeddings
109
+
110
+ if len(set(canonical_reference_ids)) != len(canonical_reference_ids):
111
+ raise ValueError(f"Reference {self.row_id_key} values must be unique.")
112
+ if len(set(self.row_ids)) != len(self.row_ids):
113
+ raise ValueError(f"Embedding artifact {self.row_id_key} values must be unique.")
114
+
115
+ reference_set = set(canonical_reference_ids)
116
+ artifact_set = set(self.row_ids)
117
+ if reference_set != artifact_set:
118
+ missing = sorted(reference_set - artifact_set)
119
+ extra = sorted(artifact_set - reference_set)
120
+ raise ValueError(
121
+ f"{id_display_name} mismatch between reference dataset and embedding artifact. "
122
+ f"Missing: {missing}. Extra: {extra}."
123
+ )
124
+
125
+ index_by_row_id = {row_id: index for index, row_id in enumerate(self.row_ids)}
126
+ row_indices = [index_by_row_id[row_id] for row_id in canonical_reference_ids]
127
+ reordered = np.asarray(self.embeddings)[row_indices]
128
+ return jnp.asarray(reordered, dtype=jnp.float32)
@@ -0,0 +1,191 @@
1
+ """IndexedViewSource - Lazy-loading view into a data source.
2
+
3
+ This module provides IndexedViewSource, which wraps an existing DataSourceModule
4
+ and provides access only to elements at specified indices. Elements are loaded
5
+ on-demand from the underlying source, enabling lazy loading for large datasets.
6
+ """
7
+
8
+ import logging
9
+ from dataclasses import dataclass
10
+ from typing import Iterator
11
+
12
+ import jax
13
+ import jax.numpy as jnp
14
+ from flax import nnx
15
+
16
+ from datarax.core.config import StructuralConfig
17
+ from datarax.core.data_source import DataSourceModule
18
+ from datarax.typing import Element
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class IndexedViewSourceConfig(StructuralConfig):
25
+ """Configuration for IndexedViewSource.
26
+
27
+ Attributes:
28
+ shuffle: Whether to shuffle the view indices on initialization and reset
29
+ seed: Random seed for shuffling (optional)
30
+ """
31
+
32
+ shuffle: bool = False
33
+ seed: int | None = None
34
+
35
+
36
+ class IndexedViewSource(DataSourceModule):
37
+ """Lazy-loading view into a data source using index mapping.
38
+
39
+ This source wraps an existing DataSourceModule and provides access
40
+ only to elements at specified indices. Elements are loaded ON-DEMAND
41
+ from the underlying source, enabling lazy loading for large datasets.
42
+
43
+ Key Features:
44
+
45
+ - LAZY LOADING: Elements fetched from underlying source only when accessed
46
+ - Memory efficient: Only stores indices, not actual data
47
+ - Preserves underlying source's lazy loading behavior
48
+ - Supports shuffling of view indices (not underlying data)
49
+
50
+ Example:
51
+ ```python
52
+ # Create view of first 1000 elements
53
+ indices = jnp.arange(1000)
54
+ config = IndexedViewSourceConfig()
55
+ view = IndexedViewSource(config, original_source, indices)
56
+ view[0] # Fetches original_source[indices[0]] lazily
57
+ ```
58
+
59
+ Args:
60
+ config: Configuration for the view source
61
+ source: Underlying data source to wrap
62
+ indices: Array of indices into the source to expose
63
+ rngs: Random number generators for shuffling
64
+ name: Optional name for the module
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ config: IndexedViewSourceConfig,
70
+ source: DataSourceModule,
71
+ indices: jnp.ndarray,
72
+ *,
73
+ rngs: nnx.Rngs | None = None,
74
+ name: str | None = None,
75
+ ):
76
+ """Initialize IndexedViewSource.
77
+
78
+ Args:
79
+ config: Configuration for the view source
80
+ source: Underlying data source to wrap
81
+ indices: Array of indices into the source to expose
82
+ rngs: Random number generators for shuffling
83
+ name: Optional name for the module
84
+ """
85
+ super().__init__(config, rngs=rngs, name=name)
86
+ self._source = source
87
+ self._indices = indices
88
+ self._view_indices = jnp.arange(len(indices)) # Local view ordering
89
+ self._current_idx = 0
90
+
91
+ # Apply initial shuffle if configured
92
+ if config.shuffle:
93
+ self._shuffle_view()
94
+
95
+ def _shuffle_view(self) -> None:
96
+ """Shuffle the view indices (not the underlying data)."""
97
+ if self.rngs is not None and "shuffle" in self.rngs:
98
+ key = self.rngs.shuffle()
99
+ elif self.config.seed is not None:
100
+ key = jax.random.key(self.config.seed)
101
+ else:
102
+ key = jax.random.key(0)
103
+
104
+ self._view_indices = jax.random.permutation(key, self._view_indices)
105
+
106
+ def __len__(self) -> int:
107
+ """Return number of elements in the view."""
108
+ return len(self._indices)
109
+
110
+ def __getitem__(self, idx: int) -> Element | None:
111
+ """Get element at view index (LAZY - fetches from underlying source).
112
+
113
+ Args:
114
+ idx: Index into the VIEW (0 to len(view)-1)
115
+
116
+ Returns:
117
+ Element from underlying source at mapped index, or None if out of bounds
118
+ """
119
+ if idx < 0 or idx >= len(self._indices):
120
+ return None
121
+
122
+ # Map view index -> shuffled view index -> original source index
123
+ view_idx = int(self._view_indices[idx])
124
+ source_idx = int(self._indices[view_idx])
125
+
126
+ # LAZY: Fetch from underlying source only now
127
+ return self._source[source_idx]
128
+
129
+ def __iter__(self) -> Iterator[Element]:
130
+ """Iterate over view elements (LAZY - fetches on demand)."""
131
+ self._current_idx = 0
132
+ return self
133
+
134
+ def __next__(self) -> Element:
135
+ """Get next element (LAZY)."""
136
+ if self._current_idx >= len(self._indices):
137
+ raise StopIteration
138
+
139
+ element = self[self._current_idx]
140
+ self._current_idx += 1
141
+
142
+ if element is None:
143
+ raise StopIteration
144
+
145
+ return element
146
+
147
+ def reset(self, seed: int | None = None) -> None:
148
+ """Reset iteration and optionally reshuffle.
149
+
150
+ Args:
151
+ seed: Optional new seed for shuffling
152
+ """
153
+ self._current_idx = 0
154
+
155
+ if self.config.shuffle:
156
+ if seed is not None:
157
+ # Update seed and reshuffle
158
+ key = jax.random.key(seed)
159
+ self._view_indices = jax.random.permutation(key, jnp.arange(len(self._indices)))
160
+ else:
161
+ self._shuffle_view()
162
+
163
+ def get_batch(self, batch_size: int, key: jax.Array | None = None) -> list[Element]:
164
+ """Get next batch of elements (LAZY).
165
+
166
+ Args:
167
+ batch_size: Number of elements to fetch
168
+ key: Optional RNG key (unused, for interface compatibility)
169
+
170
+ Returns:
171
+ List of elements
172
+ """
173
+ batch = []
174
+ for _ in range(batch_size):
175
+ if self._current_idx >= len(self._indices):
176
+ break
177
+ element = self[self._current_idx]
178
+ if element is not None:
179
+ batch.append(element)
180
+ self._current_idx += 1
181
+ return batch
182
+
183
+ @property
184
+ def underlying_source(self) -> DataSourceModule:
185
+ """Access the underlying data source."""
186
+ return self._source
187
+
188
+ @property
189
+ def source_indices(self) -> jnp.ndarray:
190
+ """Get the indices into the underlying source."""
191
+ return self._indices