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,269 @@
|
|
|
1
|
+
"""Differentiable CRISPR Guide Scoring Operator.
|
|
2
|
+
|
|
3
|
+
This module implements a DeepCRISPR-inspired differentiable guide RNA scoring
|
|
4
|
+
operator using a CNN architecture to predict on-target efficiency.
|
|
5
|
+
|
|
6
|
+
The architecture is inspired by DeepCRISPR which uses:
|
|
7
|
+
1. A deep convolutional denoising neural network (DCDNN) autoencoder
|
|
8
|
+
for unsupervised representation learning
|
|
9
|
+
2. A CNN classifier for efficiency prediction
|
|
10
|
+
|
|
11
|
+
This implementation provides a simplified but differentiable version that:
|
|
12
|
+
- Uses 1D convolutions over the one-hot encoded sequence
|
|
13
|
+
- Supports optional epigenetic feature channels
|
|
14
|
+
- Outputs efficiency scores in [0, 1]
|
|
15
|
+
|
|
16
|
+
For SpCas9, the standard input is 20nt guide + 3nt PAM = 23nt context.
|
|
17
|
+
|
|
18
|
+
References:
|
|
19
|
+
Chuai et al. (2018). "DeepCRISPR: Optimized CRISPR guide RNA design
|
|
20
|
+
by deep learning." Genome Biology.
|
|
21
|
+
https://github.com/bm2-lab/DeepCRISPR
|
|
22
|
+
|
|
23
|
+
Liu et al. (2021). "Enhancing CRISPR-Cas9 gRNA efficiency prediction
|
|
24
|
+
by data integration and deep learning." Nature Communications.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
import jax.numpy as jnp
|
|
32
|
+
from artifex.generative_models.core.base import MLP
|
|
33
|
+
from datarax.core.config import OperatorConfig
|
|
34
|
+
from datarax.core.operator import OperatorModule
|
|
35
|
+
from flax import nnx
|
|
36
|
+
from diffbio.utils.nn_utils import ARTIFEX_RELU_MLP_KWARGS
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class CRISPRScorerConfig(OperatorConfig):
|
|
43
|
+
"""Configuration for DifferentiableCRISPRScorer.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
guide_length: Length of guide RNA sequence (typically 20-23 nt).
|
|
47
|
+
alphabet_size: Size of nucleotide alphabet (4 for A/C/G/T).
|
|
48
|
+
hidden_channels: CNN hidden channel dimensions.
|
|
49
|
+
fc_dims: Fully connected layer dimensions.
|
|
50
|
+
dropout_rate: Dropout rate for regularization.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
guide_length: int = 23
|
|
54
|
+
alphabet_size: int = 4
|
|
55
|
+
hidden_channels: tuple[int, ...] = (64, 128, 256)
|
|
56
|
+
fc_dims: tuple[int, ...] = (256, 128)
|
|
57
|
+
dropout_rate: float = 0.2
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
"""Validate configuration."""
|
|
61
|
+
super().__post_init__()
|
|
62
|
+
if not self.hidden_channels:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"CRISPRScorerConfig.hidden_channels must contain at least one channel."
|
|
65
|
+
)
|
|
66
|
+
if not self.fc_dims:
|
|
67
|
+
raise ValueError("CRISPRScorerConfig.fc_dims must contain at least one hidden layer.")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DifferentiableCRISPRScorer(OperatorModule):
|
|
71
|
+
"""DeepCRISPR-style differentiable guide RNA scoring.
|
|
72
|
+
|
|
73
|
+
This operator uses a 1D CNN architecture to predict CRISPR guide RNA
|
|
74
|
+
on-target efficiency from sequence features. The model learns sequence
|
|
75
|
+
patterns that correlate with efficient target cleavage.
|
|
76
|
+
|
|
77
|
+
The architecture consists of:
|
|
78
|
+
1. 1D convolutional layers for sequence feature extraction
|
|
79
|
+
2. Batch normalization and ReLU activations
|
|
80
|
+
3. Fully connected layers for efficiency prediction
|
|
81
|
+
4. Sigmoid output for efficiency score in [0, 1]
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
config: Operator configuration.
|
|
85
|
+
conv_layers: 1D convolutional layers.
|
|
86
|
+
conv_bn: Batch normalization layers for conv.
|
|
87
|
+
ffn_backbone: Shared Artifex MLP for score prediction.
|
|
88
|
+
output_head: Final output layer.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
```python
|
|
92
|
+
from diffbio.operators.crispr import (
|
|
93
|
+
DifferentiableCRISPRScorer,
|
|
94
|
+
CRISPRScorerConfig,
|
|
95
|
+
)
|
|
96
|
+
config = CRISPRScorerConfig(guide_length=23)
|
|
97
|
+
scorer = DifferentiableCRISPRScorer(config, rngs=nnx.Rngs(42))
|
|
98
|
+
data = {"guides": guide_sequences} # (n_guides, length, 4)
|
|
99
|
+
result, _, _ = scorer.apply(data, {}, None)
|
|
100
|
+
scores = result["efficiency_scores"] # (n_guides,)
|
|
101
|
+
```
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
config: CRISPRScorerConfig,
|
|
107
|
+
*,
|
|
108
|
+
rngs: nnx.Rngs,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Initialize the CRISPR scorer.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
config: Operator configuration.
|
|
114
|
+
rngs: Flax NNX random number generators.
|
|
115
|
+
"""
|
|
116
|
+
super().__init__(config, rngs=rngs)
|
|
117
|
+
|
|
118
|
+
channel_pairs = zip((config.alphabet_size, *config.hidden_channels), config.hidden_channels)
|
|
119
|
+
self.conv_layers = nnx.List(
|
|
120
|
+
[
|
|
121
|
+
nnx.Conv(
|
|
122
|
+
in_features=in_channels,
|
|
123
|
+
out_features=out_channels,
|
|
124
|
+
kernel_size=(3,),
|
|
125
|
+
padding="SAME",
|
|
126
|
+
rngs=rngs,
|
|
127
|
+
)
|
|
128
|
+
for in_channels, out_channels in channel_pairs
|
|
129
|
+
]
|
|
130
|
+
)
|
|
131
|
+
self.conv_bn = nnx.List(
|
|
132
|
+
[nnx.BatchNorm(out_channels, rngs=rngs) for out_channels in config.hidden_channels]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Calculate flattened size after convolutions
|
|
136
|
+
# With SAME padding, spatial size is preserved
|
|
137
|
+
flat_size = config.guide_length * config.hidden_channels[-1]
|
|
138
|
+
|
|
139
|
+
self.ffn_backbone = MLP(
|
|
140
|
+
hidden_dims=list(config.fc_dims),
|
|
141
|
+
in_features=flat_size,
|
|
142
|
+
dropout_rate=config.dropout_rate,
|
|
143
|
+
rngs=rngs,
|
|
144
|
+
**ARTIFEX_RELU_MLP_KWARGS,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Output head for efficiency score
|
|
148
|
+
self.output_head = nnx.Linear(config.fc_dims[-1], 1, rngs=rngs)
|
|
149
|
+
|
|
150
|
+
def extract_features(self, guides: jnp.ndarray) -> jnp.ndarray:
|
|
151
|
+
"""Extract features from guide sequences using CNN.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
guides: One-hot encoded guides (n_guides, guide_length, 4).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Feature vectors (n_guides, feature_dim).
|
|
158
|
+
"""
|
|
159
|
+
# Input shape: (batch, length, channels)
|
|
160
|
+
x = guides
|
|
161
|
+
|
|
162
|
+
# Apply 1D convolutions with batch norm and ReLU
|
|
163
|
+
for conv, bn in zip(self.conv_layers, self.conv_bn):
|
|
164
|
+
x = conv(x)
|
|
165
|
+
x = bn(x)
|
|
166
|
+
x = nnx.relu(x)
|
|
167
|
+
|
|
168
|
+
# Flatten: (batch, length, channels) -> (batch, length * channels)
|
|
169
|
+
batch_size = x.shape[0]
|
|
170
|
+
x = x.reshape(batch_size, -1)
|
|
171
|
+
|
|
172
|
+
return x
|
|
173
|
+
|
|
174
|
+
def predict_efficiency(self, features: jnp.ndarray) -> jnp.ndarray:
|
|
175
|
+
"""Predict efficiency score from features.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
features: Feature vectors (n_guides, feature_dim).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Efficiency scores (n_guides,) in range [0, 1].
|
|
182
|
+
"""
|
|
183
|
+
backbone_output = self.ffn_backbone(features)
|
|
184
|
+
if isinstance(backbone_output, tuple):
|
|
185
|
+
raise TypeError("CRISPR scorer backbone must return a single tensor output.")
|
|
186
|
+
|
|
187
|
+
# Output layer with sigmoid for [0, 1] output
|
|
188
|
+
x = self.output_head(backbone_output)
|
|
189
|
+
scores = nnx.sigmoid(x).squeeze(-1)
|
|
190
|
+
|
|
191
|
+
return scores
|
|
192
|
+
|
|
193
|
+
def apply(
|
|
194
|
+
self,
|
|
195
|
+
data: dict[str, Any],
|
|
196
|
+
state: dict[str, Any],
|
|
197
|
+
metadata: dict[str, Any] | None,
|
|
198
|
+
random_params: Any = None,
|
|
199
|
+
stats: dict[str, Any] | None = None,
|
|
200
|
+
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any] | None]:
|
|
201
|
+
"""Apply CRISPR scoring to guide sequences.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
data: Dictionary containing:
|
|
205
|
+
- "guides": One-hot encoded guides (n_guides, guide_length, 4).
|
|
206
|
+
state: Per-element state (passed through).
|
|
207
|
+
metadata: Optional metadata (passed through).
|
|
208
|
+
random_params: Random parameters for stochastic operations.
|
|
209
|
+
stats: Optional statistics dictionary.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Tuple of (transformed_data, state, metadata) where transformed_data
|
|
213
|
+
contains:
|
|
214
|
+
|
|
215
|
+
- "guides": Original guide sequences.
|
|
216
|
+
- "efficiency_scores": Predicted efficiency (n_guides,).
|
|
217
|
+
- "features": Extracted feature vectors.
|
|
218
|
+
"""
|
|
219
|
+
guides = data["guides"]
|
|
220
|
+
|
|
221
|
+
# Extract features using CNN
|
|
222
|
+
features = self.extract_features(guides)
|
|
223
|
+
|
|
224
|
+
# Predict efficiency scores
|
|
225
|
+
efficiency_scores = self.predict_efficiency(features)
|
|
226
|
+
|
|
227
|
+
# Build output
|
|
228
|
+
output = {
|
|
229
|
+
**data,
|
|
230
|
+
"efficiency_scores": efficiency_scores,
|
|
231
|
+
"features": features,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return output, state, metadata
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def create_crispr_scorer(
|
|
238
|
+
guide_length: int = 23,
|
|
239
|
+
hidden_channels: tuple[int, ...] = (64, 128, 256),
|
|
240
|
+
fc_dims: tuple[int, ...] = (256, 128),
|
|
241
|
+
dropout_rate: float = 0.2,
|
|
242
|
+
seed: int = 42,
|
|
243
|
+
) -> DifferentiableCRISPRScorer:
|
|
244
|
+
"""Factory function to create a CRISPR scorer.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
guide_length: Length of guide RNA sequence.
|
|
248
|
+
hidden_channels: CNN hidden channel dimensions.
|
|
249
|
+
fc_dims: Fully connected layer dimensions.
|
|
250
|
+
dropout_rate: Dropout rate for regularization.
|
|
251
|
+
seed: Random seed for initialization.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Configured DifferentiableCRISPRScorer instance.
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
```python
|
|
258
|
+
scorer = create_crispr_scorer(guide_length=23)
|
|
259
|
+
result, _, _ = scorer.apply({"guides": data}, {}, None)
|
|
260
|
+
```
|
|
261
|
+
"""
|
|
262
|
+
config = CRISPRScorerConfig(
|
|
263
|
+
guide_length=guide_length,
|
|
264
|
+
hidden_channels=hidden_channels,
|
|
265
|
+
fc_dims=fc_dims,
|
|
266
|
+
dropout_rate=dropout_rate,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return DifferentiableCRISPRScorer(config, rngs=nnx.Rngs(seed))
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Drug discovery operators for differentiable chemoinformatics.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable operators for molecular property prediction,
|
|
4
|
+
fingerprint computation, and similarity scoring.
|
|
5
|
+
|
|
6
|
+
Operators:
|
|
7
|
+
MolecularPropertyPredictor: ChemProp-style MPNN for property prediction
|
|
8
|
+
DifferentiableMolecularFingerprint: Neural graph fingerprints
|
|
9
|
+
CircularFingerprintOperator: Differentiable ECFP/Morgan fingerprints
|
|
10
|
+
MACCSKeysOperator: Differentiable MACCS 166 structural keys
|
|
11
|
+
AttentiveFP: Attention-based molecular fingerprint (Xiong et al. 2019)
|
|
12
|
+
ADMETPredictor: Multi-task ADMET property prediction
|
|
13
|
+
MolecularSimilarityOperator: Differentiable Tanimoto/cosine similarity
|
|
14
|
+
DifferentiableDTIPipeline: Integrated differentiable DTI scorer
|
|
15
|
+
|
|
16
|
+
Utilities:
|
|
17
|
+
smiles_to_graph: Convert SMILES to molecular graph
|
|
18
|
+
batch_smiles_to_graphs: Batch conversion with padding
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from diffbio.operators.drug_discovery.admet_predictor import (
|
|
22
|
+
ADMET_TASK_NAMES,
|
|
23
|
+
ADMET_TASK_TYPES,
|
|
24
|
+
ADMETConfig,
|
|
25
|
+
ADMETPredictor,
|
|
26
|
+
create_admet_predictor,
|
|
27
|
+
)
|
|
28
|
+
from diffbio.operators.drug_discovery.attentive_fp import (
|
|
29
|
+
AttentiveFP,
|
|
30
|
+
AttentiveFPConfig,
|
|
31
|
+
create_attentive_fp,
|
|
32
|
+
)
|
|
33
|
+
from diffbio.operators.drug_discovery.dti import (
|
|
34
|
+
DTI_PIPELINE_INTEGRATION_LAYER,
|
|
35
|
+
PROTEIN_ONE_HOT_PREPROCESSING_VERSION,
|
|
36
|
+
DTIPipelineConfig,
|
|
37
|
+
DifferentiableDTIPipeline,
|
|
38
|
+
build_dti_pipeline_inputs,
|
|
39
|
+
encode_protein_sequences,
|
|
40
|
+
)
|
|
41
|
+
from diffbio.operators.drug_discovery.fingerprint import (
|
|
42
|
+
CircularFingerprintConfig,
|
|
43
|
+
CircularFingerprintOperator,
|
|
44
|
+
DifferentiableMolecularFingerprint,
|
|
45
|
+
MolecularFingerprintConfig,
|
|
46
|
+
create_ecfp4_operator,
|
|
47
|
+
create_ecfp6_operator,
|
|
48
|
+
create_fcfp4_operator,
|
|
49
|
+
create_fingerprint_operator,
|
|
50
|
+
)
|
|
51
|
+
from diffbio.operators.drug_discovery.maccs_keys import (
|
|
52
|
+
MACCSKeysConfig,
|
|
53
|
+
MACCSKeysOperator,
|
|
54
|
+
create_maccs_operator,
|
|
55
|
+
)
|
|
56
|
+
from diffbio.operators.drug_discovery.message_passing import (
|
|
57
|
+
MessagePassingLayer,
|
|
58
|
+
StackedMessagePassing,
|
|
59
|
+
)
|
|
60
|
+
from diffbio.operators.drug_discovery.primitives import (
|
|
61
|
+
AtomFeatureConfig,
|
|
62
|
+
DEFAULT_ATOM_CONFIG,
|
|
63
|
+
DEFAULT_ATOM_FEATURES,
|
|
64
|
+
batch_smiles_to_graphs,
|
|
65
|
+
smiles_to_graph,
|
|
66
|
+
)
|
|
67
|
+
from diffbio.operators.drug_discovery.property_predictor import (
|
|
68
|
+
MolecularPropertyConfig,
|
|
69
|
+
MolecularPropertyPredictor,
|
|
70
|
+
create_property_predictor,
|
|
71
|
+
)
|
|
72
|
+
from diffbio.operators.drug_discovery.similarity import (
|
|
73
|
+
MolecularSimilarityConfig,
|
|
74
|
+
MolecularSimilarityOperator,
|
|
75
|
+
cosine_similarity,
|
|
76
|
+
create_similarity_operator,
|
|
77
|
+
dice_similarity,
|
|
78
|
+
tanimoto_similarity,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
# Primitives
|
|
83
|
+
"smiles_to_graph",
|
|
84
|
+
"batch_smiles_to_graphs",
|
|
85
|
+
"DEFAULT_ATOM_FEATURES",
|
|
86
|
+
"DEFAULT_ATOM_CONFIG",
|
|
87
|
+
"AtomFeatureConfig",
|
|
88
|
+
# Message Passing
|
|
89
|
+
"MessagePassingLayer",
|
|
90
|
+
"StackedMessagePassing",
|
|
91
|
+
# Property Prediction
|
|
92
|
+
"MolecularPropertyConfig",
|
|
93
|
+
"MolecularPropertyPredictor",
|
|
94
|
+
"create_property_predictor",
|
|
95
|
+
# ADMET Prediction
|
|
96
|
+
"ADMETConfig",
|
|
97
|
+
"ADMETPredictor",
|
|
98
|
+
"create_admet_predictor",
|
|
99
|
+
"ADMET_TASK_NAMES",
|
|
100
|
+
"ADMET_TASK_TYPES",
|
|
101
|
+
# DTI Pipeline
|
|
102
|
+
"DTI_PIPELINE_INTEGRATION_LAYER",
|
|
103
|
+
"PROTEIN_ONE_HOT_PREPROCESSING_VERSION",
|
|
104
|
+
"DTIPipelineConfig",
|
|
105
|
+
"DifferentiableDTIPipeline",
|
|
106
|
+
"build_dti_pipeline_inputs",
|
|
107
|
+
"encode_protein_sequences",
|
|
108
|
+
# Neural Fingerprints
|
|
109
|
+
"MolecularFingerprintConfig",
|
|
110
|
+
"DifferentiableMolecularFingerprint",
|
|
111
|
+
"create_fingerprint_operator",
|
|
112
|
+
# Circular Fingerprints (ECFP/Morgan)
|
|
113
|
+
"CircularFingerprintConfig",
|
|
114
|
+
"CircularFingerprintOperator",
|
|
115
|
+
"create_ecfp4_operator",
|
|
116
|
+
"create_ecfp6_operator",
|
|
117
|
+
"create_fcfp4_operator",
|
|
118
|
+
# MACCS Keys
|
|
119
|
+
"MACCSKeysConfig",
|
|
120
|
+
"MACCSKeysOperator",
|
|
121
|
+
"create_maccs_operator",
|
|
122
|
+
# AttentiveFP
|
|
123
|
+
"AttentiveFPConfig",
|
|
124
|
+
"AttentiveFP",
|
|
125
|
+
"create_attentive_fp",
|
|
126
|
+
# Similarity
|
|
127
|
+
"MolecularSimilarityConfig",
|
|
128
|
+
"MolecularSimilarityOperator",
|
|
129
|
+
"create_similarity_operator",
|
|
130
|
+
"tanimoto_similarity",
|
|
131
|
+
"cosine_similarity",
|
|
132
|
+
"dice_similarity",
|
|
133
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Shared helpers for graph-based drug-discovery operators."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
import jax.numpy as jnp
|
|
7
|
+
from flax import nnx
|
|
8
|
+
|
|
9
|
+
from diffbio.operators.drug_discovery.message_passing import StackedMessagePassing
|
|
10
|
+
from diffbio.utils.nn_utils import ensure_rngs
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def stabilize_operator_id(module: Any) -> None:
|
|
16
|
+
"""Mark operator unique ID as static for NNX/JAX transformations."""
|
|
17
|
+
module._unique_id = nnx.static(module._unique_id)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_encoder(
|
|
21
|
+
*,
|
|
22
|
+
hidden_dim: int,
|
|
23
|
+
num_layers: int,
|
|
24
|
+
in_features: int,
|
|
25
|
+
rngs: nnx.Rngs,
|
|
26
|
+
num_edge_features: int | None = None,
|
|
27
|
+
) -> StackedMessagePassing:
|
|
28
|
+
"""Create a message-passing encoder with optional edge features."""
|
|
29
|
+
encoder_kwargs: dict[str, Any] = {
|
|
30
|
+
"hidden_dim": hidden_dim,
|
|
31
|
+
"num_layers": num_layers,
|
|
32
|
+
"in_features": in_features,
|
|
33
|
+
"rngs": rngs,
|
|
34
|
+
}
|
|
35
|
+
if num_edge_features is not None:
|
|
36
|
+
encoder_kwargs["num_edge_features"] = num_edge_features
|
|
37
|
+
return StackedMessagePassing(**encoder_kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _require_config_attr(config: Any, attr: str) -> Any:
|
|
41
|
+
"""Read a required config attribute with a clear error message."""
|
|
42
|
+
if not hasattr(config, attr):
|
|
43
|
+
raise AttributeError(
|
|
44
|
+
f"{type(config).__name__} must define '{attr}' for graph encoder initialization."
|
|
45
|
+
)
|
|
46
|
+
return getattr(config, attr)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def initialize_graph_encoder(
|
|
50
|
+
module: Any,
|
|
51
|
+
*,
|
|
52
|
+
rngs: nnx.Rngs | None,
|
|
53
|
+
hidden_dim: int,
|
|
54
|
+
num_layers: int,
|
|
55
|
+
in_features: int,
|
|
56
|
+
num_edge_features: int | None = None,
|
|
57
|
+
attr: str = "encoder",
|
|
58
|
+
) -> nnx.Rngs:
|
|
59
|
+
"""Stabilize ID, ensure RNGs, and attach a message-passing encoder."""
|
|
60
|
+
stabilize_operator_id(module)
|
|
61
|
+
resolved_rngs = ensure_rngs(rngs)
|
|
62
|
+
setattr(
|
|
63
|
+
module,
|
|
64
|
+
attr,
|
|
65
|
+
build_encoder(
|
|
66
|
+
hidden_dim=hidden_dim,
|
|
67
|
+
num_layers=num_layers,
|
|
68
|
+
in_features=in_features,
|
|
69
|
+
rngs=resolved_rngs,
|
|
70
|
+
num_edge_features=num_edge_features,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
return resolved_rngs
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def initialize_graph_encoder_from_config(
|
|
77
|
+
module: Any,
|
|
78
|
+
config: Any,
|
|
79
|
+
*,
|
|
80
|
+
rngs: nnx.Rngs | None,
|
|
81
|
+
num_layers_attr: str = "num_message_passing_steps",
|
|
82
|
+
hidden_dim_attr: str = "hidden_dim",
|
|
83
|
+
in_features_attr: str = "in_features",
|
|
84
|
+
num_edge_features_attr: str = "num_edge_features",
|
|
85
|
+
attr: str = "encoder",
|
|
86
|
+
) -> nnx.Rngs:
|
|
87
|
+
"""Initialize a standard graph encoder from a config object."""
|
|
88
|
+
hidden_dim = _require_config_attr(config, hidden_dim_attr)
|
|
89
|
+
num_layers = _require_config_attr(config, num_layers_attr)
|
|
90
|
+
in_features = _require_config_attr(config, in_features_attr)
|
|
91
|
+
num_edge_features = getattr(config, num_edge_features_attr, None)
|
|
92
|
+
|
|
93
|
+
return initialize_graph_encoder(
|
|
94
|
+
module,
|
|
95
|
+
rngs=rngs,
|
|
96
|
+
hidden_dim=hidden_dim,
|
|
97
|
+
num_layers=num_layers,
|
|
98
|
+
in_features=in_features,
|
|
99
|
+
num_edge_features=num_edge_features,
|
|
100
|
+
attr=attr,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def build_optional_dropout(rate: float, *, rngs: nnx.Rngs) -> nnx.Dropout | None:
|
|
105
|
+
"""Create a dropout module only when the configured rate is positive."""
|
|
106
|
+
if rate <= 0:
|
|
107
|
+
return None
|
|
108
|
+
return nnx.Dropout(rate=rate, rngs=rngs)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def unpack_graph_inputs(data: Mapping[str, Any]) -> tuple[Any, Any, Any, Any]:
|
|
112
|
+
"""Extract standard molecular graph tensors from an input dictionary."""
|
|
113
|
+
return (
|
|
114
|
+
data["node_features"],
|
|
115
|
+
data["adjacency"],
|
|
116
|
+
data.get("edge_features"),
|
|
117
|
+
data.get("node_mask"),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def graph_sum_readout(
|
|
122
|
+
data: Mapping[str, Any],
|
|
123
|
+
encoder: StackedMessagePassing,
|
|
124
|
+
*,
|
|
125
|
+
dropout: nnx.Dropout | None = None,
|
|
126
|
+
) -> jnp.ndarray:
|
|
127
|
+
"""Encode a graph and sum-pool node states into a graph representation."""
|
|
128
|
+
node_features, adjacency, edge_features, node_mask = unpack_graph_inputs(data)
|
|
129
|
+
|
|
130
|
+
node_hidden = encoder(node_features, adjacency, edge_features)
|
|
131
|
+
if node_mask is not None:
|
|
132
|
+
node_hidden = node_hidden * node_mask[:, None]
|
|
133
|
+
|
|
134
|
+
graph_repr = jnp.sum(node_hidden, axis=0)
|
|
135
|
+
if dropout is not None:
|
|
136
|
+
graph_repr = dropout(graph_repr)
|
|
137
|
+
return graph_repr
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def attach_fingerprint(data: Mapping[str, Any], fingerprint: Any) -> dict[str, Any]:
|
|
141
|
+
"""Return a shallow copy with a standardized fingerprint output field."""
|
|
142
|
+
return {**data, "fingerprint": fingerprint}
|