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,345 @@
1
+ """Statistical loss functions for differentiable bioinformatics.
2
+
3
+ This module provides differentiable implementations of statistical loss
4
+ functions commonly used in bioinformatics applications.
5
+
6
+ Includes:
7
+ - zinb_negative_log_likelihood: Zero-Inflated Negative Binomial NLL
8
+ - NegativeBinomialLoss: For count data modeling (scRNA-seq, RNA-seq)
9
+ - VAELoss: ELBO loss for variational autoencoders
10
+ - HMMLikelihoodLoss: Negative log-likelihood for HMM sequence models
11
+ """
12
+
13
+ import jax
14
+ import jax.numpy as jnp
15
+ from flax import nnx
16
+ from jaxtyping import Array, Float, Int
17
+
18
+ from diffbio.constants import EPSILON
19
+ from diffbio.core import soft_ops
20
+
21
+
22
+ def zinb_negative_log_likelihood(
23
+ counts: Float[Array, "... n_genes"],
24
+ log_rate: Float[Array, "... n_genes"],
25
+ log_theta: Float[Array, "... n_genes"],
26
+ pi_logit: Float[Array, "... n_genes"],
27
+ ) -> Float[Array, ""]:
28
+ """Zero-Inflated Negative Binomial negative log-likelihood.
29
+
30
+ Uses the scVI-style logit-space formulation for numerical stability.
31
+ All log-sigmoid terms are computed via softplus, avoiding explicit
32
+ materialisation of ``sigmoid(pi_logit)`` which is unstable when
33
+ ``pi_logit`` is large positive (pi near 1, so 1-pi near 0).
34
+
35
+ Key identities::
36
+
37
+ log(sigmoid(pi)) = -softplus(-pi)
38
+ log(1-sigmoid(pi)) = -softplus(pi)
39
+
40
+ ZINB: ``P(x) = sigmoid(pi) * delta_0(x) + (1 - sigmoid(pi)) * NB(x; mu, theta)``
41
+ where ``mu = exp(log_rate)``, ``theta = exp(log_theta)``.
42
+
43
+ Args:
44
+ counts: Observed counts.
45
+ log_rate: Log mean parameter from decoder.
46
+ log_theta: Log dispersion parameter.
47
+ pi_logit: Logit of zero-inflation probability.
48
+
49
+ Returns:
50
+ Negative log-likelihood (scalar, summed over all elements).
51
+ """
52
+ mu = jnp.exp(log_rate)
53
+ theta = jnp.exp(jnp.clip(log_theta, -10.0, 10.0))
54
+ eps = EPSILON
55
+
56
+ # Log-space sigmoid computations (numerically stable)
57
+ softplus_pi = jax.nn.softplus(-pi_logit) # = -log(sigmoid(pi_logit))
58
+ log_theta_mu = jnp.log(theta + mu + eps)
59
+
60
+ # NB(0) in log-space combined with dropout logit
61
+ pi_theta_log = -pi_logit + theta * (jnp.log(theta + eps) - log_theta_mu)
62
+
63
+ # Case x == 0: log[sigmoid(pi) + (1 - sigmoid(pi)) * NB(0)]
64
+ case_zero = jax.nn.softplus(pi_theta_log) - softplus_pi
65
+
66
+ # Case x > 0: log[(1 - sigmoid(pi)) * NB(x)]
67
+ case_nonzero = (
68
+ -softplus_pi
69
+ + pi_theta_log
70
+ + counts * (jnp.log(mu + eps) - log_theta_mu)
71
+ + jax.scipy.special.gammaln(counts + theta)
72
+ - jax.scipy.special.gammaln(theta)
73
+ - jax.scipy.special.gammaln(counts + 1.0)
74
+ )
75
+
76
+ is_zero = soft_ops.less(counts, jnp.array(eps), softness=0.01)
77
+ log_prob = is_zero * case_zero + (1.0 - is_zero) * case_nonzero
78
+
79
+ return -jnp.sum(log_prob)
80
+
81
+
82
+ class NegativeBinomialLoss(nnx.Module):
83
+ """Negative binomial log-likelihood loss for count data.
84
+
85
+ The negative binomial distribution is parameterized by mean (mu) and
86
+ dispersion (theta), suitable for overdispersed count data like RNA-seq.
87
+
88
+ NB(x | mu, theta) = Gamma(x + theta) / (Gamma(theta) * Gamma(x + 1))
89
+ * (theta / (theta + mu))^theta
90
+ * (mu / (theta + mu))^x
91
+
92
+ Args:
93
+ eps: Small constant for numerical stability.
94
+ rngs: Flax NNX random number generators.
95
+
96
+ Example:
97
+ ```python
98
+ loss_fn = NegativeBinomialLoss(rngs=nnx.Rngs(42))
99
+ loss = loss_fn(counts, mu, theta)
100
+ ```
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ eps: float = 1e-8,
106
+ *,
107
+ rngs: nnx.Rngs | None = None,
108
+ ):
109
+ """Initialize the NB loss.
110
+
111
+ Args:
112
+ eps: Numerical stability constant.
113
+ rngs: Random number generators (not used, for API consistency).
114
+ """
115
+ super().__init__()
116
+ self.eps = eps
117
+
118
+ def __call__(
119
+ self,
120
+ counts: Float[Array, "batch genes"],
121
+ mu: Float[Array, "batch genes"],
122
+ theta: Float[Array, "genes"],
123
+ ) -> Float[Array, ""]:
124
+ """Compute negative binomial negative log-likelihood.
125
+
126
+ Args:
127
+ counts: Observed counts.
128
+ mu: Predicted mean.
129
+ theta: Dispersion parameter (per gene).
130
+
131
+ Returns:
132
+ Mean negative log-likelihood (scalar).
133
+ """
134
+ # Ensure positive values
135
+ mu = jnp.maximum(mu, self.eps)
136
+ theta = jnp.maximum(theta, self.eps)
137
+
138
+ # Log-likelihood of NB distribution
139
+ # log P(x | mu, theta) = log Gamma(x + theta) - log Gamma(theta) - log Gamma(x + 1)
140
+ # + theta * log(theta / (theta + mu))
141
+ # + x * log(mu / (theta + mu))
142
+
143
+ log_theta_mu = jnp.log(theta[None, :] + mu + self.eps)
144
+
145
+ ll = (
146
+ jax.scipy.special.gammaln(counts + theta[None, :])
147
+ - jax.scipy.special.gammaln(theta[None, :])
148
+ - jax.scipy.special.gammaln(counts + 1)
149
+ + theta[None, :] * (jnp.log(theta[None, :] + self.eps) - log_theta_mu)
150
+ + counts * (jnp.log(mu + self.eps) - log_theta_mu)
151
+ )
152
+
153
+ # Return mean negative log-likelihood
154
+ return -jnp.mean(ll)
155
+
156
+
157
+ class VAELoss(nnx.Module):
158
+ """Variational autoencoder ELBO loss.
159
+
160
+ Combines reconstruction loss with KL divergence regularization:
161
+ ELBO = E[log p(x|z)] - KL(q(z|x) || p(z))
162
+
163
+ For Gaussian encoder and prior:
164
+ KL = -0.5 * sum(1 + log(var) - mean^2 - var)
165
+
166
+ Args:
167
+ kl_weight: Weight for KL divergence term (beta-VAE).
168
+ reconstruction_type: Type of reconstruction loss ("mse" or "bce").
169
+ rngs: Flax NNX random number generators.
170
+
171
+ Example:
172
+ ```python
173
+ loss_fn = VAELoss(kl_weight=1.0, rngs=nnx.Rngs(42))
174
+ loss = loss_fn(x, x_recon, mean, logvar)
175
+ ```
176
+ """
177
+
178
+ def __init__(
179
+ self,
180
+ kl_weight: float = 1.0,
181
+ reconstruction_type: str = "mse",
182
+ *,
183
+ rngs: nnx.Rngs | None = None,
184
+ ):
185
+ """Initialize the VAE loss.
186
+
187
+ Args:
188
+ kl_weight: Weight for KL term.
189
+ reconstruction_type: "mse" or "bce".
190
+ rngs: Random number generators (not used, for API consistency).
191
+ """
192
+ super().__init__()
193
+ self.kl_weight = kl_weight
194
+ self.reconstruction_type = reconstruction_type
195
+
196
+ def __call__(
197
+ self,
198
+ x: Float[Array, "batch features"],
199
+ x_recon: Float[Array, "batch features"],
200
+ mean: Float[Array, "batch latent"],
201
+ logvar: Float[Array, "batch latent"],
202
+ ) -> Float[Array, ""]:
203
+ """Compute VAE ELBO loss.
204
+
205
+ Args:
206
+ x: Original input.
207
+ x_recon: Reconstructed input.
208
+ mean: Encoder mean.
209
+ logvar: Encoder log-variance.
210
+
211
+ Returns:
212
+ Negative ELBO (scalar).
213
+ """
214
+ # Reconstruction loss
215
+ if self.reconstruction_type == "mse":
216
+ recon_loss = jnp.mean((x - x_recon) ** 2)
217
+ else: # bce
218
+ x_recon = jax.nn.sigmoid(x_recon)
219
+ recon_loss = -jnp.mean(
220
+ x * jnp.log(x_recon + 1e-8) + (1 - x) * jnp.log(1 - x_recon + 1e-8)
221
+ )
222
+
223
+ # KL divergence: -0.5 * sum(1 + log(var) - mean^2 - var)
224
+ kl_loss = -0.5 * jnp.mean(1 + logvar - mean**2 - jnp.exp(logvar))
225
+
226
+ # Total loss
227
+ return recon_loss + self.kl_weight * kl_loss
228
+
229
+
230
+ class HMMLikelihoodLoss(nnx.Module):
231
+ """HMM negative log-likelihood loss.
232
+
233
+ Computes the negative log-likelihood of sequences under a Hidden
234
+ Markov Model using the forward algorithm with logsumexp for stability.
235
+
236
+ Args:
237
+ n_states: Number of hidden states.
238
+ n_emissions: Number of emission symbols.
239
+ rngs: Flax NNX random number generators.
240
+
241
+ Example:
242
+ ```python
243
+ loss_fn = HMMLikelihoodLoss(n_states=3, n_emissions=4, rngs=rnx.Rngs(42))
244
+ nll = loss_fn(observations)
245
+ ```
246
+ """
247
+
248
+ def __init__(
249
+ self,
250
+ n_states: int,
251
+ n_emissions: int,
252
+ *,
253
+ rngs: nnx.Rngs | None = None,
254
+ ):
255
+ """Initialize the HMM loss.
256
+
257
+ Args:
258
+ n_states: Number of hidden states.
259
+ n_emissions: Number of emission symbols.
260
+ rngs: Random number generators.
261
+ """
262
+ super().__init__()
263
+
264
+ if rngs is None:
265
+ rngs = nnx.Rngs(0)
266
+
267
+ self.n_states = n_states
268
+ self.n_emissions = n_emissions
269
+
270
+ key = rngs.params()
271
+ k1, k2, k3 = jax.random.split(key, 3)
272
+
273
+ # Learnable parameters (log-space for stability)
274
+ # Initial state distribution
275
+ self.log_initial = nnx.Param(jax.random.normal(k1, (n_states,)) * 0.1)
276
+
277
+ # Transition matrix (log probabilities)
278
+ self.log_transitions = nnx.Param(jax.random.normal(k2, (n_states, n_states)) * 0.1)
279
+
280
+ # Emission matrix (log probabilities)
281
+ self.log_emissions = nnx.Param(jax.random.normal(k3, (n_states, n_emissions)) * 0.1)
282
+
283
+ def _get_log_initial(self) -> Float[Array, "n_states"]:
284
+ """Get normalized log initial distribution."""
285
+ return jax.nn.log_softmax(self.log_initial[...])
286
+
287
+ def _get_log_transitions(self) -> Float[Array, "n_states n_states"]:
288
+ """Get normalized log transition matrix."""
289
+ return jax.nn.log_softmax(self.log_transitions[...], axis=-1)
290
+
291
+ def _get_log_emissions(self) -> Float[Array, "n_states n_emissions"]:
292
+ """Get normalized log emission matrix."""
293
+ return jax.nn.log_softmax(self.log_emissions[...], axis=-1)
294
+
295
+ def _forward_single(
296
+ self,
297
+ observations: Int[Array, "seq_len"],
298
+ ) -> Float[Array, ""]:
299
+ """Forward algorithm for a single sequence.
300
+
301
+ Args:
302
+ observations: Integer-encoded observations.
303
+
304
+ Returns:
305
+ Log-likelihood of the sequence.
306
+ """
307
+ log_init = self._get_log_initial()
308
+ log_trans = self._get_log_transitions()
309
+ log_emit = self._get_log_emissions()
310
+
311
+ # Initialize with first observation
312
+ log_alpha = log_init + log_emit[:, observations[0]]
313
+
314
+ # Forward pass
315
+ def step(log_alpha, obs):
316
+ # log_alpha: (n_states,)
317
+ # Transition: log_alpha[i] + log_trans[i, j] for all j
318
+ log_alpha_expanded = log_alpha[:, None] + log_trans # (n_states, n_states)
319
+ log_alpha_new = jax.scipy.special.logsumexp(log_alpha_expanded, axis=0)
320
+ # Add emission
321
+ log_alpha_new = log_alpha_new + log_emit[:, obs]
322
+ return log_alpha_new, None
323
+
324
+ log_alpha, _ = jax.lax.scan(step, log_alpha, observations[1:])
325
+
326
+ # Final log-likelihood
327
+ return jax.scipy.special.logsumexp(log_alpha)
328
+
329
+ def __call__(
330
+ self,
331
+ observations: Int[Array, "batch seq_len"],
332
+ ) -> Float[Array, ""]:
333
+ """Compute mean negative log-likelihood over batch.
334
+
335
+ Args:
336
+ observations: Batch of integer-encoded sequences.
337
+
338
+ Returns:
339
+ Mean negative log-likelihood (scalar).
340
+ """
341
+ # Compute log-likelihood for each sequence
342
+ log_probs = jax.vmap(self._forward_single)(observations)
343
+
344
+ # Return mean negative log-likelihood
345
+ return -jnp.mean(log_probs)
@@ -0,0 +1,60 @@
1
+ """Differentiable bioinformatics operators for DiffBio.
2
+
3
+ This module provides differentiable operators for common bioinformatics
4
+ operations such as quality filtering, sequence alignment, variant calling,
5
+ epigenomics analysis, and RNA-seq processing.
6
+
7
+ All operators extend Datarax's OperatorModule for seamless integration.
8
+ """
9
+
10
+ from diffbio.operators.quality_filter import (
11
+ DifferentiableQualityFilter,
12
+ QualityFilterConfig,
13
+ )
14
+
15
+ # Import submodules for convenient access
16
+ from diffbio.operators import alignment
17
+ from diffbio.operators import assembly
18
+ from diffbio.operators import crispr
19
+ from diffbio.operators import drug_discovery
20
+ from diffbio.operators import epigenomics
21
+ from diffbio.operators import foundation_models
22
+ from diffbio.operators import mapping
23
+ from diffbio.operators import metabolomics
24
+ from diffbio.operators import molecular_dynamics
25
+ from diffbio.operators import multiomics
26
+ from diffbio.operators import normalization
27
+ from diffbio.operators import population
28
+ from diffbio.operators import preprocessing
29
+ from diffbio.operators import protein
30
+ from diffbio.operators import rna_structure
31
+ from diffbio.operators import rnaseq
32
+ from diffbio.operators import singlecell
33
+ from diffbio.operators import statistical
34
+ from diffbio.operators import variant
35
+
36
+ __all__ = [
37
+ # Core quality filter
38
+ "DifferentiableQualityFilter",
39
+ "QualityFilterConfig",
40
+ # Submodules
41
+ "alignment",
42
+ "assembly",
43
+ "crispr",
44
+ "drug_discovery",
45
+ "epigenomics",
46
+ "foundation_models",
47
+ "mapping",
48
+ "metabolomics",
49
+ "molecular_dynamics",
50
+ "multiomics",
51
+ "normalization",
52
+ "population",
53
+ "preprocessing",
54
+ "protein",
55
+ "rna_structure",
56
+ "rnaseq",
57
+ "singlecell",
58
+ "statistical",
59
+ "variant",
60
+ ]
@@ -0,0 +1,197 @@
1
+ """Shared count-VAE building blocks for DiffBio operators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from artifex.generative_models.core.base import MLP
8
+ from flax import nnx
9
+ import jax
10
+ import jax.numpy as jnp
11
+ from jaxtyping import Array, Float
12
+
13
+ from diffbio.losses.statistical_losses import zinb_negative_log_likelihood
14
+ from diffbio.utils.nn_utils import ensure_rngs
15
+
16
+
17
+ class CountVAEBackboneMixin:
18
+ """Shared encoder/decoder backbone for count-based VAEs."""
19
+
20
+ encoder_backbone: MLP | None
21
+ fc_mean: nnx.Linear
22
+ fc_logvar: nnx.Linear
23
+ decoder_backbone: MLP | None
24
+ fc_output: nnx.Linear
25
+ n_genes: int
26
+ stream_name: Any
27
+
28
+ def _init_count_vae_backbone(
29
+ self,
30
+ *,
31
+ n_inputs: int,
32
+ latent_dim: int,
33
+ hidden_dims: list[int],
34
+ n_outputs: int,
35
+ rngs: nnx.Rngs | None,
36
+ ) -> None:
37
+ """Initialise the shared count-VAE encoder and decoder layers."""
38
+ safe_rngs = ensure_rngs(rngs)
39
+
40
+ encoder_hidden_dims = list(hidden_dims)
41
+ decoder_hidden_dims = list(reversed(hidden_dims))
42
+
43
+ if encoder_hidden_dims:
44
+ self.encoder_backbone = MLP(
45
+ hidden_dims=encoder_hidden_dims,
46
+ in_features=n_inputs,
47
+ activation="relu",
48
+ output_activation="relu",
49
+ use_batch_norm=False,
50
+ rngs=safe_rngs,
51
+ )
52
+ encoder_out_dim = encoder_hidden_dims[-1]
53
+ else:
54
+ self.encoder_backbone = None
55
+ encoder_out_dim = n_inputs
56
+
57
+ self.fc_mean = nnx.Linear(
58
+ in_features=encoder_out_dim,
59
+ out_features=latent_dim,
60
+ rngs=safe_rngs,
61
+ )
62
+ self.fc_logvar = nnx.Linear(
63
+ in_features=encoder_out_dim,
64
+ out_features=latent_dim,
65
+ rngs=safe_rngs,
66
+ )
67
+
68
+ if decoder_hidden_dims:
69
+ self.decoder_backbone = MLP(
70
+ hidden_dims=decoder_hidden_dims,
71
+ in_features=latent_dim,
72
+ activation="relu",
73
+ output_activation="relu",
74
+ use_batch_norm=False,
75
+ rngs=safe_rngs,
76
+ )
77
+ decoder_out_dim = decoder_hidden_dims[-1]
78
+ else:
79
+ self.decoder_backbone = None
80
+ decoder_out_dim = latent_dim
81
+
82
+ self.fc_output = nnx.Linear(
83
+ in_features=decoder_out_dim,
84
+ out_features=n_outputs,
85
+ rngs=safe_rngs,
86
+ )
87
+
88
+ def _init_count_vae_operator(
89
+ self,
90
+ *,
91
+ config: Any,
92
+ rngs: nnx.Rngs | None,
93
+ ) -> nnx.Rngs:
94
+ """Initialise shared count-VAE operator state and return safe RNGs."""
95
+ safe_rngs = ensure_rngs(rngs)
96
+ self.n_genes = config.n_genes
97
+ self.stream_name = nnx.static(config.stream_name)
98
+ self._init_count_vae_backbone(
99
+ n_inputs=config.n_genes,
100
+ latent_dim=config.latent_dim,
101
+ hidden_dims=config.hidden_dims,
102
+ n_outputs=config.n_genes,
103
+ rngs=safe_rngs,
104
+ )
105
+ return safe_rngs
106
+
107
+ def encode(
108
+ self,
109
+ counts: Float[Array, "... n_genes"],
110
+ ) -> tuple[Float[Array, "... latent_dim"], Float[Array, "... latent_dim"]]:
111
+ """Encode count vectors to latent Gaussian parameters."""
112
+ x = jnp.log1p(counts)
113
+ if self.encoder_backbone is not None:
114
+ encoded: jax.Array = self.encoder_backbone(x)
115
+ x = encoded
116
+ mean = self.fc_mean(x)
117
+ logvar = jnp.clip(self.fc_logvar(x), -10.0, 10.0)
118
+ return mean, logvar
119
+
120
+ def decode_hidden(
121
+ self,
122
+ z: Float[Array, "... latent_dim"],
123
+ ) -> Float[Array, "... hidden_dim"]:
124
+ """Decode latent vectors to the shared decoder hidden representation."""
125
+ if self.decoder_backbone is None:
126
+ return z
127
+ decoded: jax.Array = self.decoder_backbone(z)
128
+ return decoded
129
+
130
+ def decode_rates(
131
+ self,
132
+ z: Float[Array, "... latent_dim"],
133
+ ) -> Float[Array, "... n_outputs"]:
134
+ """Decode latent vectors directly to output log-rates."""
135
+ return self.fc_output(self.decode_hidden(z))
136
+
137
+
138
+ class CountVAEBackbone(CountVAEBackboneMixin, nnx.Module):
139
+ """Concrete test harness for the shared count-VAE backbone."""
140
+
141
+ def __init__(
142
+ self,
143
+ *,
144
+ n_inputs: int,
145
+ latent_dim: int,
146
+ hidden_dims: list[int],
147
+ n_outputs: int,
148
+ rngs: nnx.Rngs | None = None,
149
+ ) -> None:
150
+ """Initialise a standalone shared count-VAE backbone."""
151
+ super().__init__()
152
+ self._init_count_vae_backbone(
153
+ n_inputs=n_inputs,
154
+ latent_dim=latent_dim,
155
+ hidden_dims=hidden_dims,
156
+ n_outputs=n_outputs,
157
+ rngs=rngs,
158
+ )
159
+
160
+
161
+ class CountReconstructionMixin:
162
+ """Shared reconstruction losses for count-based VAE operators."""
163
+
164
+ @staticmethod
165
+ def _poisson_nll(
166
+ counts: Float[Array, "... n_genes"],
167
+ log_rate: Float[Array, "... n_genes"],
168
+ ) -> Float[Array, ""]:
169
+ """Compute Poisson negative log-likelihood."""
170
+ rate = jnp.exp(log_rate)
171
+ return jnp.sum(rate - counts * log_rate)
172
+
173
+ @staticmethod
174
+ def _zinb_nll(
175
+ counts: Float[Array, "... n_genes"],
176
+ log_rate: Float[Array, "... n_genes"],
177
+ log_theta: Float[Array, "... n_genes"],
178
+ pi_logit: Float[Array, "... n_genes"],
179
+ ) -> Float[Array, ""]:
180
+ """Compute Zero-Inflated Negative Binomial negative log-likelihood."""
181
+ return zinb_negative_log_likelihood(counts, log_rate, log_theta, pi_logit)
182
+
183
+ def reconstruction_loss(
184
+ self,
185
+ counts: Float[Array, "... n_genes"],
186
+ decode_output: dict[str, jax.Array],
187
+ ) -> Float[Array, ""]:
188
+ """Compute reconstruction loss for Poisson or ZINB decoder outputs."""
189
+ log_rate = decode_output["log_rate"]
190
+ if "log_theta" in decode_output:
191
+ return self._zinb_nll(
192
+ counts,
193
+ log_rate,
194
+ decode_output["log_theta"],
195
+ decode_output["pi_logit"],
196
+ )
197
+ return self._poisson_nll(counts, log_rate)
@@ -0,0 +1,65 @@
1
+ """Shared scalar-loss balancing helpers for DiffBio operators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import Any
7
+
8
+ from flax import nnx
9
+ from jaxtyping import Array, Float
10
+ from opifex.core.physics.gradnorm import GradNormBalancer
11
+
12
+ from diffbio.utils.nn_utils import ensure_rngs
13
+
14
+
15
+ def combine_scalar_losses(
16
+ losses: Mapping[str, Float[Array, ""]],
17
+ *,
18
+ use_gradnorm: bool,
19
+ rngs: nnx.Rngs | None = None,
20
+ ) -> Float[Array, ""]:
21
+ """Combine scalar losses with optional GradNorm-based balancing.
22
+
23
+ Args:
24
+ losses: Named scalar losses to combine.
25
+ use_gradnorm: Whether to balance losses with ``GradNormBalancer``.
26
+ rngs: Optional random generators used when constructing GradNorm.
27
+
28
+ Returns:
29
+ Combined scalar loss.
30
+
31
+ Raises:
32
+ ValueError: If *losses* is empty.
33
+ """
34
+ if not losses:
35
+ msg = "losses must contain at least one scalar loss"
36
+ raise ValueError(msg)
37
+
38
+ loss_values = list(losses.values())
39
+ if use_gradnorm:
40
+ balancer = GradNormBalancer(
41
+ num_losses=len(loss_values),
42
+ rngs=ensure_rngs(rngs),
43
+ )
44
+ return balancer(loss_values)
45
+
46
+ total_loss = loss_values[0]
47
+ for loss_value in loss_values[1:]:
48
+ total_loss = total_loss + loss_value
49
+ return total_loss
50
+
51
+
52
+ class LossBalancingMixin:
53
+ """Reusable operator mixin exposing ``compute_balanced_loss``."""
54
+
55
+ config: Any
56
+
57
+ def compute_balanced_loss(
58
+ self,
59
+ losses: Mapping[str, Float[Array, ""]],
60
+ ) -> Float[Array, ""]:
61
+ """Combine operator loss terms using the config's GradNorm flag."""
62
+ return combine_scalar_losses(
63
+ losses,
64
+ use_gradnorm=bool(getattr(self.config, "use_gradnorm", False)),
65
+ )