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,277 @@
|
|
|
1
|
+
"""Optimal-transport trajectory inference between two single-cell timepoints.
|
|
2
|
+
|
|
3
|
+
This module implements Waddington-OT-style trajectory inference using
|
|
4
|
+
entropy-regularised optimal transport. Given gene-expression matrices at
|
|
5
|
+
two timepoints, the operator:
|
|
6
|
+
|
|
7
|
+
1. Computes a squared-Euclidean expression cost matrix between all cell
|
|
8
|
+
pairs across the two timepoints.
|
|
9
|
+
2. Solves for the transport plan via the shared ``SinkhornLayer`` from
|
|
10
|
+
``diffbio.core.optimal_transport`` (DRY).
|
|
11
|
+
3. Estimates per-cell growth rates from the transport-plan row sums.
|
|
12
|
+
4. Interpolates an intermediate cell distribution at a configurable time
|
|
13
|
+
between the two observations.
|
|
14
|
+
|
|
15
|
+
All operations are fully differentiable through JAX, enabling gradient-based
|
|
16
|
+
optimisation of upstream embeddings or transport parameters.
|
|
17
|
+
|
|
18
|
+
References:
|
|
19
|
+
Schiebinger et al., "Optimal-Transport Analysis of Single-Cell Gene
|
|
20
|
+
Expression Identifies Developmental Trajectories in Reprogramming",
|
|
21
|
+
Cell 2019.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
import jax.numpy as jnp
|
|
29
|
+
from datarax.core.config import OperatorConfig
|
|
30
|
+
from datarax.core.operator import OperatorModule
|
|
31
|
+
from flax import nnx
|
|
32
|
+
from jaxtyping import Array, Float, PyTree
|
|
33
|
+
|
|
34
|
+
from diffbio.constants import EPSILON
|
|
35
|
+
from diffbio.core.optimal_transport import SinkhornLayer
|
|
36
|
+
from diffbio.utils.nn_utils import ensure_rngs
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"OTTrajectoryConfig",
|
|
42
|
+
"DifferentiableOTTrajectory",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Configuration
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class OTTrajectoryConfig(OperatorConfig):
|
|
53
|
+
"""Configuration for OT-based trajectory inference.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
n_genes: Number of input genes per cell.
|
|
57
|
+
sinkhorn_epsilon: Entropy regularisation strength for the Sinkhorn
|
|
58
|
+
solver. Larger values produce smoother transport plans.
|
|
59
|
+
sinkhorn_iters: Number of Sinkhorn iterations.
|
|
60
|
+
growth_rate_regularization: Scaling factor applied to raw row-sums
|
|
61
|
+
before normalisation. Higher values amplify growth-rate variation.
|
|
62
|
+
interpolation_time: Fraction in (0, 1) at which to compute the
|
|
63
|
+
interpolated cell distribution between t1 and t2.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
n_genes: int = 200
|
|
67
|
+
sinkhorn_epsilon: float = 0.1
|
|
68
|
+
sinkhorn_iters: int = 100
|
|
69
|
+
growth_rate_regularization: float = 1.0
|
|
70
|
+
interpolation_time: float = 0.5
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Operator
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class DifferentiableOTTrajectory(OperatorModule):
|
|
79
|
+
"""Waddington-OT-style differentiable trajectory inference.
|
|
80
|
+
|
|
81
|
+
Computes an optimal-transport plan between cell populations at two
|
|
82
|
+
timepoints, estimates per-cell growth (proliferation) rates, and
|
|
83
|
+
interpolates an intermediate cell distribution.
|
|
84
|
+
|
|
85
|
+
Algorithm:
|
|
86
|
+
1. Build the squared-Euclidean cost matrix ``C[i,j] = ||x_i - y_j||^2``
|
|
87
|
+
between cells at t1 and t2.
|
|
88
|
+
2. Compute the entropy-regularised transport plan via ``SinkhornLayer``.
|
|
89
|
+
3. Derive growth rates from the transport plan: cells that transport to
|
|
90
|
+
more targets in t2 have higher proliferation. Normalise so that
|
|
91
|
+
``mean(growth_rates) == 1``.
|
|
92
|
+
4. Interpolate an intermediate distribution at time *s*:
|
|
93
|
+
``x_interp = (1-s) * x_t1 + s * (T @ x_t2) / T.sum(axis=1)``
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
config: OTTrajectoryConfig with operator parameters.
|
|
97
|
+
rngs: Flax NNX random number generators.
|
|
98
|
+
name: Optional operator name.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> config = OTTrajectoryConfig(n_genes=100, sinkhorn_iters=50)
|
|
102
|
+
>>> op = DifferentiableOTTrajectory(config)
|
|
103
|
+
>>> data = {
|
|
104
|
+
... "counts_t1": jnp.ones((20, 100)),
|
|
105
|
+
... "counts_t2": jnp.ones((25, 100)),
|
|
106
|
+
... }
|
|
107
|
+
>>> result, state, meta = op.apply(data, {}, None)
|
|
108
|
+
>>> result["transport_plan"].shape
|
|
109
|
+
(20, 25)
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
config: OTTrajectoryConfig,
|
|
115
|
+
*,
|
|
116
|
+
rngs: nnx.Rngs | None = None,
|
|
117
|
+
name: str | None = None,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Initialize the OT trajectory operator.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
config: OT trajectory configuration.
|
|
123
|
+
rngs: Random number generators (for API consistency).
|
|
124
|
+
name: Optional operator name.
|
|
125
|
+
"""
|
|
126
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
127
|
+
|
|
128
|
+
rngs = ensure_rngs(rngs)
|
|
129
|
+
|
|
130
|
+
self.sinkhorn = SinkhornLayer(
|
|
131
|
+
epsilon=config.sinkhorn_epsilon,
|
|
132
|
+
num_iters=config.sinkhorn_iters,
|
|
133
|
+
rngs=rngs,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# -- Internal helpers ---------------------------------------------------
|
|
137
|
+
|
|
138
|
+
def _compute_expression_cost(
|
|
139
|
+
self,
|
|
140
|
+
counts_t1: Float[Array, "n1 g"],
|
|
141
|
+
counts_t2: Float[Array, "n2 g"],
|
|
142
|
+
) -> Float[Array, "n1 n2"]:
|
|
143
|
+
"""Compute the squared-Euclidean expression cost matrix.
|
|
144
|
+
|
|
145
|
+
Uses the expansion ``||a - b||^2 = ||a||^2 + ||b||^2 - 2 a . b``
|
|
146
|
+
for efficiency.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
counts_t1: Expression matrix at timepoint 1.
|
|
150
|
+
counts_t2: Expression matrix at timepoint 2.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Cost matrix of shape ``(n1, n2)``.
|
|
154
|
+
"""
|
|
155
|
+
sq1 = jnp.sum(counts_t1**2, axis=-1, keepdims=True) # (n1, 1)
|
|
156
|
+
sq2 = jnp.sum(counts_t2**2, axis=-1) # (n2,)
|
|
157
|
+
dot = jnp.dot(counts_t1, counts_t2.T) # (n1, n2)
|
|
158
|
+
cost = sq1 + sq2 - 2.0 * dot
|
|
159
|
+
return jnp.maximum(cost, 0.0)
|
|
160
|
+
|
|
161
|
+
def _estimate_growth_rates(
|
|
162
|
+
self,
|
|
163
|
+
transport_plan: Float[Array, "n1 n2"],
|
|
164
|
+
) -> Float[Array, "n1"]:
|
|
165
|
+
"""Estimate per-cell growth rates from the transport plan.
|
|
166
|
+
|
|
167
|
+
Cells whose row in the transport plan sums to a larger value are
|
|
168
|
+
inferred to be proliferating (they contribute mass to more cells
|
|
169
|
+
in the next timepoint). The rates are normalised so that
|
|
170
|
+
``mean(growth_rates) == 1``.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
transport_plan: Optimal transport plan ``(n1, n2)``.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Normalised growth rates ``(n1,)``.
|
|
177
|
+
"""
|
|
178
|
+
raw_rates = jnp.sum(transport_plan, axis=1)
|
|
179
|
+
mean_rate = jnp.mean(raw_rates) + EPSILON
|
|
180
|
+
return raw_rates / mean_rate
|
|
181
|
+
|
|
182
|
+
def _interpolate_trajectory(
|
|
183
|
+
self,
|
|
184
|
+
counts_t1: Float[Array, "n1 g"],
|
|
185
|
+
counts_t2: Float[Array, "n2 g"],
|
|
186
|
+
transport_plan: Float[Array, "n1 n2"],
|
|
187
|
+
interpolation_time: float,
|
|
188
|
+
) -> Float[Array, "n1 g"]:
|
|
189
|
+
"""Interpolate cell states at an intermediate timepoint.
|
|
190
|
+
|
|
191
|
+
For each cell *i* in t1, the transported expression is the weighted
|
|
192
|
+
average of t2 cells according to the transport plan. The interpolated
|
|
193
|
+
state is a convex combination of the original t1 expression and the
|
|
194
|
+
transported expression.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
counts_t1: Expression at timepoint 1.
|
|
198
|
+
counts_t2: Expression at timepoint 2.
|
|
199
|
+
transport_plan: Optimal transport plan.
|
|
200
|
+
interpolation_time: Fraction *s* in (0, 1).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Interpolated expression matrix ``(n1, g)``.
|
|
204
|
+
"""
|
|
205
|
+
# Row-normalise the transport plan so each row sums to 1
|
|
206
|
+
row_sums = jnp.sum(transport_plan, axis=1, keepdims=True) + EPSILON
|
|
207
|
+
plan_normalised = transport_plan / row_sums
|
|
208
|
+
|
|
209
|
+
# Transported expression: weighted average of t2 cells
|
|
210
|
+
transported = plan_normalised @ counts_t2 # (n1, g)
|
|
211
|
+
|
|
212
|
+
# Convex interpolation
|
|
213
|
+
s = interpolation_time
|
|
214
|
+
return (1.0 - s) * counts_t1 + s * transported
|
|
215
|
+
|
|
216
|
+
# -- Public apply -------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
def apply(
|
|
219
|
+
self,
|
|
220
|
+
data: PyTree,
|
|
221
|
+
state: PyTree,
|
|
222
|
+
metadata: dict[str, Any] | None,
|
|
223
|
+
random_params: Any = None,
|
|
224
|
+
stats: dict[str, Any] | None = None,
|
|
225
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
226
|
+
"""Apply OT-based trajectory inference to two-timepoint expression data.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
data: Dictionary containing:
|
|
230
|
+
- ``"counts_t1"``: Expression matrix at timepoint 1 ``(n1, g)``
|
|
231
|
+
- ``"counts_t2"``: Expression matrix at timepoint 2 ``(n2, g)``
|
|
232
|
+
state: Element state (passed through unchanged).
|
|
233
|
+
metadata: Element metadata (passed through unchanged).
|
|
234
|
+
random_params: Not used (non-stochastic operator).
|
|
235
|
+
stats: Not used.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Tuple of (transformed_data, state, metadata):
|
|
239
|
+
- transformed_data contains all original keys plus:
|
|
240
|
+
|
|
241
|
+
- ``"transport_plan"``: OT plan ``(n1, n2)``
|
|
242
|
+
- ``"growth_rates"``: Per-cell growth rates ``(n1,)``
|
|
243
|
+
- ``"interpolated_counts"``: Interpolated expression
|
|
244
|
+
at the configured midpoint ``(n1, g)``
|
|
245
|
+
- state is passed through unchanged
|
|
246
|
+
- metadata is passed through unchanged
|
|
247
|
+
"""
|
|
248
|
+
counts_t1: Float[Array, "n1 g"] = data["counts_t1"]
|
|
249
|
+
counts_t2: Float[Array, "n2 g"] = data["counts_t2"]
|
|
250
|
+
|
|
251
|
+
n1 = counts_t1.shape[0]
|
|
252
|
+
n2 = counts_t2.shape[0]
|
|
253
|
+
|
|
254
|
+
# Step 1: Expression cost matrix
|
|
255
|
+
cost = self._compute_expression_cost(counts_t1, counts_t2)
|
|
256
|
+
|
|
257
|
+
# Step 2: Solve OT via Sinkhorn with uniform marginals
|
|
258
|
+
a = jnp.ones(n1) / n1
|
|
259
|
+
b = jnp.ones(n2) / n2
|
|
260
|
+
transport_plan = self.sinkhorn(cost, a, b)
|
|
261
|
+
|
|
262
|
+
# Step 3: Growth rates from row sums
|
|
263
|
+
growth_rates = self._estimate_growth_rates(transport_plan)
|
|
264
|
+
|
|
265
|
+
# Step 4: Interpolated expression at configured time
|
|
266
|
+
interpolated = self._interpolate_trajectory(
|
|
267
|
+
counts_t1, counts_t2, transport_plan, self.config.interpolation_time
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
transformed_data = {
|
|
271
|
+
**data,
|
|
272
|
+
"transport_plan": transport_plan,
|
|
273
|
+
"growth_rates": growth_rates,
|
|
274
|
+
"interpolated_counts": interpolated,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return transformed_data, state, metadata
|