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,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
|
+
)
|