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,361 @@
|
|
|
1
|
+
"""RNA velocity estimation via Neural ODEs.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable RNA velocity estimation using neural
|
|
4
|
+
networks to learn splicing kinetics and integrate ODEs.
|
|
5
|
+
|
|
6
|
+
Key technique: Uses neural networks to learn per-gene kinetics parameters
|
|
7
|
+
(transcription, splicing, degradation rates) and integrates the splicing
|
|
8
|
+
ODE differentiably using Euler method.
|
|
9
|
+
|
|
10
|
+
Applications: Inferring cell state transitions and developmental trajectories
|
|
11
|
+
from single-cell RNA-seq data with spliced/unspliced counts.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import jax
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
from datarax.core.config import OperatorConfig
|
|
21
|
+
from datarax.core.operator import OperatorModule
|
|
22
|
+
from flax import nnx
|
|
23
|
+
from jaxtyping import Array, Float, PyTree
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class VelocityConfig(OperatorConfig):
|
|
30
|
+
"""Configuration for DifferentiableVelocity.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
n_genes: Number of genes.
|
|
34
|
+
hidden_dim: Hidden dimension for neural networks.
|
|
35
|
+
dt: Time step for ODE integration.
|
|
36
|
+
n_steps: Number of integration steps.
|
|
37
|
+
kinetics_model: Type of kinetics model ("standard" or "dynamical").
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
n_genes: int = 2000
|
|
41
|
+
hidden_dim: int = 64
|
|
42
|
+
dt: float = 0.1
|
|
43
|
+
n_steps: int = 10
|
|
44
|
+
kinetics_model: str = "standard"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TimeEncoder(nnx.Module):
|
|
48
|
+
"""Encoder for estimating latent time from expression."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
n_genes: int,
|
|
53
|
+
hidden_dim: int,
|
|
54
|
+
*,
|
|
55
|
+
rngs: nnx.Rngs,
|
|
56
|
+
):
|
|
57
|
+
"""Initialize the time encoder.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
n_genes: Number of input genes.
|
|
61
|
+
hidden_dim: Hidden dimension.
|
|
62
|
+
rngs: Random number generators.
|
|
63
|
+
"""
|
|
64
|
+
super().__init__()
|
|
65
|
+
|
|
66
|
+
# Encode spliced + unspliced to latent time
|
|
67
|
+
self.linear1 = nnx.Linear(
|
|
68
|
+
in_features=n_genes * 2,
|
|
69
|
+
out_features=hidden_dim,
|
|
70
|
+
rngs=rngs,
|
|
71
|
+
)
|
|
72
|
+
self.linear2 = nnx.Linear(
|
|
73
|
+
in_features=hidden_dim,
|
|
74
|
+
out_features=hidden_dim,
|
|
75
|
+
rngs=rngs,
|
|
76
|
+
)
|
|
77
|
+
self.time_proj = nnx.Linear(
|
|
78
|
+
in_features=hidden_dim,
|
|
79
|
+
out_features=1,
|
|
80
|
+
rngs=rngs,
|
|
81
|
+
)
|
|
82
|
+
self.norm = nnx.LayerNorm(num_features=hidden_dim, rngs=rngs)
|
|
83
|
+
|
|
84
|
+
def __call__(
|
|
85
|
+
self,
|
|
86
|
+
spliced: Float[Array, "n_cells n_genes"],
|
|
87
|
+
unspliced: Float[Array, "n_cells n_genes"],
|
|
88
|
+
) -> Float[Array, "n_cells"]:
|
|
89
|
+
"""Estimate latent time for each cell.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
spliced: Spliced (mature) mRNA counts.
|
|
93
|
+
unspliced: Unspliced (nascent) mRNA counts.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Latent time estimates per cell (bounded 0-1).
|
|
97
|
+
"""
|
|
98
|
+
# Concatenate and log-transform
|
|
99
|
+
x = jnp.concatenate([jnp.log1p(spliced), jnp.log1p(unspliced)], axis=-1)
|
|
100
|
+
|
|
101
|
+
# Encode
|
|
102
|
+
x = nnx.gelu(self.linear1(x))
|
|
103
|
+
x = self.norm(nnx.gelu(self.linear2(x)))
|
|
104
|
+
|
|
105
|
+
# Project to time (sigmoid for 0-1 range)
|
|
106
|
+
time = jax.nn.sigmoid(self.time_proj(x)).squeeze(-1)
|
|
107
|
+
|
|
108
|
+
return time
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class KineticsEncoder(nnx.Module):
|
|
112
|
+
"""Encoder for learning per-gene kinetics parameters."""
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
n_genes: int,
|
|
117
|
+
*,
|
|
118
|
+
rngs: nnx.Rngs,
|
|
119
|
+
):
|
|
120
|
+
"""Initialize the kinetics encoder.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
n_genes: Number of genes.
|
|
124
|
+
rngs: Random number generators.
|
|
125
|
+
"""
|
|
126
|
+
super().__init__()
|
|
127
|
+
|
|
128
|
+
# Learnable per-gene kinetics (alpha, beta, gamma)
|
|
129
|
+
# Initialize with reasonable defaults
|
|
130
|
+
key = rngs.params()
|
|
131
|
+
k1, k2, k3 = jax.random.split(key, 3)
|
|
132
|
+
|
|
133
|
+
# Transcription rate (alpha): typically 0.1-10
|
|
134
|
+
self.log_alpha = nnx.Param(jax.random.normal(k1, (n_genes,)) * 0.5)
|
|
135
|
+
|
|
136
|
+
# Splicing rate (beta): typically 0.1-1
|
|
137
|
+
self.log_beta = nnx.Param(jax.random.normal(k2, (n_genes,)) * 0.5 - 1.0)
|
|
138
|
+
|
|
139
|
+
# Degradation rate (gamma): typically 0.01-0.5
|
|
140
|
+
self.log_gamma = nnx.Param(jax.random.normal(k3, (n_genes,)) * 0.5 - 2.0)
|
|
141
|
+
|
|
142
|
+
def __call__(
|
|
143
|
+
self,
|
|
144
|
+
) -> tuple[
|
|
145
|
+
Float[Array, "n_genes"],
|
|
146
|
+
Float[Array, "n_genes"],
|
|
147
|
+
Float[Array, "n_genes"],
|
|
148
|
+
]:
|
|
149
|
+
"""Get kinetics parameters.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Tuple of (alpha, beta, gamma) - all positive via softplus.
|
|
153
|
+
"""
|
|
154
|
+
alpha = jax.nn.softplus(self.log_alpha[...])
|
|
155
|
+
beta = jax.nn.softplus(self.log_beta[...])
|
|
156
|
+
gamma = jax.nn.softplus(self.log_gamma[...])
|
|
157
|
+
|
|
158
|
+
return alpha, beta, gamma
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DifferentiableVelocity(OperatorModule):
|
|
162
|
+
"""Differentiable RNA velocity estimation via Neural ODEs.
|
|
163
|
+
|
|
164
|
+
This operator estimates RNA velocity from spliced and unspliced
|
|
165
|
+
counts using learned kinetics parameters and differentiable ODE
|
|
166
|
+
integration.
|
|
167
|
+
|
|
168
|
+
Algorithm:
|
|
169
|
+
1. Encode expression to latent time per cell
|
|
170
|
+
2. Learn per-gene kinetics (alpha, beta, gamma)
|
|
171
|
+
3. Compute velocity from splicing ODE:
|
|
172
|
+
ds/dt = beta * u - gamma * s
|
|
173
|
+
du/dt = alpha - beta * u
|
|
174
|
+
4. Integrate ODE using Euler method
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
config: VelocityConfig with model parameters.
|
|
178
|
+
rngs: Flax NNX random number generators.
|
|
179
|
+
name: Optional operator name.
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
```python
|
|
183
|
+
config = VelocityConfig(n_genes=2000)
|
|
184
|
+
velocity = DifferentiableVelocity(config, rngs=nnx.Rngs(42))
|
|
185
|
+
data = {"spliced": spliced, "unspliced": unspliced}
|
|
186
|
+
result, state, meta = velocity.apply(data, {}, None)
|
|
187
|
+
```
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(
|
|
191
|
+
self,
|
|
192
|
+
config: VelocityConfig,
|
|
193
|
+
*,
|
|
194
|
+
rngs: nnx.Rngs | None = None,
|
|
195
|
+
name: str | None = None,
|
|
196
|
+
):
|
|
197
|
+
"""Initialize the velocity operator.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
config: Velocity configuration.
|
|
201
|
+
rngs: Random number generators for initialization.
|
|
202
|
+
name: Optional operator name.
|
|
203
|
+
"""
|
|
204
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
205
|
+
|
|
206
|
+
if rngs is None:
|
|
207
|
+
rngs = nnx.Rngs(0)
|
|
208
|
+
|
|
209
|
+
self.n_genes = config.n_genes
|
|
210
|
+
self.dt = config.dt
|
|
211
|
+
self.n_steps = config.n_steps
|
|
212
|
+
|
|
213
|
+
# Time encoder
|
|
214
|
+
self.time_encoder = TimeEncoder(
|
|
215
|
+
n_genes=config.n_genes,
|
|
216
|
+
hidden_dim=config.hidden_dim,
|
|
217
|
+
rngs=rngs,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Kinetics encoder
|
|
221
|
+
self.kinetics_encoder = KineticsEncoder(
|
|
222
|
+
n_genes=config.n_genes,
|
|
223
|
+
rngs=rngs,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _compute_velocity(
|
|
227
|
+
self,
|
|
228
|
+
spliced: Float[Array, "n_cells n_genes"],
|
|
229
|
+
unspliced: Float[Array, "n_cells n_genes"],
|
|
230
|
+
beta: Float[Array, "n_genes"],
|
|
231
|
+
gamma: Float[Array, "n_genes"],
|
|
232
|
+
) -> Float[Array, "n_cells n_genes"]:
|
|
233
|
+
"""Compute RNA velocity from splicing dynamics.
|
|
234
|
+
|
|
235
|
+
The standard RNA velocity model:
|
|
236
|
+
ds/dt = beta * u - gamma * s
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
spliced: Spliced counts.
|
|
240
|
+
unspliced: Unspliced counts.
|
|
241
|
+
beta: Splicing rate.
|
|
242
|
+
gamma: Degradation rate.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Velocity (ds/dt) for each cell-gene pair.
|
|
246
|
+
"""
|
|
247
|
+
# Velocity is the rate of change of spliced mRNA
|
|
248
|
+
# ds/dt = splicing_in - degradation_out
|
|
249
|
+
# ds/dt = beta * unspliced - gamma * spliced
|
|
250
|
+
velocity = beta[None, :] * unspliced - gamma[None, :] * spliced
|
|
251
|
+
|
|
252
|
+
return velocity
|
|
253
|
+
|
|
254
|
+
def _euler_step(
|
|
255
|
+
self,
|
|
256
|
+
s: Float[Array, "n_cells n_genes"],
|
|
257
|
+
u: Float[Array, "n_cells n_genes"],
|
|
258
|
+
alpha: Float[Array, "n_genes"],
|
|
259
|
+
beta: Float[Array, "n_genes"],
|
|
260
|
+
gamma: Float[Array, "n_genes"],
|
|
261
|
+
dt: float,
|
|
262
|
+
) -> tuple[Float[Array, "n_cells n_genes"], Float[Array, "n_cells n_genes"]]:
|
|
263
|
+
"""Single Euler integration step.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
s: Current spliced counts.
|
|
267
|
+
u: Current unspliced counts.
|
|
268
|
+
alpha: Transcription rate.
|
|
269
|
+
beta: Splicing rate.
|
|
270
|
+
gamma: Degradation rate.
|
|
271
|
+
dt: Time step.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Tuple of (new_spliced, new_unspliced).
|
|
275
|
+
"""
|
|
276
|
+
# ODE system:
|
|
277
|
+
# du/dt = alpha - beta * u
|
|
278
|
+
# ds/dt = beta * u - gamma * s
|
|
279
|
+
du_dt = alpha[None, :] - beta[None, :] * u
|
|
280
|
+
ds_dt = beta[None, :] * u - gamma[None, :] * s
|
|
281
|
+
|
|
282
|
+
# Euler update
|
|
283
|
+
u_new = u + dt * du_dt
|
|
284
|
+
s_new = s + dt * ds_dt
|
|
285
|
+
|
|
286
|
+
# Ensure non-negative
|
|
287
|
+
u_new = jnp.maximum(u_new, 0.0)
|
|
288
|
+
s_new = jnp.maximum(s_new, 0.0)
|
|
289
|
+
|
|
290
|
+
return s_new, u_new
|
|
291
|
+
|
|
292
|
+
def apply(
|
|
293
|
+
self,
|
|
294
|
+
data: PyTree,
|
|
295
|
+
state: PyTree,
|
|
296
|
+
metadata: dict[str, Any] | None,
|
|
297
|
+
random_params: Any = None,
|
|
298
|
+
stats: dict[str, Any] | None = None,
|
|
299
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
300
|
+
"""Apply RNA velocity estimation.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
data: Dictionary containing:
|
|
304
|
+
- "spliced": Spliced mRNA counts (n_cells, n_genes)
|
|
305
|
+
- "unspliced": Unspliced mRNA counts (n_cells, n_genes)
|
|
306
|
+
state: Element state (passed through unchanged)
|
|
307
|
+
metadata: Element metadata (passed through unchanged)
|
|
308
|
+
random_params: Not used
|
|
309
|
+
stats: Not used
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Tuple of (transformed_data, state, metadata):
|
|
313
|
+
- transformed_data contains:
|
|
314
|
+
|
|
315
|
+
- "spliced": Original spliced counts
|
|
316
|
+
- "unspliced": Original unspliced counts
|
|
317
|
+
- "velocity": RNA velocity estimates
|
|
318
|
+
- "latent_time": Estimated latent time per cell
|
|
319
|
+
- "alpha": Transcription rate per gene
|
|
320
|
+
- "beta": Splicing rate per gene
|
|
321
|
+
- "gamma": Degradation rate per gene
|
|
322
|
+
- "projected_spliced": Projected future spliced
|
|
323
|
+
- state is passed through unchanged
|
|
324
|
+
- metadata is passed through unchanged
|
|
325
|
+
"""
|
|
326
|
+
spliced = data["spliced"]
|
|
327
|
+
unspliced = data["unspliced"]
|
|
328
|
+
|
|
329
|
+
# Estimate latent time per cell
|
|
330
|
+
latent_time = self.time_encoder(spliced, unspliced)
|
|
331
|
+
|
|
332
|
+
# Get kinetics parameters
|
|
333
|
+
alpha, beta, gamma = self.kinetics_encoder()
|
|
334
|
+
|
|
335
|
+
# Compute velocity
|
|
336
|
+
velocity = self._compute_velocity(spliced, unspliced, beta, gamma)
|
|
337
|
+
|
|
338
|
+
# Project forward using learned dynamics via jax.lax.scan
|
|
339
|
+
def _euler_body(
|
|
340
|
+
carry: tuple[jax.Array, jax.Array], _: None
|
|
341
|
+
) -> tuple[tuple[jax.Array, jax.Array], None]:
|
|
342
|
+
s, u = carry
|
|
343
|
+
s_new, u_new = self._euler_step(s, u, alpha, beta, gamma, self.dt)
|
|
344
|
+
return (s_new, u_new), None
|
|
345
|
+
|
|
346
|
+
(s_proj, u_proj), _ = jax.lax.scan(
|
|
347
|
+
_euler_body, (spliced, unspliced), None, length=self.n_steps
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
transformed_data = {
|
|
351
|
+
"spliced": spliced,
|
|
352
|
+
"unspliced": unspliced,
|
|
353
|
+
"velocity": velocity,
|
|
354
|
+
"latent_time": latent_time,
|
|
355
|
+
"alpha": alpha,
|
|
356
|
+
"beta": beta,
|
|
357
|
+
"gamma": gamma,
|
|
358
|
+
"projected_spliced": s_proj,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Differentiable statistical model operators.
|
|
2
|
+
|
|
3
|
+
This module provides operators for:
|
|
4
|
+
|
|
5
|
+
- Hidden Markov Models with differentiable forward algorithm
|
|
6
|
+
- Negative Binomial GLM for differential expression
|
|
7
|
+
- Unrolled EM for transcript quantification
|
|
8
|
+
|
|
9
|
+
All operators maintain gradient flow for end-to-end training.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from diffbio.operators.statistical.em_quantification import (
|
|
13
|
+
DifferentiableEMQuantifier,
|
|
14
|
+
EMQuantifierConfig,
|
|
15
|
+
)
|
|
16
|
+
from diffbio.operators.statistical.hmm import (
|
|
17
|
+
DifferentiableHMM,
|
|
18
|
+
HMMConfig,
|
|
19
|
+
)
|
|
20
|
+
from diffbio.operators.statistical.nb_glm import (
|
|
21
|
+
DifferentiableNBGLM,
|
|
22
|
+
NBGLMConfig,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# HMM
|
|
27
|
+
"HMMConfig",
|
|
28
|
+
"DifferentiableHMM",
|
|
29
|
+
# Negative Binomial GLM
|
|
30
|
+
"NBGLMConfig",
|
|
31
|
+
"DifferentiableNBGLM",
|
|
32
|
+
# EM Quantification
|
|
33
|
+
"EMQuantifierConfig",
|
|
34
|
+
"DifferentiableEMQuantifier",
|
|
35
|
+
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Differentiable EM-based transcript quantification operator.
|
|
2
|
+
|
|
3
|
+
This module provides an unrolled EM algorithm for transcript
|
|
4
|
+
quantification, inspired by Salmon and Kallisto.
|
|
5
|
+
|
|
6
|
+
Key technique: Fixed number of EM iterations enables gradient flow
|
|
7
|
+
through all steps of the algorithm.
|
|
8
|
+
|
|
9
|
+
Applications: RNA-seq transcript quantification, isoform abundance estimation.
|
|
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, PyTree
|
|
27
|
+
|
|
28
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class EMQuantifierConfig(OperatorConfig):
|
|
35
|
+
"""Configuration for DifferentiableEMQuantifier.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
n_transcripts: Number of transcripts to quantify.
|
|
39
|
+
n_iterations: Fixed number of EM iterations (for unrolling).
|
|
40
|
+
temperature: Temperature for softmax in E-step.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
n_transcripts: int = 1000
|
|
44
|
+
n_iterations: int = 10
|
|
45
|
+
temperature: float = 1.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DifferentiableEMQuantifier(TemperatureOperator):
|
|
49
|
+
"""Differentiable EM for transcript quantification.
|
|
50
|
+
|
|
51
|
+
This operator implements the EM algorithm for estimating transcript
|
|
52
|
+
abundances from read-to-transcript compatibility data. The fixed
|
|
53
|
+
number of iterations enables gradient flow through the entire
|
|
54
|
+
quantification process.
|
|
55
|
+
|
|
56
|
+
Algorithm:
|
|
57
|
+
1. Initialize abundances (learnable prior)
|
|
58
|
+
2. E-step: Probabilistic assignment of reads to transcripts
|
|
59
|
+
weights = softmax(compatibility * abundances / temperature)
|
|
60
|
+
3. M-step: Update abundances from weighted counts
|
|
61
|
+
abundances = sum(weights) / effective_lengths
|
|
62
|
+
abundances = abundances / sum(abundances)
|
|
63
|
+
4. Repeat for n_iterations
|
|
64
|
+
|
|
65
|
+
Inherits from TemperatureOperator to get:
|
|
66
|
+
|
|
67
|
+
- _temperature property for temperature-controlled smoothing
|
|
68
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
69
|
+
- soft_argmax() for soft position selection
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
config: EMQuantifierConfig with model parameters.
|
|
73
|
+
rngs: Flax NNX random number generators.
|
|
74
|
+
name: Optional operator name.
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
```python
|
|
78
|
+
config = EMQuantifierConfig(n_transcripts=1000, n_iterations=10)
|
|
79
|
+
quantifier = DifferentiableEMQuantifier(config, rngs=nnx.Rngs(42))
|
|
80
|
+
data = {"compatibility": compat_matrix, "effective_lengths": eff_lens}
|
|
81
|
+
result, state, meta = quantifier.apply(data, {}, None)
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
config: EMQuantifierConfig,
|
|
88
|
+
*,
|
|
89
|
+
rngs: nnx.Rngs | None = None,
|
|
90
|
+
name: str | None = None,
|
|
91
|
+
):
|
|
92
|
+
"""Initialize the EM quantifier operator.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
config: EM quantifier configuration.
|
|
96
|
+
rngs: Random number generators for initialization.
|
|
97
|
+
name: Optional operator name.
|
|
98
|
+
"""
|
|
99
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
100
|
+
|
|
101
|
+
if rngs is None:
|
|
102
|
+
rngs = nnx.Rngs(0)
|
|
103
|
+
|
|
104
|
+
self.n_transcripts = config.n_transcripts
|
|
105
|
+
self.n_iterations = config.n_iterations
|
|
106
|
+
# Temperature is now managed by TemperatureOperator via self._temperature
|
|
107
|
+
|
|
108
|
+
# Initialize log abundances (will be normalized via softmax)
|
|
109
|
+
# Shape: (n_transcripts,)
|
|
110
|
+
key = rngs.params()
|
|
111
|
+
init_log_abundances = jax.random.normal(key, (config.n_transcripts,)) * 0.01
|
|
112
|
+
self.log_initial_abundances = nnx.Param(init_log_abundances)
|
|
113
|
+
|
|
114
|
+
def get_initial_abundances(self) -> Float[Array, "n_transcripts"]:
|
|
115
|
+
"""Get normalized initial abundances.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Initial abundance distribution (n_transcripts,), sums to 1.
|
|
119
|
+
"""
|
|
120
|
+
return jax.nn.softmax(self.log_initial_abundances[...])
|
|
121
|
+
|
|
122
|
+
def em_step(
|
|
123
|
+
self,
|
|
124
|
+
abundances: Float[Array, "n_transcripts"],
|
|
125
|
+
compatibility: Float[Array, "n_reads n_transcripts"],
|
|
126
|
+
effective_lengths: Float[Array, "n_transcripts"],
|
|
127
|
+
) -> Float[Array, "n_transcripts"]:
|
|
128
|
+
"""Perform one EM iteration.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
abundances: Current abundance estimates.
|
|
132
|
+
compatibility: Read-transcript compatibility matrix.
|
|
133
|
+
effective_lengths: Effective transcript lengths.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Updated abundance estimates.
|
|
137
|
+
"""
|
|
138
|
+
# E-step: Compute read assignment probabilities
|
|
139
|
+
# P(transcript | read) propto compatibility * abundance / length
|
|
140
|
+
rate = abundances / (effective_lengths + 1e-8) # (n_transcripts,)
|
|
141
|
+
|
|
142
|
+
# Score for each read-transcript pair
|
|
143
|
+
scores = compatibility * rate # (n_reads, n_transcripts)
|
|
144
|
+
|
|
145
|
+
# Normalize per read (softmax with temperature)
|
|
146
|
+
# Use inherited _temperature property from TemperatureOperator
|
|
147
|
+
weights = jax.nn.softmax(
|
|
148
|
+
jnp.log(scores + 1e-10) / self._temperature, axis=1
|
|
149
|
+
) # (n_reads, n_transcripts)
|
|
150
|
+
|
|
151
|
+
# M-step: Update abundances
|
|
152
|
+
# Expected count for each transcript
|
|
153
|
+
expected_counts = jnp.sum(weights, axis=0) # (n_transcripts,)
|
|
154
|
+
|
|
155
|
+
# Normalize by effective length
|
|
156
|
+
new_abundances = expected_counts / (effective_lengths + 1e-8)
|
|
157
|
+
|
|
158
|
+
# Normalize to sum to 1
|
|
159
|
+
new_abundances = new_abundances / (jnp.sum(new_abundances) + 1e-8)
|
|
160
|
+
|
|
161
|
+
return new_abundances
|
|
162
|
+
|
|
163
|
+
def quantify(
|
|
164
|
+
self,
|
|
165
|
+
compatibility: Float[Array, "n_reads n_transcripts"],
|
|
166
|
+
effective_lengths: Float[Array, "n_transcripts"],
|
|
167
|
+
) -> Float[Array, "n_transcripts"]:
|
|
168
|
+
"""Run EM algorithm for quantification.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
compatibility: Read-transcript compatibility matrix.
|
|
172
|
+
effective_lengths: Effective transcript lengths.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Final transcript abundance estimates.
|
|
176
|
+
"""
|
|
177
|
+
# Initialize abundances
|
|
178
|
+
abundances = self.get_initial_abundances()
|
|
179
|
+
|
|
180
|
+
# Run fixed number of EM iterations
|
|
181
|
+
def em_iteration(abundances, _):
|
|
182
|
+
new_abundances = self.em_step(abundances, compatibility, effective_lengths)
|
|
183
|
+
return new_abundances, None
|
|
184
|
+
|
|
185
|
+
final_abundances, _ = jax.lax.scan(em_iteration, abundances, None, length=self.n_iterations)
|
|
186
|
+
|
|
187
|
+
return final_abundances
|
|
188
|
+
|
|
189
|
+
def compute_tpm(
|
|
190
|
+
self,
|
|
191
|
+
abundances: Float[Array, "n_transcripts"],
|
|
192
|
+
effective_lengths: Float[Array, "n_transcripts"],
|
|
193
|
+
) -> Float[Array, "n_transcripts"]:
|
|
194
|
+
"""Convert abundances to TPM (Transcripts Per Million).
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
abundances: Normalized abundance estimates.
|
|
198
|
+
effective_lengths: Effective transcript lengths.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
TPM values (sum to 1 million).
|
|
202
|
+
"""
|
|
203
|
+
# TPM = (abundance / length) / sum(abundance / length) * 1e6
|
|
204
|
+
rate = abundances / (effective_lengths + 1e-8)
|
|
205
|
+
tpm = rate / (jnp.sum(rate) + 1e-8) * 1e6
|
|
206
|
+
return tpm
|
|
207
|
+
|
|
208
|
+
def apply(
|
|
209
|
+
self,
|
|
210
|
+
data: PyTree,
|
|
211
|
+
state: PyTree,
|
|
212
|
+
metadata: dict[str, Any] | None,
|
|
213
|
+
random_params: Any = None,
|
|
214
|
+
stats: dict[str, Any] | None = None,
|
|
215
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
216
|
+
"""Apply EM quantification to read assignment data.
|
|
217
|
+
|
|
218
|
+
This method runs the EM algorithm to estimate transcript
|
|
219
|
+
abundances from read-transcript compatibility data.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
data: Dictionary containing:
|
|
223
|
+
- "compatibility": Read-transcript compatibility matrix
|
|
224
|
+
(n_reads, n_transcripts)
|
|
225
|
+
- "effective_lengths": Effective transcript lengths
|
|
226
|
+
(n_transcripts,)
|
|
227
|
+
state: Element state (passed through unchanged)
|
|
228
|
+
metadata: Element metadata (passed through unchanged)
|
|
229
|
+
random_params: Not used (deterministic operator)
|
|
230
|
+
stats: Not used
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Tuple of (transformed_data, state, metadata):
|
|
234
|
+
- transformed_data contains:
|
|
235
|
+
|
|
236
|
+
- "compatibility": Original compatibility matrix
|
|
237
|
+
- "effective_lengths": Original effective lengths
|
|
238
|
+
- "abundances": Estimated transcript abundances
|
|
239
|
+
- "tpm": TPM (Transcripts Per Million) values
|
|
240
|
+
- state is passed through unchanged
|
|
241
|
+
- metadata is passed through unchanged
|
|
242
|
+
"""
|
|
243
|
+
compatibility = data["compatibility"]
|
|
244
|
+
effective_lengths = data["effective_lengths"]
|
|
245
|
+
|
|
246
|
+
# Run EM quantification
|
|
247
|
+
abundances = self.quantify(compatibility, effective_lengths)
|
|
248
|
+
|
|
249
|
+
# Compute TPM
|
|
250
|
+
tpm = self.compute_tpm(abundances, effective_lengths)
|
|
251
|
+
|
|
252
|
+
# Build output data
|
|
253
|
+
transformed_data = {
|
|
254
|
+
"compatibility": compatibility,
|
|
255
|
+
"effective_lengths": effective_lengths,
|
|
256
|
+
"abundances": abundances,
|
|
257
|
+
"tpm": tpm,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return transformed_data, state, metadata
|