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,234 @@
|
|
|
1
|
+
"""Differentiable Hidden Markov Model operator.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable implementation of the HMM
|
|
4
|
+
forward algorithm using logsumexp for numerical stability.
|
|
5
|
+
|
|
6
|
+
Key technique: Use logsumexp instead of direct probability multiplication
|
|
7
|
+
to maintain numerical stability and enable gradient flow.
|
|
8
|
+
|
|
9
|
+
Applications: Gene finding, chromatin state annotation, profile search.
|
|
10
|
+
|
|
11
|
+
Inherits from HMMOperator to get:
|
|
12
|
+
|
|
13
|
+
- forward_pass() for likelihood computation
|
|
14
|
+
- forward_backward_posteriors() for posterior computation
|
|
15
|
+
- get_log_transition_matrix(), get_log_emission_matrix(),
|
|
16
|
+
get_log_initial_distribution() for parameter access
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import jax
|
|
24
|
+
import jax.numpy as jnp
|
|
25
|
+
from datarax.core.config import OperatorConfig
|
|
26
|
+
from flax import nnx
|
|
27
|
+
from jaxtyping import Array, Float, Int, PyTree
|
|
28
|
+
|
|
29
|
+
from diffbio.core.base_operators import HMMOperator
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class HMMConfig(OperatorConfig):
|
|
36
|
+
"""Configuration for DifferentiableHMM.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
num_states: Number of hidden states.
|
|
40
|
+
num_emissions: Number of possible emissions (e.g., 4 for DNA).
|
|
41
|
+
temperature: Temperature for softmax operations.
|
|
42
|
+
learnable_transitions: Whether transition probabilities are learnable.
|
|
43
|
+
learnable_emissions: Whether emission probabilities are learnable.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
num_states: int = 3
|
|
47
|
+
num_emissions: int = 4
|
|
48
|
+
temperature: float = 1.0
|
|
49
|
+
learnable_transitions: bool = True
|
|
50
|
+
learnable_emissions: bool = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DifferentiableHMM(HMMOperator):
|
|
54
|
+
"""Differentiable Hidden Markov Model.
|
|
55
|
+
|
|
56
|
+
This operator implements the HMM forward algorithm with differentiable
|
|
57
|
+
operations, enabling gradient-based learning of transition and emission
|
|
58
|
+
parameters.
|
|
59
|
+
|
|
60
|
+
The forward algorithm computes P(observations | model) using dynamic
|
|
61
|
+
programming with logsumexp for numerical stability:
|
|
62
|
+
|
|
63
|
+
alpha[t, j] = sum_i(alpha[t-1, i] * A[i,j]) * B[j, o_t]
|
|
64
|
+
|
|
65
|
+
In log space:
|
|
66
|
+
log_alpha[t, j] = logsumexp_i(log_alpha[t-1, i] + log_A[i,j]) + log_B[j, o_t]
|
|
67
|
+
|
|
68
|
+
Inherits from HMMOperator to get:
|
|
69
|
+
|
|
70
|
+
- forward_pass() for likelihood computation
|
|
71
|
+
- forward_backward_posteriors() for posterior computation
|
|
72
|
+
- get_log_transition_matrix(), get_log_emission_matrix(),
|
|
73
|
+
get_log_initial_distribution() for parameter access
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
config: HMMConfig with model parameters.
|
|
77
|
+
rngs: Flax NNX random number generators.
|
|
78
|
+
name: Optional operator name.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
```python
|
|
82
|
+
config = HMMConfig(num_states=3, num_emissions=4)
|
|
83
|
+
hmm = DifferentiableHMM(config, rngs=nnx.Rngs(42))
|
|
84
|
+
data = {"observations": jnp.array([0, 1, 2, 3])}
|
|
85
|
+
result, state, meta = hmm.apply(data, {}, None)
|
|
86
|
+
```
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
config: HMMConfig,
|
|
92
|
+
*,
|
|
93
|
+
rngs: nnx.Rngs | None = None,
|
|
94
|
+
name: str | None = None,
|
|
95
|
+
):
|
|
96
|
+
"""Initialize the HMM operator.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
config: HMM configuration.
|
|
100
|
+
rngs: Random number generators for initialization.
|
|
101
|
+
name: Optional operator name.
|
|
102
|
+
"""
|
|
103
|
+
# HMMOperator handles parameter initialization
|
|
104
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
105
|
+
|
|
106
|
+
# get_log_transition_matrix() is inherited from HMMOperator
|
|
107
|
+
# get_log_emission_matrix() is inherited from HMMOperator
|
|
108
|
+
# get_log_initial_distribution() is inherited from HMMOperator
|
|
109
|
+
|
|
110
|
+
def forward(
|
|
111
|
+
self,
|
|
112
|
+
observations: Int[Array, "seq_len"],
|
|
113
|
+
) -> Float[Array, ""]:
|
|
114
|
+
"""Compute log probability of observations using forward algorithm.
|
|
115
|
+
|
|
116
|
+
Delegates to inherited forward_pass() from HMMOperator.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
observations: Integer-encoded observations (seq_len,).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Log probability of the observation sequence.
|
|
123
|
+
"""
|
|
124
|
+
return self.forward_pass(observations)
|
|
125
|
+
|
|
126
|
+
def forward_soft(
|
|
127
|
+
self,
|
|
128
|
+
observations: Float[Array, "seq_len n_emissions"],
|
|
129
|
+
) -> Float[Array, ""]:
|
|
130
|
+
"""Compute log probability with soft (probabilistic) observations.
|
|
131
|
+
|
|
132
|
+
This variant accepts soft observations (probability distributions
|
|
133
|
+
over emissions) for fully differentiable operation.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
observations: Soft observations (seq_len, n_emissions).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Log probability of the observation sequence.
|
|
140
|
+
"""
|
|
141
|
+
log_trans = self.get_log_transition_matrix()
|
|
142
|
+
log_emit = self.get_log_emission_matrix()
|
|
143
|
+
log_init = self.get_log_initial_distribution()
|
|
144
|
+
|
|
145
|
+
# Soft emission: sum over emissions weighted by observation probs
|
|
146
|
+
# log P(o_t | state) = logsumexp(log_emit + log(o_t))
|
|
147
|
+
def soft_emission(obs: Float[Array, "n_emissions"]) -> Float[Array, "n_states"]:
|
|
148
|
+
"""Compute log emission probability for soft observation vector."""
|
|
149
|
+
# obs is (n_emissions,), log_emit is (n_states, n_emissions)
|
|
150
|
+
log_obs = jnp.log(obs + 1e-10)
|
|
151
|
+
return jax.scipy.special.logsumexp(log_emit + log_obs, axis=1)
|
|
152
|
+
|
|
153
|
+
# Initialize
|
|
154
|
+
log_alpha = log_init + soft_emission(observations[0])
|
|
155
|
+
|
|
156
|
+
# Forward pass
|
|
157
|
+
def forward_step(
|
|
158
|
+
log_alpha: Float[Array, "n_states"], obs: Float[Array, "n_emissions"]
|
|
159
|
+
) -> tuple[Float[Array, "n_states"], None]:
|
|
160
|
+
"""Advance the forward variable by one time step."""
|
|
161
|
+
log_alpha_expanded = log_alpha[:, None]
|
|
162
|
+
log_alpha_new = jax.scipy.special.logsumexp(log_alpha_expanded + log_trans, axis=0)
|
|
163
|
+
log_alpha_new = log_alpha_new + soft_emission(obs)
|
|
164
|
+
return log_alpha_new, None
|
|
165
|
+
|
|
166
|
+
log_alpha, _ = jax.lax.scan(forward_step, log_alpha, observations[1:])
|
|
167
|
+
|
|
168
|
+
log_prob = jax.scipy.special.logsumexp(log_alpha)
|
|
169
|
+
|
|
170
|
+
return log_prob
|
|
171
|
+
|
|
172
|
+
def forward_backward(
|
|
173
|
+
self,
|
|
174
|
+
observations: Int[Array, "seq_len"],
|
|
175
|
+
) -> Float[Array, "seq_len num_states"]:
|
|
176
|
+
"""Compute state posteriors using forward-backward algorithm.
|
|
177
|
+
|
|
178
|
+
Delegates to inherited forward_backward_posteriors() from HMMOperator.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
observations: Integer-encoded observations.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
State posteriors P(state_t | observations) for each position.
|
|
185
|
+
"""
|
|
186
|
+
return self.forward_backward_posteriors(observations)
|
|
187
|
+
|
|
188
|
+
def apply(
|
|
189
|
+
self,
|
|
190
|
+
data: PyTree,
|
|
191
|
+
state: PyTree,
|
|
192
|
+
metadata: dict[str, Any] | None,
|
|
193
|
+
random_params: Any = None,
|
|
194
|
+
stats: dict[str, Any] | None = None,
|
|
195
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
196
|
+
"""Apply HMM to observation sequence.
|
|
197
|
+
|
|
198
|
+
This method computes the log-likelihood and state posteriors
|
|
199
|
+
for a given observation sequence.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
data: Dictionary containing:
|
|
203
|
+
- "observations": Integer-encoded observations (seq_len,)
|
|
204
|
+
state: Element state (passed through unchanged)
|
|
205
|
+
metadata: Element metadata (passed through unchanged)
|
|
206
|
+
random_params: Not used (deterministic operator)
|
|
207
|
+
stats: Not used
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Tuple of (transformed_data, state, metadata):
|
|
211
|
+
- transformed_data contains:
|
|
212
|
+
|
|
213
|
+
- "observations": Original observations
|
|
214
|
+
- "log_likelihood": Log probability of sequence
|
|
215
|
+
- "state_posteriors": P(state | observations) at each position
|
|
216
|
+
- state is passed through unchanged
|
|
217
|
+
- metadata is passed through unchanged
|
|
218
|
+
"""
|
|
219
|
+
observations = data["observations"]
|
|
220
|
+
|
|
221
|
+
# Compute log likelihood
|
|
222
|
+
log_likelihood = self.forward(observations)
|
|
223
|
+
|
|
224
|
+
# Compute state posteriors
|
|
225
|
+
state_posteriors = self.forward_backward(observations)
|
|
226
|
+
|
|
227
|
+
# Build output data
|
|
228
|
+
transformed_data = {
|
|
229
|
+
"observations": observations,
|
|
230
|
+
"log_likelihood": log_likelihood,
|
|
231
|
+
"state_posteriors": state_posteriors,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Differentiable Negative Binomial GLM operator.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable implementation of the negative
|
|
4
|
+
binomial generalized linear model for differential expression analysis,
|
|
5
|
+
inspired by DESeq2.
|
|
6
|
+
|
|
7
|
+
Key technique: Parameterize the NB mean through a log-linear model
|
|
8
|
+
and estimate dispersion parameters per gene.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import jax
|
|
16
|
+
import jax.numpy as jnp
|
|
17
|
+
from datarax.core.config import OperatorConfig
|
|
18
|
+
from datarax.core.operator import OperatorModule
|
|
19
|
+
from flax import nnx
|
|
20
|
+
from jaxtyping import Array, Float, PyTree
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class NBGLMConfig(OperatorConfig):
|
|
27
|
+
"""Configuration for DifferentiableNBGLM.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
n_features: Number of features (genes).
|
|
31
|
+
n_covariates: Number of covariates in design matrix.
|
|
32
|
+
estimate_dispersion: Whether to estimate dispersion parameters.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
n_features: int = 2000
|
|
36
|
+
n_covariates: int = 2
|
|
37
|
+
estimate_dispersion: bool = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DifferentiableNBGLM(OperatorModule):
|
|
41
|
+
"""Differentiable Negative Binomial GLM for differential expression.
|
|
42
|
+
|
|
43
|
+
This operator implements a negative binomial GLM where:
|
|
44
|
+
- log(mu) = X @ beta (design matrix @ coefficients)
|
|
45
|
+
- P(count | mu, dispersion) = NB(count; mu, dispersion)
|
|
46
|
+
|
|
47
|
+
Gradients flow through both the coefficient (beta) and dispersion
|
|
48
|
+
parameters, enabling end-to-end learning.
|
|
49
|
+
|
|
50
|
+
The negative binomial distribution is parameterized as:
|
|
51
|
+
- mean = mu
|
|
52
|
+
- variance = mu + mu^2 / dispersion
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
config: NBGLMConfig with model parameters.
|
|
56
|
+
rngs: Flax NNX random number generators.
|
|
57
|
+
name: Optional operator name.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
```python
|
|
61
|
+
config = NBGLMConfig(n_features=2000, n_covariates=2)
|
|
62
|
+
glm = DifferentiableNBGLM(config, rngs=nnx.Rngs(42))
|
|
63
|
+
data = {"counts": counts, "design": design_row, "size_factor": sf}
|
|
64
|
+
result, state, meta = glm.apply(data, {}, None)
|
|
65
|
+
```
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
config: NBGLMConfig,
|
|
71
|
+
*,
|
|
72
|
+
rngs: nnx.Rngs | None = None,
|
|
73
|
+
name: str | None = None,
|
|
74
|
+
):
|
|
75
|
+
"""Initialize the NB GLM operator.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config: NB GLM configuration.
|
|
79
|
+
rngs: Random number generators for initialization.
|
|
80
|
+
name: Optional operator name.
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
83
|
+
|
|
84
|
+
if rngs is None:
|
|
85
|
+
rngs = nnx.Rngs(0)
|
|
86
|
+
|
|
87
|
+
self.n_features = config.n_features
|
|
88
|
+
self.n_covariates = config.n_covariates
|
|
89
|
+
|
|
90
|
+
# Initialize coefficients (beta)
|
|
91
|
+
# Shape: (n_covariates, n_features)
|
|
92
|
+
key = rngs.params()
|
|
93
|
+
init_beta = jax.random.normal(key, (config.n_covariates, config.n_features)) * 0.1
|
|
94
|
+
self.beta = nnx.Param(init_beta)
|
|
95
|
+
|
|
96
|
+
# Initialize log dispersion parameters
|
|
97
|
+
# Shape: (n_features,)
|
|
98
|
+
# Start with dispersion = 1.0 (log_dispersion = 0)
|
|
99
|
+
self.log_dispersion = nnx.Param(jnp.zeros(config.n_features))
|
|
100
|
+
|
|
101
|
+
def get_coefficients(self) -> Float[Array, "n_covariates n_features"]:
|
|
102
|
+
"""Get coefficient matrix.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Coefficient matrix beta (n_covariates, n_features).
|
|
106
|
+
"""
|
|
107
|
+
return self.beta[...]
|
|
108
|
+
|
|
109
|
+
def get_dispersion(self) -> Float[Array, "n_features"]:
|
|
110
|
+
"""Get dispersion parameters.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dispersion parameters (n_features,), always positive.
|
|
114
|
+
"""
|
|
115
|
+
# Use softplus to ensure positivity
|
|
116
|
+
return jax.nn.softplus(self.log_dispersion[...]) + 1e-4
|
|
117
|
+
|
|
118
|
+
def predict_mean(
|
|
119
|
+
self,
|
|
120
|
+
design: Float[Array, "n_covariates"],
|
|
121
|
+
size_factor: Float[Array, ""],
|
|
122
|
+
) -> Float[Array, "n_features"]:
|
|
123
|
+
"""Predict mean expression for a sample.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
design: Design matrix row for this sample.
|
|
127
|
+
size_factor: Library size normalization factor.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Predicted mean expression (n_features,).
|
|
131
|
+
"""
|
|
132
|
+
beta = self.get_coefficients()
|
|
133
|
+
|
|
134
|
+
# log(mu) = design @ beta
|
|
135
|
+
# design is (n_covariates,), beta is (n_covariates, n_features)
|
|
136
|
+
log_mu = jnp.dot(design, beta)
|
|
137
|
+
|
|
138
|
+
# Add size factor effect
|
|
139
|
+
log_mu = log_mu + jnp.log(size_factor + 1e-8)
|
|
140
|
+
|
|
141
|
+
# Exponentiate to get mean
|
|
142
|
+
mu = jnp.exp(log_mu)
|
|
143
|
+
|
|
144
|
+
return mu
|
|
145
|
+
|
|
146
|
+
def negative_binomial_log_prob(
|
|
147
|
+
self,
|
|
148
|
+
counts: Float[Array, "n_features"],
|
|
149
|
+
design: Float[Array, "n_covariates"],
|
|
150
|
+
size_factor: Float[Array, ""],
|
|
151
|
+
) -> Float[Array, ""]:
|
|
152
|
+
"""Compute negative binomial log probability.
|
|
153
|
+
|
|
154
|
+
Uses the parameterization where:
|
|
155
|
+
- variance = mu + mu^2 / dispersion
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
counts: Observed counts.
|
|
159
|
+
design: Design matrix row.
|
|
160
|
+
size_factor: Size factor for normalization.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Total log probability (scalar).
|
|
164
|
+
"""
|
|
165
|
+
mu = self.predict_mean(design, size_factor)
|
|
166
|
+
dispersion = self.get_dispersion()
|
|
167
|
+
|
|
168
|
+
# Negative binomial log probability
|
|
169
|
+
# NB(k; mu, r) where r = dispersion
|
|
170
|
+
# log P(k) = log(Gamma(k + r)) - log(Gamma(k + 1)) - log(Gamma(r))
|
|
171
|
+
# + r * log(r / (r + mu)) + k * log(mu / (r + mu))
|
|
172
|
+
|
|
173
|
+
r = dispersion
|
|
174
|
+
k = counts
|
|
175
|
+
|
|
176
|
+
# Compute log probability using the NB PMF
|
|
177
|
+
# Use jax.scipy.special functions for numerical stability
|
|
178
|
+
log_prob = (
|
|
179
|
+
jax.scipy.special.gammaln(k + r)
|
|
180
|
+
- jax.scipy.special.gammaln(k + 1)
|
|
181
|
+
- jax.scipy.special.gammaln(r)
|
|
182
|
+
+ r * jnp.log(r / (r + mu + 1e-8))
|
|
183
|
+
+ k * jnp.log(mu / (r + mu + 1e-8) + 1e-8)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Sum over features
|
|
187
|
+
total_log_prob = jnp.sum(log_prob)
|
|
188
|
+
|
|
189
|
+
return total_log_prob
|
|
190
|
+
|
|
191
|
+
def batch_log_likelihood(
|
|
192
|
+
self,
|
|
193
|
+
counts: Float[Array, "n_samples n_features"],
|
|
194
|
+
design: Float[Array, "n_samples n_covariates"],
|
|
195
|
+
size_factors: Float[Array, "n_samples"],
|
|
196
|
+
) -> Float[Array, ""]:
|
|
197
|
+
"""Compute log likelihood for a batch of samples.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
counts: Count matrix (n_samples, n_features).
|
|
201
|
+
design: Design matrix (n_samples, n_covariates).
|
|
202
|
+
size_factors: Size factors (n_samples,).
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Total log likelihood.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def sample_log_prob(args):
|
|
209
|
+
c, d, s = args
|
|
210
|
+
return self.negative_binomial_log_prob(c, d, s)
|
|
211
|
+
|
|
212
|
+
log_probs = jax.vmap(sample_log_prob)((counts, design, size_factors))
|
|
213
|
+
return jnp.sum(log_probs)
|
|
214
|
+
|
|
215
|
+
def apply(
|
|
216
|
+
self,
|
|
217
|
+
data: PyTree,
|
|
218
|
+
state: PyTree,
|
|
219
|
+
metadata: dict[str, Any] | None,
|
|
220
|
+
random_params: Any = None,
|
|
221
|
+
stats: dict[str, Any] | None = None,
|
|
222
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
223
|
+
"""Apply NB GLM to count data.
|
|
224
|
+
|
|
225
|
+
This method computes the log likelihood and predicted mean
|
|
226
|
+
for a given sample.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
data: Dictionary containing:
|
|
230
|
+
- "counts": Gene counts (n_features,)
|
|
231
|
+
- "design": Design matrix row (n_covariates,)
|
|
232
|
+
- "size_factor": Library size factor (scalar)
|
|
233
|
+
state: Element state (passed through unchanged)
|
|
234
|
+
metadata: Element metadata (passed through unchanged)
|
|
235
|
+
random_params: Not used (deterministic operator)
|
|
236
|
+
stats: Not used
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Tuple of (transformed_data, state, metadata):
|
|
240
|
+
- transformed_data contains:
|
|
241
|
+
|
|
242
|
+
- "counts": Original counts
|
|
243
|
+
- "log_likelihood": Log probability of counts
|
|
244
|
+
- "predicted_mean": Predicted expression
|
|
245
|
+
- "dispersion": Dispersion parameters
|
|
246
|
+
- state is passed through unchanged
|
|
247
|
+
- metadata is passed through unchanged
|
|
248
|
+
"""
|
|
249
|
+
counts = data["counts"]
|
|
250
|
+
design = data["design"]
|
|
251
|
+
size_factor = data["size_factor"]
|
|
252
|
+
|
|
253
|
+
# Compute log likelihood
|
|
254
|
+
log_likelihood = self.negative_binomial_log_prob(counts, design, size_factor)
|
|
255
|
+
|
|
256
|
+
# Compute predicted mean
|
|
257
|
+
predicted_mean = self.predict_mean(design, size_factor)
|
|
258
|
+
|
|
259
|
+
# Get dispersion
|
|
260
|
+
dispersion = self.get_dispersion()
|
|
261
|
+
|
|
262
|
+
# Build output data
|
|
263
|
+
transformed_data = {
|
|
264
|
+
"counts": counts,
|
|
265
|
+
"design": design,
|
|
266
|
+
"size_factor": size_factor,
|
|
267
|
+
"log_likelihood": log_likelihood,
|
|
268
|
+
"predicted_mean": predicted_mean,
|
|
269
|
+
"dispersion": dispersion,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Variant calling operators for differentiable variant detection.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable components for variant calling:
|
|
4
|
+
- DifferentiablePileup: Generates pileup from aligned reads
|
|
5
|
+
- DeepVariantStylePileup: DeepVariant-style multi-channel pileup images
|
|
6
|
+
- VariantClassifier: MLP-based variant classifier
|
|
7
|
+
- CellTypeAwareVariantClassifier: Cell-type-weighted variant classifier
|
|
8
|
+
- CNNVariantClassifier: CNN-based pileup image classifier (DeepVariant-style)
|
|
9
|
+
- SoftVariantQualityFilter: GMM-based quality filtering (VQSR-style)
|
|
10
|
+
- DifferentiableCNVSegmentation: Attention-based CNV detection
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from diffbio.operators.variant.classifier import (
|
|
14
|
+
CellTypeAwareVariantClassifier,
|
|
15
|
+
CellTypeAwareVariantClassifierConfig,
|
|
16
|
+
VariantClassifier,
|
|
17
|
+
VariantClassifierConfig,
|
|
18
|
+
)
|
|
19
|
+
from diffbio.operators.variant.cnn_classifier import (
|
|
20
|
+
CNNVariantClassifier,
|
|
21
|
+
CNNVariantClassifierConfig,
|
|
22
|
+
)
|
|
23
|
+
from diffbio.operators.variant.cnv_segmentation import (
|
|
24
|
+
CNVSegmentationConfig,
|
|
25
|
+
DifferentiableCNVSegmentation,
|
|
26
|
+
EnhancedCNVSegmentation,
|
|
27
|
+
EnhancedCNVSegmentationConfig,
|
|
28
|
+
)
|
|
29
|
+
from diffbio.operators.variant.deepvariant_pileup import (
|
|
30
|
+
DeepVariantPileupConfig,
|
|
31
|
+
DeepVariantStylePileup,
|
|
32
|
+
)
|
|
33
|
+
from diffbio.operators.variant.pileup import DifferentiablePileup, PileupConfig
|
|
34
|
+
from diffbio.operators.variant.quality_recalibration import (
|
|
35
|
+
SoftVariantQualityFilter,
|
|
36
|
+
VariantQualityFilterConfig,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Pileup
|
|
41
|
+
"DifferentiablePileup",
|
|
42
|
+
"PileupConfig",
|
|
43
|
+
# DeepVariant-style Pileup
|
|
44
|
+
"DeepVariantStylePileup",
|
|
45
|
+
"DeepVariantPileupConfig",
|
|
46
|
+
# MLP Classifier
|
|
47
|
+
"VariantClassifier",
|
|
48
|
+
"VariantClassifierConfig",
|
|
49
|
+
# Cell-Type-Aware Classifier
|
|
50
|
+
"CellTypeAwareVariantClassifier",
|
|
51
|
+
"CellTypeAwareVariantClassifierConfig",
|
|
52
|
+
# CNN Classifier
|
|
53
|
+
"CNNVariantClassifier",
|
|
54
|
+
"CNNVariantClassifierConfig",
|
|
55
|
+
# Quality Filter
|
|
56
|
+
"SoftVariantQualityFilter",
|
|
57
|
+
"VariantQualityFilterConfig",
|
|
58
|
+
# CNV Segmentation
|
|
59
|
+
"CNVSegmentationConfig",
|
|
60
|
+
"DifferentiableCNVSegmentation",
|
|
61
|
+
# Enhanced CNV Segmentation
|
|
62
|
+
"EnhancedCNVSegmentation",
|
|
63
|
+
"EnhancedCNVSegmentationConfig",
|
|
64
|
+
]
|