Evaluation
T1 — Temporal gene-expression distribution prediction. single-cell RNA · predicts expression.
Scoring
These metrics should reflect what a virtual embryo is for: predicting in silico what an embryo does that is otherwise only knowable by running the experiment. Each answers one biological question; how the questions combine into a single leaderboard number is set out at the end of this section.
Four metrics rank Task 1, each its own weighted question: DE gene recovery 25%, change direction 25%, cell-state distribution 30%, gene-gene co-variation 20%. Task 1 has no spatial component, so no shape term applies.
Notation
pred_X, true_X and ref_X are [n_cells, n_genes] log-normalised expression matrices — the submission, the held-out target, and the reference the change is measured against (the preceding stage for Tasks 1 and 2, the matched wild type for Task 3). An overbar denotes the pseudobulk, the mean over cells, a vector of length n_genes. pred_C and true_C are [n_cells, 3] coordinate matrices, Tasks 2 and 3 only.
Two properties hold by construction across the whole panel. A submission chooses its own coordinate frame — every spatial term is invariant to translation and rotation, and outside the stated laterality gap to reflection. And a submission chooses its own cell count: nothing requires n_pred = n_true, and where a count difference would otherwise confound a comparison both clouds are first subsampled to a shared size. No submission ever carries cell-type labels; every metric that needs a type uses a frozen probe q(·) trained once on the real target and applied identically to every entry.
DE gene recovery
25% of the scoreDid you get the key up- and down-genes right?
Differential Expression Score (DES)
common/core_metrics.py::de_score, de_genes, _signed_overlapThe differential expression score evaluates how accurately a model recovers the genes that development, or a knockout, actually moved. A differential-expression call is the readout most single-cell experiments are run for and the input to everything downstream, so a model that cannot reproduce it has not reproduced the finding, whatever else it got right.
First, the truth declares its own DE genes. For each gene we test the target against the reference with a two-sided Mann-Whitney U test, control the false discovery rate with the Benjamini-Hochberg procedure at α = 0.05, and apply an effect-size floor that removes changes too small to mean anything biologically:
and split that set by direction into up- and down-regulated genes:
Next, the prediction is given no say in how many genes it names. It is asked for exactly n_u = |U_true| up-genes and n_d = |D_true| down-genes, chosen by its own log fold change, so that naming a thousand genes to catch fifty cannot help:
We then match the two sets sign for sign and normalise to the size of the true set:
Finally, that overlap is scored against a measured null rather than against zero. Highly-expressed genes are mechanically easier to call differentially expressed, and they overlap substantially with the genes that really are developmental DEGs — so a submission that has learned nothing except which genes are highly expressed scored 0.61 on a naive overlap, against 0.17 for the best real baseline. The null is the same construction applied to a prediction that knows only the reference’s own expression level, in both directions, since scaling the reference down games the score as readily as scaling it up:
0 means no better than knowing gene expression level alone, 1 means the truth’s own DE call was recovered exactly, and a negative score means the prediction is pointing the wrong genes in the wrong direction.
Change direction
25% of the scoreGenome-wide direction and ranking of the change.
Directional Concordance Score (DCS)
common/core_metrics.py::de_directionThe directional concordance score measures whether a model moved genes in the direction the real embryo moves them, across the whole transcriptome rather than only the called set. Where DES asks which genes changed, this asks whether the change went the right way — so a model that has the shape of the response right but its magnitude wrong still scores.
First, we take the predicted and observed log fold changes against the reference, and rank-transform both along with the reference’s own expression profile:
Next, letting C be the 3 × 3 Pearson correlation matrix of those three rank vectors, we take the partial correlation of predicted against observed change, controlling for reference expression level:
That control is not a refinement, it is the metric. A real developmental or perturbation response is itself correlated with how highly a gene is already expressed, because highly-expressed genes tend to be the ones under active regulation at that stage. A plain correlation therefore pays a submission for reproducing a relationship that carries no model signal at all: submitting 0.99× the reference scored 0.31 before the control was added. Partialling the reference out leaves only the agreement its own expression level cannot already explain.
A no-change prediction returns 0, as does the degenerate case where the prediction’s entire signal is collinear with reference expression level and there is nothing left to credit.
Cell-state distribution
30% of the scoreThe right cell states, in the right proportions.
Maximum Mean Discrepancy (MMD)
common/core_metrics.py::mmd_unbiasedMaximum mean discrepancy measures whether the predicted population of cells is the same population as the observed one — the right cell states in the right proportions. A stage of development is a mixture of progenitors, differentiating cells and terminal types all at once, not one average cell, and this is the term that holds a submission to that.
First, both the prediction and the target — up to 2000 cells each — are projected into a 30-component PCA basis fitted on the target alone, so that a submission cannot bend the space it is scored in to its own advantage.
Next, for each of five RBF kernel bandwidths γ_s = s · γ₀ with s ∈ {0.25, 0.5, 1, 2, 4}, spread around the target’s own median-heuristic bandwidth γ₀, we compute the squared discrepancy between the two sets of cells:
Finally, we average over the five bandwidths, so the score sees both coarse population shifts and fine within-population structure rather than committing to one length scale:
Dropping the kernel diagonal — the i ≠ i′ and j ≠ j′ conditions — makes this an unbiased estimator. The biased version put a perfect prediction at roughly 2/n rather than 0, a finite-sample artefact that a naive rescaling then mistook for the attainable ceiling.
Gene-gene co-variation
20% of the scoreCo-regulation preserved, not just per-gene marginals.
Co-expression Structure Score (CSS)
common/core_metrics.py::variogram_scoreThe co-expression structure score measures whether genes that move together in the real embryo still move together in the prediction. A cell-fate programme is a set of co-regulated genes — a transcription factor and its targets rising in step — and this is the only term in the panel that constrains it. Every other expression metric here is a function of per-gene marginals or lives in a PCA space, so a submission can get every gene’s individual distribution exactly right while scrambling which genes co-vary with which, and pass all of them.
First, we sample 20,000 random gene pairs (i, j) with i ≠ j. For each pair, in each dataset, we take the geostatistical variogram at p = 0.5 — applied across gene pairs instead of spatial lags:
Finally, the score is the mean squared difference between the predicted and observed variograms over those pairs — a direct measurement of whether pairwise gene-gene covariance structure survived the prediction:
0 for a perfect match.
Overall score
Everything above measures one question. This is how they become the number on the leaderboard.
Per-metric skill
A raw metric value is not comparable across tasks, or even across metrics — some are bounded correlations, some are unbounded distances. Each one is therefore rescaled against two published anchors: a floor model (copy_last for Tasks 1 and 2, wt_identity for Task 3) and the attainable ceiling (half the held-out target scored against its other, disjoint half — deliberately not the target resubmitted verbatim, which no honest generative model can reach).
Let d(m) be a metric’s distance from the ceiling, and d_floor the floor’s own distance from the ceiling — one fixed “floor-unit” per metric:
The rescaling is hyperbolic rather than linear, which bounds the result to (0, 1] by construction instead of by clipping:
At the ceiling, d = 0 and skill = 1. At the floor, d = d_floor and skill = 0.5 — the floor sits at the midpoint of the scale, not at zero. As a model gets arbitrarily worse, skill approaches zero asymptotically without ever reaching it, so two very different degrees of failure stay numerically distinct instead of both flattening onto the same value. A model that beats the ceiling is clipped to exactly 1, since the ceiling is a half-versus-half estimate rather than a true optimum.
An earlier linear ratio was unbounded below, so a model well past the floor could read as −500% skill. Fixing it at the root rather than clipping afterwards matters: clipping would have collapsed every failure past the floor onto an identical flat zero, throwing away real information.
Two rules attach here. A metric whose ideal value is 0 rather than an extreme — a signed log ratio or log slope — is scored on its absolute value first, as a lower-is-better term. And a metric that is declared but comes back missing or NaN counts as skill 0, the worst case on this scale, never quietly dropped from the average; otherwise a model would be judged on an easier subset of questions than its competitors.
Combining the questions
Turning the panel into a single ranking has to keep the metric count from doubling as a weight. Task 2’s shape question happens to need three distance functions; averaging every metric flat would silently give “is the shape right” three times the say of “did you get the change right”, for no reason other than that shape is harder to capture in one number.
So metrics are combined by an explicit within-group weight that sums to 1, and groups by an explicit group weight that also sums to 1:
Because every skill(m) already lies in (0, 1], every weighted average of them — within a group, and again across groups — is automatically in (0, 100] on the printed scale. No clipping appears anywhere in the aggregation. A task score of 50 reads as “exactly matches the floor” on every task; above 50 is real improvement, below 50 real regression, and the scale never runs out of room in either direction.
Task 1 in full
The weights that apply to this task, as declared in the scoring table.
| Question | Weight | Metrics |
|---|---|---|
| DE gene recovery | 25% | DES |
| Change direction | 25% | DCS |
| Cell-state distribution | 30% | MMD |
| Gene-gene co-variation | 20% | CSS |