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,274 @@
|
|
|
1
|
+
"""Soft Variant Quality Filter for VQSR-style recalibration.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable variant quality filter using
|
|
4
|
+
a Gaussian Mixture Model for scoring and sigmoid-based soft filtering.
|
|
5
|
+
|
|
6
|
+
Key technique: Differentiable GMM enables end-to-end learning of
|
|
7
|
+
quality distributions, with sigmoid thresholds maintaining gradients.
|
|
8
|
+
|
|
9
|
+
Applications: VQSR-style variant filtering, quality score recalibration.
|
|
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 import soft_ops
|
|
29
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class VariantQualityFilterConfig(OperatorConfig):
|
|
36
|
+
"""Configuration for SoftVariantQualityFilter.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
n_components: Number of GMM components.
|
|
40
|
+
n_features: Number of variant features.
|
|
41
|
+
threshold: Quality score threshold for filtering.
|
|
42
|
+
temperature: Temperature for softmax/sigmoid operations.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
n_components: int = 3
|
|
46
|
+
n_features: int = 4 # depth, qual, strand_bias, mapq
|
|
47
|
+
threshold: float = 0.5
|
|
48
|
+
temperature: float = 1.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SoftVariantQualityFilter(TemperatureOperator):
|
|
52
|
+
"""Differentiable variant quality filter using GMM.
|
|
53
|
+
|
|
54
|
+
This operator implements VQSR-style variant quality recalibration
|
|
55
|
+
using a learnable Gaussian Mixture Model. Variants are scored
|
|
56
|
+
by their likelihood under the GMM, and soft filtering is applied
|
|
57
|
+
via sigmoid thresholds.
|
|
58
|
+
|
|
59
|
+
Algorithm:
|
|
60
|
+
1. Compute GMM component responsibilities (E-step style)
|
|
61
|
+
2. Score variants by weighted log-likelihood
|
|
62
|
+
3. Apply sigmoid threshold for soft filtering
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
config: VariantQualityFilterConfig with model parameters.
|
|
66
|
+
rngs: Flax NNX random number generators.
|
|
67
|
+
name: Optional operator name.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
```python
|
|
71
|
+
config = VariantQualityFilterConfig(n_components=3)
|
|
72
|
+
filter_op = SoftVariantQualityFilter(config, rngs=nnx.Rngs(42))
|
|
73
|
+
data = {"variant_features": features} # (n_variants, n_features)
|
|
74
|
+
result, state, meta = filter_op.apply(data, {}, None)
|
|
75
|
+
```
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
config: VariantQualityFilterConfig,
|
|
81
|
+
*,
|
|
82
|
+
rngs: nnx.Rngs | None = None,
|
|
83
|
+
name: str | None = None,
|
|
84
|
+
):
|
|
85
|
+
"""Initialize the quality filter.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
config: Filter configuration.
|
|
89
|
+
rngs: Random number generators for initialization.
|
|
90
|
+
name: Optional operator name.
|
|
91
|
+
"""
|
|
92
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
93
|
+
|
|
94
|
+
if rngs is None:
|
|
95
|
+
rngs = nnx.Rngs(0)
|
|
96
|
+
|
|
97
|
+
self.n_components = config.n_components
|
|
98
|
+
self.n_features = config.n_features
|
|
99
|
+
self.threshold = config.threshold
|
|
100
|
+
# Temperature is managed by TemperatureOperator via self._temperature
|
|
101
|
+
|
|
102
|
+
# Initialize GMM parameters
|
|
103
|
+
# Component means: (n_components, n_features)
|
|
104
|
+
key = rngs.params()
|
|
105
|
+
init_means = jax.random.normal(key, (config.n_components, config.n_features)) * 0.5
|
|
106
|
+
self.means = nnx.Param(init_means)
|
|
107
|
+
|
|
108
|
+
# Component log variances (diagonal covariance): (n_components, n_features)
|
|
109
|
+
key = rngs.params()
|
|
110
|
+
init_log_var = jax.random.normal(key, (config.n_components, config.n_features)) * 0.1
|
|
111
|
+
self.log_variances = nnx.Param(init_log_var)
|
|
112
|
+
|
|
113
|
+
# Component mixing weights (unnormalized): (n_components,)
|
|
114
|
+
key = rngs.params()
|
|
115
|
+
noise = jax.random.normal(key, (config.n_components,)) * 0.1
|
|
116
|
+
init_weights = jnp.ones(config.n_components) + noise
|
|
117
|
+
self.log_mixing_weights = nnx.Param(init_weights)
|
|
118
|
+
|
|
119
|
+
# Learned quality score projection
|
|
120
|
+
self.quality_proj = nnx.Linear(config.n_features, 1, rngs=rngs)
|
|
121
|
+
|
|
122
|
+
def get_mixing_weights(self) -> Float[Array, "n_components"]:
|
|
123
|
+
"""Get normalized mixing weights.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Mixing weights summing to 1.
|
|
127
|
+
"""
|
|
128
|
+
return jax.nn.softmax(self.log_mixing_weights[...] / self._temperature)
|
|
129
|
+
|
|
130
|
+
def get_variances(self) -> Float[Array, "n_components n_features"]:
|
|
131
|
+
"""Get positive variances from log parameters.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Variance values.
|
|
135
|
+
"""
|
|
136
|
+
return jnp.exp(self.log_variances[...])
|
|
137
|
+
|
|
138
|
+
def compute_component_log_probs(
|
|
139
|
+
self,
|
|
140
|
+
features: Float[Array, "n_variants n_features"],
|
|
141
|
+
) -> Float[Array, "n_variants n_components"]:
|
|
142
|
+
"""Compute log probability under each GMM component.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
features: Variant feature vectors.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Log probability for each variant under each component.
|
|
149
|
+
"""
|
|
150
|
+
means = self.means[...] # (n_components, n_features)
|
|
151
|
+
variances = self.get_variances() # (n_components, n_features)
|
|
152
|
+
|
|
153
|
+
# Expand for broadcasting
|
|
154
|
+
# features: (n_variants, 1, n_features)
|
|
155
|
+
# means: (1, n_components, n_features)
|
|
156
|
+
features_exp = features[:, None, :]
|
|
157
|
+
means_exp = means[None, :, :]
|
|
158
|
+
variances_exp = variances[None, :, :]
|
|
159
|
+
|
|
160
|
+
# Gaussian log probability (diagonal covariance)
|
|
161
|
+
# log p(x | mu, sigma^2) = -0.5 * sum((x - mu)^2 / sigma^2 + log(2*pi*sigma^2))
|
|
162
|
+
diff_sq = (features_exp - means_exp) ** 2
|
|
163
|
+
log_prob = -0.5 * jnp.sum(
|
|
164
|
+
diff_sq / variances_exp + jnp.log(2 * jnp.pi * variances_exp), axis=-1
|
|
165
|
+
) # (n_variants, n_components)
|
|
166
|
+
|
|
167
|
+
return log_prob
|
|
168
|
+
|
|
169
|
+
def compute_responsibilities(
|
|
170
|
+
self,
|
|
171
|
+
features: Float[Array, "n_variants n_features"],
|
|
172
|
+
) -> Float[Array, "n_variants n_components"]:
|
|
173
|
+
"""Compute component responsibilities (soft assignments).
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
features: Variant feature vectors.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Responsibility matrix (probability each variant belongs to each component).
|
|
180
|
+
"""
|
|
181
|
+
log_probs = self.compute_component_log_probs(features) # (n_variants, n_components)
|
|
182
|
+
mixing_weights = self.get_mixing_weights() # (n_components,)
|
|
183
|
+
|
|
184
|
+
# log P(x, z) = log P(x | z) + log P(z)
|
|
185
|
+
log_joint = log_probs + jnp.log(mixing_weights + 1e-10)
|
|
186
|
+
|
|
187
|
+
# Responsibilities via softmax (with temperature)
|
|
188
|
+
responsibilities = jax.nn.softmax(log_joint / self._temperature, axis=-1)
|
|
189
|
+
|
|
190
|
+
return responsibilities
|
|
191
|
+
|
|
192
|
+
def compute_quality_scores(
|
|
193
|
+
self,
|
|
194
|
+
features: Float[Array, "n_variants n_features"],
|
|
195
|
+
) -> Float[Array, "n_variants"]:
|
|
196
|
+
"""Compute quality scores for variants.
|
|
197
|
+
|
|
198
|
+
Combines GMM likelihood with learned projection.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
features: Variant feature vectors.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Quality scores in [0, 1] range.
|
|
205
|
+
"""
|
|
206
|
+
# GMM-based score
|
|
207
|
+
log_probs = self.compute_component_log_probs(features) # (n_variants, n_components)
|
|
208
|
+
mixing_weights = self.get_mixing_weights() # (n_components,)
|
|
209
|
+
|
|
210
|
+
# Total log likelihood under mixture
|
|
211
|
+
log_likelihood = jax.scipy.special.logsumexp(
|
|
212
|
+
log_probs + jnp.log(mixing_weights + 1e-10), axis=-1
|
|
213
|
+
) # (n_variants,)
|
|
214
|
+
|
|
215
|
+
# Learned quality projection
|
|
216
|
+
learned_quality = self.quality_proj(features).squeeze(-1) # (n_variants,)
|
|
217
|
+
|
|
218
|
+
# Combine and normalize to [0, 1]
|
|
219
|
+
combined_score = log_likelihood + learned_quality
|
|
220
|
+
quality_scores = jax.nn.sigmoid(combined_score)
|
|
221
|
+
|
|
222
|
+
return quality_scores
|
|
223
|
+
|
|
224
|
+
def apply(
|
|
225
|
+
self,
|
|
226
|
+
data: PyTree,
|
|
227
|
+
state: PyTree,
|
|
228
|
+
metadata: dict[str, Any] | None,
|
|
229
|
+
random_params: Any = None,
|
|
230
|
+
stats: dict[str, Any] | None = None,
|
|
231
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
232
|
+
"""Apply quality filtering to variants.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
data: Dictionary containing:
|
|
236
|
+
- "variant_features": Feature vectors (n_variants, n_features)
|
|
237
|
+
state: Element state (passed through unchanged)
|
|
238
|
+
metadata: Element metadata (passed through unchanged)
|
|
239
|
+
random_params: Not used
|
|
240
|
+
stats: Not used
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Tuple of (transformed_data, state, metadata):
|
|
244
|
+
- transformed_data contains:
|
|
245
|
+
|
|
246
|
+
- "variant_features": Original features
|
|
247
|
+
- "quality_scores": Computed quality scores [0, 1]
|
|
248
|
+
- "filter_weights": Soft filter weights [0, 1]
|
|
249
|
+
- "component_probs": GMM component responsibilities
|
|
250
|
+
- state is passed through unchanged
|
|
251
|
+
- metadata is passed through unchanged
|
|
252
|
+
"""
|
|
253
|
+
features = data["variant_features"]
|
|
254
|
+
|
|
255
|
+
# Compute quality scores
|
|
256
|
+
quality_scores = self.compute_quality_scores(features)
|
|
257
|
+
|
|
258
|
+
# Soft filter weights using sigmoid threshold
|
|
259
|
+
filter_weights = soft_ops.greater(
|
|
260
|
+
quality_scores, self.threshold, softness=self._temperature
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Component responsibilities
|
|
264
|
+
component_probs = self.compute_responsibilities(features)
|
|
265
|
+
|
|
266
|
+
# Build output data
|
|
267
|
+
transformed_data = {
|
|
268
|
+
"variant_features": features,
|
|
269
|
+
"quality_scores": quality_scores,
|
|
270
|
+
"filter_weights": filter_weights,
|
|
271
|
+
"component_probs": component_probs,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Pre-built differentiable pipeline templates.
|
|
2
|
+
|
|
3
|
+
This module provides ready-to-use pipeline templates for common bioinformatics
|
|
4
|
+
workflows such as variant calling, preprocessing, differential expression,
|
|
5
|
+
and single-cell analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from diffbio.pipelines.differential_expression import (
|
|
9
|
+
DEPipelineConfig,
|
|
10
|
+
DifferentialExpressionPipeline,
|
|
11
|
+
)
|
|
12
|
+
from diffbio.pipelines.enhanced_variant_calling import (
|
|
13
|
+
EnhancedVariantCallingPipeline,
|
|
14
|
+
EnhancedVariantCallingPipelineConfig,
|
|
15
|
+
create_enhanced_variant_calling_pipeline,
|
|
16
|
+
)
|
|
17
|
+
from diffbio.pipelines.preprocessing import (
|
|
18
|
+
PreprocessingPipeline,
|
|
19
|
+
PreprocessingPipelineConfig,
|
|
20
|
+
create_preprocessing_pipeline,
|
|
21
|
+
)
|
|
22
|
+
from diffbio.pipelines.single_cell import (
|
|
23
|
+
SingleCellPipeline,
|
|
24
|
+
SingleCellPipelineConfig,
|
|
25
|
+
create_single_cell_pipeline,
|
|
26
|
+
)
|
|
27
|
+
from diffbio.pipelines.perturbation import (
|
|
28
|
+
PerturbationPipeline,
|
|
29
|
+
PerturbationPipelineConfig,
|
|
30
|
+
PerturbationPipelineResult,
|
|
31
|
+
)
|
|
32
|
+
from diffbio.pipelines.variant_calling import (
|
|
33
|
+
VariantCallingPipeline,
|
|
34
|
+
VariantCallingPipelineConfig,
|
|
35
|
+
create_cnn_variant_pipeline,
|
|
36
|
+
create_variant_calling_pipeline,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
# Differential Expression
|
|
42
|
+
"DEPipelineConfig",
|
|
43
|
+
"DifferentialExpressionPipeline",
|
|
44
|
+
# Enhanced Variant Calling
|
|
45
|
+
"EnhancedVariantCallingPipeline",
|
|
46
|
+
"EnhancedVariantCallingPipelineConfig",
|
|
47
|
+
"create_enhanced_variant_calling_pipeline",
|
|
48
|
+
# Preprocessing
|
|
49
|
+
"PreprocessingPipeline",
|
|
50
|
+
"PreprocessingPipelineConfig",
|
|
51
|
+
"create_preprocessing_pipeline",
|
|
52
|
+
# Single-Cell Analysis
|
|
53
|
+
"SingleCellPipeline",
|
|
54
|
+
"SingleCellPipelineConfig",
|
|
55
|
+
"create_single_cell_pipeline",
|
|
56
|
+
# Perturbation
|
|
57
|
+
"PerturbationPipeline",
|
|
58
|
+
"PerturbationPipelineConfig",
|
|
59
|
+
"PerturbationPipelineResult",
|
|
60
|
+
# Variant calling
|
|
61
|
+
"VariantCallingPipeline",
|
|
62
|
+
"VariantCallingPipelineConfig",
|
|
63
|
+
"create_variant_calling_pipeline",
|
|
64
|
+
"create_cnn_variant_pipeline",
|
|
65
|
+
]
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Differentiable differential expression analysis pipeline.
|
|
2
|
+
|
|
3
|
+
This module implements an end-to-end differentiable differential expression
|
|
4
|
+
pipeline inspired by DESeq2, with negative binomial modeling and size factor
|
|
5
|
+
normalization.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import flax.nnx as nnx
|
|
13
|
+
import jax
|
|
14
|
+
import jax.numpy as jnp
|
|
15
|
+
from datarax.core.config import OperatorConfig
|
|
16
|
+
from datarax.core.operator import OperatorModule
|
|
17
|
+
|
|
18
|
+
from diffbio.core import soft_ops
|
|
19
|
+
from diffbio.operators.statistical.nb_glm import DifferentiableNBGLM, NBGLMConfig
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class DEPipelineConfig(OperatorConfig):
|
|
26
|
+
"""Configuration for differential expression pipeline.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
n_genes: Number of genes to analyze.
|
|
30
|
+
n_conditions: Number of conditions (covariates) in design matrix.
|
|
31
|
+
alpha: Significance threshold for differential expression.
|
|
32
|
+
use_size_factors: Whether to compute and use size factors.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
n_genes: int = 1000
|
|
36
|
+
n_conditions: int = 2
|
|
37
|
+
alpha: float = 0.05
|
|
38
|
+
use_size_factors: bool = True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DifferentialExpressionPipeline(OperatorModule):
|
|
42
|
+
"""End-to-end differentiable differential expression analysis.
|
|
43
|
+
|
|
44
|
+
This pipeline implements a DESeq2-style analysis with:
|
|
45
|
+
1. Size factor normalization (median-of-ratios)
|
|
46
|
+
2. Negative binomial GLM fitting
|
|
47
|
+
3. Wald test for significance
|
|
48
|
+
4. Multiple testing correction (soft approximation)
|
|
49
|
+
|
|
50
|
+
All steps maintain gradient flow for end-to-end learning.
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
```python
|
|
54
|
+
config = DEPipelineConfig(
|
|
55
|
+
n_genes=5000,
|
|
56
|
+
n_conditions=2,
|
|
57
|
+
)
|
|
58
|
+
pipeline = DifferentialExpressionPipeline(config, rngs=rngs)
|
|
59
|
+
|
|
60
|
+
data = {
|
|
61
|
+
"counts": count_matrix, # (n_samples, n_genes)
|
|
62
|
+
"design": design_matrix, # (n_samples, n_conditions)
|
|
63
|
+
}
|
|
64
|
+
result, state, metadata = pipeline.apply(data, {}, None)
|
|
65
|
+
lfc = result["log_fold_change"]
|
|
66
|
+
pvals = result["p_values"]
|
|
67
|
+
significant = result["significant"]
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, config: DEPipelineConfig, *, rngs: nnx.Rngs | None = None):
|
|
72
|
+
"""Initialize the differential expression pipeline.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
config: Configuration for the pipeline.
|
|
76
|
+
rngs: Random number generators for initialization.
|
|
77
|
+
"""
|
|
78
|
+
super().__init__(config, rngs=rngs)
|
|
79
|
+
self.config = config
|
|
80
|
+
|
|
81
|
+
if rngs is None:
|
|
82
|
+
rngs = nnx.Rngs(0)
|
|
83
|
+
|
|
84
|
+
# Initialize the NB GLM
|
|
85
|
+
nb_config = NBGLMConfig(
|
|
86
|
+
n_features=config.n_genes,
|
|
87
|
+
n_covariates=config.n_conditions,
|
|
88
|
+
estimate_dispersion=True,
|
|
89
|
+
stream_name=config.stream_name,
|
|
90
|
+
)
|
|
91
|
+
self.nb_glm = DifferentiableNBGLM(nb_config, rngs=rngs)
|
|
92
|
+
|
|
93
|
+
def _compute_size_factors(self, counts: jax.Array) -> jax.Array:
|
|
94
|
+
"""Compute size factors using median-of-ratios method (DESeq2 style).
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
counts: Count matrix of shape (n_samples, n_genes).
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Size factors of shape (n_samples,).
|
|
101
|
+
"""
|
|
102
|
+
# Compute geometric mean per gene (reference sample)
|
|
103
|
+
# Add pseudocount for numerical stability
|
|
104
|
+
log_counts = jnp.log(counts + 1)
|
|
105
|
+
geo_mean_log = jnp.mean(log_counts, axis=0)
|
|
106
|
+
|
|
107
|
+
# Compute ratios to geometric mean
|
|
108
|
+
log_ratios = log_counts - geo_mean_log[None, :]
|
|
109
|
+
|
|
110
|
+
# Size factor = median of ratios for each sample
|
|
111
|
+
size_factors = jnp.exp(soft_ops.median(log_ratios, axis=1, softness=0.1))
|
|
112
|
+
|
|
113
|
+
# Normalize to have geometric mean of 1
|
|
114
|
+
size_factors = size_factors / jnp.exp(jnp.mean(jnp.log(size_factors + 1e-8)))
|
|
115
|
+
|
|
116
|
+
return size_factors
|
|
117
|
+
|
|
118
|
+
def _compute_wald_statistic(
|
|
119
|
+
self,
|
|
120
|
+
beta: jax.Array,
|
|
121
|
+
dispersion: jax.Array,
|
|
122
|
+
design: jax.Array,
|
|
123
|
+
size_factors: jax.Array,
|
|
124
|
+
) -> tuple[jax.Array, jax.Array]:
|
|
125
|
+
"""Compute Wald test statistics for differential expression.
|
|
126
|
+
|
|
127
|
+
The Wald statistic tests H0: beta[coef_idx] = 0.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
beta: Coefficient matrix of shape (n_conditions, n_genes).
|
|
131
|
+
dispersion: Dispersion parameters of shape (n_genes,).
|
|
132
|
+
design: Design matrix of shape (n_samples, n_conditions).
|
|
133
|
+
size_factors: Size factors of shape (n_samples,).
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Tuple of (wald_statistic, standard_error).
|
|
137
|
+
"""
|
|
138
|
+
# Compute predicted means
|
|
139
|
+
log_mu = jnp.dot(design, beta) # (n_samples, n_genes)
|
|
140
|
+
mu = jnp.exp(log_mu) * size_factors[:, None]
|
|
141
|
+
|
|
142
|
+
# Compute variance of NB distribution
|
|
143
|
+
# Var = mu + mu^2 / dispersion
|
|
144
|
+
variance = mu + jnp.square(mu) / (dispersion[None, :] + 1e-8)
|
|
145
|
+
|
|
146
|
+
# Fisher information for beta (approximate)
|
|
147
|
+
# I = X^T W X where W = diag(mu^2 / variance)
|
|
148
|
+
weights = jnp.square(mu) / (variance + 1e-8)
|
|
149
|
+
|
|
150
|
+
# Standard error of beta[1] (treatment effect)
|
|
151
|
+
# SE = sqrt(diag((X^T W X)^{-1}))
|
|
152
|
+
# Simplified: use diagonal approximation
|
|
153
|
+
|
|
154
|
+
# For the treatment coefficient (index 1), approximate SE
|
|
155
|
+
design_sq = jnp.square(design[:, 1:2]) # Treatment column
|
|
156
|
+
weighted_design = jnp.sum(weights * design_sq, axis=0)
|
|
157
|
+
se = 1.0 / jnp.sqrt(weighted_design + 1e-8)
|
|
158
|
+
|
|
159
|
+
# Wald statistic for treatment coefficient
|
|
160
|
+
wald_stat = beta[1, :] / (se + 1e-8)
|
|
161
|
+
|
|
162
|
+
return wald_stat, se
|
|
163
|
+
|
|
164
|
+
def _wald_to_pvalue(self, wald_stat: jax.Array) -> jax.Array:
|
|
165
|
+
"""Convert Wald statistic to p-value using soft normal CDF.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
wald_stat: Wald statistics of shape (n_genes,).
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Two-sided p-values of shape (n_genes,).
|
|
172
|
+
"""
|
|
173
|
+
# Two-sided p-value using standard normal
|
|
174
|
+
# p = 2 * (1 - Phi(|z|))
|
|
175
|
+
# Use jax.scipy.stats.norm.sf for survival function
|
|
176
|
+
|
|
177
|
+
# Soft approximation using sigmoid for differentiability
|
|
178
|
+
# Approximate normal CDF: Phi(x) ≈ sigmoid(1.7 * x)
|
|
179
|
+
abs_z = jnp.abs(wald_stat)
|
|
180
|
+
p_one_sided = 1.0 - jax.nn.sigmoid(1.7 * abs_z)
|
|
181
|
+
p_values = 2.0 * p_one_sided
|
|
182
|
+
|
|
183
|
+
# Clamp to [0, 1]
|
|
184
|
+
p_values = soft_ops.clip(p_values, 0.0, 1.0, softness=0.1)
|
|
185
|
+
|
|
186
|
+
return p_values
|
|
187
|
+
|
|
188
|
+
def _soft_significance(
|
|
189
|
+
self, p_values: jax.Array, alpha: float, temperature: float = 0.1
|
|
190
|
+
) -> jax.Array:
|
|
191
|
+
"""Compute soft significance indicator.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
p_values: P-values of shape (n_genes,).
|
|
195
|
+
alpha: Significance threshold.
|
|
196
|
+
temperature: Temperature for sigmoid smoothing.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Soft significance indicators of shape (n_genes,).
|
|
200
|
+
"""
|
|
201
|
+
# Soft thresholding: significant if p < alpha
|
|
202
|
+
return soft_ops.less(p_values, alpha, softness=temperature)
|
|
203
|
+
|
|
204
|
+
def apply(
|
|
205
|
+
self,
|
|
206
|
+
data: dict[str, Any],
|
|
207
|
+
state: dict[str, Any],
|
|
208
|
+
metadata: dict | None,
|
|
209
|
+
random_params: dict | None = None,
|
|
210
|
+
stats: dict | None = None,
|
|
211
|
+
) -> tuple[dict, dict, dict | None]:
|
|
212
|
+
"""Apply differential expression analysis.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
data: Dictionary containing:
|
|
216
|
+
- 'counts': Count matrix of shape (n_samples, n_genes)
|
|
217
|
+
- 'design': Design matrix of shape (n_samples, n_conditions)
|
|
218
|
+
state: Operator state dictionary.
|
|
219
|
+
metadata: Optional metadata dictionary.
|
|
220
|
+
random_params: Optional random parameters (unused).
|
|
221
|
+
stats: Optional statistics dictionary (unused).
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Tuple of (output_data, state, metadata) where output_data contains:
|
|
225
|
+
|
|
226
|
+
- 'counts': Original count matrix
|
|
227
|
+
- 'design': Original design matrix
|
|
228
|
+
- 'size_factors': Computed size factors
|
|
229
|
+
- 'predicted_mean': Predicted mean expression
|
|
230
|
+
- 'log_fold_change': Log2 fold change estimates
|
|
231
|
+
- 'wald_statistic': Wald test statistics
|
|
232
|
+
- 'standard_error': Standard errors
|
|
233
|
+
- 'p_values': P-values for differential expression
|
|
234
|
+
- 'significant': Soft significance indicators
|
|
235
|
+
"""
|
|
236
|
+
del random_params, stats # Unused
|
|
237
|
+
|
|
238
|
+
counts = data["counts"]
|
|
239
|
+
design = data["design"]
|
|
240
|
+
|
|
241
|
+
# Compute size factors
|
|
242
|
+
if self.config.use_size_factors:
|
|
243
|
+
size_factors = self._compute_size_factors(counts)
|
|
244
|
+
else:
|
|
245
|
+
size_factors = jnp.ones(counts.shape[0])
|
|
246
|
+
|
|
247
|
+
# Fit NB GLM for each sample
|
|
248
|
+
# The NB GLM expects single samples, so we process in batch
|
|
249
|
+
beta = self.nb_glm.beta[...]
|
|
250
|
+
dispersion = jnp.exp(self.nb_glm.log_dispersion[...])
|
|
251
|
+
|
|
252
|
+
# Compute predicted means
|
|
253
|
+
log_mu = jnp.dot(design, beta)
|
|
254
|
+
predicted_mean = jnp.exp(log_mu) * size_factors[:, None]
|
|
255
|
+
|
|
256
|
+
# Compute Wald statistics
|
|
257
|
+
wald_stat, se = self._compute_wald_statistic(beta, dispersion, design, size_factors)
|
|
258
|
+
|
|
259
|
+
# Convert to p-values
|
|
260
|
+
p_values = self._wald_to_pvalue(wald_stat)
|
|
261
|
+
|
|
262
|
+
# Compute log fold change (treatment coefficient in log2 scale)
|
|
263
|
+
log_fold_change = beta[1, :] / jnp.log(2)
|
|
264
|
+
|
|
265
|
+
# Soft significance
|
|
266
|
+
significant = self._soft_significance(p_values, self.config.alpha)
|
|
267
|
+
|
|
268
|
+
output_data = {
|
|
269
|
+
**data,
|
|
270
|
+
"size_factors": size_factors,
|
|
271
|
+
"predicted_mean": predicted_mean,
|
|
272
|
+
"log_fold_change": log_fold_change,
|
|
273
|
+
"wald_statistic": wald_stat,
|
|
274
|
+
"standard_error": se,
|
|
275
|
+
"p_values": p_values,
|
|
276
|
+
"significant": significant,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return output_data, state, metadata
|