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,252 @@
|
|
|
1
|
+
"""Graph Neural Network-based assembly navigator.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable approach to assembly graph
|
|
4
|
+
traversal using message passing neural networks.
|
|
5
|
+
|
|
6
|
+
Key technique: Uses graph attention for message passing between nodes,
|
|
7
|
+
then scores edges for soft path selection, enabling gradient flow
|
|
8
|
+
through the assembly process.
|
|
9
|
+
|
|
10
|
+
Applications: Differentiable genome assembly, assembly polishing,
|
|
11
|
+
scaffolding optimization.
|
|
12
|
+
|
|
13
|
+
Inherits from GraphOperator to get:
|
|
14
|
+
|
|
15
|
+
- scatter_aggregate() for message aggregation
|
|
16
|
+
- global_pool() for graph-level pooling
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import jax.numpy as jnp
|
|
24
|
+
from flax import nnx
|
|
25
|
+
from jaxtyping import Array, Float, Int, PyTree
|
|
26
|
+
|
|
27
|
+
from diffbio.configs import TemperatureConfig
|
|
28
|
+
from diffbio.core import soft_ops
|
|
29
|
+
from diffbio.core.base_operators import GraphOperator
|
|
30
|
+
from diffbio.core.gnn_components import GraphAttentionBlock
|
|
31
|
+
from diffbio.utils.nn_utils import init_learnable_param
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class GNNAssemblyNavigatorConfig(TemperatureConfig):
|
|
38
|
+
"""Configuration for GNNAssemblyNavigator.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
node_features: Dimension of input node features.
|
|
42
|
+
hidden_dim: Hidden dimension for GNN layers.
|
|
43
|
+
num_layers: Number of GNN layers.
|
|
44
|
+
num_heads: Number of attention heads.
|
|
45
|
+
edge_features: Dimension of edge features.
|
|
46
|
+
dropout_rate: Dropout rate for regularization.
|
|
47
|
+
temperature: Temperature for softmax operations.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
node_features: int = 64
|
|
51
|
+
hidden_dim: int = 128
|
|
52
|
+
num_layers: int = 3
|
|
53
|
+
num_heads: int = 4
|
|
54
|
+
edge_features: int = 8
|
|
55
|
+
dropout_rate: float = 0.1
|
|
56
|
+
temperature: float = 1.0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class GNNAssemblyNavigator(GraphOperator):
|
|
60
|
+
"""Graph Neural Network for assembly graph traversal.
|
|
61
|
+
|
|
62
|
+
This operator uses message passing with graph attention to update
|
|
63
|
+
node embeddings and predict edge traversal probabilities for
|
|
64
|
+
differentiable assembly.
|
|
65
|
+
|
|
66
|
+
Algorithm:
|
|
67
|
+
1. Project input node features to hidden dimension
|
|
68
|
+
2. Apply multiple GNN layers with graph attention
|
|
69
|
+
3. Compute edge scores from source/target node embeddings
|
|
70
|
+
4. Apply sigmoid for traversal probabilities
|
|
71
|
+
5. Compute path confidence from edge scores
|
|
72
|
+
|
|
73
|
+
Inherits from GraphOperator to get:
|
|
74
|
+
|
|
75
|
+
- scatter_aggregate() for message aggregation utilities
|
|
76
|
+
- global_pool() for graph-level pooling
|
|
77
|
+
|
|
78
|
+
Uses temperature-controlled smoothing:
|
|
79
|
+
- _temperature property for temperature-controlled sigmoid
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
config: GNNAssemblyNavigatorConfig with model parameters.
|
|
83
|
+
rngs: Flax NNX random number generators.
|
|
84
|
+
name: Optional operator name.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
```python
|
|
88
|
+
config = GNNAssemblyNavigatorConfig(hidden_dim=128)
|
|
89
|
+
navigator = GNNAssemblyNavigator(config, rngs=nnx.Rngs(42))
|
|
90
|
+
data = {"node_features": nodes, "edge_index": edges, "edge_features": edge_attr}
|
|
91
|
+
result, state, meta = navigator.apply(data, {}, None)
|
|
92
|
+
```
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
config: GNNAssemblyNavigatorConfig,
|
|
98
|
+
*,
|
|
99
|
+
rngs: nnx.Rngs | None = None,
|
|
100
|
+
name: str | None = None,
|
|
101
|
+
):
|
|
102
|
+
"""Initialize the GNN assembly navigator.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
config: Navigator configuration.
|
|
106
|
+
rngs: Random number generators for initialization.
|
|
107
|
+
name: Optional operator name.
|
|
108
|
+
"""
|
|
109
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
110
|
+
|
|
111
|
+
if rngs is None:
|
|
112
|
+
rngs = nnx.Rngs(0)
|
|
113
|
+
|
|
114
|
+
self.hidden_dim = config.hidden_dim
|
|
115
|
+
|
|
116
|
+
# Temperature management (similar to TemperatureOperator pattern)
|
|
117
|
+
if config.learnable_temperature:
|
|
118
|
+
self._temperature_param = init_learnable_param(config.temperature)
|
|
119
|
+
else:
|
|
120
|
+
self._temperature_param = None
|
|
121
|
+
self._fixed_temperature = config.temperature
|
|
122
|
+
|
|
123
|
+
# Input projection
|
|
124
|
+
self.input_projection = nnx.Linear(
|
|
125
|
+
in_features=config.node_features,
|
|
126
|
+
out_features=config.hidden_dim,
|
|
127
|
+
rngs=rngs,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# GNN layers
|
|
131
|
+
self.gnn_layers = nnx.List(
|
|
132
|
+
[
|
|
133
|
+
GraphAttentionBlock(
|
|
134
|
+
hidden_dim=config.hidden_dim,
|
|
135
|
+
num_heads=config.num_heads,
|
|
136
|
+
edge_features=config.edge_features,
|
|
137
|
+
dropout_rate=config.dropout_rate,
|
|
138
|
+
rngs=rngs,
|
|
139
|
+
)
|
|
140
|
+
for _ in range(config.num_layers)
|
|
141
|
+
]
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Edge scoring MLP
|
|
145
|
+
self.edge_mlp = nnx.Linear(
|
|
146
|
+
in_features=config.hidden_dim * 2,
|
|
147
|
+
out_features=1,
|
|
148
|
+
rngs=rngs,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def _temperature(self) -> Array | float:
|
|
153
|
+
"""Get current temperature value."""
|
|
154
|
+
if self._temperature_param is not None:
|
|
155
|
+
return jnp.abs(self._temperature_param[...]) + 1e-6
|
|
156
|
+
return self._fixed_temperature
|
|
157
|
+
|
|
158
|
+
def compute_edge_scores(
|
|
159
|
+
self,
|
|
160
|
+
node_embeddings: Float[Array, "n_nodes hidden_dim"],
|
|
161
|
+
edge_index: Int[Array, "2 n_edges"],
|
|
162
|
+
) -> Float[Array, "n_edges"]:
|
|
163
|
+
"""Compute edge scores from node embeddings.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
node_embeddings: Node embedding matrix.
|
|
167
|
+
edge_index: Edge indices (source, target).
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Score for each edge.
|
|
171
|
+
"""
|
|
172
|
+
sources = edge_index[0]
|
|
173
|
+
targets = edge_index[1]
|
|
174
|
+
|
|
175
|
+
# Get source and target embeddings
|
|
176
|
+
source_emb = node_embeddings[sources] # (n_edges, hidden_dim)
|
|
177
|
+
target_emb = node_embeddings[targets] # (n_edges, hidden_dim)
|
|
178
|
+
|
|
179
|
+
# Concatenate and score
|
|
180
|
+
edge_repr = jnp.concatenate([source_emb, target_emb], axis=-1)
|
|
181
|
+
scores = self.edge_mlp(edge_repr).squeeze(-1) # (n_edges,)
|
|
182
|
+
|
|
183
|
+
return scores
|
|
184
|
+
|
|
185
|
+
def apply(
|
|
186
|
+
self,
|
|
187
|
+
data: PyTree,
|
|
188
|
+
state: PyTree,
|
|
189
|
+
metadata: dict[str, Any] | None,
|
|
190
|
+
random_params: Any = None,
|
|
191
|
+
stats: dict[str, Any] | None = None,
|
|
192
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
193
|
+
"""Apply GNN assembly navigation.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
data: Dictionary containing:
|
|
197
|
+
- "node_features": Node features (n_nodes, node_features)
|
|
198
|
+
- "edge_index": Edge indices (2, n_edges)
|
|
199
|
+
- "edge_features": Edge features (n_edges, edge_features)
|
|
200
|
+
state: Element state (passed through unchanged)
|
|
201
|
+
metadata: Element metadata (passed through unchanged)
|
|
202
|
+
random_params: Not used
|
|
203
|
+
stats: Not used
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Tuple of (transformed_data, state, metadata):
|
|
207
|
+
- transformed_data contains:
|
|
208
|
+
|
|
209
|
+
- "node_features": Original node features
|
|
210
|
+
- "edge_index": Original edge indices
|
|
211
|
+
- "edge_features": Original edge features
|
|
212
|
+
- "node_embeddings": Updated node embeddings
|
|
213
|
+
- "edge_scores": Scores for each edge
|
|
214
|
+
- "traversal_probs": Sigmoid probabilities for traversal
|
|
215
|
+
- "path_confidence": Confidence score for paths
|
|
216
|
+
- state is passed through unchanged
|
|
217
|
+
- metadata is passed through unchanged
|
|
218
|
+
"""
|
|
219
|
+
node_features = data["node_features"]
|
|
220
|
+
edge_index = data["edge_index"]
|
|
221
|
+
edge_features = data["edge_features"]
|
|
222
|
+
|
|
223
|
+
# Project to hidden dimension
|
|
224
|
+
node_emb = self.input_projection(node_features)
|
|
225
|
+
|
|
226
|
+
# Apply GNN layers
|
|
227
|
+
deterministic = not self.config.stochastic
|
|
228
|
+
for layer in self.gnn_layers:
|
|
229
|
+
node_emb = layer(node_emb, edge_index, edge_features, deterministic=deterministic)
|
|
230
|
+
|
|
231
|
+
# Compute edge scores
|
|
232
|
+
edge_scores = self.compute_edge_scores(node_emb, edge_index)
|
|
233
|
+
|
|
234
|
+
# Traversal probabilities via sigmoid
|
|
235
|
+
# Use inherited _temperature property for temperature-controlled sigmoid
|
|
236
|
+
traversal_probs = soft_ops.greater(edge_scores, 0.0, softness=self._temperature)
|
|
237
|
+
|
|
238
|
+
# Path confidence: mean probability weighted by score magnitude
|
|
239
|
+
path_confidence = jnp.mean(traversal_probs * jnp.abs(edge_scores))
|
|
240
|
+
|
|
241
|
+
# Build output
|
|
242
|
+
transformed_data = {
|
|
243
|
+
"node_features": node_features,
|
|
244
|
+
"edge_index": edge_index,
|
|
245
|
+
"edge_features": edge_features,
|
|
246
|
+
"node_embeddings": node_emb,
|
|
247
|
+
"edge_scores": edge_scores,
|
|
248
|
+
"traversal_probs": traversal_probs,
|
|
249
|
+
"path_confidence": path_confidence,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Differentiable metagenomic binning operators.
|
|
2
|
+
|
|
3
|
+
This module provides VAE-based approaches to metagenomic binning
|
|
4
|
+
inspired by VAMB (Variational Autoencoders for Metagenomic Binning).
|
|
5
|
+
|
|
6
|
+
The approach encodes tetranucleotide frequencies (TNF) and abundance
|
|
7
|
+
profiles into a latent space where contigs from the same genome cluster together.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import jax
|
|
15
|
+
import jax.numpy as jnp
|
|
16
|
+
from artifex.generative_models.core.base import MLP
|
|
17
|
+
from flax import nnx
|
|
18
|
+
from jaxtyping import Array, Float
|
|
19
|
+
|
|
20
|
+
from diffbio.configs import TemperatureConfig, apply_stochastic_sampling_defaults
|
|
21
|
+
from diffbio.core.base_operators import EncoderDecoderOperator, TemperatureOperator
|
|
22
|
+
from diffbio.utils.nn_utils import ARTIFEX_RELU_BATCH_NORM_MLP_KWARGS
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class MetagenomicBinnerConfig(TemperatureConfig):
|
|
29
|
+
"""Configuration for metagenomic binning VAE.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
n_tnf_features: Number of tetranucleotide frequency features (default 136).
|
|
33
|
+
n_abundance_features: Number of sample abundance features.
|
|
34
|
+
latent_dim: Dimension of the latent space.
|
|
35
|
+
hidden_dims: tuple of hidden layer dimensions for encoder/decoder.
|
|
36
|
+
dropout_rate: Dropout rate for regularization.
|
|
37
|
+
beta: KL divergence weight (beta-VAE).
|
|
38
|
+
n_clusters: Number of clusters for soft binning.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
n_tnf_features: int = 136 # 4^4 / 2 for canonical k-mers
|
|
42
|
+
n_abundance_features: int = 10
|
|
43
|
+
latent_dim: int = 32
|
|
44
|
+
hidden_dims: tuple[int, ...] = (512, 256)
|
|
45
|
+
dropout_rate: float = 0.2
|
|
46
|
+
beta: float = 1.0
|
|
47
|
+
n_clusters: int = 100
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
"""Set stochastic config and validate."""
|
|
51
|
+
apply_stochastic_sampling_defaults(self)
|
|
52
|
+
super().__post_init__()
|
|
53
|
+
if not self.hidden_dims:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"MetagenomicBinnerConfig.hidden_dims must contain at least one hidden layer."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DifferentiableMetagenomicBinner(TemperatureOperator, EncoderDecoderOperator):
|
|
60
|
+
"""VAMB-style differentiable metagenomic binning.
|
|
61
|
+
|
|
62
|
+
This operator implements a Variational Autoencoder for metagenomic binning,
|
|
63
|
+
encoding tetranucleotide frequencies (TNF) and abundance profiles into a
|
|
64
|
+
shared latent space where contigs from the same genome cluster together.
|
|
65
|
+
|
|
66
|
+
The approach is fully differentiable, enabling:
|
|
67
|
+
- End-to-end optimization with downstream tasks
|
|
68
|
+
- Soft cluster assignments via temperature-controlled softmax
|
|
69
|
+
- Integration with neural abundance estimation
|
|
70
|
+
|
|
71
|
+
Input data structure:
|
|
72
|
+
- tnf: Float[Array, "n_contigs n_tnf"] - Tetranucleotide frequencies
|
|
73
|
+
- abundance: Float[Array, "n_contigs n_samples"] - Sample abundances
|
|
74
|
+
|
|
75
|
+
Output data structure (adds):
|
|
76
|
+
- latent_z: Float[Array, "n_contigs latent_dim"] - Latent representations
|
|
77
|
+
- latent_mu: Float[Array, "n_contigs latent_dim"] - Latent means
|
|
78
|
+
- latent_logvar: Float[Array, "n_contigs latent_dim"] - Latent log variance
|
|
79
|
+
- cluster_assignments: Float[Array, "n_contigs n_clusters"] - Soft bins
|
|
80
|
+
- reconstructed_tnf: Float[Array, "n_contigs n_tnf"] - Reconstructed TNF
|
|
81
|
+
- reconstructed_abundance: Float[Array, "n_contigs n_samples"] - Recon. abundance
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
```python
|
|
85
|
+
config = MetagenomicBinnerConfig(n_abundance_features=5, n_clusters=50)
|
|
86
|
+
binner = DifferentiableMetagenomicBinner(config, rngs=nnx.Rngs(42))
|
|
87
|
+
result, state, meta = binner.apply(data, {}, None)
|
|
88
|
+
bins = result["cluster_assignments"].argmax(axis=-1)
|
|
89
|
+
```
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
config: MetagenomicBinnerConfig,
|
|
95
|
+
*,
|
|
96
|
+
rngs: nnx.Rngs,
|
|
97
|
+
name: str | None = None,
|
|
98
|
+
):
|
|
99
|
+
"""Initialize the metagenomic binner.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
config: Binner configuration.
|
|
103
|
+
rngs: Random number generators.
|
|
104
|
+
name: Optional name for the operator.
|
|
105
|
+
"""
|
|
106
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
107
|
+
|
|
108
|
+
input_dim = config.n_tnf_features + config.n_abundance_features
|
|
109
|
+
|
|
110
|
+
self.encoder_backbone = MLP(
|
|
111
|
+
hidden_dims=list(config.hidden_dims),
|
|
112
|
+
in_features=input_dim,
|
|
113
|
+
dropout_rate=config.dropout_rate,
|
|
114
|
+
rngs=rngs,
|
|
115
|
+
**ARTIFEX_RELU_BATCH_NORM_MLP_KWARGS,
|
|
116
|
+
)
|
|
117
|
+
self.fc_latent = nnx.List(
|
|
118
|
+
[
|
|
119
|
+
nnx.Linear(config.hidden_dims[-1], config.latent_dim, rngs=rngs),
|
|
120
|
+
nnx.Linear(config.hidden_dims[-1], config.latent_dim, rngs=rngs),
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
decoder_hidden_dims = list(reversed(config.hidden_dims))
|
|
125
|
+
self.decoder_backbone = MLP(
|
|
126
|
+
hidden_dims=decoder_hidden_dims,
|
|
127
|
+
in_features=config.latent_dim,
|
|
128
|
+
dropout_rate=config.dropout_rate,
|
|
129
|
+
rngs=rngs,
|
|
130
|
+
**ARTIFEX_RELU_BATCH_NORM_MLP_KWARGS,
|
|
131
|
+
)
|
|
132
|
+
self.decoder_heads = nnx.List(
|
|
133
|
+
[
|
|
134
|
+
nnx.Linear(decoder_hidden_dims[-1], config.n_tnf_features, rngs=rngs),
|
|
135
|
+
nnx.Linear(decoder_hidden_dims[-1], config.n_abundance_features, rngs=rngs),
|
|
136
|
+
]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Tracks train/eval mode for latent sampling even when no stochastic
|
|
140
|
+
# submodule on the encoder is active for a given configuration.
|
|
141
|
+
self.latent_sampling_mode = nnx.Dropout(rate=0.0, rngs=rngs)
|
|
142
|
+
|
|
143
|
+
# Learnable cluster centroids
|
|
144
|
+
self.centroids = nnx.Param(
|
|
145
|
+
jax.random.normal(rngs.params(), (config.n_clusters, config.latent_dim)) * 0.1
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def encode(
|
|
149
|
+
self, x: Float[Array, "batch input_dim"]
|
|
150
|
+
) -> tuple[Float[Array, "batch latent"], Float[Array, "batch latent"]]:
|
|
151
|
+
"""Encode input to latent distribution.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
x: Concatenated TNF and abundance features.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Tuple of (mu, logvar).
|
|
158
|
+
"""
|
|
159
|
+
encoded = self.encoder_backbone(x)
|
|
160
|
+
if isinstance(encoded, tuple):
|
|
161
|
+
raise TypeError("Metagenomic binner encoder backbone must return a single tensor.")
|
|
162
|
+
|
|
163
|
+
fc_mu, fc_logvar = self.fc_latent
|
|
164
|
+
mu = fc_mu(encoded)
|
|
165
|
+
logvar = fc_logvar(encoded)
|
|
166
|
+
return mu, logvar
|
|
167
|
+
|
|
168
|
+
def decode(
|
|
169
|
+
self, z: Float[Array, "batch latent"]
|
|
170
|
+
) -> tuple[Float[Array, "batch n_tnf"], Float[Array, "batch n_abundance"]]:
|
|
171
|
+
"""Decode latent to reconstructed features.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
z: Latent representation.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Tuple of (tnf, abundance).
|
|
178
|
+
"""
|
|
179
|
+
decoded = self.decoder_backbone(z)
|
|
180
|
+
if isinstance(decoded, tuple):
|
|
181
|
+
raise TypeError("Metagenomic binner decoder backbone must return a single tensor.")
|
|
182
|
+
|
|
183
|
+
fc_tnf, fc_abundance = self.decoder_heads
|
|
184
|
+
# TNF uses softmax (frequencies sum to 1)
|
|
185
|
+
tnf_recon = nnx.softmax(fc_tnf(decoded), axis=-1)
|
|
186
|
+
# Abundance uses softplus (positive values)
|
|
187
|
+
abundance_recon = nnx.softplus(fc_abundance(decoded))
|
|
188
|
+
|
|
189
|
+
return tnf_recon, abundance_recon
|
|
190
|
+
|
|
191
|
+
def soft_cluster(self, z: Float[Array, "batch latent"]) -> Float[Array, "batch n_clusters"]:
|
|
192
|
+
"""Compute soft cluster assignments.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
z: Latent representations.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Soft cluster assignment probabilities.
|
|
199
|
+
"""
|
|
200
|
+
# Compute squared distances to centroids
|
|
201
|
+
z_expanded = z[:, None, :] # (batch, 1, latent)
|
|
202
|
+
centroids_expanded = self.centroids[...][None, :, :] # (1, n_clusters, latent)
|
|
203
|
+
sq_distances = jnp.sum((z_expanded - centroids_expanded) ** 2, axis=-1)
|
|
204
|
+
|
|
205
|
+
# Soft assignment via softmax
|
|
206
|
+
temperature = jnp.maximum(self._temperature, 1e-6)
|
|
207
|
+
assignments = nnx.softmax(-sq_distances / temperature, axis=-1)
|
|
208
|
+
return assignments
|
|
209
|
+
|
|
210
|
+
def apply(
|
|
211
|
+
self,
|
|
212
|
+
data: dict[str, Array],
|
|
213
|
+
state: dict[str, Any],
|
|
214
|
+
metadata: dict[str, Any] | None,
|
|
215
|
+
random_params: Any = None, # noqa: ARG002
|
|
216
|
+
stats: dict[str, Any] | None = None, # noqa: ARG002
|
|
217
|
+
) -> tuple[dict[str, Array], dict[str, Any], dict[str, Any] | None]:
|
|
218
|
+
"""Apply metagenomic binning.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
data: Input data containing:
|
|
222
|
+
- tnf: Float[Array, "n_contigs n_tnf"]
|
|
223
|
+
- abundance: Float[Array, "n_contigs n_samples"]
|
|
224
|
+
state: Element state (passed through).
|
|
225
|
+
metadata: Element metadata (passed through).
|
|
226
|
+
random_params: Random parameters.
|
|
227
|
+
stats: Optional statistics dict.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Tuple of (output_data, state, metadata).
|
|
231
|
+
"""
|
|
232
|
+
tnf = data["tnf"]
|
|
233
|
+
abundance = data["abundance"]
|
|
234
|
+
|
|
235
|
+
# Concatenate features
|
|
236
|
+
x = jnp.concatenate([tnf, abundance], axis=-1)
|
|
237
|
+
|
|
238
|
+
# Encode
|
|
239
|
+
mu, logvar = self.encode(x)
|
|
240
|
+
|
|
241
|
+
# Sample latent (use mu during eval for determinism)
|
|
242
|
+
if self.latent_sampling_mode.deterministic:
|
|
243
|
+
z = mu # Eval mode: deterministic
|
|
244
|
+
else:
|
|
245
|
+
z = self.reparameterize(mu, logvar) # Train mode: stochastic
|
|
246
|
+
|
|
247
|
+
# Decode
|
|
248
|
+
tnf_recon, abundance_recon = self.decode(z)
|
|
249
|
+
|
|
250
|
+
# Soft cluster assignments
|
|
251
|
+
cluster_assignments = self.soft_cluster(z)
|
|
252
|
+
|
|
253
|
+
# Build output
|
|
254
|
+
output_data = {
|
|
255
|
+
**data,
|
|
256
|
+
"latent_z": z,
|
|
257
|
+
"latent_mu": mu,
|
|
258
|
+
"latent_logvar": logvar,
|
|
259
|
+
"cluster_assignments": cluster_assignments,
|
|
260
|
+
"reconstructed_tnf": tnf_recon,
|
|
261
|
+
"reconstructed_abundance": abundance_recon,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return output_data, state, metadata
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def create_metagenomic_binner(
|
|
268
|
+
n_abundance_features: int = 10,
|
|
269
|
+
n_clusters: int = 100,
|
|
270
|
+
latent_dim: int = 32,
|
|
271
|
+
hidden_dims: tuple[int, ...] | None = None,
|
|
272
|
+
seed: int = 42,
|
|
273
|
+
) -> DifferentiableMetagenomicBinner:
|
|
274
|
+
"""Factory function to create a metagenomic binner.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
n_abundance_features: Number of sample abundance features.
|
|
278
|
+
n_clusters: Number of clusters/bins.
|
|
279
|
+
latent_dim: Dimension of latent space.
|
|
280
|
+
hidden_dims: Hidden layer dimensions.
|
|
281
|
+
seed: Random seed.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Configured DifferentiableMetagenomicBinner instance.
|
|
285
|
+
"""
|
|
286
|
+
if hidden_dims is None:
|
|
287
|
+
hidden_dims = (512, 256)
|
|
288
|
+
|
|
289
|
+
config = MetagenomicBinnerConfig(
|
|
290
|
+
n_abundance_features=n_abundance_features,
|
|
291
|
+
n_clusters=n_clusters,
|
|
292
|
+
latent_dim=latent_dim,
|
|
293
|
+
hidden_dims=hidden_dims,
|
|
294
|
+
)
|
|
295
|
+
rngs = nnx.Rngs(seed)
|
|
296
|
+
return DifferentiableMetagenomicBinner(config, rngs=rngs)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""CRISPR guide design operators.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable operators for CRISPR guide RNA
|
|
4
|
+
design and scoring, including on-target efficiency prediction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from diffbio.operators.crispr.guide_scoring import (
|
|
8
|
+
CRISPRScorerConfig,
|
|
9
|
+
DifferentiableCRISPRScorer,
|
|
10
|
+
create_crispr_scorer,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CRISPRScorerConfig",
|
|
15
|
+
"DifferentiableCRISPRScorer",
|
|
16
|
+
"create_crispr_scorer",
|
|
17
|
+
]
|