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,243 @@
1
+ """Differentiable differential distribution operator for single-cell analysis.
2
+
3
+ This module provides a differentiable implementation of the KS-test and
4
+ pattern classification for detecting distributional differences between
5
+ two conditions in single-cell expression data, inspired by scDD
6
+ (Korthauer et al., Genome Biology 2016).
7
+
8
+ Key technique: Replace the hard empirical CDF step function with a
9
+ sigmoid-smoothed soft CDF, and replace the hard max in the KS statistic
10
+ with logsumexp-based soft_max from TemperatureOperator.
11
+
12
+ Applications: Identifying genes with differential distributions (shift,
13
+ scale, both, or none) between conditions in scRNA-seq experiments.
14
+ """
15
+
16
+ import logging
17
+ from dataclasses import dataclass
18
+ from typing import Any
19
+
20
+ import jax
21
+ import jax.numpy as jnp
22
+ from datarax.core.config import OperatorConfig
23
+ from flax import nnx
24
+ from jaxtyping import Array, Float, PyTree
25
+
26
+ from diffbio.constants import EPSILON
27
+
28
+ from diffbio.core.base_operators import TemperatureOperator
29
+ from diffbio.utils.nn_utils import ensure_rngs
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class DifferentialDistributionConfig(OperatorConfig):
36
+ """Configuration for differentiable differential distribution testing.
37
+
38
+ Attributes:
39
+ n_genes: Number of genes to analyse.
40
+ temperature: Temperature controlling sigmoid smoothness in the soft
41
+ CDF and logsumexp soft max. Lower values yield sharper
42
+ approximations closer to the true KS statistic.
43
+ learnable_temperature: Whether temperature is a learnable parameter.
44
+ n_pattern_classes: Number of distributional pattern categories.
45
+ Default 4 corresponds to (shift, scale, both, none).
46
+ """
47
+
48
+ n_genes: int = 2000
49
+ temperature: float = 1.0
50
+ learnable_temperature: bool = False
51
+ n_pattern_classes: int = 4
52
+
53
+
54
+ class DifferentiableDifferentialDistribution(TemperatureOperator):
55
+ """Differentiable KS-test with learned pattern classification.
56
+
57
+ For each gene, this operator:
58
+
59
+ 1. Splits cells into two conditions based on binary condition labels.
60
+ 2. Computes a soft empirical CDF using sigmoid smoothing:
61
+ ``soft_CDF(x, values) = mean(sigmoid((x - values) / temperature))``
62
+ 3. Computes a soft KS statistic as the smooth maximum of
63
+ ``|CDF_A(x) - CDF_B(x)|`` over evaluation points, using logsumexp.
64
+ 4. Extracts distributional features (mean shift, variance ratio,
65
+ zero-proportion difference) and passes them through a learned
66
+ linear head to classify each gene into one of the pattern categories
67
+ (shift, scale, both, none).
68
+
69
+ Inherits from TemperatureOperator to get:
70
+
71
+ - _temperature property for temperature-controlled smoothing
72
+ - soft_max() for logsumexp-based smooth maximum
73
+
74
+ Args:
75
+ config: DifferentialDistributionConfig with model parameters.
76
+ rngs: Flax NNX random number generators.
77
+ name: Optional operator name.
78
+
79
+ Example:
80
+ ```python
81
+ config = DifferentialDistributionConfig(n_genes=2000, temperature=1.0)
82
+ op = DifferentiableDifferentialDistribution(config, rngs=nnx.Rngs(42))
83
+ data = {"counts": counts, "condition_labels": labels}
84
+ result, state, meta = op.apply(data, {}, None)
85
+ ```
86
+ """
87
+
88
+ # Number of features extracted per gene for pattern classification:
89
+ # mean_shift, variance_ratio, zero_proportion_diff
90
+ _N_PATTERN_FEATURES: int = 3
91
+
92
+ def __init__(
93
+ self,
94
+ config: DifferentialDistributionConfig,
95
+ *,
96
+ rngs: nnx.Rngs | None = None,
97
+ name: str | None = None,
98
+ ) -> None:
99
+ """Initialize the differentiable differential distribution operator.
100
+
101
+ Args:
102
+ config: Differential distribution configuration.
103
+ rngs: Random number generators for parameter initialisation.
104
+ name: Optional operator name.
105
+ """
106
+ super().__init__(config, rngs=rngs, name=name)
107
+
108
+ self.n_genes = config.n_genes
109
+ self.n_pattern_classes = config.n_pattern_classes
110
+
111
+ rngs_safe = ensure_rngs(rngs)
112
+
113
+ # Learned linear head: pattern features -> pattern logits
114
+ self.pattern_head = nnx.Linear(
115
+ in_features=self._N_PATTERN_FEATURES,
116
+ out_features=self.n_pattern_classes,
117
+ rngs=rngs_safe,
118
+ )
119
+
120
+ def _process_single_gene(
121
+ self,
122
+ gene_values: Float[Array, "n_cells"],
123
+ condition_mask: Float[Array, "n_cells"],
124
+ ) -> tuple[Float[Array, ""], Float[Array, "n_patterns"]]:
125
+ """Process a single gene: compute KS stat and pattern logits.
126
+
127
+ Args:
128
+ gene_values: Expression values for one gene across all cells.
129
+ condition_mask: Binary mask (0/1) indicating condition membership.
130
+
131
+ Returns:
132
+ Tuple of (ks_statistic, pattern_logits).
133
+ """
134
+ # Soft splitting: weight contributions by condition membership
135
+ # condition_mask=0 -> condition A, condition_mask=1 -> condition B
136
+ mask_a = 1.0 - condition_mask
137
+ mask_b = condition_mask
138
+
139
+ n_a = jnp.sum(mask_a) + EPSILON
140
+ n_b = jnp.sum(mask_b) + EPSILON
141
+
142
+ # Weighted values for each condition using soft masks
143
+ # For the CDF computation, we use all values but weight by condition
144
+ # To handle variable-size splits in a JIT-compatible way, we compute
145
+ # weighted statistics instead of explicit splits.
146
+
147
+ # For KS: evaluate soft CDF using the full set of values, but weight
148
+ # the indicator functions by condition membership.
149
+ eval_points = gene_values # Evaluate at all cell values
150
+
151
+ temp = self._temperature
152
+ # diff: (n_cells, n_cells) -- eval_points[i] vs gene_values[j]
153
+ diff = eval_points[:, None] - gene_values[None, :]
154
+ sigmoid_vals = jax.nn.sigmoid(diff / temp)
155
+
156
+ # Weighted CDF for condition A: sum(sigmoid * mask_a) / n_a
157
+ cdf_a = jnp.sum(sigmoid_vals * mask_a[None, :], axis=1) / n_a
158
+ # Weighted CDF for condition B: sum(sigmoid * mask_b) / n_b
159
+ cdf_b = jnp.sum(sigmoid_vals * mask_b[None, :], axis=1) / n_b
160
+
161
+ abs_diff = jnp.abs(cdf_a - cdf_b)
162
+ # Use softmax-weighted sum as smooth max: sum_i(x_i * softmax(x_i/T))
163
+ # This stays within [min(x), max(x)] unlike logsumexp which overshoots.
164
+ temp = self._temperature
165
+ weights = jax.nn.softmax(abs_diff / (temp + EPSILON))
166
+ ks_stat = jnp.sum(abs_diff * weights)
167
+
168
+ # Pattern features using weighted statistics
169
+ mean_a = jnp.sum(gene_values * mask_a) / n_a
170
+ mean_b = jnp.sum(gene_values * mask_b) / n_b
171
+ mean_shift = jnp.abs(mean_a - mean_b)
172
+
173
+ var_a = jnp.sum(mask_a * (gene_values - mean_a) ** 2) / n_a + EPSILON
174
+ var_b = jnp.sum(mask_b * (gene_values - mean_b) ** 2) / n_b + EPSILON
175
+ variance_ratio = jax.nn.sigmoid(jnp.log(var_a / var_b))
176
+
177
+ # Soft zero fraction per condition
178
+ soft_zero = jax.nn.sigmoid(-gene_values / (temp + EPSILON))
179
+ frac_zero_a = jnp.sum(soft_zero * mask_a) / n_a
180
+ frac_zero_b = jnp.sum(soft_zero * mask_b) / n_b
181
+ zero_diff = jnp.abs(frac_zero_a - frac_zero_b)
182
+
183
+ features = jnp.stack([mean_shift, variance_ratio, zero_diff])
184
+ pattern_logits = self.pattern_head(features)
185
+
186
+ return ks_stat, pattern_logits
187
+
188
+ def apply(
189
+ self,
190
+ data: PyTree,
191
+ state: PyTree,
192
+ metadata: dict[str, Any] | None,
193
+ random_params: Any = None,
194
+ stats: dict[str, Any] | None = None,
195
+ ) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
196
+ """Apply differentiable differential distribution testing.
197
+
198
+ For each gene, computes a soft KS statistic and classifies the
199
+ distributional difference pattern using a learned linear head.
200
+
201
+ Args:
202
+ data: Dictionary containing:
203
+ - "counts": Gene expression matrix (n_cells, n_genes)
204
+ - "condition_labels": Binary condition labels (n_cells,)
205
+ state: Element state (passed through unchanged).
206
+ metadata: Element metadata (passed through unchanged).
207
+ random_params: Not used.
208
+ stats: Not used.
209
+
210
+ Returns:
211
+ Tuple of (transformed_data, state, metadata):
212
+ - transformed_data contains:
213
+
214
+ - "counts": Original expression counts
215
+ - "condition_labels": Original condition labels
216
+ - "ks_statistics": Soft KS statistic per gene (n_genes,)
217
+ - "pattern_logits": Pattern class logits (n_genes, n_patterns)
218
+ - "pattern_labels": Predicted pattern labels (n_genes,)
219
+ - state is passed through unchanged
220
+ - metadata is passed through unchanged
221
+ """
222
+ counts = data["counts"]
223
+ condition_labels = data["condition_labels"]
224
+
225
+ # Process all genes in parallel using vmap over gene dimension (axis 1)
226
+ def process_gene(
227
+ gene_col: Float[Array, "n_cells"],
228
+ ) -> tuple[Float[Array, ""], Float[Array, "n_patterns"]]:
229
+ return self._process_single_gene(gene_col, condition_labels)
230
+
231
+ # vmap over columns (genes) of counts: (n_cells, n_genes) -> per-gene
232
+ ks_statistics, pattern_logits = jax.vmap(process_gene, in_axes=1)(counts)
233
+
234
+ pattern_labels = jnp.argmax(pattern_logits, axis=-1)
235
+
236
+ transformed_data = {
237
+ **data,
238
+ "ks_statistics": ks_statistics,
239
+ "pattern_logits": pattern_logits,
240
+ "pattern_labels": pattern_labels,
241
+ }
242
+
243
+ return transformed_data, state, metadata