Tasks/Life Sciences/Single-Cell Omics

Batch-integrated cell embedding from scATAC-seq peaks (PBMC)

Integrate scATAC batches into one clean embedding

pbmc_batch_correction Life Sciences Single-Cell Omics
instruction.mdthis is what the agent is given

Single-cell data is collected in separate batches (samples, labs, runs), and technical batch effects can dominate and obscure real biology. You inherit a no-correction PCA embedding (which keeps batch variation) and must produce a low-dimensional embedding that removes batch variation while preserving cell-type structure, using only each cell's binary peak set and its batch label. The embedding is re-computed by a sealed verifier on a hidden split and scored by how well cell types stay clustered, so only a method that genuinely integrates across batches counts.

Hard Constraints

  • Submit an algorithm (embed), not a precomputed embedding — the verifier re-runs your code on hidden cells.
  • Keep the exact signature embed(atac, batch_labels, peak_names) -> np.ndarray (n_cells, d).
  • Input peaks are binary; you also receive each cell's integer batch id. You do not receive cell-type labels — the embedding must be unsupervised w.r.t. cell type (the judge uses cell types only for scoring).
  • Output a real-valued embedding with a modest d in [2, 512] (e.g. 10–128); finite values, one row per input cell, or the submission scores 0.
  • There is no network at grade time. Do not hardcode or look up answers.

What You Have

  • The workspace /app/:
  • Visible data (/app/data/): pbmc_atlas_atac.h5ad — binary peak-cell matrix (var_names="chr-start-end") with obs['batch'] and (for local scoring only) obs['annot'].
  • The editable baseline /app/methods/main/this directory is what gets graded: the no-correction PCA embedding. Improve it in place or rewrite it entirely.
  • Your self-check surface (free): python /app/selfcheck.py runs your embedding on the visible cells and reports cell-type NMI (best-over-resolution Leiden, or a KMeans fallback). This is a proxy — the sealed split differs, so a good self-check is necessary, not sufficient.

What You Submit

Edit /app/methods/main/solver.py to expose:

def embed(atac, batch_labels, peak_names):
    # atac         : scipy.sparse CSR (n_cells, n_peaks), binary {0,1}
    # batch_labels : np.ndarray (n_cells,) integer batch ids
    # peak_names   : list[str] length n_peaks, "chr-start-end"
    # returns      : np.ndarray float (n_cells, d) cell embedding, d in [2, 512]

You may add helper modules next to solver.py. The runtime provides numpy / scipy / scikit-learn / torch (and anndata / scanpy for the self-check). There is no submit step and no per-attempt feedback — self-check for as long as your run window allows, then leave your best solver.py in place; it is graded once at the end on the hidden cells.

How It Is Judged

After your run, the verifier runs your embed on the hidden split (your code runs in an isolated subprocess that receives only the peaks + batch ids, never the cell types). It clusters the embedding with Leiden at a grid of resolutions and scores the best normalized mutual information (NMI) between the clustering and the hidden cell-type labels:

metric = max over resolutions of  NMI(Leiden(embedding, res), cell_types)

The metric is cell-type NMI (higher is better). A no-correction embedding keeps batch variation; strong methods mix batches while keeping cell types separable.

Metric

cell-type NMI on the sealed PBMC scATAC split · higher is better

max over Leiden resolutions of NMI(clustering, hidden cell types); scIB protocol, seed 2002

anchorvisible setheld-outreward
Bno-correction PCA baseline0.71720.65550.00
SEpiFoundation SOTA0.73780.50
Uperfect cluster match1.00001.00
normalisation
m <= B0
B < m <= S0.5 * (m - B) / (S - B)
S < m <= U0.5 + 0.5 * (m - S) / (U - S)
m > U1

m = this run's held-out metric  ·  B = no-correction PCA baseline  ·  S = EpiFoundation SOTA  ·  U = perfect cluster match

Linear in NMI, no transform. One hidden split, one number, so nothing is averaged. NMI <= 1 leaves the last branch unreachable.

Rollouts

377 minwall clock
$89.05spend
148.3Mtokens
13versions, 7 kept
0.68 0.70 0.72 0.74 0.76 0.78 $0 $20 $40 $60 $80 cumulative spend on the run full visible self-check NMI, higher is better v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12
keptrevertedno scoreturning point
  1. v0Inherited starter: 50-d TF-IDF plus TruncatedSVD LSI, batch labels unused0.71727 min · $0.59
  2. v1L2-normalise each 50-d LSI row to unit length, cosine geometry0.68220 min · $1.89
  3. v2Merge peaks into 10 kb genomic windows before the TF-IDF and SVD0.719470 min · $9.54
  4. v3Scale each LSI axis by exp(-3.5*eta2), eta2 = batch share of its varianceSuppress the directions batch explains, rather than translating batches onto each other.0.771186 min · $12.69
  5. v4Size-adaptive penalty: 3.5 * min(1, (n_cells / 60000) ** 0.75)Smaller graphs wanted less shrinkage in refits; the cap leaves the 67.8k result untouched.not re-measured182 min · $41.83
  6. v5Multiply IDF by a clipped peak-width prior, (width / median) ** -0.250.7704217 min · $52.00
  7. v6Randomized SVD at n_iter=10, n_oversamples=25 instead of the defaults0.7653257 min · $56.89
  8. v7Change only the randomized SVD seed, 2002 to 7770.7623294 min · $63.77
  9. v8Memory-only rewrite: bincount document frequencies, canonicalised CSRbit-identical to v4323 min · $70.95
  10. v9Concatenate a second LSI reweighted by community-minus-batch peak association0.7642348 min · $78.75
  11. v10Refined view at weight 0.5 to 30k cells, faded to zero by 60k, v8 aboveKeep the subset gain in the size range it appeared in and the v8 base where it reversed.not re-measured360 min · $82.83
  12. v11Wrap the refinement path so any failure returns the v8 base embeddingnot re-measured375 min · $88.11
  13. v12Drop the inner KMeans substitute so a failed backend reaches that fallbacknot re-measured376 min · $88.46

The accepted line was last measured on the full visible split at v3, $12.7 in. The other $76 went on rejected branches and identity arguments.

On the hidden set

held-out metricreward
no-correction PCA baseline0.65550.00
EpiFoundation SOTA0.73780.50
perfect cluster match1.00001.00
this run0.64530