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,118 @@
|
|
|
1
|
+
"""Shared scaffolding for masked-gene transformer operators.
|
|
2
|
+
|
|
3
|
+
These helpers centralize the common single-cell transformer setup used by the
|
|
4
|
+
foundation-model and imputation operators so both paths share one encoder
|
|
5
|
+
construction and one mask/input preparation flow.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import jax
|
|
14
|
+
import jax.numpy as jnp
|
|
15
|
+
from datarax.core.config import OperatorConfig
|
|
16
|
+
from flax import nnx
|
|
17
|
+
from jaxtyping import Array, PyTree
|
|
18
|
+
|
|
19
|
+
from diffbio.operators.foundation_models.transformer_encoder import (
|
|
20
|
+
TransformerSequenceEncoder,
|
|
21
|
+
TransformerSequenceEncoderConfig,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class MaskedGeneTransformerConfigBase(OperatorConfig):
|
|
27
|
+
"""Shared config fields for masked-gene transformer operators."""
|
|
28
|
+
|
|
29
|
+
n_genes: int = 2000
|
|
30
|
+
hidden_dim: int = 128
|
|
31
|
+
num_layers: int = 2
|
|
32
|
+
num_heads: int = 4
|
|
33
|
+
mask_ratio: float = 0.15
|
|
34
|
+
dropout_rate: float = 0.1
|
|
35
|
+
|
|
36
|
+
def __post_init__(self) -> None:
|
|
37
|
+
"""Default masked-gene operators to sampled stochastic execution."""
|
|
38
|
+
object.__setattr__(self, "stochastic", True)
|
|
39
|
+
if self.stream_name is None:
|
|
40
|
+
object.__setattr__(self, "stream_name", "sample")
|
|
41
|
+
super().__post_init__()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_masked_gene_transformer_encoder(
|
|
45
|
+
config: MaskedGeneTransformerConfigBase,
|
|
46
|
+
*,
|
|
47
|
+
rngs: nnx.Rngs,
|
|
48
|
+
) -> TransformerSequenceEncoder:
|
|
49
|
+
"""Build the shared token-embedding transformer encoder contract."""
|
|
50
|
+
encoder_config = TransformerSequenceEncoderConfig(
|
|
51
|
+
hidden_dim=config.hidden_dim,
|
|
52
|
+
num_layers=config.num_layers,
|
|
53
|
+
num_heads=config.num_heads,
|
|
54
|
+
intermediate_dim=4 * config.hidden_dim,
|
|
55
|
+
max_length=config.n_genes,
|
|
56
|
+
input_embedding_type="token_embedding",
|
|
57
|
+
vocab_size=config.n_genes,
|
|
58
|
+
dropout_rate=config.dropout_rate,
|
|
59
|
+
pooling="mean",
|
|
60
|
+
)
|
|
61
|
+
return TransformerSequenceEncoder(encoder_config, rngs=rngs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_masked_gene_mask(
|
|
65
|
+
*,
|
|
66
|
+
random_params: Any,
|
|
67
|
+
mask_ratio: float,
|
|
68
|
+
n_genes: int,
|
|
69
|
+
) -> Array:
|
|
70
|
+
"""Build a per-gene binary mask for masked-gene transformer operators."""
|
|
71
|
+
if random_params is not None and mask_ratio > 0:
|
|
72
|
+
noise = jax.random.uniform(random_params, (n_genes,))
|
|
73
|
+
return (noise < mask_ratio).astype(jnp.float32)
|
|
74
|
+
return jnp.zeros(n_genes, dtype=jnp.float32)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def prepare_masked_gene_batch(
|
|
78
|
+
data: PyTree,
|
|
79
|
+
*,
|
|
80
|
+
random_params: Any,
|
|
81
|
+
mask_ratio: float,
|
|
82
|
+
) -> tuple[Array, Array, Array]:
|
|
83
|
+
"""Extract counts, int32 gene IDs, and the shared masking vector."""
|
|
84
|
+
counts = data["counts"]
|
|
85
|
+
gene_ids = jnp.asarray(data["gene_ids"], dtype=jnp.int32)
|
|
86
|
+
mask = build_masked_gene_mask(
|
|
87
|
+
random_params=random_params,
|
|
88
|
+
mask_ratio=mask_ratio,
|
|
89
|
+
n_genes=int(counts.shape[1]),
|
|
90
|
+
)
|
|
91
|
+
return counts, gene_ids, mask
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MaskedGeneTransformerOperatorMixin:
|
|
95
|
+
"""Mixin for operators built on the shared masked-gene transformer flow."""
|
|
96
|
+
|
|
97
|
+
config: MaskedGeneTransformerConfigBase
|
|
98
|
+
|
|
99
|
+
def generate_random_params(
|
|
100
|
+
self,
|
|
101
|
+
rng: jax.Array,
|
|
102
|
+
data_shapes: PyTree,
|
|
103
|
+
) -> jax.Array:
|
|
104
|
+
"""Return the RNG key used for reproducible masking inside apply."""
|
|
105
|
+
del data_shapes
|
|
106
|
+
return rng
|
|
107
|
+
|
|
108
|
+
def prepare_masked_gene_batch(
|
|
109
|
+
self,
|
|
110
|
+
data: PyTree,
|
|
111
|
+
random_params: Any,
|
|
112
|
+
) -> tuple[Array, Array, Array]:
|
|
113
|
+
"""Prepare shared masked-gene inputs for per-cell `vmap` execution."""
|
|
114
|
+
return prepare_masked_gene_batch(
|
|
115
|
+
data,
|
|
116
|
+
random_params=random_params,
|
|
117
|
+
mask_ratio=self.config.mask_ratio,
|
|
118
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Shared validation for transformer-style operator configs."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_transformer_encoder_shape(
|
|
5
|
+
*,
|
|
6
|
+
hidden_dim: int,
|
|
7
|
+
num_layers: int,
|
|
8
|
+
num_heads: int,
|
|
9
|
+
intermediate_dim: int,
|
|
10
|
+
max_length: int,
|
|
11
|
+
dropout_rate: float,
|
|
12
|
+
) -> None:
|
|
13
|
+
"""Validate common transformer encoder hyperparameters."""
|
|
14
|
+
if hidden_dim <= 0:
|
|
15
|
+
raise ValueError("hidden_dim must be positive.")
|
|
16
|
+
if num_layers <= 0:
|
|
17
|
+
raise ValueError("num_layers must be positive.")
|
|
18
|
+
if num_heads <= 0:
|
|
19
|
+
raise ValueError("num_heads must be positive.")
|
|
20
|
+
if hidden_dim % num_heads != 0:
|
|
21
|
+
raise ValueError("hidden_dim must be divisible by num_heads.")
|
|
22
|
+
if intermediate_dim <= 0:
|
|
23
|
+
raise ValueError("intermediate_dim must be positive.")
|
|
24
|
+
if max_length <= 0:
|
|
25
|
+
raise ValueError("max_length must be positive.")
|
|
26
|
+
if not 0.0 <= dropout_rate < 1.0:
|
|
27
|
+
raise ValueError("dropout_rate must be in [0.0, 1.0).")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TransformerEncoderShapeValidationMixin:
|
|
31
|
+
"""Mixin that validates shared transformer encoder dimensions."""
|
|
32
|
+
|
|
33
|
+
hidden_dim: int
|
|
34
|
+
num_layers: int
|
|
35
|
+
num_heads: int
|
|
36
|
+
intermediate_dim: int
|
|
37
|
+
max_length: int
|
|
38
|
+
dropout_rate: float
|
|
39
|
+
|
|
40
|
+
def __post_init__(self) -> None:
|
|
41
|
+
"""Run base config validation and shared transformer checks."""
|
|
42
|
+
super().__post_init__()
|
|
43
|
+
validate_transformer_encoder_shape(
|
|
44
|
+
hidden_dim=self.hidden_dim,
|
|
45
|
+
num_layers=self.num_layers,
|
|
46
|
+
num_heads=self.num_heads,
|
|
47
|
+
intermediate_dim=self.intermediate_dim,
|
|
48
|
+
max_length=self.max_length,
|
|
49
|
+
dropout_rate=self.dropout_rate,
|
|
50
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Differentiable sequence alignment operators.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable implementations of sequence alignment
|
|
4
|
+
algorithms including smooth Smith-Waterman for local alignment,
|
|
5
|
+
profile HMM search for domain detection, and soft progressive MSA.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from diffbio.operators.alignment.profile_hmm import (
|
|
9
|
+
ProfileHMMConfig,
|
|
10
|
+
ProfileHMMSearch,
|
|
11
|
+
)
|
|
12
|
+
from diffbio.operators.alignment.scoring import (
|
|
13
|
+
PROTEIN_ALPHABET,
|
|
14
|
+
ScoringMatrix,
|
|
15
|
+
create_dna_scoring_matrix,
|
|
16
|
+
create_rna_scoring_matrix,
|
|
17
|
+
get_blosum62,
|
|
18
|
+
get_dna_simple,
|
|
19
|
+
get_rna_simple,
|
|
20
|
+
)
|
|
21
|
+
from diffbio.operators.alignment.smith_waterman import (
|
|
22
|
+
AlignmentResult,
|
|
23
|
+
SmithWatermanConfig,
|
|
24
|
+
SmoothSmithWaterman,
|
|
25
|
+
)
|
|
26
|
+
from diffbio.operators.alignment.soft_msa import (
|
|
27
|
+
SoftProgressiveMSA,
|
|
28
|
+
SoftProgressiveMSAConfig,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Profile HMM
|
|
34
|
+
"ProfileHMMConfig",
|
|
35
|
+
"ProfileHMMSearch",
|
|
36
|
+
# Scoring
|
|
37
|
+
"PROTEIN_ALPHABET",
|
|
38
|
+
"ScoringMatrix",
|
|
39
|
+
"create_dna_scoring_matrix",
|
|
40
|
+
"create_rna_scoring_matrix",
|
|
41
|
+
"get_blosum62",
|
|
42
|
+
"get_dna_simple",
|
|
43
|
+
"get_rna_simple",
|
|
44
|
+
# Smith-Waterman
|
|
45
|
+
"AlignmentResult",
|
|
46
|
+
"SmithWatermanConfig",
|
|
47
|
+
"SmoothSmithWaterman",
|
|
48
|
+
# Soft MSA
|
|
49
|
+
"SoftProgressiveMSA",
|
|
50
|
+
"SoftProgressiveMSAConfig",
|
|
51
|
+
]
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""Profile HMM search operator for HMMER-style sequence alignment.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable implementation of profile HMM
|
|
4
|
+
search, enabling gradient-based learning of profile parameters.
|
|
5
|
+
|
|
6
|
+
Key technique: Use the forward algorithm with logsumexp for
|
|
7
|
+
differentiable profile-sequence alignment scoring.
|
|
8
|
+
|
|
9
|
+
Applications: Protein domain detection, remote homology search.
|
|
10
|
+
|
|
11
|
+
Inherits from TemperatureOperator to get:
|
|
12
|
+
|
|
13
|
+
- _temperature property for temperature-controlled smoothing
|
|
14
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
15
|
+
- soft_argmax() for soft position selection
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import jax
|
|
23
|
+
import jax.numpy as jnp
|
|
24
|
+
from datarax.core.config import OperatorConfig
|
|
25
|
+
from flax import nnx
|
|
26
|
+
from jaxtyping import Array, Float, PyTree
|
|
27
|
+
|
|
28
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ProfileHMMConfig(OperatorConfig):
|
|
35
|
+
"""Configuration for ProfileHMMSearch.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
profile_length: Length of the profile (number of match states).
|
|
39
|
+
alphabet_size: Size of sequence alphabet (20 for protein, 4 for DNA).
|
|
40
|
+
temperature: Temperature for softmax operations.
|
|
41
|
+
learnable_profile: Whether profile parameters are learnable.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
cacheable: bool = True
|
|
45
|
+
profile_length: int = 100
|
|
46
|
+
alphabet_size: int = 20 # Amino acids by default
|
|
47
|
+
temperature: float = 1.0
|
|
48
|
+
learnable_profile: bool = True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProfileHMMSearch(TemperatureOperator):
|
|
52
|
+
"""Profile HMM search with differentiable scoring.
|
|
53
|
+
|
|
54
|
+
This operator implements a simplified profile HMM with match, insert,
|
|
55
|
+
and delete states. The forward algorithm computes the alignment score
|
|
56
|
+
differentiably using logsumexp.
|
|
57
|
+
|
|
58
|
+
Profile HMM structure (per position):
|
|
59
|
+
- Match state: emits according to position-specific distribution
|
|
60
|
+
- Insert state: emits according to background distribution
|
|
61
|
+
- Delete state: silent (no emission)
|
|
62
|
+
|
|
63
|
+
Transitions:
|
|
64
|
+
- M->M, M->I, M->D (from match)
|
|
65
|
+
- I->M, I->I (from insert)
|
|
66
|
+
- D->M, D->D (from delete)
|
|
67
|
+
|
|
68
|
+
Inherits from TemperatureOperator to get:
|
|
69
|
+
|
|
70
|
+
- _temperature property for temperature-controlled smoothing
|
|
71
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
72
|
+
- soft_argmax() for soft position selection
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
config: ProfileHMMConfig with model parameters.
|
|
76
|
+
rngs: Flax NNX random number generators.
|
|
77
|
+
name: Optional operator name.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
```python
|
|
81
|
+
config = ProfileHMMConfig(profile_length=100, alphabet_size=20)
|
|
82
|
+
profiler = ProfileHMMSearch(config, rngs=nnx.Rngs(42))
|
|
83
|
+
data = {"sequence": one_hot_sequence}
|
|
84
|
+
result, state, meta = profiler.apply(data, {}, None)
|
|
85
|
+
```
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
config: ProfileHMMConfig,
|
|
91
|
+
*,
|
|
92
|
+
rngs: nnx.Rngs | None = None,
|
|
93
|
+
name: str | None = None,
|
|
94
|
+
):
|
|
95
|
+
"""Initialize the profile HMM operator.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
config: Profile HMM configuration.
|
|
99
|
+
rngs: Random number generators for initialization.
|
|
100
|
+
name: Optional operator name.
|
|
101
|
+
"""
|
|
102
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
103
|
+
|
|
104
|
+
if rngs is None:
|
|
105
|
+
rngs = nnx.Rngs(0)
|
|
106
|
+
|
|
107
|
+
self.profile_length = config.profile_length
|
|
108
|
+
self.alphabet_size = config.alphabet_size
|
|
109
|
+
# Temperature is now managed by TemperatureOperator via self._temperature
|
|
110
|
+
|
|
111
|
+
# Initialize match emissions (profile_length, alphabet_size)
|
|
112
|
+
key = rngs.params()
|
|
113
|
+
init_match = jax.random.normal(key, (config.profile_length, config.alphabet_size)) * 0.1
|
|
114
|
+
self.log_match_emissions = nnx.Param(init_match)
|
|
115
|
+
|
|
116
|
+
# Initialize insert emissions (profile_length, alphabet_size)
|
|
117
|
+
# Insert states use near-uniform distribution
|
|
118
|
+
key = rngs.params()
|
|
119
|
+
init_insert = jax.random.normal(key, (config.profile_length, config.alphabet_size)) * 0.01
|
|
120
|
+
self.log_insert_emissions = nnx.Param(init_insert)
|
|
121
|
+
|
|
122
|
+
# Initialize transition parameters
|
|
123
|
+
# For each position: [M->M, M->I, M->D, I->M, I->I, D->M, D->D]
|
|
124
|
+
key = rngs.params()
|
|
125
|
+
# Default: prefer M->M transitions
|
|
126
|
+
init_trans = jnp.zeros((config.profile_length, 7))
|
|
127
|
+
init_trans = init_trans.at[:, 0].set(2.0) # M->M bias
|
|
128
|
+
init_trans = init_trans.at[:, 3].set(1.0) # I->M bias
|
|
129
|
+
init_trans = init_trans.at[:, 5].set(1.0) # D->M bias
|
|
130
|
+
init_trans = init_trans + jax.random.normal(key, init_trans.shape) * 0.1
|
|
131
|
+
self.log_transitions = nnx.Param(init_trans)
|
|
132
|
+
|
|
133
|
+
def get_match_emissions(self) -> Float[Array, "profile_length alphabet_size"]:
|
|
134
|
+
"""Get normalized match emission probabilities.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Log match emission probabilities.
|
|
138
|
+
"""
|
|
139
|
+
return jax.nn.log_softmax(self.log_match_emissions[...] / self._temperature, axis=1)
|
|
140
|
+
|
|
141
|
+
def get_insert_emissions(self) -> Float[Array, "profile_length alphabet_size"]:
|
|
142
|
+
"""Get normalized insert emission probabilities.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Log insert emission probabilities.
|
|
146
|
+
"""
|
|
147
|
+
return jax.nn.log_softmax(self.log_insert_emissions[...] / self._temperature, axis=1)
|
|
148
|
+
|
|
149
|
+
def get_transitions(self) -> dict[str, Float[Array, "profile_length"]]:
|
|
150
|
+
"""Get normalized transition probabilities.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary with log transition probabilities for each type.
|
|
154
|
+
"""
|
|
155
|
+
trans = self.log_transitions[...]
|
|
156
|
+
|
|
157
|
+
# Normalize M->* transitions (positions 0, 1, 2)
|
|
158
|
+
log_m_trans = jax.nn.log_softmax(trans[:, :3] / self._temperature, axis=1)
|
|
159
|
+
|
|
160
|
+
# Normalize I->* transitions (positions 3, 4)
|
|
161
|
+
log_i_trans = jax.nn.log_softmax(trans[:, 3:5] / self._temperature, axis=1)
|
|
162
|
+
|
|
163
|
+
# Normalize D->* transitions (positions 5, 6)
|
|
164
|
+
log_d_trans = jax.nn.log_softmax(trans[:, 5:7] / self._temperature, axis=1)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"m_to_m": log_m_trans[:, 0],
|
|
168
|
+
"m_to_i": log_m_trans[:, 1],
|
|
169
|
+
"m_to_d": log_m_trans[:, 2],
|
|
170
|
+
"i_to_m": log_i_trans[:, 0],
|
|
171
|
+
"i_to_i": log_i_trans[:, 1],
|
|
172
|
+
"d_to_m": log_d_trans[:, 0],
|
|
173
|
+
"d_to_d": log_d_trans[:, 1],
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def score_sequence(
|
|
177
|
+
self,
|
|
178
|
+
sequence: Float[Array, "seq_len alphabet_size"],
|
|
179
|
+
) -> Float[Array, ""]:
|
|
180
|
+
"""Score a sequence against the profile using forward algorithm.
|
|
181
|
+
|
|
182
|
+
Computes log P(sequence | profile) using dynamic programming.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
sequence: One-hot encoded sequence.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Log probability (alignment score).
|
|
189
|
+
"""
|
|
190
|
+
seq_len = sequence.shape[0]
|
|
191
|
+
log_match = self.get_match_emissions()
|
|
192
|
+
log_insert = self.get_insert_emissions()
|
|
193
|
+
trans = self.get_transitions()
|
|
194
|
+
|
|
195
|
+
# DP matrices: (seq_pos, profile_pos)
|
|
196
|
+
# We use a simplified forward algorithm
|
|
197
|
+
|
|
198
|
+
# Emission scores for each sequence position at each profile position
|
|
199
|
+
# match_scores[i, j] = log P(seq[i] | Match[j])
|
|
200
|
+
match_scores = jnp.einsum("sa,pa->sp", sequence, jnp.exp(log_match))
|
|
201
|
+
match_scores = jnp.log(match_scores + 1e-10)
|
|
202
|
+
|
|
203
|
+
# Insert scores per position (use position-specific insert emissions)
|
|
204
|
+
insert_scores = jnp.einsum("sa,pa->sp", sequence, jnp.exp(log_insert))
|
|
205
|
+
insert_scores = jnp.log(insert_scores + 1e-10)
|
|
206
|
+
|
|
207
|
+
# Initialize DP
|
|
208
|
+
# log_M[j] = log probability of being in Match state j
|
|
209
|
+
# log_I[j] = log probability of being in Insert state j
|
|
210
|
+
# log_D[j] = log probability of being in Delete state j
|
|
211
|
+
|
|
212
|
+
neg_inf = -1e10
|
|
213
|
+
|
|
214
|
+
# Initial state: can start at any match state with decreasing probability
|
|
215
|
+
# or go through leading deletes
|
|
216
|
+
log_M = jnp.full(self.profile_length, neg_inf)
|
|
217
|
+
log_I = jnp.full(self.profile_length, neg_inf)
|
|
218
|
+
log_D = jnp.full(self.profile_length, neg_inf)
|
|
219
|
+
|
|
220
|
+
# Start: emit first sequence position at first match state
|
|
221
|
+
log_M = log_M.at[0].set(match_scores[0, 0])
|
|
222
|
+
log_I = log_I.at[0].set(insert_scores[0, 0])
|
|
223
|
+
|
|
224
|
+
# Forward pass
|
|
225
|
+
def forward_step(carry, seq_idx):
|
|
226
|
+
log_M, log_I, log_D = carry
|
|
227
|
+
obs_match = match_scores[seq_idx]
|
|
228
|
+
obs_insert = insert_scores[seq_idx]
|
|
229
|
+
|
|
230
|
+
# New M states: can come from M, I, or D at previous profile position
|
|
231
|
+
# M[j] <- M[j-1] + M->M + emit[j]
|
|
232
|
+
# <- I[j-1] + I->M + emit[j]
|
|
233
|
+
# <- D[j-1] + D->M + emit[j]
|
|
234
|
+
new_log_M = jnp.full(self.profile_length, neg_inf)
|
|
235
|
+
|
|
236
|
+
# From previous M (M[j-1] -> M[j] for j=1..L-1)
|
|
237
|
+
from_M = log_M[:-1] + trans["m_to_m"][:-1]
|
|
238
|
+
# From previous I (I[j-1] -> M[j] for j=1..L-1)
|
|
239
|
+
from_I = log_I[:-1] + trans["i_to_m"][:-1]
|
|
240
|
+
# From previous D (D[j-1] -> M[j] for j=1..L-1)
|
|
241
|
+
from_D = log_D[:-1] + trans["d_to_m"][:-1]
|
|
242
|
+
|
|
243
|
+
# Combine and add emission
|
|
244
|
+
combined = jax.scipy.special.logsumexp(jnp.stack([from_M, from_I, from_D]), axis=0)
|
|
245
|
+
new_log_M = new_log_M.at[1:].set(combined + obs_match[1:])
|
|
246
|
+
|
|
247
|
+
# Can also start fresh at position 0
|
|
248
|
+
new_log_M = new_log_M.at[0].set(
|
|
249
|
+
jax.scipy.special.logsumexp(jnp.array([new_log_M[0], obs_match[0]]))
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# New I states: can come from M or I at same profile position
|
|
253
|
+
from_M_to_I = log_M + trans["m_to_i"]
|
|
254
|
+
from_I_to_I = log_I + trans["i_to_i"]
|
|
255
|
+
new_log_I = (
|
|
256
|
+
jax.scipy.special.logsumexp(jnp.stack([from_M_to_I, from_I_to_I]), axis=0)
|
|
257
|
+
+ obs_insert
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# D states don't emit, handled separately
|
|
261
|
+
# For simplicity, we skip explicit D state tracking in emissions
|
|
262
|
+
# and just allow gaps through the transition structure
|
|
263
|
+
new_log_D = log_D # Simplified: D states updated via M transitions
|
|
264
|
+
|
|
265
|
+
return (new_log_M, new_log_I, new_log_D), None
|
|
266
|
+
|
|
267
|
+
# Scan over sequence positions (skip first, handled in init)
|
|
268
|
+
(final_M, final_I, final_D), _ = jax.lax.scan(
|
|
269
|
+
forward_step, (log_M, log_I, log_D), jnp.arange(1, seq_len)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Final score: sum over all final states
|
|
273
|
+
score = jax.scipy.special.logsumexp(jnp.concatenate([final_M, final_I]))
|
|
274
|
+
|
|
275
|
+
return score
|
|
276
|
+
|
|
277
|
+
def compute_posteriors(
|
|
278
|
+
self,
|
|
279
|
+
sequence: Float[Array, "seq_len alphabet_size"],
|
|
280
|
+
) -> Float[Array, "seq_len profile_length 3"]:
|
|
281
|
+
"""Compute state posteriors (simplified).
|
|
282
|
+
|
|
283
|
+
Returns soft alignment between sequence and profile positions.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
sequence: One-hot encoded sequence.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Posterior probabilities for M/I/D states at each position.
|
|
290
|
+
"""
|
|
291
|
+
seq_len = sequence.shape[0]
|
|
292
|
+
log_match = self.get_match_emissions()
|
|
293
|
+
|
|
294
|
+
# Simplified: just compute match scores as posteriors
|
|
295
|
+
match_scores = jnp.einsum("sa,pa->sp", sequence, jnp.exp(log_match))
|
|
296
|
+
|
|
297
|
+
# Normalize across profile positions for each sequence position
|
|
298
|
+
posteriors = jax.nn.softmax(match_scores / self._temperature, axis=1)
|
|
299
|
+
|
|
300
|
+
# Expand to include I/D placeholder dimensions
|
|
301
|
+
# Shape: (seq_len, profile_length, 3) where dim 2 is [M, I, D]
|
|
302
|
+
full_posteriors = jnp.zeros((seq_len, self.profile_length, 3))
|
|
303
|
+
full_posteriors = full_posteriors.at[:, :, 0].set(posteriors)
|
|
304
|
+
|
|
305
|
+
return full_posteriors
|
|
306
|
+
|
|
307
|
+
def apply(
|
|
308
|
+
self,
|
|
309
|
+
data: PyTree,
|
|
310
|
+
state: PyTree,
|
|
311
|
+
metadata: dict[str, Any] | None,
|
|
312
|
+
random_params: Any = None,
|
|
313
|
+
stats: dict[str, Any] | None = None,
|
|
314
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
315
|
+
"""Apply profile HMM search to sequence.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
data: Dictionary containing:
|
|
319
|
+
- "sequence": One-hot encoded sequence (seq_len, alphabet_size)
|
|
320
|
+
state: Element state (passed through unchanged)
|
|
321
|
+
metadata: Element metadata (passed through unchanged)
|
|
322
|
+
random_params: Not used (deterministic operator)
|
|
323
|
+
stats: Not used
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Tuple of (transformed_data, state, metadata):
|
|
327
|
+
- transformed_data contains:
|
|
328
|
+
|
|
329
|
+
- "sequence": Original sequence
|
|
330
|
+
- "score": Profile alignment score
|
|
331
|
+
- "state_posteriors": Soft state assignments
|
|
332
|
+
- state is passed through unchanged
|
|
333
|
+
- metadata is passed through unchanged
|
|
334
|
+
"""
|
|
335
|
+
sequence = data["sequence"]
|
|
336
|
+
|
|
337
|
+
# Compute alignment score
|
|
338
|
+
score = self.score_sequence(sequence)
|
|
339
|
+
|
|
340
|
+
# Compute state posteriors
|
|
341
|
+
state_posteriors = self.compute_posteriors(sequence)
|
|
342
|
+
|
|
343
|
+
# Build output data
|
|
344
|
+
transformed_data = {
|
|
345
|
+
"sequence": sequence,
|
|
346
|
+
"score": score,
|
|
347
|
+
"state_posteriors": state_posteriors,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return transformed_data, state, metadata
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Scoring matrices for sequence alignment.
|
|
2
|
+
|
|
3
|
+
This module provides pre-defined scoring matrices and utilities for
|
|
4
|
+
creating custom scoring matrices for DNA, RNA, and protein alignment.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import logging
|
|
9
|
+
from typing import NamedTuple
|
|
10
|
+
|
|
11
|
+
import jax.numpy as jnp
|
|
12
|
+
from jaxtyping import Array, Float
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ScoringMatrix(NamedTuple):
|
|
18
|
+
"""Scoring matrix with metadata.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
matrix: The scoring matrix array.
|
|
22
|
+
alphabet: The alphabet string (e.g., "ACGT" for DNA).
|
|
23
|
+
name: Optional name for the matrix.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
matrix: Float[Array, "alphabet alphabet"]
|
|
27
|
+
alphabet: str
|
|
28
|
+
name: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_dna_scoring_matrix(
|
|
32
|
+
match: float = 2.0,
|
|
33
|
+
mismatch: float = -1.0,
|
|
34
|
+
) -> Float[Array, "4 4"]:
|
|
35
|
+
"""Create a simple DNA scoring matrix.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
match: Score for matching nucleotides (diagonal).
|
|
39
|
+
mismatch: Score for mismatching nucleotides (off-diagonal).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
4x4 scoring matrix for DNA (A, C, G, T order).
|
|
43
|
+
"""
|
|
44
|
+
# Create identity matrix scaled by match score
|
|
45
|
+
identity = jnp.eye(4) * match
|
|
46
|
+
# Create off-diagonal with mismatch score
|
|
47
|
+
off_diag = (jnp.ones((4, 4)) - jnp.eye(4)) * mismatch
|
|
48
|
+
return identity + off_diag
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_rna_scoring_matrix(
|
|
52
|
+
match: float = 2.0,
|
|
53
|
+
mismatch: float = -1.0,
|
|
54
|
+
) -> Float[Array, "4 4"]:
|
|
55
|
+
"""Create a simple RNA scoring matrix.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
match: Score for matching nucleotides (diagonal).
|
|
59
|
+
mismatch: Score for mismatching nucleotides (off-diagonal).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
4x4 scoring matrix for RNA (A, C, G, U order).
|
|
63
|
+
"""
|
|
64
|
+
# RNA uses same simple scoring as DNA
|
|
65
|
+
return create_dna_scoring_matrix(match, mismatch)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@functools.cache
|
|
69
|
+
def get_dna_simple() -> Float[Array, "4 4"]:
|
|
70
|
+
"""Get pre-defined DNA scoring matrix (simple match/mismatch).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
4x4 scoring matrix with match=2.0, mismatch=-1.0.
|
|
74
|
+
"""
|
|
75
|
+
return create_dna_scoring_matrix(match=2.0, mismatch=-1.0)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@functools.cache
|
|
79
|
+
def get_rna_simple() -> Float[Array, "4 4"]:
|
|
80
|
+
"""Get pre-defined RNA scoring matrix (simple match/mismatch).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
4x4 scoring matrix with match=2.0, mismatch=-1.0.
|
|
84
|
+
"""
|
|
85
|
+
return create_rna_scoring_matrix(match=2.0, mismatch=-1.0)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# BLOSUM62 scoring matrix for proteins (20 amino acids)
|
|
89
|
+
# Standard BLOSUM62 matrix values
|
|
90
|
+
# Amino acid order: A, R, N, D, C, Q, E, G, H, I, L, K, M, F, P, S, T, W, Y, V
|
|
91
|
+
_BLOSUM62_VALUES = [
|
|
92
|
+
# A R N D C Q E G H I L K M F P S T W Y V
|
|
93
|
+
[4, -1, -2, -2, 0, -1, -1, 0, -2, -1, -1, -1, -1, -2, -1, 1, 0, -3, -2, 0], # A
|
|
94
|
+
[-1, 5, 0, -2, -3, 1, 0, -2, 0, -3, -2, 2, -1, -3, -2, -1, -1, -3, -2, -3], # R
|
|
95
|
+
[-2, 0, 6, 1, -3, 0, 0, 0, 1, -3, -3, 0, -2, -3, -2, 1, 0, -4, -2, -3], # N
|
|
96
|
+
[-2, -2, 1, 6, -3, 0, 2, -1, -1, -3, -4, -1, -3, -3, -1, 0, -1, -4, -3, -3], # D
|
|
97
|
+
[0, -3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1, -1, -2, -2, -1], # C
|
|
98
|
+
[-1, 1, 0, 0, -3, 5, 2, -2, 0, -3, -2, 1, 0, -3, -1, 0, -1, -2, -1, -2], # Q
|
|
99
|
+
[-1, 0, 0, 2, -4, 2, 5, -2, 0, -3, -3, 1, -2, -3, -1, 0, -1, -3, -2, -2], # E
|
|
100
|
+
[0, -2, 0, -1, -3, -2, -2, 6, -2, -4, -4, -2, -3, -3, -2, 0, -2, -2, -3, -3], # G
|
|
101
|
+
[-2, 0, 1, -1, -3, 0, 0, -2, 8, -3, -3, -1, -2, -1, -2, -1, -2, -2, 2, -3], # H
|
|
102
|
+
[-1, -3, -3, -3, -1, -3, -3, -4, -3, 4, 2, -3, 1, 0, -3, -2, -1, -3, -1, 3], # I
|
|
103
|
+
[-1, -2, -3, -4, -1, -2, -3, -4, -3, 2, 4, -2, 2, 0, -3, -2, -1, -2, -1, 1], # L
|
|
104
|
+
[-1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -2, 5, -1, -3, -1, 0, -1, -3, -2, -2], # K
|
|
105
|
+
[-1, -1, -2, -3, -1, 0, -2, -3, -2, 1, 2, -1, 5, 0, -2, -1, -1, -1, -1, 1], # M
|
|
106
|
+
[-2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6, -4, -2, -2, 1, 3, -1], # F
|
|
107
|
+
[-1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7, -1, -1, -4, -3, -2], # P
|
|
108
|
+
[1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4, 1, -3, -2, -2], # S
|
|
109
|
+
[0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 5, -2, -2, 0], # T
|
|
110
|
+
[-3, -3, -4, -4, -2, -2, -3, -2, -2, -3, -2, -3, -1, 1, -4, -3, -2, 11, 2, -3], # W
|
|
111
|
+
[-2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 7, -1], # Y
|
|
112
|
+
[0, -3, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3, -1, 4], # V
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@functools.cache
|
|
117
|
+
def get_blosum62() -> Float[Array, "20 20"]:
|
|
118
|
+
"""Get BLOSUM62 scoring matrix for protein alignment.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
20x20 scoring matrix for 20 standard amino acids.
|
|
122
|
+
"""
|
|
123
|
+
return jnp.array(_BLOSUM62_VALUES, dtype=jnp.float32)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Amino acid alphabet for BLOSUM62
|
|
127
|
+
PROTEIN_ALPHABET = "ARNDCQEGHILKMFPSTWYV"
|