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.
Files changed (202) hide show
  1. diffbio/__init__.py +39 -0
  2. diffbio/configs.py +75 -0
  3. diffbio/constants.py +204 -0
  4. diffbio/core/__init__.py +127 -0
  5. diffbio/core/base_operators.py +612 -0
  6. diffbio/core/data_types.py +260 -0
  7. diffbio/core/gnn_components.py +629 -0
  8. diffbio/core/graph_utils.py +149 -0
  9. diffbio/core/neural_components.py +270 -0
  10. diffbio/core/optimal_transport.py +133 -0
  11. diffbio/core/soft_ops/__init__.py +216 -0
  12. diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
  13. diffbio/core/soft_ops/_projections_simplex.py +240 -0
  14. diffbio/core/soft_ops/_projections_transport.py +508 -0
  15. diffbio/core/soft_ops/_sorting_network.py +204 -0
  16. diffbio/core/soft_ops/_types.py +15 -0
  17. diffbio/core/soft_ops/_utils.py +342 -0
  18. diffbio/core/soft_ops/autograd_safe.py +120 -0
  19. diffbio/core/soft_ops/comparison.py +235 -0
  20. diffbio/core/soft_ops/elementwise.py +309 -0
  21. diffbio/core/soft_ops/logical.py +146 -0
  22. diffbio/core/soft_ops/quantile.py +376 -0
  23. diffbio/core/soft_ops/selection.py +236 -0
  24. diffbio/core/soft_ops/sorting.py +926 -0
  25. diffbio/core/soft_ops/straight_through.py +261 -0
  26. diffbio/core/uncertainty.py +279 -0
  27. diffbio/evaluation/__init__.py +42 -0
  28. diffbio/evaluation/adapters.py +409 -0
  29. diffbio/evaluation/graders.py +223 -0
  30. diffbio/evaluation/problem.py +157 -0
  31. diffbio/evaluation/runner.py +277 -0
  32. diffbio/losses/__init__.py +59 -0
  33. diffbio/losses/alignment_losses.py +222 -0
  34. diffbio/losses/biological_regularization.py +288 -0
  35. diffbio/losses/metric_losses.py +139 -0
  36. diffbio/losses/singlecell_losses.py +387 -0
  37. diffbio/losses/statistical_losses.py +345 -0
  38. diffbio/operators/__init__.py +60 -0
  39. diffbio/operators/_count_vae.py +197 -0
  40. diffbio/operators/_loss_balancing.py +65 -0
  41. diffbio/operators/_masked_gene_transformer.py +118 -0
  42. diffbio/operators/_transformer_validation.py +50 -0
  43. diffbio/operators/alignment/__init__.py +51 -0
  44. diffbio/operators/alignment/profile_hmm.py +350 -0
  45. diffbio/operators/alignment/scoring.py +127 -0
  46. diffbio/operators/alignment/smith_waterman.py +261 -0
  47. diffbio/operators/alignment/soft_msa.py +419 -0
  48. diffbio/operators/assembly/__init__.py +27 -0
  49. diffbio/operators/assembly/gnn_assembly.py +252 -0
  50. diffbio/operators/assembly/metagenomic_binning.py +296 -0
  51. diffbio/operators/crispr/__init__.py +17 -0
  52. diffbio/operators/crispr/guide_scoring.py +269 -0
  53. diffbio/operators/drug_discovery/__init__.py +133 -0
  54. diffbio/operators/drug_discovery/_graph_utils.py +142 -0
  55. diffbio/operators/drug_discovery/admet_predictor.py +285 -0
  56. diffbio/operators/drug_discovery/attentive_fp.py +411 -0
  57. diffbio/operators/drug_discovery/dti.py +261 -0
  58. diffbio/operators/drug_discovery/fingerprint.py +490 -0
  59. diffbio/operators/drug_discovery/maccs_keys.py +267 -0
  60. diffbio/operators/drug_discovery/message_passing.py +200 -0
  61. diffbio/operators/drug_discovery/primitives.py +242 -0
  62. diffbio/operators/drug_discovery/property_predictor.py +163 -0
  63. diffbio/operators/drug_discovery/similarity.py +193 -0
  64. diffbio/operators/epigenomics/__init__.py +35 -0
  65. diffbio/operators/epigenomics/chromatin_state.py +491 -0
  66. diffbio/operators/epigenomics/contextual.py +288 -0
  67. diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
  68. diffbio/operators/epigenomics/peak_calling.py +555 -0
  69. diffbio/operators/foundation_models/__init__.py +119 -0
  70. diffbio/operators/foundation_models/adapters.py +114 -0
  71. diffbio/operators/foundation_models/contracts.py +245 -0
  72. diffbio/operators/foundation_models/embedding_probe.py +83 -0
  73. diffbio/operators/foundation_models/experimental.py +128 -0
  74. diffbio/operators/foundation_models/foundation_model.py +332 -0
  75. diffbio/operators/foundation_models/frozen.py +59 -0
  76. diffbio/operators/foundation_models/precomputed.py +270 -0
  77. diffbio/operators/foundation_models/transformer_encoder.py +564 -0
  78. diffbio/operators/mapping/__init__.py +17 -0
  79. diffbio/operators/mapping/neural_mapper.py +493 -0
  80. diffbio/operators/metabolomics/__init__.py +39 -0
  81. diffbio/operators/metabolomics/spectral_similarity.py +315 -0
  82. diffbio/operators/molecular_dynamics/__init__.py +51 -0
  83. diffbio/operators/molecular_dynamics/force_field.py +265 -0
  84. diffbio/operators/molecular_dynamics/integrator.py +304 -0
  85. diffbio/operators/molecular_dynamics/primitives.py +115 -0
  86. diffbio/operators/multiomics/__init__.py +38 -0
  87. diffbio/operators/multiomics/hic_contact.py +377 -0
  88. diffbio/operators/multiomics/multiomics_vae.py +325 -0
  89. diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
  90. diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
  91. diffbio/operators/normalization/__init__.py +42 -0
  92. diffbio/operators/normalization/embedding.py +222 -0
  93. diffbio/operators/normalization/phate.py +400 -0
  94. diffbio/operators/normalization/umap.py +261 -0
  95. diffbio/operators/normalization/vae_normalizer.py +258 -0
  96. diffbio/operators/population/__init__.py +17 -0
  97. diffbio/operators/population/ancestry_estimation.py +274 -0
  98. diffbio/operators/preprocessing/__init__.py +76 -0
  99. diffbio/operators/preprocessing/adapter_removal.py +311 -0
  100. diffbio/operators/preprocessing/duplicate_filter.py +317 -0
  101. diffbio/operators/preprocessing/error_correction.py +287 -0
  102. diffbio/operators/protein/__init__.py +31 -0
  103. diffbio/operators/protein/secondary_structure.py +509 -0
  104. diffbio/operators/quality_filter.py +128 -0
  105. diffbio/operators/rna_structure/__init__.py +35 -0
  106. diffbio/operators/rna_structure/rna_folding.py +509 -0
  107. diffbio/operators/rnaseq/__init__.py +23 -0
  108. diffbio/operators/rnaseq/motif_discovery.py +251 -0
  109. diffbio/operators/rnaseq/splicing_psi.py +216 -0
  110. diffbio/operators/singlecell/__init__.py +193 -0
  111. diffbio/operators/singlecell/ambient_removal.py +333 -0
  112. diffbio/operators/singlecell/archetypes.py +191 -0
  113. diffbio/operators/singlecell/batch_correction.py +288 -0
  114. diffbio/operators/singlecell/cell_annotation.py +519 -0
  115. diffbio/operators/singlecell/communication.py +704 -0
  116. diffbio/operators/singlecell/differential_distribution.py +243 -0
  117. diffbio/operators/singlecell/doublet_detection.py +657 -0
  118. diffbio/operators/singlecell/downsampling.py +166 -0
  119. diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
  120. diffbio/operators/singlecell/grn_inference.py +336 -0
  121. diffbio/operators/singlecell/imputation.py +429 -0
  122. diffbio/operators/singlecell/knockdown_filter.py +176 -0
  123. diffbio/operators/singlecell/ot_trajectory.py +277 -0
  124. diffbio/operators/singlecell/simulation.py +444 -0
  125. diffbio/operators/singlecell/sindy_grn.py +247 -0
  126. diffbio/operators/singlecell/soft_clustering.py +211 -0
  127. diffbio/operators/singlecell/spatial_domains.py +677 -0
  128. diffbio/operators/singlecell/switch_de.py +184 -0
  129. diffbio/operators/singlecell/trajectory.py +447 -0
  130. diffbio/operators/singlecell/velocity.py +361 -0
  131. diffbio/operators/statistical/__init__.py +35 -0
  132. diffbio/operators/statistical/em_quantification.py +260 -0
  133. diffbio/operators/statistical/hmm.py +234 -0
  134. diffbio/operators/statistical/nb_glm.py +272 -0
  135. diffbio/operators/variant/__init__.py +64 -0
  136. diffbio/operators/variant/classifier.py +333 -0
  137. diffbio/operators/variant/cnn_classifier.py +255 -0
  138. diffbio/operators/variant/cnv_segmentation.py +678 -0
  139. diffbio/operators/variant/deepvariant_pileup.py +426 -0
  140. diffbio/operators/variant/pileup.py +240 -0
  141. diffbio/operators/variant/quality_recalibration.py +274 -0
  142. diffbio/pipelines/__init__.py +65 -0
  143. diffbio/pipelines/differential_expression.py +279 -0
  144. diffbio/pipelines/enhanced_variant_calling.py +326 -0
  145. diffbio/pipelines/perturbation.py +407 -0
  146. diffbio/pipelines/preprocessing.py +267 -0
  147. diffbio/pipelines/single_cell.py +366 -0
  148. diffbio/pipelines/variant_calling.py +490 -0
  149. diffbio/samplers/__init__.py +9 -0
  150. diffbio/samplers/perturbation_sampler.py +142 -0
  151. diffbio/sequences/__init__.py +34 -0
  152. diffbio/sequences/dna.py +239 -0
  153. diffbio/sources/__init__.py +149 -0
  154. diffbio/sources/_anndata_shared.py +89 -0
  155. diffbio/sources/_batch_iteration.py +37 -0
  156. diffbio/sources/_benchmark_source.py +152 -0
  157. diffbio/sources/_indexed_batch_source.py +38 -0
  158. diffbio/sources/_utils.py +45 -0
  159. diffbio/sources/anndata_interop.py +387 -0
  160. diffbio/sources/anndata_source.py +361 -0
  161. diffbio/sources/archive_ii.py +174 -0
  162. diffbio/sources/balifam.py +207 -0
  163. diffbio/sources/bam.py +265 -0
  164. diffbio/sources/bengrn_ground_truth.py +306 -0
  165. diffbio/sources/contextual_epigenomics.py +242 -0
  166. diffbio/sources/dti.py +359 -0
  167. diffbio/sources/embeddings.py +203 -0
  168. diffbio/sources/encode_peaks.py +223 -0
  169. diffbio/sources/fasta.py +226 -0
  170. diffbio/sources/immune_human.py +172 -0
  171. diffbio/sources/indexed_embeddings.py +128 -0
  172. diffbio/sources/indexed_view.py +191 -0
  173. diffbio/sources/molnet.py +493 -0
  174. diffbio/sources/multiomics.py +279 -0
  175. diffbio/sources/pancreas.py +108 -0
  176. diffbio/sources/perturbation/__init__.py +69 -0
  177. diffbio/sources/perturbation/_types.py +51 -0
  178. diffbio/sources/perturbation/_utils.py +125 -0
  179. diffbio/sources/perturbation/concat_source.py +115 -0
  180. diffbio/sources/perturbation/control_mapping.py +215 -0
  181. diffbio/sources/perturbation/experiment_config.py +261 -0
  182. diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
  183. diffbio/sources/perturbation/output_space.py +52 -0
  184. diffbio/sources/perturbation/perturbation_source.py +513 -0
  185. diffbio/sources/seqfish.py +145 -0
  186. diffbio/sources/sequence_foundation.py +68 -0
  187. diffbio/sources/singlecell_foundation.py +68 -0
  188. diffbio/splitters/__init__.py +63 -0
  189. diffbio/splitters/base.py +251 -0
  190. diffbio/splitters/molecular.py +330 -0
  191. diffbio/splitters/perturbation.py +199 -0
  192. diffbio/splitters/random.py +217 -0
  193. diffbio/splitters/sequence.py +201 -0
  194. diffbio/utils/__init__.py +55 -0
  195. diffbio/utils/dependency_runtime.py +115 -0
  196. diffbio/utils/nn_utils.py +157 -0
  197. diffbio/utils/quality.py +45 -0
  198. diffbio/utils/training.py +585 -0
  199. diffbio-0.1.0.dist-info/METADATA +480 -0
  200. diffbio-0.1.0.dist-info/RECORD +202 -0
  201. diffbio-0.1.0.dist-info/WHEEL +4 -0
  202. 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
+ ]