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,251 @@
1
+ """Differentiable motif discovery (MEME-style).
2
+
3
+ This module implements a differentiable version of motif discovery with
4
+ PWM (Position Weight Matrix) learning for end-to-end gradient flow.
5
+
6
+ Inherits from TemperatureOperator to get:
7
+
8
+ - _temperature property for temperature-controlled smoothing
9
+ - soft_max() for logsumexp-based smooth maximum
10
+ - soft_argmax() for soft position selection
11
+ """
12
+
13
+ import logging
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+ import flax.nnx as nnx
18
+ import jax
19
+ import jax.numpy as jnp
20
+ from datarax.core.config import OperatorConfig
21
+
22
+ from diffbio.core import soft_ops
23
+ from diffbio.core.base_operators import TemperatureOperator
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class MotifDiscoveryConfig(OperatorConfig):
30
+ """Configuration for differentiable motif discovery.
31
+
32
+ Attributes:
33
+ motif_width: Width of the motif (number of positions).
34
+ num_motifs: Number of motifs to discover.
35
+ alphabet_size: Size of the sequence alphabet (4 for DNA).
36
+ temperature: Temperature for soft operations.
37
+ background_prior: Prior probability for background model.
38
+ stream_name: Name of the data stream to process.
39
+ """
40
+
41
+ motif_width: int = 12
42
+ num_motifs: int = 1
43
+ alphabet_size: int = 4
44
+ temperature: float = 1.0
45
+ learnable_temperature: bool = True
46
+ background_prior: float = 0.25 # Uniform for DNA
47
+
48
+
49
+ class DifferentiableMotifDiscovery(TemperatureOperator):
50
+ """Differentiable motif discovery with PWM learning.
51
+
52
+ This operator implements a simplified differentiable version of MEME-style
53
+ motif discovery. It learns Position Weight Matrices (PWMs) that represent
54
+ sequence motifs and scans sequences to find motif occurrences.
55
+
56
+ The motif score at position i is computed as:
57
+ score(i) = sum_j PWM[j, seq[i+j]]
58
+
59
+ For one-hot encoded sequences, this is equivalent to:
60
+ score(i) = sum_j sum_k seq[i+j, k] * log(PWM[j, k])
61
+
62
+ Example:
63
+ ```python
64
+ config = MotifDiscoveryConfig(
65
+ motif_width=12,
66
+ num_motifs=3,
67
+ )
68
+ motif_op = DifferentiableMotifDiscovery(config, rngs=rngs)
69
+
70
+ data = {"sequence": one_hot_sequence} # (length, alphabet_size)
71
+ result, state, metadata = motif_op.apply(data, {}, None)
72
+ motif_scores = result["motif_scores"] # (num_positions, num_motifs)
73
+ pwm = result["pwm"] # (num_motifs, motif_width, alphabet_size)
74
+ ```
75
+ """
76
+
77
+ def __init__(self, config: MotifDiscoveryConfig, *, rngs: nnx.Rngs | None = None):
78
+ """Initialize the motif discovery operator.
79
+
80
+ Args:
81
+ config: Configuration for the operator.
82
+ rngs: Random number generators for initialization.
83
+ """
84
+ super().__init__(config, rngs=rngs)
85
+ self.config = config
86
+
87
+ if rngs is None:
88
+ rngs = nnx.Rngs(0)
89
+
90
+ key = rngs.params() if hasattr(rngs, "params") else jax.random.key(0)
91
+
92
+ # Initialize PWM logits (before softmax normalization)
93
+ # Shape: (num_motifs, motif_width, alphabet_size)
94
+ # Initialize near uniform with small random noise
95
+ pwm_init = (
96
+ jax.random.normal(
97
+ key,
98
+ (config.num_motifs, config.motif_width, config.alphabet_size),
99
+ )
100
+ * 0.1
101
+ )
102
+
103
+ self.pwm_logits = nnx.Param(pwm_init)
104
+
105
+ # Temperature is managed by TemperatureOperator via self._temperature
106
+
107
+ def _get_pwm(self) -> jax.Array:
108
+ """Get normalized PWM from logits.
109
+
110
+ Returns:
111
+ PWM of shape (num_motifs, motif_width, alphabet_size) with
112
+ probabilities summing to 1 over the alphabet dimension.
113
+ """
114
+ temperature = jnp.abs(self._temperature) + 1e-6
115
+ return jax.nn.softmax(self.pwm_logits[...] / temperature, axis=-1)
116
+
117
+ def _scan_single_motif(self, sequence: jax.Array, pwm: jax.Array) -> jax.Array:
118
+ """Scan a sequence with a single PWM using convolution.
119
+
120
+ Args:
121
+ sequence: One-hot encoded sequence of shape (length, alphabet_size).
122
+ pwm: PWM of shape (motif_width, alphabet_size).
123
+
124
+ Returns:
125
+ Motif scores at each valid position, shape (num_positions,).
126
+ """
127
+ motif_width = pwm.shape[0]
128
+ seq_length = sequence.shape[0]
129
+ num_positions = seq_length - motif_width + 1
130
+
131
+ # Use log-odds scoring
132
+ # log_pwm = log(PWM) - log(background)
133
+ background = self.config.background_prior
134
+ log_pwm = jnp.log(pwm + 1e-8) - jnp.log(background)
135
+
136
+ # Compute score at each position using sliding window
137
+ def score_at_position(start_idx):
138
+ window = jax.lax.dynamic_slice(
139
+ sequence, (start_idx, 0), (motif_width, self.config.alphabet_size)
140
+ )
141
+ # Score = sum of log-odds weighted by sequence
142
+ score = jnp.sum(window * log_pwm)
143
+ return score
144
+
145
+ positions = jnp.arange(num_positions)
146
+ scores = jax.vmap(score_at_position)(positions)
147
+
148
+ return scores
149
+
150
+ def _scan_sequence(self, sequence: jax.Array) -> jax.Array:
151
+ """Scan a sequence with all motifs.
152
+
153
+ Args:
154
+ sequence: One-hot encoded sequence of shape (length, alphabet_size).
155
+
156
+ Returns:
157
+ Motif scores of shape (num_positions, num_motifs).
158
+ """
159
+ pwm = self._get_pwm()
160
+
161
+ # Scan with each motif
162
+ def scan_with_motif(pwm_single):
163
+ return self._scan_single_motif(sequence, pwm_single)
164
+
165
+ # Shape: (num_motifs, num_positions)
166
+ all_scores = jax.vmap(scan_with_motif)(pwm)
167
+
168
+ # Transpose to (num_positions, num_motifs)
169
+ return all_scores.T
170
+
171
+ def _find_motif_positions(self, scores: jax.Array, threshold: float = 0.0) -> jax.Array:
172
+ """Find soft motif positions based on scores.
173
+
174
+ Args:
175
+ scores: Motif scores of shape (num_positions, num_motifs).
176
+ threshold: Score threshold for calling a motif hit.
177
+
178
+ Returns:
179
+ Soft position indicators of shape (num_positions, num_motifs).
180
+ """
181
+ temperature = jnp.abs(self._temperature) + 1e-6
182
+ return soft_ops.greater(scores, threshold, softness=temperature)
183
+
184
+ def _apply_single(self, sequence: jax.Array) -> dict:
185
+ """Apply motif discovery to a single sequence.
186
+
187
+ Args:
188
+ sequence: One-hot encoded sequence of shape (length, alphabet_size).
189
+
190
+ Returns:
191
+ Dictionary with motif scores, positions, and PWM.
192
+ """
193
+ # Get current PWM
194
+ pwm = self._get_pwm()
195
+
196
+ # Scan sequence
197
+ motif_scores = self._scan_sequence(sequence)
198
+
199
+ # Find soft motif positions
200
+ motif_positions = self._find_motif_positions(motif_scores)
201
+
202
+ return {
203
+ "motif_scores": motif_scores,
204
+ "motif_positions": motif_positions,
205
+ "pwm": pwm,
206
+ }
207
+
208
+ def apply(
209
+ self,
210
+ data: dict[str, Any],
211
+ state: dict[str, Any],
212
+ metadata: dict | None,
213
+ random_params: dict | None = None,
214
+ stats: dict | None = None,
215
+ ) -> tuple[dict, dict, dict | None]:
216
+ """Apply motif discovery to sequence data.
217
+
218
+ Args:
219
+ data: Dictionary containing:
220
+ - 'sequence': One-hot encoded sequence(s) of shape
221
+ (length, alphabet_size) or (batch, length, alphabet_size)
222
+ state: Operator state dictionary.
223
+ metadata: Optional metadata dictionary.
224
+ random_params: Optional random parameters (unused).
225
+ stats: Optional statistics dictionary (unused).
226
+
227
+ Returns:
228
+ Tuple of (output_data, state, metadata) where output_data contains:
229
+
230
+ - 'sequence': Original sequence data
231
+ - 'motif_scores': Log-odds scores at each position
232
+ - 'motif_positions': Soft motif occurrence indicators
233
+ - 'pwm': Current Position Weight Matrix
234
+ """
235
+ del random_params, stats # Unused
236
+
237
+ sequence = data["sequence"]
238
+
239
+ # Handle single vs batched input
240
+ single_input = sequence.ndim == 2
241
+ if single_input:
242
+ result = self._apply_single(sequence)
243
+ else:
244
+ # Batched input - vmap over batch dimension
245
+ result = jax.vmap(self._apply_single)(sequence)
246
+ # PWM is shared, take from first (they're all the same)
247
+ result["pwm"] = self._get_pwm()
248
+
249
+ output_data = {**data, **result}
250
+
251
+ return output_data, state, metadata
@@ -0,0 +1,216 @@
1
+ """Differentiable splicing PSI (Percent Spliced In) calculation.
2
+
3
+ This module implements a differentiable PSI calculation operator for
4
+ alternative splicing analysis with end-to-end gradient flow.
5
+
6
+ Inherits from TemperatureOperator to get:
7
+
8
+ - _temperature property for temperature-controlled smoothing
9
+ - soft_max() for logsumexp-based smooth maximum
10
+ - soft_argmax() for soft position selection
11
+ """
12
+
13
+ import logging
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+ import flax.nnx as nnx
18
+ import jax
19
+ import jax.numpy as jnp
20
+ from datarax.core.config import OperatorConfig
21
+
22
+ from diffbio.core import soft_ops
23
+ from diffbio.core.base_operators import TemperatureOperator
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class SplicingPSIConfig(OperatorConfig):
30
+ """Configuration for differentiable PSI calculation.
31
+
32
+ Attributes:
33
+ pseudocount: Pseudocount added for numerical stability and regularization.
34
+ temperature: Temperature for confidence calculation.
35
+ min_total_reads: Minimum total reads for reliable PSI estimation.
36
+ stream_name: Name of the data stream to process.
37
+ """
38
+
39
+ pseudocount: float = 1.0
40
+ temperature: float = 1.0
41
+ learnable_temperature: bool = True
42
+ min_total_reads: int = 10
43
+
44
+
45
+ class SplicingPSI(TemperatureOperator):
46
+ """Differentiable PSI calculation for alternative splicing analysis.
47
+
48
+ PSI (Percent Spliced In) quantifies alternative splicing by computing
49
+ the fraction of transcripts that include a specific exon or splice site.
50
+
51
+ The standard PSI formula is:
52
+ PSI = inclusion_reads / (inclusion_reads + exclusion_reads)
53
+
54
+ This operator adds:
55
+ - Learnable pseudocount for regularization
56
+ - Confidence estimation based on read coverage
57
+ - Full differentiability for end-to-end training
58
+
59
+ Example:
60
+ ```python
61
+ config = SplicingPSIConfig(
62
+ pseudocount=1.0,
63
+ min_total_reads=10,
64
+ )
65
+ psi_op = SplicingPSI(config, rngs=rngs)
66
+
67
+ data = {
68
+ "inclusion_counts": inclusion_reads, # Junction reads supporting inclusion
69
+ "exclusion_counts": exclusion_reads, # Junction reads supporting exclusion
70
+ }
71
+ result, state, metadata = psi_op.apply(data, {}, None)
72
+ psi_values = result["psi"]
73
+ confidence = result["psi_confidence"]
74
+ ```
75
+ """
76
+
77
+ def __init__(self, config: SplicingPSIConfig, *, rngs: nnx.Rngs | None = None):
78
+ """Initialize the PSI operator.
79
+
80
+ Args:
81
+ config: Configuration for the operator.
82
+ rngs: Random number generators for initialization.
83
+ """
84
+ super().__init__(config, rngs=rngs)
85
+ self.config = config
86
+
87
+ # Learnable pseudocount (must be positive)
88
+ self.pseudocount = nnx.Param(jnp.array(config.pseudocount))
89
+
90
+ # Temperature is managed by TemperatureOperator via self._temperature
91
+
92
+ def _compute_psi(
93
+ self, inclusion: jax.Array, exclusion: jax.Array, pseudocount: jax.Array | float
94
+ ) -> jax.Array:
95
+ """Compute PSI with pseudocount for numerical stability.
96
+
97
+ Args:
98
+ inclusion: Inclusion junction read counts.
99
+ exclusion: Exclusion junction read counts.
100
+ pseudocount: Pseudocount for regularization.
101
+
102
+ Returns:
103
+ PSI values in [0, 1].
104
+ """
105
+ # Add pseudocount to both numerator and denominator terms
106
+ inc_adj = inclusion + pseudocount
107
+ exc_adj = exclusion + pseudocount
108
+
109
+ # PSI = inclusion / (inclusion + exclusion)
110
+ psi = inc_adj / (inc_adj + exc_adj)
111
+
112
+ return psi
113
+
114
+ def _compute_confidence(
115
+ self, inclusion: jax.Array, exclusion: jax.Array, temperature: jax.Array | float
116
+ ) -> jax.Array:
117
+ """Compute confidence in PSI estimate based on read coverage.
118
+
119
+ Higher total reads = higher confidence in the PSI estimate.
120
+
121
+ Args:
122
+ inclusion: Inclusion junction read counts.
123
+ exclusion: Exclusion junction read counts.
124
+ temperature: Temperature for sigmoid scaling.
125
+
126
+ Returns:
127
+ Confidence values in [0, 1].
128
+ """
129
+ total_reads = inclusion + exclusion
130
+ min_reads = self.config.min_total_reads
131
+
132
+ # Sigmoid-based confidence: approaches 1 as reads increase
133
+ # Centered around min_total_reads
134
+ confidence = soft_ops.greater(
135
+ total_reads, min_reads, softness=temperature * min_reads + 1e-6
136
+ )
137
+
138
+ return confidence
139
+
140
+ def _compute_delta_psi_variance(
141
+ self, inclusion: jax.Array, exclusion: jax.Array, psi: jax.Array
142
+ ) -> jax.Array:
143
+ """Compute variance of PSI estimate using beta-binomial model.
144
+
145
+ This approximates the variance of PSI under a binomial model,
146
+ which can be used for significance testing.
147
+
148
+ Args:
149
+ inclusion: Inclusion junction read counts.
150
+ exclusion: Exclusion junction read counts.
151
+ psi: Computed PSI values.
152
+
153
+ Returns:
154
+ Variance estimates for each PSI value.
155
+ """
156
+ total = inclusion + exclusion + 2 * jnp.abs(self.pseudocount[...])
157
+
158
+ # Variance of beta distribution: psi * (1 - psi) / (n + 1)
159
+ variance = (psi * (1 - psi)) / (total + 1)
160
+
161
+ return variance
162
+
163
+ def apply(
164
+ self,
165
+ data: dict[str, Any],
166
+ state: dict[str, Any],
167
+ metadata: dict | None,
168
+ random_params: dict | None = None,
169
+ stats: dict | None = None,
170
+ ) -> tuple[dict, dict, dict | None]:
171
+ """Apply PSI calculation to junction read counts.
172
+
173
+ Args:
174
+ data: Dictionary containing:
175
+ - 'inclusion_counts': Reads supporting exon inclusion
176
+ - 'exclusion_counts': Reads supporting exon exclusion
177
+ state: Operator state dictionary.
178
+ metadata: Optional metadata dictionary.
179
+ random_params: Optional random parameters (unused).
180
+ stats: Optional statistics dictionary (unused).
181
+
182
+ Returns:
183
+ Tuple of (output_data, state, metadata) where output_data contains:
184
+
185
+ - 'inclusion_counts': Original inclusion counts
186
+ - 'exclusion_counts': Original exclusion counts
187
+ - 'psi': Computed PSI values
188
+ - 'psi_confidence': Confidence in PSI estimates
189
+ - 'psi_variance': Variance of PSI estimates
190
+ """
191
+ del random_params, stats # Unused
192
+
193
+ inclusion = data["inclusion_counts"]
194
+ exclusion = data["exclusion_counts"]
195
+
196
+ # Get learnable parameters (ensure positive)
197
+ pseudocount = jnp.abs(self.pseudocount[...]) + 1e-6
198
+ temperature = jnp.abs(self._temperature) + 1e-6
199
+
200
+ # Compute PSI
201
+ psi = self._compute_psi(inclusion, exclusion, pseudocount)
202
+
203
+ # Compute confidence
204
+ confidence = self._compute_confidence(inclusion, exclusion, temperature)
205
+
206
+ # Compute variance for significance testing
207
+ variance = self._compute_delta_psi_variance(inclusion, exclusion, psi)
208
+
209
+ output_data = {
210
+ **data,
211
+ "psi": psi,
212
+ "psi_confidence": confidence,
213
+ "psi_variance": variance,
214
+ }
215
+
216
+ return output_data, state, metadata
@@ -0,0 +1,193 @@
1
+ """Single-cell analysis operators for differentiable scRNA-seq processing.
2
+
3
+ This module provides differentiable components for single-cell analysis:
4
+ - DifferentiableAmbientRemoval: CellBender-style ambient RNA removal
5
+ - DifferentiableCellAnnotator: Cell type annotation (celltypist/cellassign/scanvi)
6
+ - DifferentiableDiffusionImputer: MAGIC-style diffusion imputation
7
+ - DifferentiableDoubletScorer: Scrublet-style doublet detection
8
+ - DifferentiableCellCommunication: GNN-based cell-cell communication analysis
9
+ - DifferentiableGRN: GATv2-based gene regulatory network inference
10
+ - DifferentiableLigandReceptor: Ligand-receptor co-expression scoring
11
+ - SoftKMeansClustering: Differentiable soft k-means clustering
12
+ - DifferentiableHarmony: Harmony-style batch correction
13
+ - DifferentiableMMDBatchCorrection: MMD-regularised autoencoder batch correction
14
+ - DifferentiableWGANBatchCorrection: Adversarial (WGAN) batch correction
15
+ - DifferentiableSwitchDE: Sigmoidal switch differential expression
16
+ - DifferentiableVelocity: RNA velocity via Neural ODEs
17
+ - DifferentiablePseudotime: Diffusion-map pseudotime ordering
18
+ - DifferentiableFateProbability: Absorption-based fate estimation
19
+ - DifferentiableSpatialDomain: STAGATE-style spatial domain identification
20
+ - DifferentiablePASTEAlignment: PASTE-style spatial slice alignment
21
+ - DifferentiableDifferentialDistribution: scDD-style differential distribution testing
22
+ - DifferentiableSimulator: Splatter-style single-cell count simulation
23
+ - DifferentiableArchetypalAnalysis: PCHA-style archetypal analysis
24
+ - DifferentiableOTTrajectory: Waddington-OT-style optimal transport trajectory
25
+ """
26
+
27
+ from diffbio.operators.singlecell.archetypes import (
28
+ ArchetypalAnalysisConfig,
29
+ DifferentiableArchetypalAnalysis,
30
+ )
31
+ from diffbio.operators.singlecell.ambient_removal import (
32
+ AmbientRemovalConfig,
33
+ DifferentiableAmbientRemoval,
34
+ )
35
+ from diffbio.operators.singlecell.cell_annotation import (
36
+ CellAnnotatorConfig,
37
+ DifferentiableCellAnnotator,
38
+ )
39
+ from diffbio.operators.singlecell.communication import (
40
+ CellCommunicationConfig,
41
+ DifferentiableCellCommunication,
42
+ DifferentiableLigandReceptor,
43
+ LRScoringConfig,
44
+ )
45
+ from diffbio.operators.singlecell.batch_correction import (
46
+ BatchCorrectionConfig,
47
+ DifferentiableHarmony,
48
+ )
49
+ from diffbio.operators.singlecell.enhanced_batch_correction import (
50
+ DifferentiableMMDBatchCorrection,
51
+ DifferentiableWGANBatchCorrection,
52
+ MMDBatchCorrectionConfig,
53
+ WGANBatchCorrectionConfig,
54
+ )
55
+ from diffbio.operators.singlecell.grn_inference import (
56
+ DifferentiableGRN,
57
+ GRNInferenceConfig,
58
+ )
59
+ from diffbio.operators.singlecell.doublet_detection import (
60
+ DifferentiableDoubletScorer,
61
+ DifferentiableSoloDetector,
62
+ DoubletScorerConfig,
63
+ SoloDetectorConfig,
64
+ )
65
+ from diffbio.operators.singlecell.imputation import (
66
+ DifferentiableDiffusionImputer,
67
+ DifferentiableTransformerDenoiser,
68
+ DiffusionImputerConfig,
69
+ TransformerDenoiserConfig,
70
+ )
71
+ from diffbio.operators.singlecell.ot_trajectory import (
72
+ DifferentiableOTTrajectory,
73
+ OTTrajectoryConfig,
74
+ )
75
+ from diffbio.operators.singlecell.soft_clustering import (
76
+ SoftClusteringConfig,
77
+ SoftKMeansClustering,
78
+ )
79
+ from diffbio.operators.singlecell.switch_de import (
80
+ DifferentiableSwitchDE,
81
+ SwitchDEConfig,
82
+ )
83
+ from diffbio.operators.singlecell.trajectory import (
84
+ DifferentiableFateProbability,
85
+ DifferentiablePseudotime,
86
+ FateProbabilityConfig,
87
+ PseudotimeConfig,
88
+ )
89
+ from diffbio.operators.singlecell.spatial_domains import (
90
+ DifferentiablePASTEAlignment,
91
+ DifferentiableSpatialDomain,
92
+ PASTEAlignmentConfig,
93
+ SpatialDomainConfig,
94
+ )
95
+ from diffbio.operators.singlecell.differential_distribution import (
96
+ DifferentiableDifferentialDistribution,
97
+ DifferentialDistributionConfig,
98
+ )
99
+ from diffbio.operators.singlecell.simulation import (
100
+ DifferentiableSimulator,
101
+ SimulationConfig,
102
+ )
103
+ from diffbio.operators.singlecell.velocity import (
104
+ DifferentiableVelocity,
105
+ VelocityConfig,
106
+ )
107
+ from diffbio.operators.singlecell.downsampling import (
108
+ DownsamplingConfig,
109
+ ReadDownsampler,
110
+ )
111
+ from diffbio.operators.singlecell.knockdown_filter import (
112
+ KnockdownFilterConfig,
113
+ OnTargetKnockdownFilter,
114
+ )
115
+ from diffbio.operators.singlecell.sindy_grn import (
116
+ SINDyGRNConfig,
117
+ SINDyGRNOperator,
118
+ )
119
+
120
+ __all__ = [
121
+ # Archetypal Analysis
122
+ "ArchetypalAnalysisConfig",
123
+ "DifferentiableArchetypalAnalysis",
124
+ # Ambient Removal
125
+ "AmbientRemovalConfig",
126
+ "DifferentiableAmbientRemoval",
127
+ # Cell Annotation
128
+ "CellAnnotatorConfig",
129
+ "DifferentiableCellAnnotator",
130
+ # Clustering
131
+ "SoftClusteringConfig",
132
+ "SoftKMeansClustering",
133
+ # Communication (L-R scoring + GNN-based)
134
+ "CellCommunicationConfig",
135
+ "DifferentiableCellCommunication",
136
+ "DifferentiableLigandReceptor",
137
+ "LRScoringConfig",
138
+ # Batch Correction (Harmony)
139
+ "BatchCorrectionConfig",
140
+ "DifferentiableHarmony",
141
+ # Batch Correction (MMD + WGAN)
142
+ "DifferentiableMMDBatchCorrection",
143
+ "DifferentiableWGANBatchCorrection",
144
+ "MMDBatchCorrectionConfig",
145
+ "WGANBatchCorrectionConfig",
146
+ # GRN Inference
147
+ "DifferentiableGRN",
148
+ "GRNInferenceConfig",
149
+ # Doublet Detection
150
+ "DifferentiableDoubletScorer",
151
+ "DifferentiableSoloDetector",
152
+ "DoubletScorerConfig",
153
+ "SoloDetectorConfig",
154
+ # Imputation
155
+ "DifferentiableDiffusionImputer",
156
+ "DifferentiableTransformerDenoiser",
157
+ "DiffusionImputerConfig",
158
+ "TransformerDenoiserConfig",
159
+ # Switch DE
160
+ "DifferentiableSwitchDE",
161
+ "SwitchDEConfig",
162
+ # Trajectory Inference
163
+ "DifferentiableFateProbability",
164
+ "DifferentiablePseudotime",
165
+ "FateProbabilityConfig",
166
+ "PseudotimeConfig",
167
+ # Spatial Domain Identification
168
+ "DifferentiablePASTEAlignment",
169
+ "DifferentiableSpatialDomain",
170
+ "PASTEAlignmentConfig",
171
+ "SpatialDomainConfig",
172
+ # Differential Distribution
173
+ "DifferentiableDifferentialDistribution",
174
+ "DifferentialDistributionConfig",
175
+ # Simulation
176
+ "DifferentiableSimulator",
177
+ "SimulationConfig",
178
+ # OT Trajectory
179
+ "DifferentiableOTTrajectory",
180
+ "OTTrajectoryConfig",
181
+ # Velocity
182
+ "DifferentiableVelocity",
183
+ "VelocityConfig",
184
+ # Downsampling
185
+ "DownsamplingConfig",
186
+ "ReadDownsampler",
187
+ # Knockdown Filter
188
+ "KnockdownFilterConfig",
189
+ "OnTargetKnockdownFilter",
190
+ # SINDy GRN
191
+ "SINDyGRNConfig",
192
+ "SINDyGRNOperator",
193
+ ]