Single-cell and perturbations
Paired gene-embedding perturbation statistics
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Compare matched gene embeddings before and after an intervention, then compare their pooled cell embeddings to supplied state embeddings. Return gene-level distances, cell-level changes, and signed state shifts.
Function signature
embedding_perturbation_comparison(
reference_embeddings: torch.Tensor,
edited_embeddings: torch.Tensor,
state_embeddings: torch.Tensor,
reference_indices: torch.Tensor,
edited_indices: torch.Tensor,
pair_mask: torch.Tensor,
cell_mask: torch.Tensor,
eps: float = 1e-08,
) -> dict[str, torch.Tensor]Worked example
Paired reencoded genes and signed state shifts
In the first cell, both matched genes move to orthogonal vectors, so each cosine distance is1 and each squared distance is2. Their mean embedding stays [0.5,0.5], so the pooled-cell distance is0. The second cell changes from[1,0] to[0,1], giving state shifts[-1,+1]. Across both cells, mean state shifts are[-0.5,+0.5] and population variances are[0.25,0.25].
import torch
reference_embeddings = torch.tensor([[[1.0, 0.0], [0.0, 1.0], [7.0, 9.0]], [[1.0, 0.0], [3.0, 8.0], [9.0, 2.0]]], dtype=torch.float64, requires_grad=True)
edited_embeddings = torch.tensor([[[0.0, 1.0], [1.0, 0.0], [8.0, 9.0], [2.0, 5.0]], [[7.0, 2.0], [0.0, 1.0], [4.0, 6.0], [1.0, 8.0]]], dtype=torch.float64, requires_grad=True)
state_embeddings = torch.tensor([[1.0, 0.0], [0.0, 1.0]], dtype=torch.float64, requires_grad=True)
reference_indices = torch.tensor([[0, 1, -1], [0, -8, 99]], dtype=torch.int64)
edited_indices = torch.tensor([[0, 1, 30], [1, -7, -1]], dtype=torch.int64)
pair_mask = torch.tensor([[True, True, False], [True, False, False]], dtype=torch.bool)
cell_mask = torch.tensor([True, True], dtype=torch.bool)
eps = 1e-08
result = embedding_perturbation_comparison(reference_embeddings=reference_embeddings, edited_embeddings=edited_embeddings, state_embeddings=state_embeddings, reference_indices=reference_indices, edited_indices=edited_indices, pair_mask=pair_mask, cell_mask=cell_mask, eps=eps)Expected return
{
'gene_cosine': torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], dtype=torch.float64),
'gene_squared_distance': torch.tensor([[2.0, 2.0, 0.0], [2.0, 0.0, 0.0]], dtype=torch.float64),
'mean_gene_cosine_distance': torch.tensor([1.0, 1.0], dtype=torch.float64),
'reference_cell': torch.tensor([[0.5, 0.5], [1.0, 0.0]], dtype=torch.float64),
'edited_cell': torch.tensor([[0.5, 0.5], [0.0, 1.0]], dtype=torch.float64),
'cell_cosine_distance': torch.tensor([2.220446049250313e-16, 1.0], dtype=torch.float64),
'state_cosine_shift': torch.tensor([[0.0, 0.0], [-1.0, 1.0]], dtype=torch.float64),
'state_shift_mean': torch.tensor([-0.5, 0.5], dtype=torch.float64),
'state_shift_variance': torch.tensor([0.25, 0.25], dtype=torch.float64),
'comparison_count': torch.tensor([2, 1], dtype=torch.int64),
}Parameters
| Parameter | Shape and meaning |
|---|---|
reference_embeddingstorch.Tensor | [B, R, D]Independently encoded reference gene states |
edited_embeddingstorch.Tensor | [B, E, D]Gene embeddings from independently encoding the edited sequence. |
state_embeddingstorch.Tensor | [S, D]Supplied cell-state prototype embeddings. |
reference_indicestorch.Tensor | [B, K]Original reference positions of the matched genes. |
edited_indicestorch.Tensor | [B, K]Positions of the same genes in the edited sequence. |
pair_masktorch.Tensor | [B, K]True for chosen same-gene comparisons |
cell_masktorch.Tensor | [B]True for included paired cells |
epsfloatDefault 1e-08 | Length floor in [10^{-8},1]. For each cosine denominator use \sqrt{\max(\sum v^2,\varepsilon^2)} independently for the two vectors; do not add eps to their product. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Supply
reference_embeddings[B,R,D],edited_embeddings[B,E,D]andstate_embeddings[S,D]of one floating dtype (\mathrm{float32}/\mathrm{float64}), withB,R,E,D,S\ge 1. The two sequences have already been encoded separately with identical chosen model/layer settings. - Supply
int64reference_indices[B,K],edited_indices[B,K], Booleanpair_mask[B,K]andcell_mask[B],K\ge 1. Effective correspondence maskM=\mathrm{pair\_mask} \land \mathrm{cell\_mask}[:,\mathrm{None}]. - A true pair aligns the SAME unperturbed gene, even if its rank position changed; the caller excludes removed/inserted/promoted genes, padding and boundary specials. Only active indices must lie in range. Masked/unreferenced embedding payloads and inactive indices are ignored, even NaNs or arbitrary integer sentinels.
- Active gathered vectors and all state prototypes must be finite. Repeated active correspondence indices are permitted, count as repeated observations and must accumulate gradients; a biological one-gene-one-pair protocol should normally supply unique alignments. Define:
C(u,v)=\sum_{d}(u[d]\cdot v[d]) / (\sqrt{\operatorname{max}(\sum_{d} u[d]^{2},\varepsilon^{2})}\cdot \sqrt{\operatorname{max}(\sum_{d} v[d]^{2},\varepsilon^{2})}).\varepsilondefaults to10^{-8}and must lie in[10^{-8},1]. Thus a zero vector has cosine zero, and a valid zero/zero pair has cosine distance one.- On each active pair output
\mathrm{gene\_cosine}=C(r,e)and\mathrm{gene\_squared\_distance}=\sum_{d}(r-e)^{2}; output zero at inactive pairs.\mathrm{comparison\_count}[b]=n_{b}=\sum_{k} M[b,k]. - Pool BOTH
reference_cell[b,D]andedited_cell[b,D]by averaging the corresponding matched vectors overn_b, or zero whenn_{b}=0.mean_gene_cosine_distance[b]is the average of1-\mathrm{gene\_cosine}over active pairs;\mathrm{cell\_cosine\_distance}[b]=1-C(\mathrm{reference\_cell},\mathrm{edited\_cell}). - These two distances differ in general; output zero for either when
n_{b}=0. - For nonempty cells,
\mathrm{state\_cosine\_shift}[b,s]=C(\mathrm{edited\_cell}[b],\mathrm{state\_embeddings}[s])-C(\mathrm{reference\_cell}[b],\mathrm{state\_embeddings}[s]); positive means closer in cosine similarity to that supplied prototype. Empty/excluded rows are zero. - Over the
Ncells withn_{b}>0, returnstate_shift_mean[S]and\mathrm{state\_shift\_variance}[S]=\sum_{b}(\mathrm{shift}[b,s]-\operatorname{mean}[s])^{2}/N(POPULATION variance, notN-1). WhenN=0both are zero, with finite zero gradients.comparison_countisint64; every other output preserves floating dtype. - No data-dependent resampling,
p-value, encoder execution or learned classifier is involved. Active out-of-range indices, nonfinite active values/prototypes and invalid\varepsilonraiseValueError.
Why this matters
An alignment preserves gene identity across reordered sequences; pooled and per-gene cosine changes then answer different biological questions.
Starter code
You write the body of this function in the BioTorch workspace. Your code runs in an isolated PyTorch sandbox; Run checks the public examples and Submit grades the full test suite, including gradients.
import torch
def embedding_perturbation_comparison(
reference_embeddings: torch.Tensor, # (B, R, D)
edited_embeddings: torch.Tensor, # (B, E, D)
state_embeddings: torch.Tensor, # (S, D)
reference_indices: torch.Tensor, # (B, K)
edited_indices: torch.Tensor, # (B, K)
pair_mask: torch.Tensor, # (B, K)
cell_mask: torch.Tensor, # (B,)
eps: float = 1e-08,
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
gene_cosine: (B, K).
gene_squared_distance: (B, K).
mean_gene_cosine_distance: (B,).
reference_cell: (B, D).
edited_cell: (B, D).
cell_cosine_distance: (B,).
state_cosine_shift: (B, S).
state_shift_mean: (S,).
state_shift_variance: (S,).
comparison_count: (B,)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement embedding_perturbation_comparison")
Ready to implement it?
3 progressive hints, a solution walkthrough, and grading that checks values, shapes and gradients are waiting in the workspace.
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.