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,251 @@
|
|
|
1
|
+
"""Differentiable motif discovery (MEME-style).
|
|
2
|
+
|
|
3
|
+
This module implements a differentiable version of motif discovery with
|
|
4
|
+
PWM (Position Weight Matrix) learning for end-to-end gradient flow.
|
|
5
|
+
|
|
6
|
+
Inherits from TemperatureOperator to get:
|
|
7
|
+
|
|
8
|
+
- _temperature property for temperature-controlled smoothing
|
|
9
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
10
|
+
- soft_argmax() for soft position selection
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import flax.nnx as nnx
|
|
18
|
+
import jax
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
from datarax.core.config import OperatorConfig
|
|
21
|
+
|
|
22
|
+
from diffbio.core import soft_ops
|
|
23
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class MotifDiscoveryConfig(OperatorConfig):
|
|
30
|
+
"""Configuration for differentiable motif discovery.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
motif_width: Width of the motif (number of positions).
|
|
34
|
+
num_motifs: Number of motifs to discover.
|
|
35
|
+
alphabet_size: Size of the sequence alphabet (4 for DNA).
|
|
36
|
+
temperature: Temperature for soft operations.
|
|
37
|
+
background_prior: Prior probability for background model.
|
|
38
|
+
stream_name: Name of the data stream to process.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
motif_width: int = 12
|
|
42
|
+
num_motifs: int = 1
|
|
43
|
+
alphabet_size: int = 4
|
|
44
|
+
temperature: float = 1.0
|
|
45
|
+
learnable_temperature: bool = True
|
|
46
|
+
background_prior: float = 0.25 # Uniform for DNA
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DifferentiableMotifDiscovery(TemperatureOperator):
|
|
50
|
+
"""Differentiable motif discovery with PWM learning.
|
|
51
|
+
|
|
52
|
+
This operator implements a simplified differentiable version of MEME-style
|
|
53
|
+
motif discovery. It learns Position Weight Matrices (PWMs) that represent
|
|
54
|
+
sequence motifs and scans sequences to find motif occurrences.
|
|
55
|
+
|
|
56
|
+
The motif score at position i is computed as:
|
|
57
|
+
score(i) = sum_j PWM[j, seq[i+j]]
|
|
58
|
+
|
|
59
|
+
For one-hot encoded sequences, this is equivalent to:
|
|
60
|
+
score(i) = sum_j sum_k seq[i+j, k] * log(PWM[j, k])
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
```python
|
|
64
|
+
config = MotifDiscoveryConfig(
|
|
65
|
+
motif_width=12,
|
|
66
|
+
num_motifs=3,
|
|
67
|
+
)
|
|
68
|
+
motif_op = DifferentiableMotifDiscovery(config, rngs=rngs)
|
|
69
|
+
|
|
70
|
+
data = {"sequence": one_hot_sequence} # (length, alphabet_size)
|
|
71
|
+
result, state, metadata = motif_op.apply(data, {}, None)
|
|
72
|
+
motif_scores = result["motif_scores"] # (num_positions, num_motifs)
|
|
73
|
+
pwm = result["pwm"] # (num_motifs, motif_width, alphabet_size)
|
|
74
|
+
```
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, config: MotifDiscoveryConfig, *, rngs: nnx.Rngs | None = None):
|
|
78
|
+
"""Initialize the motif discovery operator.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
config: Configuration for the operator.
|
|
82
|
+
rngs: Random number generators for initialization.
|
|
83
|
+
"""
|
|
84
|
+
super().__init__(config, rngs=rngs)
|
|
85
|
+
self.config = config
|
|
86
|
+
|
|
87
|
+
if rngs is None:
|
|
88
|
+
rngs = nnx.Rngs(0)
|
|
89
|
+
|
|
90
|
+
key = rngs.params() if hasattr(rngs, "params") else jax.random.key(0)
|
|
91
|
+
|
|
92
|
+
# Initialize PWM logits (before softmax normalization)
|
|
93
|
+
# Shape: (num_motifs, motif_width, alphabet_size)
|
|
94
|
+
# Initialize near uniform with small random noise
|
|
95
|
+
pwm_init = (
|
|
96
|
+
jax.random.normal(
|
|
97
|
+
key,
|
|
98
|
+
(config.num_motifs, config.motif_width, config.alphabet_size),
|
|
99
|
+
)
|
|
100
|
+
* 0.1
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.pwm_logits = nnx.Param(pwm_init)
|
|
104
|
+
|
|
105
|
+
# Temperature is managed by TemperatureOperator via self._temperature
|
|
106
|
+
|
|
107
|
+
def _get_pwm(self) -> jax.Array:
|
|
108
|
+
"""Get normalized PWM from logits.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
PWM of shape (num_motifs, motif_width, alphabet_size) with
|
|
112
|
+
probabilities summing to 1 over the alphabet dimension.
|
|
113
|
+
"""
|
|
114
|
+
temperature = jnp.abs(self._temperature) + 1e-6
|
|
115
|
+
return jax.nn.softmax(self.pwm_logits[...] / temperature, axis=-1)
|
|
116
|
+
|
|
117
|
+
def _scan_single_motif(self, sequence: jax.Array, pwm: jax.Array) -> jax.Array:
|
|
118
|
+
"""Scan a sequence with a single PWM using convolution.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
sequence: One-hot encoded sequence of shape (length, alphabet_size).
|
|
122
|
+
pwm: PWM of shape (motif_width, alphabet_size).
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Motif scores at each valid position, shape (num_positions,).
|
|
126
|
+
"""
|
|
127
|
+
motif_width = pwm.shape[0]
|
|
128
|
+
seq_length = sequence.shape[0]
|
|
129
|
+
num_positions = seq_length - motif_width + 1
|
|
130
|
+
|
|
131
|
+
# Use log-odds scoring
|
|
132
|
+
# log_pwm = log(PWM) - log(background)
|
|
133
|
+
background = self.config.background_prior
|
|
134
|
+
log_pwm = jnp.log(pwm + 1e-8) - jnp.log(background)
|
|
135
|
+
|
|
136
|
+
# Compute score at each position using sliding window
|
|
137
|
+
def score_at_position(start_idx):
|
|
138
|
+
window = jax.lax.dynamic_slice(
|
|
139
|
+
sequence, (start_idx, 0), (motif_width, self.config.alphabet_size)
|
|
140
|
+
)
|
|
141
|
+
# Score = sum of log-odds weighted by sequence
|
|
142
|
+
score = jnp.sum(window * log_pwm)
|
|
143
|
+
return score
|
|
144
|
+
|
|
145
|
+
positions = jnp.arange(num_positions)
|
|
146
|
+
scores = jax.vmap(score_at_position)(positions)
|
|
147
|
+
|
|
148
|
+
return scores
|
|
149
|
+
|
|
150
|
+
def _scan_sequence(self, sequence: jax.Array) -> jax.Array:
|
|
151
|
+
"""Scan a sequence with all motifs.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
sequence: One-hot encoded sequence of shape (length, alphabet_size).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Motif scores of shape (num_positions, num_motifs).
|
|
158
|
+
"""
|
|
159
|
+
pwm = self._get_pwm()
|
|
160
|
+
|
|
161
|
+
# Scan with each motif
|
|
162
|
+
def scan_with_motif(pwm_single):
|
|
163
|
+
return self._scan_single_motif(sequence, pwm_single)
|
|
164
|
+
|
|
165
|
+
# Shape: (num_motifs, num_positions)
|
|
166
|
+
all_scores = jax.vmap(scan_with_motif)(pwm)
|
|
167
|
+
|
|
168
|
+
# Transpose to (num_positions, num_motifs)
|
|
169
|
+
return all_scores.T
|
|
170
|
+
|
|
171
|
+
def _find_motif_positions(self, scores: jax.Array, threshold: float = 0.0) -> jax.Array:
|
|
172
|
+
"""Find soft motif positions based on scores.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
scores: Motif scores of shape (num_positions, num_motifs).
|
|
176
|
+
threshold: Score threshold for calling a motif hit.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Soft position indicators of shape (num_positions, num_motifs).
|
|
180
|
+
"""
|
|
181
|
+
temperature = jnp.abs(self._temperature) + 1e-6
|
|
182
|
+
return soft_ops.greater(scores, threshold, softness=temperature)
|
|
183
|
+
|
|
184
|
+
def _apply_single(self, sequence: jax.Array) -> dict:
|
|
185
|
+
"""Apply motif discovery to a single sequence.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
sequence: One-hot encoded sequence of shape (length, alphabet_size).
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dictionary with motif scores, positions, and PWM.
|
|
192
|
+
"""
|
|
193
|
+
# Get current PWM
|
|
194
|
+
pwm = self._get_pwm()
|
|
195
|
+
|
|
196
|
+
# Scan sequence
|
|
197
|
+
motif_scores = self._scan_sequence(sequence)
|
|
198
|
+
|
|
199
|
+
# Find soft motif positions
|
|
200
|
+
motif_positions = self._find_motif_positions(motif_scores)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"motif_scores": motif_scores,
|
|
204
|
+
"motif_positions": motif_positions,
|
|
205
|
+
"pwm": pwm,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
def apply(
|
|
209
|
+
self,
|
|
210
|
+
data: dict[str, Any],
|
|
211
|
+
state: dict[str, Any],
|
|
212
|
+
metadata: dict | None,
|
|
213
|
+
random_params: dict | None = None,
|
|
214
|
+
stats: dict | None = None,
|
|
215
|
+
) -> tuple[dict, dict, dict | None]:
|
|
216
|
+
"""Apply motif discovery to sequence data.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
data: Dictionary containing:
|
|
220
|
+
- 'sequence': One-hot encoded sequence(s) of shape
|
|
221
|
+
(length, alphabet_size) or (batch, length, alphabet_size)
|
|
222
|
+
state: Operator state dictionary.
|
|
223
|
+
metadata: Optional metadata dictionary.
|
|
224
|
+
random_params: Optional random parameters (unused).
|
|
225
|
+
stats: Optional statistics dictionary (unused).
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Tuple of (output_data, state, metadata) where output_data contains:
|
|
229
|
+
|
|
230
|
+
- 'sequence': Original sequence data
|
|
231
|
+
- 'motif_scores': Log-odds scores at each position
|
|
232
|
+
- 'motif_positions': Soft motif occurrence indicators
|
|
233
|
+
- 'pwm': Current Position Weight Matrix
|
|
234
|
+
"""
|
|
235
|
+
del random_params, stats # Unused
|
|
236
|
+
|
|
237
|
+
sequence = data["sequence"]
|
|
238
|
+
|
|
239
|
+
# Handle single vs batched input
|
|
240
|
+
single_input = sequence.ndim == 2
|
|
241
|
+
if single_input:
|
|
242
|
+
result = self._apply_single(sequence)
|
|
243
|
+
else:
|
|
244
|
+
# Batched input - vmap over batch dimension
|
|
245
|
+
result = jax.vmap(self._apply_single)(sequence)
|
|
246
|
+
# PWM is shared, take from first (they're all the same)
|
|
247
|
+
result["pwm"] = self._get_pwm()
|
|
248
|
+
|
|
249
|
+
output_data = {**data, **result}
|
|
250
|
+
|
|
251
|
+
return output_data, state, metadata
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Differentiable splicing PSI (Percent Spliced In) calculation.
|
|
2
|
+
|
|
3
|
+
This module implements a differentiable PSI calculation operator for
|
|
4
|
+
alternative splicing analysis with end-to-end gradient flow.
|
|
5
|
+
|
|
6
|
+
Inherits from TemperatureOperator to get:
|
|
7
|
+
|
|
8
|
+
- _temperature property for temperature-controlled smoothing
|
|
9
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
10
|
+
- soft_argmax() for soft position selection
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import flax.nnx as nnx
|
|
18
|
+
import jax
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
from datarax.core.config import OperatorConfig
|
|
21
|
+
|
|
22
|
+
from diffbio.core import soft_ops
|
|
23
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class SplicingPSIConfig(OperatorConfig):
|
|
30
|
+
"""Configuration for differentiable PSI calculation.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
pseudocount: Pseudocount added for numerical stability and regularization.
|
|
34
|
+
temperature: Temperature for confidence calculation.
|
|
35
|
+
min_total_reads: Minimum total reads for reliable PSI estimation.
|
|
36
|
+
stream_name: Name of the data stream to process.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
pseudocount: float = 1.0
|
|
40
|
+
temperature: float = 1.0
|
|
41
|
+
learnable_temperature: bool = True
|
|
42
|
+
min_total_reads: int = 10
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SplicingPSI(TemperatureOperator):
|
|
46
|
+
"""Differentiable PSI calculation for alternative splicing analysis.
|
|
47
|
+
|
|
48
|
+
PSI (Percent Spliced In) quantifies alternative splicing by computing
|
|
49
|
+
the fraction of transcripts that include a specific exon or splice site.
|
|
50
|
+
|
|
51
|
+
The standard PSI formula is:
|
|
52
|
+
PSI = inclusion_reads / (inclusion_reads + exclusion_reads)
|
|
53
|
+
|
|
54
|
+
This operator adds:
|
|
55
|
+
- Learnable pseudocount for regularization
|
|
56
|
+
- Confidence estimation based on read coverage
|
|
57
|
+
- Full differentiability for end-to-end training
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
```python
|
|
61
|
+
config = SplicingPSIConfig(
|
|
62
|
+
pseudocount=1.0,
|
|
63
|
+
min_total_reads=10,
|
|
64
|
+
)
|
|
65
|
+
psi_op = SplicingPSI(config, rngs=rngs)
|
|
66
|
+
|
|
67
|
+
data = {
|
|
68
|
+
"inclusion_counts": inclusion_reads, # Junction reads supporting inclusion
|
|
69
|
+
"exclusion_counts": exclusion_reads, # Junction reads supporting exclusion
|
|
70
|
+
}
|
|
71
|
+
result, state, metadata = psi_op.apply(data, {}, None)
|
|
72
|
+
psi_values = result["psi"]
|
|
73
|
+
confidence = result["psi_confidence"]
|
|
74
|
+
```
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, config: SplicingPSIConfig, *, rngs: nnx.Rngs | None = None):
|
|
78
|
+
"""Initialize the PSI operator.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
config: Configuration for the operator.
|
|
82
|
+
rngs: Random number generators for initialization.
|
|
83
|
+
"""
|
|
84
|
+
super().__init__(config, rngs=rngs)
|
|
85
|
+
self.config = config
|
|
86
|
+
|
|
87
|
+
# Learnable pseudocount (must be positive)
|
|
88
|
+
self.pseudocount = nnx.Param(jnp.array(config.pseudocount))
|
|
89
|
+
|
|
90
|
+
# Temperature is managed by TemperatureOperator via self._temperature
|
|
91
|
+
|
|
92
|
+
def _compute_psi(
|
|
93
|
+
self, inclusion: jax.Array, exclusion: jax.Array, pseudocount: jax.Array | float
|
|
94
|
+
) -> jax.Array:
|
|
95
|
+
"""Compute PSI with pseudocount for numerical stability.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
inclusion: Inclusion junction read counts.
|
|
99
|
+
exclusion: Exclusion junction read counts.
|
|
100
|
+
pseudocount: Pseudocount for regularization.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
PSI values in [0, 1].
|
|
104
|
+
"""
|
|
105
|
+
# Add pseudocount to both numerator and denominator terms
|
|
106
|
+
inc_adj = inclusion + pseudocount
|
|
107
|
+
exc_adj = exclusion + pseudocount
|
|
108
|
+
|
|
109
|
+
# PSI = inclusion / (inclusion + exclusion)
|
|
110
|
+
psi = inc_adj / (inc_adj + exc_adj)
|
|
111
|
+
|
|
112
|
+
return psi
|
|
113
|
+
|
|
114
|
+
def _compute_confidence(
|
|
115
|
+
self, inclusion: jax.Array, exclusion: jax.Array, temperature: jax.Array | float
|
|
116
|
+
) -> jax.Array:
|
|
117
|
+
"""Compute confidence in PSI estimate based on read coverage.
|
|
118
|
+
|
|
119
|
+
Higher total reads = higher confidence in the PSI estimate.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
inclusion: Inclusion junction read counts.
|
|
123
|
+
exclusion: Exclusion junction read counts.
|
|
124
|
+
temperature: Temperature for sigmoid scaling.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Confidence values in [0, 1].
|
|
128
|
+
"""
|
|
129
|
+
total_reads = inclusion + exclusion
|
|
130
|
+
min_reads = self.config.min_total_reads
|
|
131
|
+
|
|
132
|
+
# Sigmoid-based confidence: approaches 1 as reads increase
|
|
133
|
+
# Centered around min_total_reads
|
|
134
|
+
confidence = soft_ops.greater(
|
|
135
|
+
total_reads, min_reads, softness=temperature * min_reads + 1e-6
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return confidence
|
|
139
|
+
|
|
140
|
+
def _compute_delta_psi_variance(
|
|
141
|
+
self, inclusion: jax.Array, exclusion: jax.Array, psi: jax.Array
|
|
142
|
+
) -> jax.Array:
|
|
143
|
+
"""Compute variance of PSI estimate using beta-binomial model.
|
|
144
|
+
|
|
145
|
+
This approximates the variance of PSI under a binomial model,
|
|
146
|
+
which can be used for significance testing.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
inclusion: Inclusion junction read counts.
|
|
150
|
+
exclusion: Exclusion junction read counts.
|
|
151
|
+
psi: Computed PSI values.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Variance estimates for each PSI value.
|
|
155
|
+
"""
|
|
156
|
+
total = inclusion + exclusion + 2 * jnp.abs(self.pseudocount[...])
|
|
157
|
+
|
|
158
|
+
# Variance of beta distribution: psi * (1 - psi) / (n + 1)
|
|
159
|
+
variance = (psi * (1 - psi)) / (total + 1)
|
|
160
|
+
|
|
161
|
+
return variance
|
|
162
|
+
|
|
163
|
+
def apply(
|
|
164
|
+
self,
|
|
165
|
+
data: dict[str, Any],
|
|
166
|
+
state: dict[str, Any],
|
|
167
|
+
metadata: dict | None,
|
|
168
|
+
random_params: dict | None = None,
|
|
169
|
+
stats: dict | None = None,
|
|
170
|
+
) -> tuple[dict, dict, dict | None]:
|
|
171
|
+
"""Apply PSI calculation to junction read counts.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
data: Dictionary containing:
|
|
175
|
+
- 'inclusion_counts': Reads supporting exon inclusion
|
|
176
|
+
- 'exclusion_counts': Reads supporting exon exclusion
|
|
177
|
+
state: Operator state dictionary.
|
|
178
|
+
metadata: Optional metadata dictionary.
|
|
179
|
+
random_params: Optional random parameters (unused).
|
|
180
|
+
stats: Optional statistics dictionary (unused).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Tuple of (output_data, state, metadata) where output_data contains:
|
|
184
|
+
|
|
185
|
+
- 'inclusion_counts': Original inclusion counts
|
|
186
|
+
- 'exclusion_counts': Original exclusion counts
|
|
187
|
+
- 'psi': Computed PSI values
|
|
188
|
+
- 'psi_confidence': Confidence in PSI estimates
|
|
189
|
+
- 'psi_variance': Variance of PSI estimates
|
|
190
|
+
"""
|
|
191
|
+
del random_params, stats # Unused
|
|
192
|
+
|
|
193
|
+
inclusion = data["inclusion_counts"]
|
|
194
|
+
exclusion = data["exclusion_counts"]
|
|
195
|
+
|
|
196
|
+
# Get learnable parameters (ensure positive)
|
|
197
|
+
pseudocount = jnp.abs(self.pseudocount[...]) + 1e-6
|
|
198
|
+
temperature = jnp.abs(self._temperature) + 1e-6
|
|
199
|
+
|
|
200
|
+
# Compute PSI
|
|
201
|
+
psi = self._compute_psi(inclusion, exclusion, pseudocount)
|
|
202
|
+
|
|
203
|
+
# Compute confidence
|
|
204
|
+
confidence = self._compute_confidence(inclusion, exclusion, temperature)
|
|
205
|
+
|
|
206
|
+
# Compute variance for significance testing
|
|
207
|
+
variance = self._compute_delta_psi_variance(inclusion, exclusion, psi)
|
|
208
|
+
|
|
209
|
+
output_data = {
|
|
210
|
+
**data,
|
|
211
|
+
"psi": psi,
|
|
212
|
+
"psi_confidence": confidence,
|
|
213
|
+
"psi_variance": variance,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return output_data, state, metadata
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Single-cell analysis operators for differentiable scRNA-seq processing.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable components for single-cell analysis:
|
|
4
|
+
- DifferentiableAmbientRemoval: CellBender-style ambient RNA removal
|
|
5
|
+
- DifferentiableCellAnnotator: Cell type annotation (celltypist/cellassign/scanvi)
|
|
6
|
+
- DifferentiableDiffusionImputer: MAGIC-style diffusion imputation
|
|
7
|
+
- DifferentiableDoubletScorer: Scrublet-style doublet detection
|
|
8
|
+
- DifferentiableCellCommunication: GNN-based cell-cell communication analysis
|
|
9
|
+
- DifferentiableGRN: GATv2-based gene regulatory network inference
|
|
10
|
+
- DifferentiableLigandReceptor: Ligand-receptor co-expression scoring
|
|
11
|
+
- SoftKMeansClustering: Differentiable soft k-means clustering
|
|
12
|
+
- DifferentiableHarmony: Harmony-style batch correction
|
|
13
|
+
- DifferentiableMMDBatchCorrection: MMD-regularised autoencoder batch correction
|
|
14
|
+
- DifferentiableWGANBatchCorrection: Adversarial (WGAN) batch correction
|
|
15
|
+
- DifferentiableSwitchDE: Sigmoidal switch differential expression
|
|
16
|
+
- DifferentiableVelocity: RNA velocity via Neural ODEs
|
|
17
|
+
- DifferentiablePseudotime: Diffusion-map pseudotime ordering
|
|
18
|
+
- DifferentiableFateProbability: Absorption-based fate estimation
|
|
19
|
+
- DifferentiableSpatialDomain: STAGATE-style spatial domain identification
|
|
20
|
+
- DifferentiablePASTEAlignment: PASTE-style spatial slice alignment
|
|
21
|
+
- DifferentiableDifferentialDistribution: scDD-style differential distribution testing
|
|
22
|
+
- DifferentiableSimulator: Splatter-style single-cell count simulation
|
|
23
|
+
- DifferentiableArchetypalAnalysis: PCHA-style archetypal analysis
|
|
24
|
+
- DifferentiableOTTrajectory: Waddington-OT-style optimal transport trajectory
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from diffbio.operators.singlecell.archetypes import (
|
|
28
|
+
ArchetypalAnalysisConfig,
|
|
29
|
+
DifferentiableArchetypalAnalysis,
|
|
30
|
+
)
|
|
31
|
+
from diffbio.operators.singlecell.ambient_removal import (
|
|
32
|
+
AmbientRemovalConfig,
|
|
33
|
+
DifferentiableAmbientRemoval,
|
|
34
|
+
)
|
|
35
|
+
from diffbio.operators.singlecell.cell_annotation import (
|
|
36
|
+
CellAnnotatorConfig,
|
|
37
|
+
DifferentiableCellAnnotator,
|
|
38
|
+
)
|
|
39
|
+
from diffbio.operators.singlecell.communication import (
|
|
40
|
+
CellCommunicationConfig,
|
|
41
|
+
DifferentiableCellCommunication,
|
|
42
|
+
DifferentiableLigandReceptor,
|
|
43
|
+
LRScoringConfig,
|
|
44
|
+
)
|
|
45
|
+
from diffbio.operators.singlecell.batch_correction import (
|
|
46
|
+
BatchCorrectionConfig,
|
|
47
|
+
DifferentiableHarmony,
|
|
48
|
+
)
|
|
49
|
+
from diffbio.operators.singlecell.enhanced_batch_correction import (
|
|
50
|
+
DifferentiableMMDBatchCorrection,
|
|
51
|
+
DifferentiableWGANBatchCorrection,
|
|
52
|
+
MMDBatchCorrectionConfig,
|
|
53
|
+
WGANBatchCorrectionConfig,
|
|
54
|
+
)
|
|
55
|
+
from diffbio.operators.singlecell.grn_inference import (
|
|
56
|
+
DifferentiableGRN,
|
|
57
|
+
GRNInferenceConfig,
|
|
58
|
+
)
|
|
59
|
+
from diffbio.operators.singlecell.doublet_detection import (
|
|
60
|
+
DifferentiableDoubletScorer,
|
|
61
|
+
DifferentiableSoloDetector,
|
|
62
|
+
DoubletScorerConfig,
|
|
63
|
+
SoloDetectorConfig,
|
|
64
|
+
)
|
|
65
|
+
from diffbio.operators.singlecell.imputation import (
|
|
66
|
+
DifferentiableDiffusionImputer,
|
|
67
|
+
DifferentiableTransformerDenoiser,
|
|
68
|
+
DiffusionImputerConfig,
|
|
69
|
+
TransformerDenoiserConfig,
|
|
70
|
+
)
|
|
71
|
+
from diffbio.operators.singlecell.ot_trajectory import (
|
|
72
|
+
DifferentiableOTTrajectory,
|
|
73
|
+
OTTrajectoryConfig,
|
|
74
|
+
)
|
|
75
|
+
from diffbio.operators.singlecell.soft_clustering import (
|
|
76
|
+
SoftClusteringConfig,
|
|
77
|
+
SoftKMeansClustering,
|
|
78
|
+
)
|
|
79
|
+
from diffbio.operators.singlecell.switch_de import (
|
|
80
|
+
DifferentiableSwitchDE,
|
|
81
|
+
SwitchDEConfig,
|
|
82
|
+
)
|
|
83
|
+
from diffbio.operators.singlecell.trajectory import (
|
|
84
|
+
DifferentiableFateProbability,
|
|
85
|
+
DifferentiablePseudotime,
|
|
86
|
+
FateProbabilityConfig,
|
|
87
|
+
PseudotimeConfig,
|
|
88
|
+
)
|
|
89
|
+
from diffbio.operators.singlecell.spatial_domains import (
|
|
90
|
+
DifferentiablePASTEAlignment,
|
|
91
|
+
DifferentiableSpatialDomain,
|
|
92
|
+
PASTEAlignmentConfig,
|
|
93
|
+
SpatialDomainConfig,
|
|
94
|
+
)
|
|
95
|
+
from diffbio.operators.singlecell.differential_distribution import (
|
|
96
|
+
DifferentiableDifferentialDistribution,
|
|
97
|
+
DifferentialDistributionConfig,
|
|
98
|
+
)
|
|
99
|
+
from diffbio.operators.singlecell.simulation import (
|
|
100
|
+
DifferentiableSimulator,
|
|
101
|
+
SimulationConfig,
|
|
102
|
+
)
|
|
103
|
+
from diffbio.operators.singlecell.velocity import (
|
|
104
|
+
DifferentiableVelocity,
|
|
105
|
+
VelocityConfig,
|
|
106
|
+
)
|
|
107
|
+
from diffbio.operators.singlecell.downsampling import (
|
|
108
|
+
DownsamplingConfig,
|
|
109
|
+
ReadDownsampler,
|
|
110
|
+
)
|
|
111
|
+
from diffbio.operators.singlecell.knockdown_filter import (
|
|
112
|
+
KnockdownFilterConfig,
|
|
113
|
+
OnTargetKnockdownFilter,
|
|
114
|
+
)
|
|
115
|
+
from diffbio.operators.singlecell.sindy_grn import (
|
|
116
|
+
SINDyGRNConfig,
|
|
117
|
+
SINDyGRNOperator,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
__all__ = [
|
|
121
|
+
# Archetypal Analysis
|
|
122
|
+
"ArchetypalAnalysisConfig",
|
|
123
|
+
"DifferentiableArchetypalAnalysis",
|
|
124
|
+
# Ambient Removal
|
|
125
|
+
"AmbientRemovalConfig",
|
|
126
|
+
"DifferentiableAmbientRemoval",
|
|
127
|
+
# Cell Annotation
|
|
128
|
+
"CellAnnotatorConfig",
|
|
129
|
+
"DifferentiableCellAnnotator",
|
|
130
|
+
# Clustering
|
|
131
|
+
"SoftClusteringConfig",
|
|
132
|
+
"SoftKMeansClustering",
|
|
133
|
+
# Communication (L-R scoring + GNN-based)
|
|
134
|
+
"CellCommunicationConfig",
|
|
135
|
+
"DifferentiableCellCommunication",
|
|
136
|
+
"DifferentiableLigandReceptor",
|
|
137
|
+
"LRScoringConfig",
|
|
138
|
+
# Batch Correction (Harmony)
|
|
139
|
+
"BatchCorrectionConfig",
|
|
140
|
+
"DifferentiableHarmony",
|
|
141
|
+
# Batch Correction (MMD + WGAN)
|
|
142
|
+
"DifferentiableMMDBatchCorrection",
|
|
143
|
+
"DifferentiableWGANBatchCorrection",
|
|
144
|
+
"MMDBatchCorrectionConfig",
|
|
145
|
+
"WGANBatchCorrectionConfig",
|
|
146
|
+
# GRN Inference
|
|
147
|
+
"DifferentiableGRN",
|
|
148
|
+
"GRNInferenceConfig",
|
|
149
|
+
# Doublet Detection
|
|
150
|
+
"DifferentiableDoubletScorer",
|
|
151
|
+
"DifferentiableSoloDetector",
|
|
152
|
+
"DoubletScorerConfig",
|
|
153
|
+
"SoloDetectorConfig",
|
|
154
|
+
# Imputation
|
|
155
|
+
"DifferentiableDiffusionImputer",
|
|
156
|
+
"DifferentiableTransformerDenoiser",
|
|
157
|
+
"DiffusionImputerConfig",
|
|
158
|
+
"TransformerDenoiserConfig",
|
|
159
|
+
# Switch DE
|
|
160
|
+
"DifferentiableSwitchDE",
|
|
161
|
+
"SwitchDEConfig",
|
|
162
|
+
# Trajectory Inference
|
|
163
|
+
"DifferentiableFateProbability",
|
|
164
|
+
"DifferentiablePseudotime",
|
|
165
|
+
"FateProbabilityConfig",
|
|
166
|
+
"PseudotimeConfig",
|
|
167
|
+
# Spatial Domain Identification
|
|
168
|
+
"DifferentiablePASTEAlignment",
|
|
169
|
+
"DifferentiableSpatialDomain",
|
|
170
|
+
"PASTEAlignmentConfig",
|
|
171
|
+
"SpatialDomainConfig",
|
|
172
|
+
# Differential Distribution
|
|
173
|
+
"DifferentiableDifferentialDistribution",
|
|
174
|
+
"DifferentialDistributionConfig",
|
|
175
|
+
# Simulation
|
|
176
|
+
"DifferentiableSimulator",
|
|
177
|
+
"SimulationConfig",
|
|
178
|
+
# OT Trajectory
|
|
179
|
+
"DifferentiableOTTrajectory",
|
|
180
|
+
"OTTrajectoryConfig",
|
|
181
|
+
# Velocity
|
|
182
|
+
"DifferentiableVelocity",
|
|
183
|
+
"VelocityConfig",
|
|
184
|
+
# Downsampling
|
|
185
|
+
"DownsamplingConfig",
|
|
186
|
+
"ReadDownsampler",
|
|
187
|
+
# Knockdown Filter
|
|
188
|
+
"KnockdownFilterConfig",
|
|
189
|
+
"OnTargetKnockdownFilter",
|
|
190
|
+
# SINDy GRN
|
|
191
|
+
"SINDyGRNConfig",
|
|
192
|
+
"SINDyGRNOperator",
|
|
193
|
+
]
|