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,274 @@
1
+ """Soft Variant Quality Filter for VQSR-style recalibration.
2
+
3
+ This module provides a differentiable variant quality filter using
4
+ a Gaussian Mixture Model for scoring and sigmoid-based soft filtering.
5
+
6
+ Key technique: Differentiable GMM enables end-to-end learning of
7
+ quality distributions, with sigmoid thresholds maintaining gradients.
8
+
9
+ Applications: VQSR-style variant filtering, quality score recalibration.
10
+
11
+ Inherits from TemperatureOperator to get:
12
+
13
+ - _temperature property for temperature-controlled smoothing
14
+ - soft_max() for logsumexp-based smooth maximum
15
+ - soft_argmax() for soft position selection
16
+ """
17
+
18
+ import logging
19
+ from dataclasses import dataclass
20
+ from typing import Any
21
+
22
+ import jax
23
+ import jax.numpy as jnp
24
+ from datarax.core.config import OperatorConfig
25
+ from flax import nnx
26
+ from jaxtyping import Array, Float, PyTree
27
+
28
+ from diffbio.core import soft_ops
29
+ from diffbio.core.base_operators import TemperatureOperator
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class VariantQualityFilterConfig(OperatorConfig):
36
+ """Configuration for SoftVariantQualityFilter.
37
+
38
+ Attributes:
39
+ n_components: Number of GMM components.
40
+ n_features: Number of variant features.
41
+ threshold: Quality score threshold for filtering.
42
+ temperature: Temperature for softmax/sigmoid operations.
43
+ """
44
+
45
+ n_components: int = 3
46
+ n_features: int = 4 # depth, qual, strand_bias, mapq
47
+ threshold: float = 0.5
48
+ temperature: float = 1.0
49
+
50
+
51
+ class SoftVariantQualityFilter(TemperatureOperator):
52
+ """Differentiable variant quality filter using GMM.
53
+
54
+ This operator implements VQSR-style variant quality recalibration
55
+ using a learnable Gaussian Mixture Model. Variants are scored
56
+ by their likelihood under the GMM, and soft filtering is applied
57
+ via sigmoid thresholds.
58
+
59
+ Algorithm:
60
+ 1. Compute GMM component responsibilities (E-step style)
61
+ 2. Score variants by weighted log-likelihood
62
+ 3. Apply sigmoid threshold for soft filtering
63
+
64
+ Args:
65
+ config: VariantQualityFilterConfig with model parameters.
66
+ rngs: Flax NNX random number generators.
67
+ name: Optional operator name.
68
+
69
+ Example:
70
+ ```python
71
+ config = VariantQualityFilterConfig(n_components=3)
72
+ filter_op = SoftVariantQualityFilter(config, rngs=nnx.Rngs(42))
73
+ data = {"variant_features": features} # (n_variants, n_features)
74
+ result, state, meta = filter_op.apply(data, {}, None)
75
+ ```
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ config: VariantQualityFilterConfig,
81
+ *,
82
+ rngs: nnx.Rngs | None = None,
83
+ name: str | None = None,
84
+ ):
85
+ """Initialize the quality filter.
86
+
87
+ Args:
88
+ config: Filter configuration.
89
+ rngs: Random number generators for initialization.
90
+ name: Optional operator name.
91
+ """
92
+ super().__init__(config, rngs=rngs, name=name)
93
+
94
+ if rngs is None:
95
+ rngs = nnx.Rngs(0)
96
+
97
+ self.n_components = config.n_components
98
+ self.n_features = config.n_features
99
+ self.threshold = config.threshold
100
+ # Temperature is managed by TemperatureOperator via self._temperature
101
+
102
+ # Initialize GMM parameters
103
+ # Component means: (n_components, n_features)
104
+ key = rngs.params()
105
+ init_means = jax.random.normal(key, (config.n_components, config.n_features)) * 0.5
106
+ self.means = nnx.Param(init_means)
107
+
108
+ # Component log variances (diagonal covariance): (n_components, n_features)
109
+ key = rngs.params()
110
+ init_log_var = jax.random.normal(key, (config.n_components, config.n_features)) * 0.1
111
+ self.log_variances = nnx.Param(init_log_var)
112
+
113
+ # Component mixing weights (unnormalized): (n_components,)
114
+ key = rngs.params()
115
+ noise = jax.random.normal(key, (config.n_components,)) * 0.1
116
+ init_weights = jnp.ones(config.n_components) + noise
117
+ self.log_mixing_weights = nnx.Param(init_weights)
118
+
119
+ # Learned quality score projection
120
+ self.quality_proj = nnx.Linear(config.n_features, 1, rngs=rngs)
121
+
122
+ def get_mixing_weights(self) -> Float[Array, "n_components"]:
123
+ """Get normalized mixing weights.
124
+
125
+ Returns:
126
+ Mixing weights summing to 1.
127
+ """
128
+ return jax.nn.softmax(self.log_mixing_weights[...] / self._temperature)
129
+
130
+ def get_variances(self) -> Float[Array, "n_components n_features"]:
131
+ """Get positive variances from log parameters.
132
+
133
+ Returns:
134
+ Variance values.
135
+ """
136
+ return jnp.exp(self.log_variances[...])
137
+
138
+ def compute_component_log_probs(
139
+ self,
140
+ features: Float[Array, "n_variants n_features"],
141
+ ) -> Float[Array, "n_variants n_components"]:
142
+ """Compute log probability under each GMM component.
143
+
144
+ Args:
145
+ features: Variant feature vectors.
146
+
147
+ Returns:
148
+ Log probability for each variant under each component.
149
+ """
150
+ means = self.means[...] # (n_components, n_features)
151
+ variances = self.get_variances() # (n_components, n_features)
152
+
153
+ # Expand for broadcasting
154
+ # features: (n_variants, 1, n_features)
155
+ # means: (1, n_components, n_features)
156
+ features_exp = features[:, None, :]
157
+ means_exp = means[None, :, :]
158
+ variances_exp = variances[None, :, :]
159
+
160
+ # Gaussian log probability (diagonal covariance)
161
+ # log p(x | mu, sigma^2) = -0.5 * sum((x - mu)^2 / sigma^2 + log(2*pi*sigma^2))
162
+ diff_sq = (features_exp - means_exp) ** 2
163
+ log_prob = -0.5 * jnp.sum(
164
+ diff_sq / variances_exp + jnp.log(2 * jnp.pi * variances_exp), axis=-1
165
+ ) # (n_variants, n_components)
166
+
167
+ return log_prob
168
+
169
+ def compute_responsibilities(
170
+ self,
171
+ features: Float[Array, "n_variants n_features"],
172
+ ) -> Float[Array, "n_variants n_components"]:
173
+ """Compute component responsibilities (soft assignments).
174
+
175
+ Args:
176
+ features: Variant feature vectors.
177
+
178
+ Returns:
179
+ Responsibility matrix (probability each variant belongs to each component).
180
+ """
181
+ log_probs = self.compute_component_log_probs(features) # (n_variants, n_components)
182
+ mixing_weights = self.get_mixing_weights() # (n_components,)
183
+
184
+ # log P(x, z) = log P(x | z) + log P(z)
185
+ log_joint = log_probs + jnp.log(mixing_weights + 1e-10)
186
+
187
+ # Responsibilities via softmax (with temperature)
188
+ responsibilities = jax.nn.softmax(log_joint / self._temperature, axis=-1)
189
+
190
+ return responsibilities
191
+
192
+ def compute_quality_scores(
193
+ self,
194
+ features: Float[Array, "n_variants n_features"],
195
+ ) -> Float[Array, "n_variants"]:
196
+ """Compute quality scores for variants.
197
+
198
+ Combines GMM likelihood with learned projection.
199
+
200
+ Args:
201
+ features: Variant feature vectors.
202
+
203
+ Returns:
204
+ Quality scores in [0, 1] range.
205
+ """
206
+ # GMM-based score
207
+ log_probs = self.compute_component_log_probs(features) # (n_variants, n_components)
208
+ mixing_weights = self.get_mixing_weights() # (n_components,)
209
+
210
+ # Total log likelihood under mixture
211
+ log_likelihood = jax.scipy.special.logsumexp(
212
+ log_probs + jnp.log(mixing_weights + 1e-10), axis=-1
213
+ ) # (n_variants,)
214
+
215
+ # Learned quality projection
216
+ learned_quality = self.quality_proj(features).squeeze(-1) # (n_variants,)
217
+
218
+ # Combine and normalize to [0, 1]
219
+ combined_score = log_likelihood + learned_quality
220
+ quality_scores = jax.nn.sigmoid(combined_score)
221
+
222
+ return quality_scores
223
+
224
+ def apply(
225
+ self,
226
+ data: PyTree,
227
+ state: PyTree,
228
+ metadata: dict[str, Any] | None,
229
+ random_params: Any = None,
230
+ stats: dict[str, Any] | None = None,
231
+ ) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
232
+ """Apply quality filtering to variants.
233
+
234
+ Args:
235
+ data: Dictionary containing:
236
+ - "variant_features": Feature vectors (n_variants, n_features)
237
+ state: Element state (passed through unchanged)
238
+ metadata: Element metadata (passed through unchanged)
239
+ random_params: Not used
240
+ stats: Not used
241
+
242
+ Returns:
243
+ Tuple of (transformed_data, state, metadata):
244
+ - transformed_data contains:
245
+
246
+ - "variant_features": Original features
247
+ - "quality_scores": Computed quality scores [0, 1]
248
+ - "filter_weights": Soft filter weights [0, 1]
249
+ - "component_probs": GMM component responsibilities
250
+ - state is passed through unchanged
251
+ - metadata is passed through unchanged
252
+ """
253
+ features = data["variant_features"]
254
+
255
+ # Compute quality scores
256
+ quality_scores = self.compute_quality_scores(features)
257
+
258
+ # Soft filter weights using sigmoid threshold
259
+ filter_weights = soft_ops.greater(
260
+ quality_scores, self.threshold, softness=self._temperature
261
+ )
262
+
263
+ # Component responsibilities
264
+ component_probs = self.compute_responsibilities(features)
265
+
266
+ # Build output data
267
+ transformed_data = {
268
+ "variant_features": features,
269
+ "quality_scores": quality_scores,
270
+ "filter_weights": filter_weights,
271
+ "component_probs": component_probs,
272
+ }
273
+
274
+ return transformed_data, state, metadata
@@ -0,0 +1,65 @@
1
+ """Pre-built differentiable pipeline templates.
2
+
3
+ This module provides ready-to-use pipeline templates for common bioinformatics
4
+ workflows such as variant calling, preprocessing, differential expression,
5
+ and single-cell analysis.
6
+ """
7
+
8
+ from diffbio.pipelines.differential_expression import (
9
+ DEPipelineConfig,
10
+ DifferentialExpressionPipeline,
11
+ )
12
+ from diffbio.pipelines.enhanced_variant_calling import (
13
+ EnhancedVariantCallingPipeline,
14
+ EnhancedVariantCallingPipelineConfig,
15
+ create_enhanced_variant_calling_pipeline,
16
+ )
17
+ from diffbio.pipelines.preprocessing import (
18
+ PreprocessingPipeline,
19
+ PreprocessingPipelineConfig,
20
+ create_preprocessing_pipeline,
21
+ )
22
+ from diffbio.pipelines.single_cell import (
23
+ SingleCellPipeline,
24
+ SingleCellPipelineConfig,
25
+ create_single_cell_pipeline,
26
+ )
27
+ from diffbio.pipelines.perturbation import (
28
+ PerturbationPipeline,
29
+ PerturbationPipelineConfig,
30
+ PerturbationPipelineResult,
31
+ )
32
+ from diffbio.pipelines.variant_calling import (
33
+ VariantCallingPipeline,
34
+ VariantCallingPipelineConfig,
35
+ create_cnn_variant_pipeline,
36
+ create_variant_calling_pipeline,
37
+ )
38
+
39
+
40
+ __all__ = [
41
+ # Differential Expression
42
+ "DEPipelineConfig",
43
+ "DifferentialExpressionPipeline",
44
+ # Enhanced Variant Calling
45
+ "EnhancedVariantCallingPipeline",
46
+ "EnhancedVariantCallingPipelineConfig",
47
+ "create_enhanced_variant_calling_pipeline",
48
+ # Preprocessing
49
+ "PreprocessingPipeline",
50
+ "PreprocessingPipelineConfig",
51
+ "create_preprocessing_pipeline",
52
+ # Single-Cell Analysis
53
+ "SingleCellPipeline",
54
+ "SingleCellPipelineConfig",
55
+ "create_single_cell_pipeline",
56
+ # Perturbation
57
+ "PerturbationPipeline",
58
+ "PerturbationPipelineConfig",
59
+ "PerturbationPipelineResult",
60
+ # Variant calling
61
+ "VariantCallingPipeline",
62
+ "VariantCallingPipelineConfig",
63
+ "create_variant_calling_pipeline",
64
+ "create_cnn_variant_pipeline",
65
+ ]
@@ -0,0 +1,279 @@
1
+ """Differentiable differential expression analysis pipeline.
2
+
3
+ This module implements an end-to-end differentiable differential expression
4
+ pipeline inspired by DESeq2, with negative binomial modeling and size factor
5
+ normalization.
6
+ """
7
+
8
+ import logging
9
+ from dataclasses import dataclass
10
+ from typing import Any
11
+
12
+ import flax.nnx as nnx
13
+ import jax
14
+ import jax.numpy as jnp
15
+ from datarax.core.config import OperatorConfig
16
+ from datarax.core.operator import OperatorModule
17
+
18
+ from diffbio.core import soft_ops
19
+ from diffbio.operators.statistical.nb_glm import DifferentiableNBGLM, NBGLMConfig
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class DEPipelineConfig(OperatorConfig):
26
+ """Configuration for differential expression pipeline.
27
+
28
+ Attributes:
29
+ n_genes: Number of genes to analyze.
30
+ n_conditions: Number of conditions (covariates) in design matrix.
31
+ alpha: Significance threshold for differential expression.
32
+ use_size_factors: Whether to compute and use size factors.
33
+ """
34
+
35
+ n_genes: int = 1000
36
+ n_conditions: int = 2
37
+ alpha: float = 0.05
38
+ use_size_factors: bool = True
39
+
40
+
41
+ class DifferentialExpressionPipeline(OperatorModule):
42
+ """End-to-end differentiable differential expression analysis.
43
+
44
+ This pipeline implements a DESeq2-style analysis with:
45
+ 1. Size factor normalization (median-of-ratios)
46
+ 2. Negative binomial GLM fitting
47
+ 3. Wald test for significance
48
+ 4. Multiple testing correction (soft approximation)
49
+
50
+ All steps maintain gradient flow for end-to-end learning.
51
+
52
+ Example:
53
+ ```python
54
+ config = DEPipelineConfig(
55
+ n_genes=5000,
56
+ n_conditions=2,
57
+ )
58
+ pipeline = DifferentialExpressionPipeline(config, rngs=rngs)
59
+
60
+ data = {
61
+ "counts": count_matrix, # (n_samples, n_genes)
62
+ "design": design_matrix, # (n_samples, n_conditions)
63
+ }
64
+ result, state, metadata = pipeline.apply(data, {}, None)
65
+ lfc = result["log_fold_change"]
66
+ pvals = result["p_values"]
67
+ significant = result["significant"]
68
+ ```
69
+ """
70
+
71
+ def __init__(self, config: DEPipelineConfig, *, rngs: nnx.Rngs | None = None):
72
+ """Initialize the differential expression pipeline.
73
+
74
+ Args:
75
+ config: Configuration for the pipeline.
76
+ rngs: Random number generators for initialization.
77
+ """
78
+ super().__init__(config, rngs=rngs)
79
+ self.config = config
80
+
81
+ if rngs is None:
82
+ rngs = nnx.Rngs(0)
83
+
84
+ # Initialize the NB GLM
85
+ nb_config = NBGLMConfig(
86
+ n_features=config.n_genes,
87
+ n_covariates=config.n_conditions,
88
+ estimate_dispersion=True,
89
+ stream_name=config.stream_name,
90
+ )
91
+ self.nb_glm = DifferentiableNBGLM(nb_config, rngs=rngs)
92
+
93
+ def _compute_size_factors(self, counts: jax.Array) -> jax.Array:
94
+ """Compute size factors using median-of-ratios method (DESeq2 style).
95
+
96
+ Args:
97
+ counts: Count matrix of shape (n_samples, n_genes).
98
+
99
+ Returns:
100
+ Size factors of shape (n_samples,).
101
+ """
102
+ # Compute geometric mean per gene (reference sample)
103
+ # Add pseudocount for numerical stability
104
+ log_counts = jnp.log(counts + 1)
105
+ geo_mean_log = jnp.mean(log_counts, axis=0)
106
+
107
+ # Compute ratios to geometric mean
108
+ log_ratios = log_counts - geo_mean_log[None, :]
109
+
110
+ # Size factor = median of ratios for each sample
111
+ size_factors = jnp.exp(soft_ops.median(log_ratios, axis=1, softness=0.1))
112
+
113
+ # Normalize to have geometric mean of 1
114
+ size_factors = size_factors / jnp.exp(jnp.mean(jnp.log(size_factors + 1e-8)))
115
+
116
+ return size_factors
117
+
118
+ def _compute_wald_statistic(
119
+ self,
120
+ beta: jax.Array,
121
+ dispersion: jax.Array,
122
+ design: jax.Array,
123
+ size_factors: jax.Array,
124
+ ) -> tuple[jax.Array, jax.Array]:
125
+ """Compute Wald test statistics for differential expression.
126
+
127
+ The Wald statistic tests H0: beta[coef_idx] = 0.
128
+
129
+ Args:
130
+ beta: Coefficient matrix of shape (n_conditions, n_genes).
131
+ dispersion: Dispersion parameters of shape (n_genes,).
132
+ design: Design matrix of shape (n_samples, n_conditions).
133
+ size_factors: Size factors of shape (n_samples,).
134
+
135
+ Returns:
136
+ Tuple of (wald_statistic, standard_error).
137
+ """
138
+ # Compute predicted means
139
+ log_mu = jnp.dot(design, beta) # (n_samples, n_genes)
140
+ mu = jnp.exp(log_mu) * size_factors[:, None]
141
+
142
+ # Compute variance of NB distribution
143
+ # Var = mu + mu^2 / dispersion
144
+ variance = mu + jnp.square(mu) / (dispersion[None, :] + 1e-8)
145
+
146
+ # Fisher information for beta (approximate)
147
+ # I = X^T W X where W = diag(mu^2 / variance)
148
+ weights = jnp.square(mu) / (variance + 1e-8)
149
+
150
+ # Standard error of beta[1] (treatment effect)
151
+ # SE = sqrt(diag((X^T W X)^{-1}))
152
+ # Simplified: use diagonal approximation
153
+
154
+ # For the treatment coefficient (index 1), approximate SE
155
+ design_sq = jnp.square(design[:, 1:2]) # Treatment column
156
+ weighted_design = jnp.sum(weights * design_sq, axis=0)
157
+ se = 1.0 / jnp.sqrt(weighted_design + 1e-8)
158
+
159
+ # Wald statistic for treatment coefficient
160
+ wald_stat = beta[1, :] / (se + 1e-8)
161
+
162
+ return wald_stat, se
163
+
164
+ def _wald_to_pvalue(self, wald_stat: jax.Array) -> jax.Array:
165
+ """Convert Wald statistic to p-value using soft normal CDF.
166
+
167
+ Args:
168
+ wald_stat: Wald statistics of shape (n_genes,).
169
+
170
+ Returns:
171
+ Two-sided p-values of shape (n_genes,).
172
+ """
173
+ # Two-sided p-value using standard normal
174
+ # p = 2 * (1 - Phi(|z|))
175
+ # Use jax.scipy.stats.norm.sf for survival function
176
+
177
+ # Soft approximation using sigmoid for differentiability
178
+ # Approximate normal CDF: Phi(x) ≈ sigmoid(1.7 * x)
179
+ abs_z = jnp.abs(wald_stat)
180
+ p_one_sided = 1.0 - jax.nn.sigmoid(1.7 * abs_z)
181
+ p_values = 2.0 * p_one_sided
182
+
183
+ # Clamp to [0, 1]
184
+ p_values = soft_ops.clip(p_values, 0.0, 1.0, softness=0.1)
185
+
186
+ return p_values
187
+
188
+ def _soft_significance(
189
+ self, p_values: jax.Array, alpha: float, temperature: float = 0.1
190
+ ) -> jax.Array:
191
+ """Compute soft significance indicator.
192
+
193
+ Args:
194
+ p_values: P-values of shape (n_genes,).
195
+ alpha: Significance threshold.
196
+ temperature: Temperature for sigmoid smoothing.
197
+
198
+ Returns:
199
+ Soft significance indicators of shape (n_genes,).
200
+ """
201
+ # Soft thresholding: significant if p < alpha
202
+ return soft_ops.less(p_values, alpha, softness=temperature)
203
+
204
+ def apply(
205
+ self,
206
+ data: dict[str, Any],
207
+ state: dict[str, Any],
208
+ metadata: dict | None,
209
+ random_params: dict | None = None,
210
+ stats: dict | None = None,
211
+ ) -> tuple[dict, dict, dict | None]:
212
+ """Apply differential expression analysis.
213
+
214
+ Args:
215
+ data: Dictionary containing:
216
+ - 'counts': Count matrix of shape (n_samples, n_genes)
217
+ - 'design': Design matrix of shape (n_samples, n_conditions)
218
+ state: Operator state dictionary.
219
+ metadata: Optional metadata dictionary.
220
+ random_params: Optional random parameters (unused).
221
+ stats: Optional statistics dictionary (unused).
222
+
223
+ Returns:
224
+ Tuple of (output_data, state, metadata) where output_data contains:
225
+
226
+ - 'counts': Original count matrix
227
+ - 'design': Original design matrix
228
+ - 'size_factors': Computed size factors
229
+ - 'predicted_mean': Predicted mean expression
230
+ - 'log_fold_change': Log2 fold change estimates
231
+ - 'wald_statistic': Wald test statistics
232
+ - 'standard_error': Standard errors
233
+ - 'p_values': P-values for differential expression
234
+ - 'significant': Soft significance indicators
235
+ """
236
+ del random_params, stats # Unused
237
+
238
+ counts = data["counts"]
239
+ design = data["design"]
240
+
241
+ # Compute size factors
242
+ if self.config.use_size_factors:
243
+ size_factors = self._compute_size_factors(counts)
244
+ else:
245
+ size_factors = jnp.ones(counts.shape[0])
246
+
247
+ # Fit NB GLM for each sample
248
+ # The NB GLM expects single samples, so we process in batch
249
+ beta = self.nb_glm.beta[...]
250
+ dispersion = jnp.exp(self.nb_glm.log_dispersion[...])
251
+
252
+ # Compute predicted means
253
+ log_mu = jnp.dot(design, beta)
254
+ predicted_mean = jnp.exp(log_mu) * size_factors[:, None]
255
+
256
+ # Compute Wald statistics
257
+ wald_stat, se = self._compute_wald_statistic(beta, dispersion, design, size_factors)
258
+
259
+ # Convert to p-values
260
+ p_values = self._wald_to_pvalue(wald_stat)
261
+
262
+ # Compute log fold change (treatment coefficient in log2 scale)
263
+ log_fold_change = beta[1, :] / jnp.log(2)
264
+
265
+ # Soft significance
266
+ significant = self._soft_significance(p_values, self.config.alpha)
267
+
268
+ output_data = {
269
+ **data,
270
+ "size_factors": size_factors,
271
+ "predicted_mean": predicted_mean,
272
+ "log_fold_change": log_fold_change,
273
+ "wald_statistic": wald_stat,
274
+ "standard_error": se,
275
+ "p_values": p_values,
276
+ "significant": significant,
277
+ }
278
+
279
+ return output_data, state, metadata