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,387 @@
1
+ """Single-cell specific loss functions for differentiable bioinformatics.
2
+
3
+ This module provides differentiable loss functions for single-cell analysis
4
+ pipelines, including batch correction, clustering, RNA velocity, and diversity.
5
+
6
+ Includes:
7
+ - BatchMixingLoss: Maximizes batch mixing in latent space
8
+ - ClusteringCompactnessLoss: Encourages tight, well-separated clusters
9
+ - VelocityConsistencyLoss: Enforces consistency between velocity and trajectory
10
+ - ShannonDiversityLoss: Shannon entropy of soft cluster assignments
11
+ - SimpsonDiversityLoss: Simpson concentration index of soft cluster assignments
12
+ """
13
+
14
+ import jax
15
+ import jax.numpy as jnp
16
+ from calibrax.metrics.functional.information import entropy
17
+ from flax import nnx
18
+ from jaxtyping import Array, Float, Int
19
+
20
+ from diffbio.constants import DISTANCE_MASK_SENTINEL
21
+ from diffbio.core import soft_ops
22
+
23
+
24
+ class BatchMixingLoss(nnx.Module):
25
+ """Loss function to maximize batch mixing in latent space.
26
+
27
+ Computes how well batches are mixed in the embedding space by measuring
28
+ the entropy of batch labels among k-nearest neighbors for each cell.
29
+ Higher entropy indicates better mixing.
30
+
31
+ The loss encourages the model to learn representations where cells from
32
+ different batches are interleaved, reducing batch effects.
33
+
34
+ Args:
35
+ n_neighbors: Number of nearest neighbors to consider.
36
+ n_batches: Number of batches (required for JIT compatibility).
37
+ temperature: Temperature for softmax in distance computation.
38
+ rngs: Flax NNX random number generators.
39
+
40
+ Example:
41
+ ```python
42
+ loss_fn = BatchMixingLoss(n_neighbors=15, n_batches=3, rngs=nnx.Rngs(42))
43
+ loss = loss_fn(embeddings, batch_labels)
44
+ ```
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ n_neighbors: int = 15,
50
+ n_batches: int = 3,
51
+ temperature: float = 1.0,
52
+ *,
53
+ rngs: nnx.Rngs | None = None,
54
+ ) -> None:
55
+ """Initialize the batch mixing loss.
56
+
57
+ Args:
58
+ n_neighbors: Number of nearest neighbors to consider.
59
+ n_batches: Number of batches (static for JIT compatibility).
60
+ temperature: Temperature for soft neighbor selection.
61
+ rngs: Random number generators (not used, for API consistency).
62
+ """
63
+ super().__init__()
64
+ self.n_neighbors = n_neighbors
65
+ self.n_batches = n_batches
66
+ self.temperature = temperature
67
+ # Precompute max entropy for normalization (static constant)
68
+ self._max_entropy = jnp.log(jnp.array(n_batches, dtype=jnp.float32))
69
+
70
+ def __call__(
71
+ self,
72
+ embeddings: Float[Array, "n_cells latent_dim"],
73
+ batch_labels: Int[Array, "n_cells"],
74
+ ) -> Float[Array, ""]:
75
+ """Compute batch mixing loss.
76
+
77
+ Args:
78
+ embeddings: Cell embeddings in latent space.
79
+ batch_labels: Integer batch label for each cell.
80
+
81
+ Returns:
82
+ Negative mean entropy of batch distribution in neighborhoods (scalar).
83
+ Lower loss means better mixing.
84
+ """
85
+ n_cells = embeddings.shape[0]
86
+ n_batches = self.n_batches
87
+
88
+ # Compute pairwise distances
89
+ # ||a - b||^2 = ||a||^2 + ||b||^2 - 2 * a.b
90
+ sq_norms = jnp.sum(embeddings**2, axis=-1)
91
+ distances = sq_norms[:, None] + sq_norms[None, :] - 2 * embeddings @ embeddings.T
92
+
93
+ # Set self-distance to inf to exclude self from neighbors
94
+ distances = distances + jnp.eye(n_cells) * DISTANCE_MASK_SENTINEL
95
+
96
+ # Soft neighbor weights using softmax over negative distances
97
+ neighbor_weights = jax.nn.softmax(-distances / self.temperature, axis=-1)
98
+
99
+ # Keep only top k neighbors (soft selection)
100
+ # Sort to get top-k, then create soft mask
101
+ sorted_dists = soft_ops.sort(distances, axis=-1, softness=self.temperature)
102
+ kth_dist = sorted_dists[:, self.n_neighbors - 1 : self.n_neighbors]
103
+ k_mask = soft_ops.less(distances, kth_dist, softness=self.temperature)
104
+
105
+ # Apply mask
106
+ neighbor_weights = neighbor_weights * k_mask
107
+ neighbor_weights = neighbor_weights / (neighbor_weights.sum(axis=-1, keepdims=True) + 1e-8)
108
+
109
+ # One-hot encode batch labels
110
+ batch_onehot = jax.nn.one_hot(batch_labels, n_batches)
111
+
112
+ # Compute batch distribution in neighborhoods
113
+ # For each cell, weighted average of neighbor batch labels
114
+ batch_dist = neighbor_weights @ batch_onehot # (n_cells, n_batches)
115
+
116
+ # Compute entropy of batch distribution
117
+ # H = -sum(p * log(p))
118
+ eps = 1e-8
119
+ per_cell_entropy = -jnp.sum(batch_dist * jnp.log(batch_dist + eps), axis=-1)
120
+
121
+ # Normalized entropy (0 to 1, higher is better)
122
+ # Use precomputed max_entropy for JIT compatibility
123
+ normalized_entropy = per_cell_entropy / (self._max_entropy + eps)
124
+
125
+ # Return negative mean entropy (lower loss = better mixing)
126
+ return -jnp.mean(normalized_entropy)
127
+
128
+
129
+ class ClusteringCompactnessLoss(nnx.Module):
130
+ """Loss function to encourage compact and well-separated clusters.
131
+
132
+ Combines two components:
133
+ 1. Compactness: Minimize within-cluster variance
134
+ 2. Separation: Maximize between-cluster distances
135
+
136
+ Works with soft cluster assignments for end-to-end differentiability.
137
+
138
+ Args:
139
+ separation_weight: Weight for the separation term.
140
+ min_separation: Minimum desired distance between cluster centers.
141
+ rngs: Flax NNX random number generators.
142
+
143
+ Example:
144
+ ```python
145
+ loss_fn = ClusteringCompactnessLoss(rngs=nnx.Rngs(42))
146
+ loss = loss_fn(embeddings, soft_assignments)
147
+ ```
148
+ """
149
+
150
+ def __init__(
151
+ self,
152
+ separation_weight: float = 1.0,
153
+ min_separation: float = 1.0,
154
+ *,
155
+ rngs: nnx.Rngs | None = None,
156
+ ) -> None:
157
+ """Initialize the clustering compactness loss.
158
+
159
+ Args:
160
+ separation_weight: Weight for separation term.
161
+ min_separation: Minimum desired distance between centroids.
162
+ rngs: Random number generators (not used, for API consistency).
163
+ """
164
+ super().__init__()
165
+ self.separation_weight = separation_weight
166
+ self.min_separation = min_separation
167
+
168
+ def __call__(
169
+ self,
170
+ embeddings: Float[Array, "n_cells latent_dim"],
171
+ assignments: Float[Array, "n_cells n_clusters"],
172
+ centroids: Float[Array, "n_clusters latent_dim"] | None = None,
173
+ ) -> Float[Array, ""]:
174
+ """Compute clustering compactness loss.
175
+
176
+ Args:
177
+ embeddings: Cell embeddings in latent space.
178
+ assignments: Soft cluster assignments (should sum to 1 per cell).
179
+ centroids: Optional cluster centroids. If provided, uses these directly
180
+ for gradient flow. If None, computes soft centroids from assignments.
181
+
182
+ Returns:
183
+ Combined compactness and separation loss (scalar).
184
+ """
185
+ n_clusters = assignments.shape[1]
186
+
187
+ # Use provided centroids or compute soft centroids from assignments
188
+ if centroids is None:
189
+ # Compute soft cluster centroids
190
+ # centroid_k = sum_i(assignment_ik * embedding_i) / sum_i(assignment_ik)
191
+ assignment_sums = assignments.sum(axis=0, keepdims=True).T # (n_clusters, 1)
192
+ centroids = (assignments.T @ embeddings) / (
193
+ assignment_sums + 1e-8
194
+ ) # (n_clusters, latent_dim)
195
+
196
+ # Compactness: weighted sum of squared distances to centroids
197
+ # For each cell, compute distance to each centroid
198
+ # Then weight by assignment
199
+ distances_to_centroids = jnp.sum(
200
+ (embeddings[:, None, :] - centroids[None, :, :]) ** 2, axis=-1
201
+ ) # (n_cells, n_clusters)
202
+
203
+ # Weighted compactness
204
+ compactness = jnp.sum(assignments * distances_to_centroids) / embeddings.shape[0]
205
+
206
+ # Separation: pairwise distances between centroids
207
+ # We want centroids to be at least min_separation apart
208
+ centroid_dists = jnp.sqrt(
209
+ jnp.sum((centroids[:, None, :] - centroids[None, :, :]) ** 2, axis=-1) + 1e-8
210
+ ) # (n_clusters, n_clusters)
211
+
212
+ # Hinge loss: penalize if distance < min_separation
213
+ # Exclude diagonal (self-distance)
214
+ mask = 1.0 - jnp.eye(n_clusters)
215
+ separation_violations = (
216
+ soft_ops.relu(self.min_separation - centroid_dists, softness=0.1) * mask
217
+ )
218
+
219
+ # Mean separation loss (excluding diagonal)
220
+ n_pairs = n_clusters * (n_clusters - 1)
221
+ separation_loss = jnp.sum(separation_violations) / (n_pairs + 1e-8)
222
+
223
+ # Combined loss
224
+ return compactness + self.separation_weight * separation_loss
225
+
226
+
227
+ class VelocityConsistencyLoss(nnx.Module):
228
+ """Loss function to enforce consistency between velocity and trajectory.
229
+
230
+ Ensures that the predicted RNA velocity is consistent with actual
231
+ expression changes over time. Combines directional (cosine) and
232
+ magnitude consistency.
233
+
234
+ Args:
235
+ dt: Time step for velocity extrapolation.
236
+ cosine_weight: Weight for directional consistency.
237
+ magnitude_weight: Weight for magnitude consistency.
238
+ rngs: Flax NNX random number generators.
239
+
240
+ Example:
241
+ ```python
242
+ loss_fn = VelocityConsistencyLoss(rngs=nnx.Rngs(42))
243
+ loss = loss_fn(expression, velocity, future_expression)
244
+ ```
245
+ """
246
+
247
+ def __init__(
248
+ self,
249
+ dt: float = 0.1,
250
+ cosine_weight: float = 1.0,
251
+ magnitude_weight: float = 1.0,
252
+ *,
253
+ rngs: nnx.Rngs | None = None,
254
+ ) -> None:
255
+ """Initialize the velocity consistency loss.
256
+
257
+ Args:
258
+ dt: Time step for velocity extrapolation.
259
+ cosine_weight: Weight for cosine similarity loss.
260
+ magnitude_weight: Weight for magnitude loss.
261
+ rngs: Random number generators (not used, for API consistency).
262
+ """
263
+ super().__init__()
264
+ self.dt = dt
265
+ self.cosine_weight = cosine_weight
266
+ self.magnitude_weight = magnitude_weight
267
+
268
+ def __call__(
269
+ self,
270
+ expression: Float[Array, "n_cells n_genes"],
271
+ velocity: Float[Array, "n_cells n_genes"],
272
+ future_expression: Float[Array, "n_cells n_genes"],
273
+ ) -> Float[Array, ""]:
274
+ """Compute velocity consistency loss.
275
+
276
+ Args:
277
+ expression: Current gene expression.
278
+ velocity: Predicted RNA velocity (rate of change).
279
+ future_expression: Future gene expression (ground truth or estimated).
280
+
281
+ Returns:
282
+ Combined directional and magnitude consistency loss (scalar).
283
+ """
284
+ # Predicted change based on velocity
285
+ predicted_delta = velocity * self.dt
286
+
287
+ # Actual change
288
+ actual_delta = future_expression - expression
289
+
290
+ # Cosine similarity loss (directional consistency)
291
+ # cosine_sim = (a . b) / (||a|| * ||b||)
292
+ eps = 1e-8
293
+ pred_norm = jnp.sqrt(jnp.sum(predicted_delta**2, axis=-1) + eps)
294
+ actual_norm = jnp.sqrt(jnp.sum(actual_delta**2, axis=-1) + eps)
295
+ dot_product = jnp.sum(predicted_delta * actual_delta, axis=-1)
296
+
297
+ cosine_sim = dot_product / (pred_norm * actual_norm)
298
+
299
+ # Cosine loss: 1 - cosine_sim (ranges from 0 to 2)
300
+ cosine_loss = jnp.mean(1 - cosine_sim)
301
+
302
+ # Magnitude loss: MSE between predicted and actual delta magnitudes
303
+ magnitude_loss = jnp.mean((pred_norm - actual_norm) ** 2)
304
+
305
+ # Combined loss
306
+ return self.cosine_weight * cosine_loss + self.magnitude_weight * magnitude_loss
307
+
308
+
309
+ class ShannonDiversityLoss(nnx.Module):
310
+ """Mean Shannon entropy of soft cluster assignments across cells.
311
+
312
+ Measures assignment diversity using Shannon entropy. Higher values indicate
313
+ more uniform (diverse) cluster assignments, while lower values indicate
314
+ concentrated assignments.
315
+
316
+ Delegates to ``calibrax.metrics.functional.information.entropy`` for the
317
+ per-cell entropy computation.
318
+
319
+ Example:
320
+ ```python
321
+ loss_fn = ShannonDiversityLoss()
322
+ # Soft cluster probabilities: (n_cells, n_clusters)
323
+ assignments = jax.nn.softmax(logits, axis=-1)
324
+ diversity = loss_fn(assignments) # scalar, higher = more diverse
325
+ ```
326
+ """
327
+
328
+ def __init__(self) -> None:
329
+ """Initialize the Shannon diversity loss."""
330
+ super().__init__()
331
+
332
+ def __call__(
333
+ self,
334
+ assignments: Float[Array, "n_cells n_clusters"],
335
+ ) -> Float[Array, ""]:
336
+ """Compute mean Shannon entropy of soft cluster assignments.
337
+
338
+ Args:
339
+ assignments: Soft cluster probabilities of shape ``(n_cells, n_clusters)``.
340
+ Each row should sum to 1.
341
+
342
+ Returns:
343
+ Mean Shannon entropy across cells (scalar). Range ``[0, log(K)]``
344
+ where ``K`` is the number of clusters.
345
+ """
346
+ # Compute per-cell Shannon entropy via calibrax, then average
347
+ per_cell_entropy = jax.vmap(entropy)(assignments)
348
+ return jnp.mean(per_cell_entropy)
349
+
350
+
351
+ class SimpsonDiversityLoss(nnx.Module):
352
+ """Mean Simpson concentration index of soft cluster assignments.
353
+
354
+ Computes the sum of squared assignment probabilities per cell, averaged
355
+ across all cells. Lower values indicate more diverse (uniform) assignments.
356
+
357
+ - Uniform assignments over K clusters yield ``1/K``.
358
+ - Fully concentrated (one-hot) assignments yield ``1.0``.
359
+
360
+ Example:
361
+ ```python
362
+ loss_fn = SimpsonDiversityLoss()
363
+ assignments = jax.nn.softmax(logits, axis=-1)
364
+ concentration = loss_fn(assignments) # scalar, lower = more diverse
365
+ ```
366
+ """
367
+
368
+ def __init__(self) -> None:
369
+ """Initialize the Simpson diversity loss."""
370
+ super().__init__()
371
+
372
+ def __call__(
373
+ self,
374
+ assignments: Float[Array, "n_cells n_clusters"],
375
+ ) -> Float[Array, ""]:
376
+ """Compute mean Simpson concentration index.
377
+
378
+ Args:
379
+ assignments: Soft cluster probabilities of shape ``(n_cells, n_clusters)``.
380
+ Each row should sum to 1.
381
+
382
+ Returns:
383
+ Mean sum-of-squared-probabilities across cells (scalar).
384
+ Range ``[1/K, 1.0]`` where ``K`` is the number of clusters.
385
+ """
386
+ per_cell_simpson = jnp.sum(assignments**2, axis=-1)
387
+ return jnp.mean(per_cell_simpson)