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,315 @@
1
+ """Differentiable spectral similarity operator for metabolomics (MS2DeepScore-style).
2
+
3
+ This module implements a Siamese neural network for predicting molecular
4
+ structural similarity from tandem mass spectra (MS/MS), based on the
5
+ MS2DeepScore architecture.
6
+
7
+ The approach uses a shared base network to generate spectral embeddings,
8
+ then computes cosine similarity between embeddings to predict structural
9
+ similarity (Tanimoto scores).
10
+
11
+ Architecture based on:
12
+ Huber et al. (2021). "MS2DeepScore: a novel deep learning similarity
13
+ measure to compare tandem mass spectra." Journal of Cheminformatics.
14
+
15
+ Key features:
16
+
17
+ - Siamese architecture with shared weights for spectrum encoding
18
+ - 200-dimensional spectral embeddings
19
+ - Cosine similarity for structure prediction
20
+ - Monte-Carlo dropout for uncertainty estimation
21
+ """
22
+
23
+ import logging
24
+ from dataclasses import dataclass
25
+ from typing import Any
26
+
27
+ import jax.numpy as jnp
28
+ from artifex.generative_models.core.base import MLP
29
+ from datarax.core.config import OperatorConfig
30
+ from datarax.core.operator import OperatorModule
31
+ from flax import nnx
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class SpectralSimilarityConfig(OperatorConfig):
38
+ """Configuration for DifferentiableSpectralSimilarity.
39
+
40
+ Attributes:
41
+ n_bins: Number of m/z bins for spectrum discretization.
42
+ Default 1000 (10-1000 m/z at 1 m/z resolution).
43
+ Original MS2DeepScore uses 10000 bins at 0.1 m/z resolution.
44
+ embedding_dim: Dimension of spectral embeddings. Default 200.
45
+ hidden_dims: Tuple of hidden layer dimensions. Default (512, 256).
46
+ Original MS2DeepScore uses (500, 500).
47
+ dropout_rate: Dropout rate for regularization. Default 0.2.
48
+ min_mz: Minimum m/z value for binning. Default 0.0.
49
+ max_mz: Maximum m/z value for binning. Default 1000.0.
50
+ use_batch_norm: Whether to use batch normalization. Default True.
51
+ """
52
+
53
+ n_bins: int = 1000
54
+ embedding_dim: int = 200
55
+ hidden_dims: tuple[int, ...] = (512, 256)
56
+ dropout_rate: float = 0.2
57
+ min_mz: float = 0.0
58
+ max_mz: float = 1000.0
59
+ use_batch_norm: bool = True
60
+
61
+
62
+ class DifferentiableSpectralSimilarity(OperatorModule):
63
+ """Siamese neural network for spectral similarity prediction.
64
+
65
+ This operator implements the MS2DeepScore architecture for predicting
66
+ molecular structural similarity from tandem mass spectra. The network
67
+ uses a shared encoder to generate spectral embeddings, then computes
68
+ cosine similarity between pairs of embeddings.
69
+
70
+ The operator supports two modes of operation:
71
+ 1. Single spectrum input: Generates embeddings for spectra
72
+ 2. Paired spectra input: Computes similarity between spectrum pairs
73
+
74
+ Architecture:
75
+ Input (n_bins) -> shared encoder MLP -> Embedding (embedding_dim)
76
+
77
+ Attributes:
78
+ config: SpectralSimilarityConfig with hyperparameters.
79
+ backbone: Shared Artifex encoder backbone producing spectral embeddings.
80
+
81
+ Example:
82
+ ```python
83
+ config = SpectralSimilarityConfig(n_bins=1000, embedding_dim=200)
84
+ operator = DifferentiableSpectralSimilarity(config, rngs=nnx.Rngs(42))
85
+ # Get embeddings for spectra
86
+ spectra = jax.random.uniform(jax.random.PRNGKey(0), (10, 1000))
87
+ result, _, _ = operator.apply({"spectra": spectra}, {}, None)
88
+ embeddings = result["embeddings"] # (10, 200)
89
+ # Compute pairwise similarity
90
+ spectra_a = jax.random.uniform(jax.random.PRNGKey(0), (5, 1000))
91
+ spectra_b = jax.random.uniform(jax.random.PRNGKey(1), (5, 1000))
92
+ result, _, _ = operator.apply(
93
+ {"spectra_a": spectra_a, "spectra_b": spectra_b}, {}, None
94
+ )
95
+ similarity = result["similarity_scores"] # (5,) in [-1, 1]
96
+ ```
97
+ """
98
+
99
+ def __init__(self, config: SpectralSimilarityConfig, *, rngs: nnx.Rngs) -> None:
100
+ """Initialize the spectral similarity operator.
101
+
102
+ Args:
103
+ config: Configuration with network hyperparameters.
104
+ rngs: Flax NNX random number generators.
105
+ """
106
+ super().__init__(config, rngs=rngs)
107
+ self.config = config
108
+
109
+ self.backbone = MLP(
110
+ hidden_dims=[*config.hidden_dims, config.embedding_dim],
111
+ in_features=config.n_bins,
112
+ activation="relu",
113
+ dropout_rate=config.dropout_rate,
114
+ output_activation=None,
115
+ use_batch_norm=config.use_batch_norm,
116
+ rngs=rngs,
117
+ )
118
+
119
+ def encode(self, spectra: jnp.ndarray) -> jnp.ndarray:
120
+ """Encode binned spectra into embeddings.
121
+
122
+ BatchNorm and Dropout respect the model's train/eval mode:
123
+ - Call model.train() before training to enable dropout and update batch stats
124
+ - Call model.eval() before inference to disable dropout and use running stats
125
+
126
+ Args:
127
+ spectra: Binned spectra with shape (n_spectra, n_bins).
128
+
129
+ Returns:
130
+ Embeddings with shape (n_spectra, embedding_dim).
131
+ """
132
+ backbone_output = self.backbone(spectra)
133
+ if isinstance(backbone_output, tuple):
134
+ raise TypeError("Spectral similarity backbone must return a single tensor output.")
135
+ return backbone_output
136
+
137
+ def cosine_similarity(
138
+ self, embeddings_a: jnp.ndarray, embeddings_b: jnp.ndarray
139
+ ) -> jnp.ndarray:
140
+ """Compute cosine similarity between embedding pairs.
141
+
142
+ Args:
143
+ embeddings_a: First set of embeddings (n, embedding_dim).
144
+ embeddings_b: Second set of embeddings (n, embedding_dim).
145
+
146
+ Returns:
147
+ Cosine similarity scores with shape (n,).
148
+ """
149
+ # Normalize embeddings
150
+ norm_a = jnp.linalg.norm(embeddings_a, axis=-1, keepdims=True)
151
+ norm_b = jnp.linalg.norm(embeddings_b, axis=-1, keepdims=True)
152
+
153
+ # Avoid division by zero
154
+ norm_a = jnp.maximum(norm_a, 1e-8)
155
+ norm_b = jnp.maximum(norm_b, 1e-8)
156
+
157
+ embeddings_a_normalized = embeddings_a / norm_a
158
+ embeddings_b_normalized = embeddings_b / norm_b
159
+
160
+ # Compute cosine similarity
161
+ similarity = jnp.sum(embeddings_a_normalized * embeddings_b_normalized, axis=-1)
162
+
163
+ return similarity
164
+
165
+ def apply(
166
+ self,
167
+ data: dict[str, Any],
168
+ state: dict[str, Any],
169
+ metadata: dict[str, Any] | None,
170
+ random_params: Any = None,
171
+ stats: dict[str, Any] | None = None,
172
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any] | None]:
173
+ """Apply the spectral similarity operator.
174
+
175
+ The operator supports two input modes:
176
+
177
+ 1. Single spectra mode (embedding generation):
178
+ Input: {"spectra": (n, n_bins)}
179
+ Output: {"embeddings": (n, embedding_dim)}
180
+
181
+ 2. Paired spectra mode (similarity computation):
182
+ Input: {"spectra_a": (n, n_bins), "spectra_b": (n, n_bins)}
183
+ Output: {"similarity_scores": (n,), "embeddings_a": ..., "embeddings_b": ...}
184
+
185
+ Args:
186
+ data: Input data dictionary with spectra.
187
+ state: Per-element state (passed through).
188
+ metadata: Optional metadata (passed through).
189
+ random_params: Random parameters (unused).
190
+ stats: Optional statistics (unused).
191
+
192
+ Returns:
193
+ Tuple of (output_data, state, metadata).
194
+ """
195
+ if "spectra" in data:
196
+ # Single spectra mode: compute embeddings
197
+ spectra = data["spectra"]
198
+ embeddings = self.encode(spectra)
199
+
200
+ output = {**data, "embeddings": embeddings}
201
+
202
+ elif "spectra_a" in data and "spectra_b" in data:
203
+ # Paired spectra mode: compute similarity
204
+ spectra_a = data["spectra_a"]
205
+ spectra_b = data["spectra_b"]
206
+
207
+ embeddings_a = self.encode(spectra_a)
208
+ embeddings_b = self.encode(spectra_b)
209
+
210
+ similarity_scores = self.cosine_similarity(embeddings_a, embeddings_b)
211
+
212
+ output = {
213
+ **data,
214
+ "embeddings_a": embeddings_a,
215
+ "embeddings_b": embeddings_b,
216
+ "similarity_scores": similarity_scores,
217
+ }
218
+
219
+ else:
220
+ raise ValueError(
221
+ "Input must contain either 'spectra' or both 'spectra_a' and 'spectra_b'"
222
+ )
223
+
224
+ return output, state, metadata
225
+
226
+
227
+ def bin_spectrum(
228
+ mz_values: jnp.ndarray,
229
+ intensities: jnp.ndarray,
230
+ n_bins: int = 1000,
231
+ min_mz: float = 0.0,
232
+ max_mz: float = 1000.0,
233
+ normalize: bool = True,
234
+ ) -> jnp.ndarray:
235
+ """Bin a mass spectrum into fixed-width m/z bins.
236
+
237
+ This function discretizes a continuous mass spectrum (m/z, intensity pairs)
238
+ into a fixed-size vector suitable for neural network input.
239
+
240
+ Args:
241
+ mz_values: Array of m/z values with shape (n_peaks,).
242
+ intensities: Array of intensity values with shape (n_peaks,).
243
+ n_bins: Number of bins to use. Default 1000.
244
+ min_mz: Minimum m/z value for binning. Default 0.0.
245
+ max_mz: Maximum m/z value for binning. Default 1000.0.
246
+ normalize: Whether to normalize intensities to max=1.0. Default True.
247
+
248
+ Returns:
249
+ Binned spectrum with shape (n_bins,).
250
+
251
+ Example:
252
+ ```python
253
+ mz = jnp.array([100.0, 200.0, 300.0])
254
+ intensity = jnp.array([0.5, 1.0, 0.3])
255
+ binned = bin_spectrum(mz, intensity, n_bins=100)
256
+ binned.shape
257
+ ```
258
+ (100,)
259
+ """
260
+ # Compute bin edges
261
+ bin_width = (max_mz - min_mz) / n_bins
262
+
263
+ # Compute bin indices for each m/z value
264
+ bin_indices = ((mz_values - min_mz) / bin_width).astype(jnp.int32)
265
+
266
+ # Clip to valid range
267
+ bin_indices = jnp.clip(bin_indices, 0, n_bins - 1)
268
+
269
+ # Create empty binned spectrum
270
+ binned = jnp.zeros(n_bins)
271
+
272
+ # Accumulate intensities in bins (use segment_sum for differentiability)
273
+ binned = binned.at[bin_indices].add(intensities)
274
+
275
+ # Normalize if requested
276
+ if normalize:
277
+ max_intensity = jnp.maximum(jnp.max(binned), 1e-8)
278
+ binned = binned / max_intensity
279
+
280
+ return binned
281
+
282
+
283
+ def create_spectral_similarity(
284
+ n_bins: int = 1000,
285
+ embedding_dim: int = 200,
286
+ hidden_dims: tuple[int, ...] = (512, 256),
287
+ dropout_rate: float = 0.2,
288
+ seed: int = 42,
289
+ ) -> DifferentiableSpectralSimilarity:
290
+ """Factory function to create a spectral similarity operator.
291
+
292
+ Args:
293
+ n_bins: Number of m/z bins. Default 1000.
294
+ embedding_dim: Embedding dimension. Default 200.
295
+ hidden_dims: Hidden layer dimensions. Default (512, 256).
296
+ dropout_rate: Dropout rate. Default 0.2.
297
+ seed: Random seed. Default 42.
298
+
299
+ Returns:
300
+ Configured DifferentiableSpectralSimilarity operator.
301
+
302
+ Example:
303
+ ```python
304
+ operator = create_spectral_similarity(n_bins=500, embedding_dim=128)
305
+ spectra = jax.random.uniform(jax.random.PRNGKey(0), (10, 500))
306
+ result, _, _ = operator.apply({"spectra": spectra}, {}, None)
307
+ ```
308
+ """
309
+ config = SpectralSimilarityConfig(
310
+ n_bins=n_bins,
311
+ embedding_dim=embedding_dim,
312
+ hidden_dims=hidden_dims,
313
+ dropout_rate=dropout_rate,
314
+ )
315
+ return DifferentiableSpectralSimilarity(config, rngs=nnx.Rngs(seed))
@@ -0,0 +1,51 @@
1
+ """Molecular dynamics operators for DiffBio.
2
+
3
+ This module provides differentiable operators for molecular dynamics simulations,
4
+ wrapping JAX-MD functionality for seamless integration with DiffBio pipelines.
5
+
6
+ Operators:
7
+ ForceFieldOperator: Compute energies and forces from particle positions
8
+ MDIntegratorOperator: Time integration for MD simulations
9
+
10
+ Factory Functions:
11
+ create_force_field: Create force field operator with specified potential
12
+ create_integrator: Create integrator operator with specified type
13
+ create_lennard_jones_operator: Create LJ force field operator (convenience)
14
+ create_verlet_integrator: Create velocity Verlet integrator (convenience)
15
+
16
+ Enums:
17
+ PotentialType: Enumeration of supported potential types
18
+
19
+ References:
20
+ Schoenholz & Cubuk (2020). JAX, M.D.: A Framework for Differentiable Physics.
21
+ NeurIPS 2020.
22
+ """
23
+
24
+ from diffbio.operators.molecular_dynamics.force_field import (
25
+ ForceFieldConfig,
26
+ ForceFieldOperator,
27
+ create_force_field,
28
+ create_lennard_jones_operator,
29
+ )
30
+ from diffbio.operators.molecular_dynamics.integrator import (
31
+ MDIntegratorConfig,
32
+ MDIntegratorOperator,
33
+ create_integrator,
34
+ create_verlet_integrator,
35
+ )
36
+ from diffbio.operators.molecular_dynamics.primitives import PotentialType
37
+
38
+ __all__ = [
39
+ # Enums
40
+ "PotentialType",
41
+ # Force field
42
+ "ForceFieldConfig",
43
+ "ForceFieldOperator",
44
+ "create_force_field",
45
+ "create_lennard_jones_operator",
46
+ # Integrator
47
+ "MDIntegratorConfig",
48
+ "MDIntegratorOperator",
49
+ "create_integrator",
50
+ "create_verlet_integrator",
51
+ ]
@@ -0,0 +1,265 @@
1
+ """Force field operators wrapping JAX-MD.
2
+
3
+ This module provides differentiable force field operators that compute
4
+ molecular energies and forces using JAX-MD's efficient implementations.
5
+ """
6
+
7
+ import logging
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ import jax
12
+ from datarax.core.config import OperatorConfig
13
+ from datarax.core.operator import OperatorModule
14
+ from flax import nnx
15
+ from jaxtyping import Array
16
+
17
+ from diffbio.operators.molecular_dynamics.primitives import (
18
+ PotentialType,
19
+ create_displacement_fn,
20
+ create_energy_fn,
21
+ create_force_fn,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ForceFieldConfig(OperatorConfig):
29
+ """Configuration for force field operator.
30
+
31
+ Attributes:
32
+ potential_type: Type of potential ("lennard_jones", "morse", "soft_sphere").
33
+ sigma: Length scale parameter (particle diameter).
34
+ epsilon: Energy scale parameter (well depth).
35
+ cutoff: Cutoff distance for interactions (in units of sigma). None for no cutoff.
36
+ box_size: Size of periodic box. None for non-periodic.
37
+ alpha: Morse potential width parameter (only for morse).
38
+ reference_positions: Optional reference positions for computing
39
+ geometric losses (chamfer/EMD) via artifex. If provided,
40
+ ``chamfer_distance`` and ``earth_mover_distance`` are added
41
+ to the output dict. Shape must match particle positions.
42
+ geometric_loss_weight: Weight for geometric loss terms.
43
+ """
44
+
45
+ potential_type: str = "lennard_jones"
46
+ sigma: float = 1.0
47
+ epsilon: float = 1.0
48
+ cutoff: float | None = 2.5
49
+ box_size: float | None = None
50
+ alpha: float = 5.0 # Morse potential parameter
51
+ geometric_loss_weight: float = 0.0
52
+
53
+
54
+ class ForceFieldOperator(OperatorModule):
55
+ """Differentiable force field operator using JAX-MD.
56
+
57
+ Computes potential energy and forces for a system of particles using
58
+ classical pairwise potentials. Forces are computed automatically via
59
+ JAX's automatic differentiation.
60
+
61
+ Supported potentials:
62
+ - lennard_jones: Standard 12-6 LJ potential
63
+ - morse: Morse potential for bonded interactions
64
+ - soft_sphere: Soft repulsive potential
65
+
66
+ Example:
67
+ ```python
68
+ config = ForceFieldConfig(potential_type="lennard_jones", box_size=10.0)
69
+ operator = ForceFieldOperator(config, rngs=nnx.Rngs(42))
70
+ data = {"positions": positions} # (n_particles, dim)
71
+ result, state, meta = operator.apply(data, {}, None)
72
+ energy = result["energy"] # scalar
73
+ forces = result["forces"] # (n_particles, dim)
74
+ ```
75
+ """
76
+
77
+ def __init__(self, config: ForceFieldConfig, *, rngs: nnx.Rngs | None = None):
78
+ """Initialize force field operator.
79
+
80
+ Args:
81
+ config: Force field configuration.
82
+ rngs: Flax NNX random number generators.
83
+ """
84
+ super().__init__(config, rngs=rngs)
85
+ self.config: ForceFieldConfig = config
86
+
87
+ # Pre-create energy and force functions (efficiency: only created once)
88
+ self._displacement_fn, _ = create_displacement_fn(config.box_size)
89
+ self._energy_fn = create_energy_fn(
90
+ self._displacement_fn,
91
+ potential_type=config.potential_type,
92
+ sigma=config.sigma,
93
+ epsilon=config.epsilon,
94
+ cutoff=config.cutoff,
95
+ alpha=config.alpha,
96
+ )
97
+ self._force_fn = create_force_fn(self._energy_fn)
98
+
99
+ def apply(
100
+ self,
101
+ data: dict[str, Any],
102
+ state: dict[str, Any],
103
+ metadata: dict[str, Any] | None,
104
+ random_params: Any = None,
105
+ stats: dict[str, Any] | None = None,
106
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any] | None]:
107
+ """Compute energy and forces for particle positions.
108
+
109
+ Args:
110
+ data: Input data containing:
111
+ - positions: Particle positions (n_particles, dim) or
112
+ (batch, n_particles, dim)
113
+ state: Per-element state (passed through).
114
+ metadata: Optional metadata.
115
+ random_params: Unused random parameters.
116
+ stats: Optional statistics dictionary.
117
+
118
+ Returns:
119
+ Tuple of:
120
+ - data with added "energy" and "forces" keys
121
+ - unchanged state
122
+ - unchanged metadata
123
+ """
124
+ positions = data["positions"]
125
+
126
+ # Handle batched input
127
+ if positions.ndim == 3:
128
+ # Batched: (batch, n_particles, dim)
129
+ batch_apply = jax.vmap(self._compute_single)
130
+ energy_vals, forces = batch_apply(positions)
131
+ else:
132
+ # Single: (n_particles, dim)
133
+ energy_vals, forces = self._compute_single(positions)
134
+
135
+ result = {
136
+ **data,
137
+ "energy": energy_vals,
138
+ "forces": forces,
139
+ }
140
+
141
+ # Optionally compute geometric losses against reference positions
142
+ if self.config.geometric_loss_weight > 0 and "reference_positions" in data:
143
+ result.update(
144
+ _compute_geometric_losses(
145
+ positions, data["reference_positions"], self.config.geometric_loss_weight
146
+ )
147
+ )
148
+
149
+ return result, state, metadata
150
+
151
+ def _compute_single(self, positions: Array) -> tuple[Array, Array]:
152
+ """Compute energy and forces for a single configuration.
153
+
154
+ Args:
155
+ positions: Particle positions (n_particles, dim).
156
+
157
+ Returns:
158
+ Tuple of (energy, forces).
159
+ """
160
+ # Use pre-created functions from __init__
161
+ total_energy = self._energy_fn(positions)
162
+ forces = self._force_fn(positions)
163
+
164
+ return total_energy, forces
165
+
166
+
167
+ def _compute_geometric_losses(
168
+ predicted: Array,
169
+ reference: Array,
170
+ weight: float,
171
+ ) -> dict[str, Array]:
172
+ """Compute artifex geometric losses between predicted and reference positions.
173
+
174
+ Args:
175
+ predicted: Predicted positions (batch, n_particles, dim) or (n_particles, dim).
176
+ reference: Reference positions with same shape.
177
+ weight: Scaling weight for the losses.
178
+
179
+ Returns:
180
+ Dict with ``chamfer_distance`` and ``earth_mover_distance`` keys.
181
+ """
182
+ from artifex.generative_models.core.losses.geometric import ( # noqa: PLC0415
183
+ chamfer_distance,
184
+ earth_mover_distance,
185
+ )
186
+
187
+ # Ensure batch dimension
188
+ if predicted.ndim == 2:
189
+ predicted = predicted[None, ...]
190
+ reference = reference[None, ...]
191
+
192
+ cd = chamfer_distance(predicted, reference) * weight
193
+ emd = earth_mover_distance(predicted, reference) * weight
194
+ return {"chamfer_distance": cd, "earth_mover_distance": emd}
195
+
196
+
197
+ def create_force_field(
198
+ potential_type: str | PotentialType = PotentialType.LENNARD_JONES,
199
+ sigma: float = 1.0,
200
+ epsilon: float = 1.0,
201
+ cutoff: float | None = 2.5,
202
+ box_size: float | None = None,
203
+ alpha: float = 5.0,
204
+ seed: int = 42,
205
+ ) -> ForceFieldOperator:
206
+ """Create a force field operator with specified potential.
207
+
208
+ Args:
209
+ potential_type: Type of potential ("lennard_jones", "morse", "soft_sphere")
210
+ or PotentialType enum.
211
+ sigma: Particle diameter (length scale).
212
+ epsilon: Well depth (energy scale).
213
+ cutoff: Cutoff distance in units of sigma. None for no cutoff.
214
+ box_size: Periodic box size. None for non-periodic.
215
+ alpha: Morse potential width parameter.
216
+ seed: Random seed for initialization.
217
+
218
+ Returns:
219
+ Configured ForceFieldOperator.
220
+ """
221
+ # Convert enum to string if needed
222
+ if isinstance(potential_type, PotentialType):
223
+ potential_type = potential_type.value
224
+
225
+ config = ForceFieldConfig(
226
+ potential_type=potential_type,
227
+ sigma=sigma,
228
+ epsilon=epsilon,
229
+ cutoff=cutoff,
230
+ box_size=box_size,
231
+ alpha=alpha,
232
+ )
233
+ return ForceFieldOperator(config, rngs=nnx.Rngs(seed))
234
+
235
+
236
+ def create_lennard_jones_operator(
237
+ sigma: float = 1.0,
238
+ epsilon: float = 1.0,
239
+ cutoff: float | None = 2.5,
240
+ box_size: float | None = None,
241
+ seed: int = 42,
242
+ ) -> ForceFieldOperator:
243
+ """Create a Lennard-Jones force field operator.
244
+
245
+ This is a convenience function for creating a force field operator
246
+ with Lennard-Jones potential.
247
+
248
+ Args:
249
+ sigma: Particle diameter (length scale).
250
+ epsilon: Well depth (energy scale).
251
+ cutoff: Cutoff distance in units of sigma. None for no cutoff.
252
+ box_size: Periodic box size. None for non-periodic.
253
+ seed: Random seed for initialization.
254
+
255
+ Returns:
256
+ Configured ForceFieldOperator.
257
+ """
258
+ return create_force_field(
259
+ potential_type=PotentialType.LENNARD_JONES,
260
+ sigma=sigma,
261
+ epsilon=epsilon,
262
+ cutoff=cutoff,
263
+ box_size=box_size,
264
+ seed=seed,
265
+ )