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,336 @@
|
|
|
1
|
+
"""Differentiable gene regulatory network inference.
|
|
2
|
+
|
|
3
|
+
This module provides a differentiable alternative to GENIE3/SCENIC for gene
|
|
4
|
+
regulatory network (GRN) inference from single-cell expression data. Instead
|
|
5
|
+
of random forest feature importance, it uses GATv2 graph attention on a
|
|
6
|
+
TF-gene bipartite graph to learn regulatory strengths.
|
|
7
|
+
|
|
8
|
+
Key technique: attention weights on a dense bipartite graph between
|
|
9
|
+
transcription factors and target genes serve as a differentiable proxy for
|
|
10
|
+
regulatory importance scores. Soft L1 sparsity via sigmoid gating promotes
|
|
11
|
+
biologically realistic sparse networks.
|
|
12
|
+
|
|
13
|
+
Algorithm:
|
|
14
|
+
1. Build a TF-gene bipartite graph (every TF connected to every gene).
|
|
15
|
+
2. Compute per-edge features from expression: concatenation of TF
|
|
16
|
+
expression, gene expression, and absolute expression difference.
|
|
17
|
+
3. Apply GATv2 attention -- attention weights between TF-gene pairs
|
|
18
|
+
represent regulatory strength.
|
|
19
|
+
4. Extract attention weights as the GRN adjacency matrix.
|
|
20
|
+
5. Apply soft L1 sparsity: ``grn * sigmoid(grn / temperature)``.
|
|
21
|
+
6. Compute TF activity: ``counts[:, tf_indices] @ grn_matrix``.
|
|
22
|
+
|
|
23
|
+
Applications: SCENIC/GENIE3-style regulatory network reconstruction,
|
|
24
|
+
transcription factor activity estimation, regulon discovery.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
import jax.numpy as jnp
|
|
32
|
+
from datarax.core.config import OperatorConfig
|
|
33
|
+
from datarax.core.operator import OperatorModule
|
|
34
|
+
from flax import nnx
|
|
35
|
+
from jaxtyping import Array, Float, Int, PyTree
|
|
36
|
+
|
|
37
|
+
from diffbio.constants import EPSILON
|
|
38
|
+
from diffbio.core import soft_ops
|
|
39
|
+
from diffbio.core.gnn_components import GATv2Layer
|
|
40
|
+
from diffbio.utils.nn_utils import ensure_rngs
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class GRNInferenceConfig(OperatorConfig):
|
|
47
|
+
"""Configuration for differentiable GRN inference.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
n_tfs: Number of transcription factors.
|
|
51
|
+
n_genes: Number of genes in the expression matrix.
|
|
52
|
+
hidden_dim: Hidden dimension for GATv2 attention (must be divisible
|
|
53
|
+
by num_heads).
|
|
54
|
+
num_heads: Number of attention heads in the GATv2 layer.
|
|
55
|
+
sparsity_temperature: Temperature for soft L1 sparsity gating.
|
|
56
|
+
Lower values produce sharper thresholding toward zero.
|
|
57
|
+
sparsity_lambda: L1 regularization weight (used by downstream loss
|
|
58
|
+
functions, not directly by the operator).
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
n_tfs: int = 50
|
|
62
|
+
n_genes: int = 2000
|
|
63
|
+
hidden_dim: int = 64
|
|
64
|
+
num_heads: int = 4
|
|
65
|
+
sparsity_temperature: float = 0.1
|
|
66
|
+
sparsity_lambda: float = 0.01
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DifferentiableGRN(OperatorModule):
|
|
70
|
+
"""Differentiable gene regulatory network inference operator.
|
|
71
|
+
|
|
72
|
+
Uses GATv2 graph attention on a TF-gene bipartite graph to infer
|
|
73
|
+
regulatory strengths. Each TF is connected to every gene; the attention
|
|
74
|
+
weight on each edge represents how strongly the TF regulates that gene.
|
|
75
|
+
|
|
76
|
+
This is a novel differentiable alternative to GENIE3's random forest
|
|
77
|
+
feature importance scoring. The key insight is that in GENIE3, each
|
|
78
|
+
gene's expression is predicted from TF expression, and feature importance
|
|
79
|
+
measures regulatory strength. Here, GATv2 attention performs an analogous
|
|
80
|
+
role: TF nodes attend to gene nodes, and the learned attention weights
|
|
81
|
+
capture regulatory relationships.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
config: GRNInferenceConfig with model parameters.
|
|
85
|
+
rngs: Flax NNX random number generators.
|
|
86
|
+
name: Optional operator name.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> config = GRNInferenceConfig(n_tfs=5, n_genes=20, hidden_dim=16)
|
|
90
|
+
>>> op = DifferentiableGRN(config, rngs=nnx.Rngs(0))
|
|
91
|
+
>>> data = {"counts": counts, "tf_indices": jnp.arange(5)}
|
|
92
|
+
>>> result, state, meta = op.apply(data, {}, None)
|
|
93
|
+
>>> result["grn_matrix"].shape
|
|
94
|
+
(5, 20)
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
config: GRNInferenceConfig,
|
|
100
|
+
*,
|
|
101
|
+
rngs: nnx.Rngs | None = None,
|
|
102
|
+
name: str | None = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Initialize the GRN inference operator.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
config: GRN inference configuration.
|
|
108
|
+
rngs: Random number generators for parameter initialization.
|
|
109
|
+
name: Optional operator name.
|
|
110
|
+
"""
|
|
111
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
112
|
+
|
|
113
|
+
rngs = ensure_rngs(rngs)
|
|
114
|
+
|
|
115
|
+
self.n_tfs = config.n_tfs
|
|
116
|
+
self.n_genes = config.n_genes
|
|
117
|
+
self.hidden_dim = config.hidden_dim
|
|
118
|
+
self.sparsity_temperature = config.sparsity_temperature
|
|
119
|
+
|
|
120
|
+
# Project 1-d expression scalars to hidden_dim for each node
|
|
121
|
+
self.node_proj = nnx.Linear(
|
|
122
|
+
in_features=1,
|
|
123
|
+
out_features=config.hidden_dim,
|
|
124
|
+
rngs=rngs,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# GATv2 layer: attention on the bipartite graph
|
|
128
|
+
# Edge features: [tf_expr, gene_expr, |tf_expr - gene_expr|] -> dim 3
|
|
129
|
+
self.gat_layer = GATv2Layer(
|
|
130
|
+
in_features=config.hidden_dim,
|
|
131
|
+
out_features=config.hidden_dim,
|
|
132
|
+
num_heads=config.num_heads,
|
|
133
|
+
edge_features=3,
|
|
134
|
+
dropout_rate=0.0,
|
|
135
|
+
rngs=rngs,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Projection from GATv2 output to scalar regulatory score per edge
|
|
139
|
+
self.score_proj = nnx.Linear(
|
|
140
|
+
in_features=config.hidden_dim * 2,
|
|
141
|
+
out_features=1,
|
|
142
|
+
rngs=rngs,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _build_bipartite_graph(
|
|
146
|
+
self,
|
|
147
|
+
n_tfs: int,
|
|
148
|
+
n_genes: int,
|
|
149
|
+
) -> Int[Array, "2 n_edges"]:
|
|
150
|
+
"""Build dense bipartite edge index between TFs and genes.
|
|
151
|
+
|
|
152
|
+
TF nodes are indexed ``[0, n_tfs)``, gene nodes are indexed
|
|
153
|
+
``[n_tfs, n_tfs + n_genes)``. Every TF is connected to every gene.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
n_tfs: Number of transcription factors.
|
|
157
|
+
n_genes: Number of genes.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Edge index array of shape ``(2, n_tfs * n_genes)`` where row 0
|
|
161
|
+
is source (TF) indices and row 1 is target (gene) indices.
|
|
162
|
+
"""
|
|
163
|
+
# TF indices: 0..n_tfs-1, gene indices: n_tfs..n_tfs+n_genes-1
|
|
164
|
+
tf_ids = jnp.arange(n_tfs)
|
|
165
|
+
gene_ids = jnp.arange(n_tfs, n_tfs + n_genes)
|
|
166
|
+
|
|
167
|
+
# Dense bipartite: every TF connected to every gene
|
|
168
|
+
# sources: each TF repeated n_genes times
|
|
169
|
+
sources = jnp.repeat(tf_ids, n_genes)
|
|
170
|
+
# targets: gene_ids tiled n_tfs times
|
|
171
|
+
targets = jnp.tile(gene_ids, n_tfs)
|
|
172
|
+
|
|
173
|
+
return jnp.stack([sources, targets], axis=0)
|
|
174
|
+
|
|
175
|
+
def _compute_edge_features(
|
|
176
|
+
self,
|
|
177
|
+
mean_counts: Float[Array, "n_genes"],
|
|
178
|
+
tf_indices: Int[Array, "n_tfs"],
|
|
179
|
+
) -> Float[Array, "n_edges 3"]:
|
|
180
|
+
"""Compute per-edge expression features for the bipartite graph.
|
|
181
|
+
|
|
182
|
+
For each TF-gene edge, the feature vector is
|
|
183
|
+
``[tf_mean_expr, gene_mean_expr, |tf_mean_expr - gene_mean_expr|]``.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
mean_counts: Mean expression per gene across cells ``(n_genes,)``.
|
|
187
|
+
tf_indices: Indices of TF genes in the expression matrix.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Edge features of shape ``(n_tfs * n_genes, 3)``.
|
|
191
|
+
"""
|
|
192
|
+
n_tfs = tf_indices.shape[0]
|
|
193
|
+
n_genes = mean_counts.shape[0]
|
|
194
|
+
|
|
195
|
+
tf_expr = mean_counts[tf_indices] # (n_tfs,)
|
|
196
|
+
|
|
197
|
+
# Expand to edge level: each TF expression repeated n_genes times
|
|
198
|
+
tf_expr_edges = jnp.repeat(tf_expr, n_genes) # (n_tfs * n_genes,)
|
|
199
|
+
gene_expr_edges = jnp.tile(mean_counts, n_tfs) # (n_tfs * n_genes,)
|
|
200
|
+
|
|
201
|
+
abs_diff = jnp.abs(tf_expr_edges - gene_expr_edges)
|
|
202
|
+
|
|
203
|
+
return jnp.stack([tf_expr_edges, gene_expr_edges, abs_diff], axis=-1)
|
|
204
|
+
|
|
205
|
+
def _extract_grn_from_attention(
|
|
206
|
+
self,
|
|
207
|
+
node_features_updated: Float[Array, "n_nodes hidden_dim"],
|
|
208
|
+
edge_index: Int[Array, "2 n_edges"],
|
|
209
|
+
n_tfs: int,
|
|
210
|
+
n_genes: int,
|
|
211
|
+
) -> Float[Array, "n_tfs n_genes"]:
|
|
212
|
+
"""Extract GRN matrix from updated node representations.
|
|
213
|
+
|
|
214
|
+
Computes a regulatory score for each TF-gene pair by concatenating
|
|
215
|
+
the updated TF and gene node features and projecting to a scalar.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
node_features_updated: Updated node features from GATv2.
|
|
219
|
+
edge_index: Bipartite edge index ``(2, n_edges)``.
|
|
220
|
+
n_tfs: Number of TFs.
|
|
221
|
+
n_genes: Number of genes.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Raw GRN matrix of shape ``(n_tfs, n_genes)``.
|
|
225
|
+
"""
|
|
226
|
+
sources = edge_index[0] # TF node indices
|
|
227
|
+
targets = edge_index[1] # Gene node indices
|
|
228
|
+
|
|
229
|
+
# Concatenate source (TF) and target (gene) features per edge
|
|
230
|
+
src_features = node_features_updated[sources] # (n_edges, hidden_dim)
|
|
231
|
+
tgt_features = node_features_updated[targets] # (n_edges, hidden_dim)
|
|
232
|
+
edge_repr = jnp.concatenate([src_features, tgt_features], axis=-1)
|
|
233
|
+
|
|
234
|
+
# Project to scalar score per edge
|
|
235
|
+
scores = self.score_proj(edge_repr).squeeze(-1) # (n_edges,)
|
|
236
|
+
|
|
237
|
+
# Reshape to (n_tfs, n_genes)
|
|
238
|
+
return scores.reshape(n_tfs, n_genes)
|
|
239
|
+
|
|
240
|
+
def _apply_soft_sparsity(
|
|
241
|
+
self,
|
|
242
|
+
grn_matrix: Float[Array, "n_tfs n_genes"],
|
|
243
|
+
) -> Float[Array, "n_tfs n_genes"]:
|
|
244
|
+
"""Apply soft L1 sparsity via sigmoid gating.
|
|
245
|
+
|
|
246
|
+
Implements ``grn * sigmoid(grn / temperature)`` which pushes small
|
|
247
|
+
values toward zero while preserving strong regulatory signals.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
grn_matrix: Raw GRN scores.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Sparsified GRN matrix.
|
|
254
|
+
"""
|
|
255
|
+
gate = soft_ops.greater(grn_matrix, 0.0, softness=self.sparsity_temperature + EPSILON)
|
|
256
|
+
return grn_matrix * gate
|
|
257
|
+
|
|
258
|
+
def apply(
|
|
259
|
+
self,
|
|
260
|
+
data: PyTree,
|
|
261
|
+
state: PyTree,
|
|
262
|
+
metadata: dict[str, Any] | None,
|
|
263
|
+
random_params: Any = None,
|
|
264
|
+
stats: dict[str, Any] | None = None,
|
|
265
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
266
|
+
"""Apply differentiable GRN inference.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
data: Dictionary containing:
|
|
270
|
+
- ``"counts"``: Gene expression matrix ``(n_cells, n_genes)``
|
|
271
|
+
- ``"tf_indices"``: Indices of TF genes ``(n_tfs,)``
|
|
272
|
+
state: Element state (passed through unchanged).
|
|
273
|
+
metadata: Element metadata (passed through unchanged).
|
|
274
|
+
random_params: Not used (non-stochastic operator).
|
|
275
|
+
stats: Not used.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Tuple of (transformed_data, state, metadata):
|
|
279
|
+
- transformed_data contains all original keys plus:
|
|
280
|
+
|
|
281
|
+
- ``"grn_matrix"``: Sparse regulatory matrix ``(n_tfs, n_genes)``
|
|
282
|
+
- ``"tf_activity"``: Per-cell TF activity ``(n_cells, n_tfs)``
|
|
283
|
+
- state is passed through unchanged
|
|
284
|
+
- metadata is passed through unchanged
|
|
285
|
+
"""
|
|
286
|
+
counts = data["counts"] # (n_cells, n_genes)
|
|
287
|
+
tf_indices = data["tf_indices"] # (n_tfs,)
|
|
288
|
+
|
|
289
|
+
n_tfs = tf_indices.shape[0]
|
|
290
|
+
n_genes = counts.shape[1]
|
|
291
|
+
|
|
292
|
+
# Step 1: Build bipartite graph
|
|
293
|
+
edge_index = self._build_bipartite_graph(n_tfs, n_genes)
|
|
294
|
+
|
|
295
|
+
# Step 2: Compute mean expression per gene across cells
|
|
296
|
+
mean_counts = jnp.mean(counts, axis=0) # (n_genes,)
|
|
297
|
+
|
|
298
|
+
# Step 3: Build node features -- one node per TF + one per gene
|
|
299
|
+
# TF nodes get mean TF expression, gene nodes get mean gene expression
|
|
300
|
+
tf_expr = mean_counts[tf_indices] # (n_tfs,)
|
|
301
|
+
all_expr = jnp.concatenate([tf_expr, mean_counts], axis=0) # (n_tfs + n_genes,)
|
|
302
|
+
|
|
303
|
+
# Project scalar expression to hidden_dim
|
|
304
|
+
node_features = self.node_proj(all_expr[:, None]) # (n_tfs + n_genes, hidden_dim)
|
|
305
|
+
|
|
306
|
+
# Step 4: Compute edge features
|
|
307
|
+
edge_features = self._compute_edge_features(mean_counts, tf_indices)
|
|
308
|
+
|
|
309
|
+
# Step 5: Apply GATv2 on bipartite graph
|
|
310
|
+
node_features_updated = self.gat_layer(
|
|
311
|
+
node_features,
|
|
312
|
+
edge_index,
|
|
313
|
+
edge_features,
|
|
314
|
+
deterministic=True,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Step 6: Extract GRN matrix from updated node features
|
|
318
|
+
raw_grn = self._extract_grn_from_attention(
|
|
319
|
+
node_features_updated, edge_index, n_tfs, n_genes
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Step 7: Apply soft L1 sparsity
|
|
323
|
+
grn_matrix = self._apply_soft_sparsity(raw_grn)
|
|
324
|
+
|
|
325
|
+
# Step 8: Compute TF activity per cell
|
|
326
|
+
# Each TF's activity in a cell is the sum of all gene expressions
|
|
327
|
+
# weighted by that TF's regulatory strengths: activity_tj = sum_g(expr_g * grn_tg)
|
|
328
|
+
tf_activity = counts @ grn_matrix.T # (n_cells, n_genes) @ (n_genes, n_tfs)
|
|
329
|
+
|
|
330
|
+
transformed_data = {
|
|
331
|
+
**data,
|
|
332
|
+
"grn_matrix": grn_matrix,
|
|
333
|
+
"tf_activity": tf_activity,
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return transformed_data, state, metadata
|