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.
- diffbio/__init__.py +39 -0
- diffbio/configs.py +75 -0
- diffbio/constants.py +204 -0
- diffbio/core/__init__.py +127 -0
- diffbio/core/base_operators.py +612 -0
- diffbio/core/data_types.py +260 -0
- diffbio/core/gnn_components.py +629 -0
- diffbio/core/graph_utils.py +149 -0
- diffbio/core/neural_components.py +270 -0
- diffbio/core/optimal_transport.py +133 -0
- diffbio/core/soft_ops/__init__.py +216 -0
- diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
- diffbio/core/soft_ops/_projections_simplex.py +240 -0
- diffbio/core/soft_ops/_projections_transport.py +508 -0
- diffbio/core/soft_ops/_sorting_network.py +204 -0
- diffbio/core/soft_ops/_types.py +15 -0
- diffbio/core/soft_ops/_utils.py +342 -0
- diffbio/core/soft_ops/autograd_safe.py +120 -0
- diffbio/core/soft_ops/comparison.py +235 -0
- diffbio/core/soft_ops/elementwise.py +309 -0
- diffbio/core/soft_ops/logical.py +146 -0
- diffbio/core/soft_ops/quantile.py +376 -0
- diffbio/core/soft_ops/selection.py +236 -0
- diffbio/core/soft_ops/sorting.py +926 -0
- diffbio/core/soft_ops/straight_through.py +261 -0
- diffbio/core/uncertainty.py +279 -0
- diffbio/evaluation/__init__.py +42 -0
- diffbio/evaluation/adapters.py +409 -0
- diffbio/evaluation/graders.py +223 -0
- diffbio/evaluation/problem.py +157 -0
- diffbio/evaluation/runner.py +277 -0
- diffbio/losses/__init__.py +59 -0
- diffbio/losses/alignment_losses.py +222 -0
- diffbio/losses/biological_regularization.py +288 -0
- diffbio/losses/metric_losses.py +139 -0
- diffbio/losses/singlecell_losses.py +387 -0
- diffbio/losses/statistical_losses.py +345 -0
- diffbio/operators/__init__.py +60 -0
- diffbio/operators/_count_vae.py +197 -0
- diffbio/operators/_loss_balancing.py +65 -0
- diffbio/operators/_masked_gene_transformer.py +118 -0
- diffbio/operators/_transformer_validation.py +50 -0
- diffbio/operators/alignment/__init__.py +51 -0
- diffbio/operators/alignment/profile_hmm.py +350 -0
- diffbio/operators/alignment/scoring.py +127 -0
- diffbio/operators/alignment/smith_waterman.py +261 -0
- diffbio/operators/alignment/soft_msa.py +419 -0
- diffbio/operators/assembly/__init__.py +27 -0
- diffbio/operators/assembly/gnn_assembly.py +252 -0
- diffbio/operators/assembly/metagenomic_binning.py +296 -0
- diffbio/operators/crispr/__init__.py +17 -0
- diffbio/operators/crispr/guide_scoring.py +269 -0
- diffbio/operators/drug_discovery/__init__.py +133 -0
- diffbio/operators/drug_discovery/_graph_utils.py +142 -0
- diffbio/operators/drug_discovery/admet_predictor.py +285 -0
- diffbio/operators/drug_discovery/attentive_fp.py +411 -0
- diffbio/operators/drug_discovery/dti.py +261 -0
- diffbio/operators/drug_discovery/fingerprint.py +490 -0
- diffbio/operators/drug_discovery/maccs_keys.py +267 -0
- diffbio/operators/drug_discovery/message_passing.py +200 -0
- diffbio/operators/drug_discovery/primitives.py +242 -0
- diffbio/operators/drug_discovery/property_predictor.py +163 -0
- diffbio/operators/drug_discovery/similarity.py +193 -0
- diffbio/operators/epigenomics/__init__.py +35 -0
- diffbio/operators/epigenomics/chromatin_state.py +491 -0
- diffbio/operators/epigenomics/contextual.py +288 -0
- diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
- diffbio/operators/epigenomics/peak_calling.py +555 -0
- diffbio/operators/foundation_models/__init__.py +119 -0
- diffbio/operators/foundation_models/adapters.py +114 -0
- diffbio/operators/foundation_models/contracts.py +245 -0
- diffbio/operators/foundation_models/embedding_probe.py +83 -0
- diffbio/operators/foundation_models/experimental.py +128 -0
- diffbio/operators/foundation_models/foundation_model.py +332 -0
- diffbio/operators/foundation_models/frozen.py +59 -0
- diffbio/operators/foundation_models/precomputed.py +270 -0
- diffbio/operators/foundation_models/transformer_encoder.py +564 -0
- diffbio/operators/mapping/__init__.py +17 -0
- diffbio/operators/mapping/neural_mapper.py +493 -0
- diffbio/operators/metabolomics/__init__.py +39 -0
- diffbio/operators/metabolomics/spectral_similarity.py +315 -0
- diffbio/operators/molecular_dynamics/__init__.py +51 -0
- diffbio/operators/molecular_dynamics/force_field.py +265 -0
- diffbio/operators/molecular_dynamics/integrator.py +304 -0
- diffbio/operators/molecular_dynamics/primitives.py +115 -0
- diffbio/operators/multiomics/__init__.py +38 -0
- diffbio/operators/multiomics/hic_contact.py +377 -0
- diffbio/operators/multiomics/multiomics_vae.py +325 -0
- diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
- diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
- diffbio/operators/normalization/__init__.py +42 -0
- diffbio/operators/normalization/embedding.py +222 -0
- diffbio/operators/normalization/phate.py +400 -0
- diffbio/operators/normalization/umap.py +261 -0
- diffbio/operators/normalization/vae_normalizer.py +258 -0
- diffbio/operators/population/__init__.py +17 -0
- diffbio/operators/population/ancestry_estimation.py +274 -0
- diffbio/operators/preprocessing/__init__.py +76 -0
- diffbio/operators/preprocessing/adapter_removal.py +311 -0
- diffbio/operators/preprocessing/duplicate_filter.py +317 -0
- diffbio/operators/preprocessing/error_correction.py +287 -0
- diffbio/operators/protein/__init__.py +31 -0
- diffbio/operators/protein/secondary_structure.py +509 -0
- diffbio/operators/quality_filter.py +128 -0
- diffbio/operators/rna_structure/__init__.py +35 -0
- diffbio/operators/rna_structure/rna_folding.py +509 -0
- diffbio/operators/rnaseq/__init__.py +23 -0
- diffbio/operators/rnaseq/motif_discovery.py +251 -0
- diffbio/operators/rnaseq/splicing_psi.py +216 -0
- diffbio/operators/singlecell/__init__.py +193 -0
- diffbio/operators/singlecell/ambient_removal.py +333 -0
- diffbio/operators/singlecell/archetypes.py +191 -0
- diffbio/operators/singlecell/batch_correction.py +288 -0
- diffbio/operators/singlecell/cell_annotation.py +519 -0
- diffbio/operators/singlecell/communication.py +704 -0
- diffbio/operators/singlecell/differential_distribution.py +243 -0
- diffbio/operators/singlecell/doublet_detection.py +657 -0
- diffbio/operators/singlecell/downsampling.py +166 -0
- diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
- diffbio/operators/singlecell/grn_inference.py +336 -0
- diffbio/operators/singlecell/imputation.py +429 -0
- diffbio/operators/singlecell/knockdown_filter.py +176 -0
- diffbio/operators/singlecell/ot_trajectory.py +277 -0
- diffbio/operators/singlecell/simulation.py +444 -0
- diffbio/operators/singlecell/sindy_grn.py +247 -0
- diffbio/operators/singlecell/soft_clustering.py +211 -0
- diffbio/operators/singlecell/spatial_domains.py +677 -0
- diffbio/operators/singlecell/switch_de.py +184 -0
- diffbio/operators/singlecell/trajectory.py +447 -0
- diffbio/operators/singlecell/velocity.py +361 -0
- diffbio/operators/statistical/__init__.py +35 -0
- diffbio/operators/statistical/em_quantification.py +260 -0
- diffbio/operators/statistical/hmm.py +234 -0
- diffbio/operators/statistical/nb_glm.py +272 -0
- diffbio/operators/variant/__init__.py +64 -0
- diffbio/operators/variant/classifier.py +333 -0
- diffbio/operators/variant/cnn_classifier.py +255 -0
- diffbio/operators/variant/cnv_segmentation.py +678 -0
- diffbio/operators/variant/deepvariant_pileup.py +426 -0
- diffbio/operators/variant/pileup.py +240 -0
- diffbio/operators/variant/quality_recalibration.py +274 -0
- diffbio/pipelines/__init__.py +65 -0
- diffbio/pipelines/differential_expression.py +279 -0
- diffbio/pipelines/enhanced_variant_calling.py +326 -0
- diffbio/pipelines/perturbation.py +407 -0
- diffbio/pipelines/preprocessing.py +267 -0
- diffbio/pipelines/single_cell.py +366 -0
- diffbio/pipelines/variant_calling.py +490 -0
- diffbio/samplers/__init__.py +9 -0
- diffbio/samplers/perturbation_sampler.py +142 -0
- diffbio/sequences/__init__.py +34 -0
- diffbio/sequences/dna.py +239 -0
- diffbio/sources/__init__.py +149 -0
- diffbio/sources/_anndata_shared.py +89 -0
- diffbio/sources/_batch_iteration.py +37 -0
- diffbio/sources/_benchmark_source.py +152 -0
- diffbio/sources/_indexed_batch_source.py +38 -0
- diffbio/sources/_utils.py +45 -0
- diffbio/sources/anndata_interop.py +387 -0
- diffbio/sources/anndata_source.py +361 -0
- diffbio/sources/archive_ii.py +174 -0
- diffbio/sources/balifam.py +207 -0
- diffbio/sources/bam.py +265 -0
- diffbio/sources/bengrn_ground_truth.py +306 -0
- diffbio/sources/contextual_epigenomics.py +242 -0
- diffbio/sources/dti.py +359 -0
- diffbio/sources/embeddings.py +203 -0
- diffbio/sources/encode_peaks.py +223 -0
- diffbio/sources/fasta.py +226 -0
- diffbio/sources/immune_human.py +172 -0
- diffbio/sources/indexed_embeddings.py +128 -0
- diffbio/sources/indexed_view.py +191 -0
- diffbio/sources/molnet.py +493 -0
- diffbio/sources/multiomics.py +279 -0
- diffbio/sources/pancreas.py +108 -0
- diffbio/sources/perturbation/__init__.py +69 -0
- diffbio/sources/perturbation/_types.py +51 -0
- diffbio/sources/perturbation/_utils.py +125 -0
- diffbio/sources/perturbation/concat_source.py +115 -0
- diffbio/sources/perturbation/control_mapping.py +215 -0
- diffbio/sources/perturbation/experiment_config.py +261 -0
- diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
- diffbio/sources/perturbation/output_space.py +52 -0
- diffbio/sources/perturbation/perturbation_source.py +513 -0
- diffbio/sources/seqfish.py +145 -0
- diffbio/sources/sequence_foundation.py +68 -0
- diffbio/sources/singlecell_foundation.py +68 -0
- diffbio/splitters/__init__.py +63 -0
- diffbio/splitters/base.py +251 -0
- diffbio/splitters/molecular.py +330 -0
- diffbio/splitters/perturbation.py +199 -0
- diffbio/splitters/random.py +217 -0
- diffbio/splitters/sequence.py +201 -0
- diffbio/utils/__init__.py +55 -0
- diffbio/utils/dependency_runtime.py +115 -0
- diffbio/utils/nn_utils.py +157 -0
- diffbio/utils/quality.py +45 -0
- diffbio/utils/training.py +585 -0
- diffbio-0.1.0.dist-info/METADATA +480 -0
- diffbio-0.1.0.dist-info/RECORD +202 -0
- diffbio-0.1.0.dist-info/WHEEL +4 -0
- diffbio-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Differentiable Harmony-style batch correction operator.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable implementation of batch correction
|
|
4
|
+
using soft clustering with batch-aware centroid updates.
|
|
5
|
+
|
|
6
|
+
Key technique: Unrolled iterations enable gradient flow through
|
|
7
|
+
the entire batch correction process.
|
|
8
|
+
|
|
9
|
+
Applications: Multi-sample integration, batch effect removal.
|
|
10
|
+
|
|
11
|
+
Inherits from TemperatureOperator to get:
|
|
12
|
+
|
|
13
|
+
- _temperature property for temperature-controlled smoothing
|
|
14
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
15
|
+
- soft_argmax() for soft position selection
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import jax
|
|
23
|
+
import jax.numpy as jnp
|
|
24
|
+
from datarax.core.config import OperatorConfig
|
|
25
|
+
from flax import nnx
|
|
26
|
+
from jaxtyping import Array, Float, Int, PyTree
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class BatchCorrectionConfig(OperatorConfig):
|
|
36
|
+
"""Configuration for DifferentiableHarmony.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
n_clusters: Number of clusters for soft assignment.
|
|
40
|
+
n_features: Dimensionality of input embeddings.
|
|
41
|
+
n_batches: Number of distinct batches.
|
|
42
|
+
n_iterations: Number of correction iterations.
|
|
43
|
+
theta: Diversity penalty parameter.
|
|
44
|
+
sigma: Soft assignment bandwidth.
|
|
45
|
+
temperature: Temperature for softmax operations.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
n_clusters: int = 100
|
|
49
|
+
n_features: int = 50
|
|
50
|
+
n_batches: int = 2
|
|
51
|
+
n_iterations: int = 10
|
|
52
|
+
theta: float = 2.0
|
|
53
|
+
sigma: float = 0.1
|
|
54
|
+
temperature: float = 1.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DifferentiableHarmony(TemperatureOperator):
|
|
58
|
+
"""Differentiable Harmony-style batch correction.
|
|
59
|
+
|
|
60
|
+
This operator implements iterative batch correction using soft
|
|
61
|
+
clustering with batch-aware updates. The fixed number of iterations
|
|
62
|
+
enables gradient flow through the entire correction process.
|
|
63
|
+
|
|
64
|
+
Algorithm:
|
|
65
|
+
1. Initialize cluster centroids from data
|
|
66
|
+
2. Soft assignment of cells to clusters
|
|
67
|
+
3. Compute batch-aware centroid corrections
|
|
68
|
+
4. Update cell embeddings toward corrected centroids
|
|
69
|
+
5. Repeat for n_iterations
|
|
70
|
+
|
|
71
|
+
Inherits from TemperatureOperator to get:
|
|
72
|
+
|
|
73
|
+
- _temperature property for temperature-controlled smoothing
|
|
74
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
75
|
+
- soft_argmax() for soft position selection
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config: BatchCorrectionConfig with model parameters.
|
|
79
|
+
rngs: Flax NNX random number generators.
|
|
80
|
+
name: Optional operator name.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
```python
|
|
84
|
+
config = BatchCorrectionConfig(n_clusters=100, n_batches=3)
|
|
85
|
+
harmony = DifferentiableHarmony(config, rngs=nnx.Rngs(42))
|
|
86
|
+
data = {"embeddings": X, "batch_labels": batch}
|
|
87
|
+
result, state, meta = harmony.apply(data, {}, None)
|
|
88
|
+
```
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
config: BatchCorrectionConfig,
|
|
94
|
+
*,
|
|
95
|
+
rngs: nnx.Rngs | None = None,
|
|
96
|
+
name: str | None = None,
|
|
97
|
+
):
|
|
98
|
+
"""Initialize the batch correction operator.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
config: Batch correction configuration.
|
|
102
|
+
rngs: Random number generators for initialization.
|
|
103
|
+
name: Optional operator name.
|
|
104
|
+
"""
|
|
105
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
106
|
+
|
|
107
|
+
rngs = rngs or nnx.Rngs(0)
|
|
108
|
+
# Temperature is now managed by TemperatureOperator via self._temperature
|
|
109
|
+
|
|
110
|
+
# Initialize cluster centroids
|
|
111
|
+
key = rngs.params()
|
|
112
|
+
centroid_shape = (config.n_clusters, config.n_features)
|
|
113
|
+
init_centroids = jax.random.normal(key, centroid_shape) * 0.1
|
|
114
|
+
self.cluster_centroids = nnx.Param(init_centroids)
|
|
115
|
+
|
|
116
|
+
def compute_soft_assignments(
|
|
117
|
+
self,
|
|
118
|
+
embeddings: Float[Array, "n_cells n_features"],
|
|
119
|
+
centroids: Float[Array, "n_clusters n_features"],
|
|
120
|
+
) -> Float[Array, "n_cells n_clusters"]:
|
|
121
|
+
"""Compute soft cluster assignments.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
embeddings: Cell embeddings.
|
|
125
|
+
centroids: Cluster centroids.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Soft assignment probabilities.
|
|
129
|
+
"""
|
|
130
|
+
# Compute squared distances
|
|
131
|
+
# ||x - c||² = ||x||² + ||c||² - 2 * x · c
|
|
132
|
+
emb_sq = jnp.sum(embeddings**2, axis=-1, keepdims=True)
|
|
133
|
+
cent_sq = jnp.sum(centroids**2, axis=-1)
|
|
134
|
+
dot_product = jnp.einsum("nf,kf->nk", embeddings, centroids)
|
|
135
|
+
distances_sq = emb_sq + cent_sq - 2 * dot_product
|
|
136
|
+
|
|
137
|
+
# Soft assignments
|
|
138
|
+
# Use inherited _temperature property from TemperatureOperator
|
|
139
|
+
assignments = jax.nn.softmax(
|
|
140
|
+
-distances_sq / (self.config.sigma * self._temperature),
|
|
141
|
+
axis=-1,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return assignments
|
|
145
|
+
|
|
146
|
+
def compute_batch_proportions(
|
|
147
|
+
self,
|
|
148
|
+
batch_labels: Int[Array, "n_cells"],
|
|
149
|
+
assignments: Float[Array, "n_cells n_clusters"],
|
|
150
|
+
) -> Float[Array, "n_clusters n_batches"]:
|
|
151
|
+
"""Compute batch proportions within each cluster.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
batch_labels: Batch assignments for each cell.
|
|
155
|
+
assignments: Soft cluster assignments.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Proportion of each batch in each cluster.
|
|
159
|
+
"""
|
|
160
|
+
# Create one-hot batch encoding
|
|
161
|
+
batch_onehot = jax.nn.one_hot(batch_labels, self.config.n_batches) # (n_cells, n_batches)
|
|
162
|
+
|
|
163
|
+
# Weighted count of each batch in each cluster
|
|
164
|
+
# (n_cells, n_clusters).T @ (n_cells, n_batches) -> (n_clusters, n_batches)
|
|
165
|
+
batch_counts = jnp.einsum("nk,nb->kb", assignments, batch_onehot)
|
|
166
|
+
|
|
167
|
+
# Normalize to get proportions
|
|
168
|
+
total_per_cluster = jnp.sum(batch_counts, axis=-1, keepdims=True) + 1e-10
|
|
169
|
+
batch_proportions = batch_counts / total_per_cluster
|
|
170
|
+
|
|
171
|
+
return batch_proportions
|
|
172
|
+
|
|
173
|
+
def correction_step(
|
|
174
|
+
self,
|
|
175
|
+
embeddings: Float[Array, "n_cells n_features"],
|
|
176
|
+
batch_labels: Int[Array, "n_cells"],
|
|
177
|
+
centroids: Float[Array, "n_clusters n_features"],
|
|
178
|
+
) -> tuple[Float[Array, "n_cells n_features"], Float[Array, "n_cells n_clusters"]]:
|
|
179
|
+
"""Perform one correction iteration.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
embeddings: Current cell embeddings.
|
|
183
|
+
batch_labels: Batch assignments.
|
|
184
|
+
centroids: Current cluster centroids.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Corrected embeddings and soft assignments.
|
|
188
|
+
"""
|
|
189
|
+
# Compute soft assignments
|
|
190
|
+
assignments = self.compute_soft_assignments(embeddings, centroids)
|
|
191
|
+
|
|
192
|
+
# Compute batch proportions
|
|
193
|
+
batch_props = self.compute_batch_proportions(batch_labels, assignments)
|
|
194
|
+
|
|
195
|
+
# Global batch proportions (target)
|
|
196
|
+
batch_onehot = jax.nn.one_hot(batch_labels, self.config.n_batches)
|
|
197
|
+
global_batch_props = jnp.mean(batch_onehot, axis=0) # (n_batches,)
|
|
198
|
+
|
|
199
|
+
# Compute correction direction for each cell
|
|
200
|
+
# Move cells toward cluster centroids weighted by assignment and batch correction
|
|
201
|
+
weighted_centroids = jnp.einsum(
|
|
202
|
+
"nk,kf->nf", assignments, centroids
|
|
203
|
+
) # (n_cells, n_features)
|
|
204
|
+
|
|
205
|
+
# Correction: small step toward weighted centroid
|
|
206
|
+
correction = (weighted_centroids - embeddings) * 0.1
|
|
207
|
+
|
|
208
|
+
# Apply batch-specific scaling
|
|
209
|
+
# Cells from over-represented batches get larger corrections
|
|
210
|
+
batch_idx = batch_labels # (n_cells,)
|
|
211
|
+
# Get batch proportion at each cell's most likely cluster
|
|
212
|
+
top_cluster = jnp.argmax(assignments, axis=-1)
|
|
213
|
+
cell_batch_prop = batch_props[top_cluster, batch_idx] # (n_cells,)
|
|
214
|
+
cell_global_prop = global_batch_props[batch_idx] # (n_cells,)
|
|
215
|
+
|
|
216
|
+
# Scale correction by how overrepresented the batch is
|
|
217
|
+
correction_scale = jnp.clip(cell_batch_prop / (cell_global_prop + 1e-10), 0.5, 2.0)
|
|
218
|
+
correction = correction * correction_scale[:, None]
|
|
219
|
+
|
|
220
|
+
# Apply correction
|
|
221
|
+
corrected = embeddings + correction
|
|
222
|
+
|
|
223
|
+
return corrected, assignments
|
|
224
|
+
|
|
225
|
+
def apply(
|
|
226
|
+
self,
|
|
227
|
+
data: PyTree,
|
|
228
|
+
state: PyTree,
|
|
229
|
+
metadata: dict[str, Any] | None,
|
|
230
|
+
random_params: Any = None,
|
|
231
|
+
stats: dict[str, Any] | None = None,
|
|
232
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
233
|
+
"""Apply batch correction to cell embeddings.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
data: Dictionary containing:
|
|
237
|
+
- "embeddings": Cell embeddings (n_cells, n_features)
|
|
238
|
+
- "batch_labels": Batch assignments (n_cells,)
|
|
239
|
+
state: Element state (passed through unchanged)
|
|
240
|
+
metadata: Element metadata (passed through unchanged)
|
|
241
|
+
random_params: Not used
|
|
242
|
+
stats: Not used
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Tuple of (transformed_data, state, metadata):
|
|
246
|
+
- transformed_data contains:
|
|
247
|
+
|
|
248
|
+
- "embeddings": Original embeddings
|
|
249
|
+
- "batch_labels": Original batch labels
|
|
250
|
+
- "corrected_embeddings": Batch-corrected embeddings
|
|
251
|
+
- "cluster_assignments": Final soft cluster assignments
|
|
252
|
+
- state is passed through unchanged
|
|
253
|
+
- metadata is passed through unchanged
|
|
254
|
+
"""
|
|
255
|
+
embeddings = data["embeddings"]
|
|
256
|
+
batch_labels = data["batch_labels"]
|
|
257
|
+
centroids = self.cluster_centroids[...]
|
|
258
|
+
|
|
259
|
+
# Run correction iterations
|
|
260
|
+
corrected = embeddings
|
|
261
|
+
assignments = None
|
|
262
|
+
|
|
263
|
+
def iteration_step(carry, _):
|
|
264
|
+
corrected_emb, centroids = carry
|
|
265
|
+
new_corrected, new_assignments = self.correction_step(
|
|
266
|
+
corrected_emb, batch_labels, centroids
|
|
267
|
+
)
|
|
268
|
+
return (new_corrected, centroids), new_assignments
|
|
269
|
+
|
|
270
|
+
(corrected, _), assignments = jax.lax.scan(
|
|
271
|
+
iteration_step,
|
|
272
|
+
(corrected, centroids),
|
|
273
|
+
None,
|
|
274
|
+
length=self.config.n_iterations,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Get final assignments
|
|
278
|
+
final_assignments = self.compute_soft_assignments(corrected, centroids)
|
|
279
|
+
|
|
280
|
+
# Build output data
|
|
281
|
+
transformed_data = {
|
|
282
|
+
"embeddings": embeddings,
|
|
283
|
+
"batch_labels": batch_labels,
|
|
284
|
+
"corrected_embeddings": corrected,
|
|
285
|
+
"cluster_assignments": final_assignments,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return transformed_data, state, metadata
|