mirpy-lib 3.1.1__tar.gz → 3.2.0__tar.gz
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.
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/.gitignore +3 -0
- mirpy_lib-3.2.0/BENCHMARKS.md +264 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/CHANGELOG.md +17 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/CLAUDE.md +105 -11
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/PKG-INFO +2 -1
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/SOURCES.md +1 -1
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/THEORY.md +54 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/api.rst +10 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/index.rst +3 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/usage.rst +37 -0
- mirpy_lib-3.2.0/mir/explain.py +529 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/repertoire.py +97 -7
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/pyproject.toml +4 -3
- mirpy_lib-3.1.1/BENCHMARKS.md +0 -80
- mirpy_lib-3.1.1/appendix/.gitignore +0 -8
- mirpy_lib-3.1.1/appendix/.latexmkrc +0 -2
- mirpy_lib-3.1.1/appendix/Makefile +0 -30
- mirpy_lib-3.1.1/appendix/README.md +0 -40
- mirpy_lib-3.1.1/appendix/data/s1_diss_curves.tsv +0 -301
- mirpy_lib-3.1.1/appendix/data/s1_diss_hist.tsv +0 -81
- mirpy_lib-3.1.1/appendix/data/s1_dist_curves.tsv +0 -301
- mirpy_lib-3.1.1/appendix/data/s1_dist_hist.tsv +0 -81
- mirpy_lib-3.1.1/appendix/data/s2_gapblock.tsv +0 -8001
- mirpy_lib-3.1.1/appendix/data/s2_sw.tsv +0 -8001
- mirpy_lib-3.1.1/appendix/data/s3.tsv +0 -8001
- mirpy_lib-3.1.1/appendix/data/theory_stats.txt +0 -11
- mirpy_lib-3.1.1/appendix/diagram_embedding.dot +0 -27
- mirpy_lib-3.1.1/appendix/diagram_embedding.pdf +0 -0
- mirpy_lib-3.1.1/appendix/fig_s1_distributions.gp +0 -33
- mirpy_lib-3.1.1/appendix/fig_s1_distributions.pdf +0 -0
- mirpy_lib-3.1.1/appendix/fig_s2_correlation.gp +0 -23
- mirpy_lib-3.1.1/appendix/fig_s2_correlation.pdf +0 -0
- mirpy_lib-3.1.1/appendix/fig_s3_prototype_source.gp +0 -16
- mirpy_lib-3.1.1/appendix/fig_s3_prototype_source.pdf +0 -0
- mirpy_lib-3.1.1/appendix/gen_theory_data.py +0 -105
- mirpy_lib-3.1.1/appendix/refs.bib +0 -1124
- mirpy_lib-3.1.1/appendix/tcremp_theory.pdf +0 -0
- mirpy_lib-3.1.1/appendix/tcremp_theory.tex +0 -2331
- mirpy_lib-3.1.1/experiments/_cohort.py +0 -121
- mirpy_lib-3.1.1/experiments/_covid.py +0 -122
- mirpy_lib-3.1.1/experiments/_hf.py +0 -72
- mirpy_lib-3.1.1/experiments/analyze_epitope_variability.py +0 -117
- mirpy_lib-3.1.1/experiments/analyze_pc_decomposition.py +0 -98
- mirpy_lib-3.1.1/experiments/analyze_prototype_counts.py +0 -73
- mirpy_lib-3.1.1/experiments/benchmark_ann.py +0 -96
- mirpy_lib-3.1.1/experiments/benchmark_clustering.py +0 -85
- mirpy_lib-3.1.1/experiments/benchmark_codec_chains.py +0 -68
- mirpy_lib-3.1.1/experiments/benchmark_codec_losslessness.py +0 -95
- mirpy_lib-3.1.1/experiments/benchmark_density_ankspond.py +0 -88
- mirpy_lib-3.1.1/experiments/benchmark_density_tcrnet.py +0 -100
- mirpy_lib-3.1.1/experiments/benchmark_density_vdjdb.py +0 -212
- mirpy_lib-3.1.1/experiments/benchmark_density_yfv.py +0 -86
- mirpy_lib-3.1.1/experiments/benchmark_igh_shm.py +0 -53
- mirpy_lib-3.1.1/experiments/benchmark_lossless_depth.py +0 -76
- mirpy_lib-3.1.1/experiments/benchmark_lossless_kpc.py +0 -106
- mirpy_lib-3.1.1/experiments/benchmark_pgen_variants.py +0 -51
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_agediverge.py +0 -157
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_aging.py +0 -113
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_cmvhla.py +0 -143
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_covidbatch.py +0 -134
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_covidhla.py +0 -89
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_covidpaired.py +0 -105
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_covidstatus.py +0 -102
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_depth.py +0 -78
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_hla.py +0 -156
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_hlapanel.py +0 -74
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_spikein.py +0 -110
- mirpy_lib-3.1.1/experiments/benchmark_repertoire_yfv.py +0 -96
- mirpy_lib-3.1.1/experiments/benchmark_vdjdb.py +0 -60
- mirpy_lib-3.1.1/experiments/reproduce_supplementary.py +0 -66
- mirpy_lib-3.1.1/experiments/train_forward_encoder.py +0 -52
- mirpy_lib-3.1.1/experiments/train_inverse_decoder.py +0 -58
- mirpy_lib-3.1.1/experiments/train_pgen_regressor.py +0 -58
- mirpy_lib-3.1.1/experiments/train_set_encoder.py +0 -66
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/LICENSE +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/README.md +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/REPERTOIRE_EMBEDDING.md +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/REPERTOIRE_LESSONS.md +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/SQRT_D_MIGRATION.md +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/assets/mirpy_dark.png +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/assets/mirpy_dark.svg +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/assets/mirpy_light.png +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/assets/mirpy_light.svg +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/Makefile +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/conf.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/docs/requirements.txt +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/environment.yml +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/aliases.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/alleles.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/bench/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/bench/metrics.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/bench/theory.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/bench/vdjdb.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/density.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/distances/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/distances/germline.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/distances/junction.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/embedding/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/embedding/pca.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/embedding/presets.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/embedding/prototypes.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/embedding/tcremp.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/bundle.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/codec.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/decoder.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/encoder.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/set_encoder.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/tokenize.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/ml/train.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/py.typed +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/README.md +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/__init__.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/build_gene_library.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/build_region_annotations.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/imgt_gene_library.txt +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/olga_gene_library.txt +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/gene_library/region_annotations.txt +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/build_germline_dist.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGH.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGK.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGL.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRA.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRB.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRD.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRG.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGH.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGK.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGL.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRA.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRB.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRD.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRG.npz +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/.gitkeep +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/generate_prototypes.py +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGH.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGK.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGL.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRA.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRB.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRD.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRG.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/manifest.json +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/mouse_TRA.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/mir/resources/prototypes/mouse_TRB.tsv +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/requirements.txt +0 -0
- {mirpy_lib-3.1.1 → mirpy_lib-3.2.0}/setup.sh +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Benchmark results — regression baseline
|
|
2
|
+
|
|
3
|
+
Recorded results for the sample-level (repertoire) embedding benchmarks (`experiments/benchmark_repertoire_*.py`),
|
|
4
|
+
so a re-run can be checked for regression. All numbers are **repeated 50-fold CV mean±std** unless noted, on
|
|
5
|
+
arda-native coordinates. Theory/interpretation: `THEORY.md` T7. Data provenance: `SOURCES.md`.
|
|
6
|
+
|
|
7
|
+
- **Env**: conda `mirpy` (Python 3.12), Apple M3 (32 GB). Seeds fixed (`seed=0`). `RUN_BENCHMARK` not required
|
|
8
|
+
for these (they are experiment scripts, not the guarded pytest tier).
|
|
9
|
+
- **Recorded**: 2026-07-14. Re-run and compare; a drop beyond ~1 std on the headline metric is a regression.
|
|
10
|
+
- **Reproducibility**: all load **local data first, HF fallback** — `aging`/`airr_hip` via `_hf.fetch`
|
|
11
|
+
(`hf_hub_download`), `airr_covid19` via `_covid.covid_path` (local `~/hf/airr_covid19` else `isalgo/airr_covid19`).
|
|
12
|
+
|
|
13
|
+
## Sample-level embedding — established regime (airr_benchmark aging, airr_hip)
|
|
14
|
+
|
|
15
|
+
| Benchmark | Command | Headline metric | Value |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| Depth-robustness (`prop:kme`) | `benchmark_repertoire_depth.py` | log–log slope `‖ΔΦ₁‖` vs `n_eff` (theory −0.5) | **−0.55** |
|
|
18
|
+
| Age regression (n=79) | `benchmark_repertoire_aging.py` | \|Spearman(age,pred)\|: diversity / kernel-mean / k-mer | **0.76** / 0.58 / 0.28 |
|
|
19
|
+
| CMV serostatus (n=240) | `benchmark_repertoire_cmvhla.py` | AUC: diversity / Φ blocks / learned | **0.83±0.05** / 0.59–0.63 / 0.49 |
|
|
20
|
+
| CMV age-matched control | `benchmark_repertoire_cmvhla.py` | AUC: diversity / age-only (decade-matched) | **0.83** / 0.45 |
|
|
21
|
+
| HLA-A\*02 (n=500, 25k) | `benchmark_repertoire_hla.py 250 25000` | AUC: second-moment / diversity | **0.623±0.048** / 0.460±0.048 |
|
|
22
|
+
| HLA panel (airr_hip, n=300) | `benchmark_repertoire_hla*panel` | second-moment AUC: B\*08 / B\*07 / B\*44 | **0.82** / 0.76 / 0.66 |
|
|
23
|
+
| Spike-in recovery | `benchmark_repertoire_spikein.py` | recall @N≤3k / FPR (clean P_gen null) | **~35–50%** / ~1.2% |
|
|
24
|
+
|
|
25
|
+
## Aging divergence — `benchmark_repertoire_agediverge.py 500000 30` (58 donors @500k)
|
|
26
|
+
|
|
27
|
+
Divergence rises with age at depth but is **diversity-coupled, not an independent axis**.
|
|
28
|
+
|
|
29
|
+
| Metric | Value | Note |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| ρ(¹D, divergence) biased KME | −0.153 | biased V-stat `1/n_eff` artifact |
|
|
32
|
+
| ρ(¹D, divergence) **unbiased** KME | **−0.052** | `mmd_matrix(unbiased=True)`; artifact removed |
|
|
33
|
+
| ρ(¹D, divergence) overlap −logF | −0.680 | clonal-expansion driven |
|
|
34
|
+
| ρ(age, divergence overlap F) | 0.704 | (0.24 at 250k — depth-dependent) |
|
|
35
|
+
| **partial ρ(age, divergence \| ¹D)** | **0.067** (p=0.64) | n.s. — no independent axis (same at 40k/250k) |
|
|
36
|
+
|
|
37
|
+
## COVID cohort (airr_covid19, 300 donors, ≤20k reads)
|
|
38
|
+
|
|
39
|
+
### Batch cookbook — `benchmark_repertoire_covidbatch.py 300 20000` → **PASS**
|
|
40
|
+
|
|
41
|
+
| signal | naive AUC | batch-residualized |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| batch identity (OvR) | 0.777 | **0.032** |
|
|
44
|
+
| COVID status (⟂̸ batch) | **0.660** | 0.409 |
|
|
45
|
+
| HLA-A\*02 (⟂ batch) | 0.596 | **0.609** |
|
|
46
|
+
|
|
47
|
+
within-mixed-batch COVID (honest) **0.539**; MMD offset:biology ratio **1.05**.
|
|
48
|
+
|
|
49
|
+
### HLA imprint class I+II — `benchmark_repertoire_covidhla.py 300 20000` (TRB)
|
|
50
|
+
|
|
51
|
+
second-moment > diversity in direction **15/17** alleles; class-II present **8/9**.
|
|
52
|
+
|
|
53
|
+
| allele | class | 2nd-moment AUC | Δ vs diversity |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| DRB1\*07:01 | II | **0.758** | +0.204 |
|
|
56
|
+
| B\*07:02 | I | 0.702 | +0.154 |
|
|
57
|
+
| DRB1\*15:01 | II | 0.702 | +0.110 |
|
|
58
|
+
| DQB1\*05:01 | II | 0.619 | +0.152 |
|
|
59
|
+
| A\*02:01 | I | 0.628 | +0.035 |
|
|
60
|
+
|
|
61
|
+
### Paired α+β — `benchmark_repertoire_covidpaired.py 300 20000`
|
|
62
|
+
|
|
63
|
+
**TRA carries the stronger HLA imprint**; paired concat sits between (noisier β dilutes).
|
|
64
|
+
|
|
65
|
+
| allele | β (TRB) | α (TRA) | α+β paired |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| A\*02 | 0.538 | **0.636** | 0.612 |
|
|
68
|
+
| B\*07 | 0.646 | **0.701** | 0.664 |
|
|
69
|
+
| DRB1\*15 | 0.749 | **0.810** | 0.796 |
|
|
70
|
+
|
|
71
|
+
### COVID biomarker — honest **negative** (`covidstatus.py` / `covidpaired.py`)
|
|
72
|
+
|
|
73
|
+
| test | β | α | paired |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| COVID status, naive | 0.673 | 0.685 | **0.702** |
|
|
76
|
+
| COVID status, batch-resid | 0.488 | 0.512 | **0.522** (chance) |
|
|
77
|
+
| witness rediscovery of paper's clones | 0.373 | **0.450** | — |
|
|
78
|
+
|
|
79
|
+
Convalescent (long-past) COVID leaves **no batch-robust bulk clonotype-identity signal** at RNA-seq depth; the
|
|
80
|
+
naive 0.66–0.72 was batch confound. Ground truth is 87% α (4393 α vs 567 β).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 2026-07-14 (pm) — spectral block, COVID witness, TCGA survival
|
|
85
|
+
|
|
86
|
+
### Spectral interaction block — `benchmark_repertoire_spectral.py 300 20000`
|
|
87
|
+
|
|
88
|
+
The opt-in top-`r` **eigenvalue** compaction of the second-moment block (new `n_eigs=` on `fit_repertoire_space`)
|
|
89
|
+
is **lossy for the HLA imprint**: HLA-A\*02 carriage lives in *which* public clones co-occur (directional), and
|
|
90
|
+
a rotation-invariant eigenvalue spectrum discards it. Default (`n_eigs=None`) keeps the full upper triangle,
|
|
91
|
+
unchanged.
|
|
92
|
+
|
|
93
|
+
| representation | block dim | HLA-A\*02 AUC |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| diversity (baseline) | 4 | 0.560±0.066 |
|
|
96
|
+
| D₂=512 top-16 eigvals | 16 | 0.545±0.093 |
|
|
97
|
+
| D₂=512 top-128 eigvals | 128 | 0.536±0.092 |
|
|
98
|
+
| D₂=256 full upper-tri | 32896 | 0.562±0.078 |
|
|
99
|
+
| **D₂=512 full upper-tri** | 131328 | **0.593±0.070** |
|
|
100
|
+
|
|
101
|
+
### COVID witness — `benchmark_repertoire_covidwitness.py 300 20000`
|
|
102
|
+
|
|
103
|
+
Why per-clonotype Fisher-significant clones "vanish" in the bulk embedding: **cohort breadth**, not read depth.
|
|
104
|
+
|
|
105
|
+
| level | finding |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Fisher genome-wide BH (150–300 donors) | **0** clones pass at any depth (20k or 120k reads) |
|
|
108
|
+
| Fisher full cohort (~1137 donors, user tmp scan) | **39 β / 4 α** clones pass BH (min q≈5e-4) |
|
|
109
|
+
| witness β: whole-cohort → **mixed-batch** | 0.510 → **0.752** (batch control is the lever) |
|
|
110
|
+
| witness β: HLA-strat median / best | 0.532 / 0.712 (best = max-selection; median ≈ whole-cohort) |
|
|
111
|
+
| witness α: whole / mixed / strat-best | 0.463 / 0.424 / 0.473 (no recovery — α witness fails) |
|
|
112
|
+
|
|
113
|
+
Batch control (mixed-batch contrast, where COVID+healthy share runs) recovers the paper's β clones; per-allele
|
|
114
|
+
HLA stratification adds more noise than signal at these donor counts. HLA+α+β is **not** the missing key — breadth is.
|
|
115
|
+
|
|
116
|
+
### COVID motif recovery at full breadth — `benchmark_repertoire_covidmotif.py 0 60000`
|
|
117
|
+
|
|
118
|
+
Using the tool that *works* at breadth (Emerson incidence Fisher) on the **full 1137-donor cohort** (761 COVID /
|
|
119
|
+
376 healthy, 60 k reads), both chains, to actually *find COVID motifs*:
|
|
120
|
+
|
|
121
|
+
| chain | genome-wide clones q<0.05 | GT-true recovered | + HLA-restricted |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| **α** | 7 | **4** | +0 |
|
|
124
|
+
| β | 0 | 0 | +0 |
|
|
125
|
+
|
|
126
|
+
The 4 recovered α clones are one coherent public family — **CAG·NYGGSQGNLIF** (single-residue variants), all
|
|
127
|
+
members of the paper's COVID-associated α **cluster 31** (27 GT clones, all `has_covid_association`). **HLA
|
|
128
|
+
restriction adds nothing** because these α clones are already *public* (present across HLA backgrounds) —
|
|
129
|
+
per-allele restriction only loses power.
|
|
130
|
+
|
|
131
|
+
**WS1 (2026-07-15) — the α/β loop closed, and it is NOT depth (refutes the earlier "β is depth-limited" note).**
|
|
132
|
+
Running β at **full *native* depth** (no downsampling) over the full 1137-donor cohort
|
|
133
|
+
(`benchmark_repertoire_covidmotif.py 0 0`) still passes **1** feature genome-wide at any `min_incidence∈{2,3,5}`
|
|
134
|
+
and recovers **0** GT-β clones — full depth does not rescue β. Nor does the *restricting allele*: a per-stratum
|
|
135
|
+
sweep (`benchmark_repertoire_covidmotif.py native_beta`, 12 class-II/B HLA carrier groups, 117–346 carriers each
|
|
136
|
+
at **native depth** ~3.5 M reads/donor) recovers **0** GT-β in every stratum — refuting "β needs full depth +
|
|
137
|
+
the restricting allele." And it is **not absence**: **253/256 GT-β clones
|
|
138
|
+
are present** in the cohort (median incidence **9** donors, max **110**). The reason is that the paper's β
|
|
139
|
+
"COVID-associated" clones are short **public bystanders** (`CASSx…YEQYF`, TRBJ2-7 — e.g. `CASSYSYEQYF` in 110
|
|
140
|
+
donors) occurring in COVID **and** healthy donors alike, so they do not discriminate status by *incidence*.
|
|
141
|
+
**The α/β asymmetry is which clones are status-enriched vs public bystanders — not depth or breadth:** α's
|
|
142
|
+
recovered `CAG·NYGGSQGNLIF` family is a genuine status-enriched public expansion; β's GT set is dominated by
|
|
143
|
+
non-discriminating convergent publics. *Caveat:* the incidence Fisher tests presence-breadth, not clonal-
|
|
144
|
+
expansion magnitude — a frequency/abundance-based test could still recover β clones enriched in *size* not
|
|
145
|
+
*breadth* (untested). Net: the recoverable "find-motifs" signal is the **public α compartment** (breadth-
|
|
146
|
+
powered, no HLA needed); the breadth-starved bulk MMD witness recovered none of it.
|
|
147
|
+
|
|
148
|
+
### TCGA — `benchmark_repertoire_tcga.py <chains> BRCA,LUAD,KIRC 50000`
|
|
149
|
+
|
|
150
|
+
Tumor-type separation is **depth-dependent** (deep IG light chains ≫ shallow TR); **survival adds nothing** over
|
|
151
|
+
clinical covariates. Base Cox C-index (age+sex+stage+log reads): BRCA 0.72, LUAD 0.66, KIRC 0.73.
|
|
152
|
+
|
|
153
|
+
| chain | tumor-type macro-OvR AUC | survival ΔC (BRCA / LUAD / KIRC) |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| TRB (median ~24 clonotypes) | 0.523 | +0.003 / −0.003 / +0.002 |
|
|
156
|
+
| IGH | 0.500 | +0.002 / +0.001 / −0.004 |
|
|
157
|
+
| **IGK (deepest chain)** | **0.666** | −0.001 / −0.004 / −0.001 |
|
|
158
|
+
| concat TRB+IGH | 0.538 | −0.002 / +0.001 / +0.001 |
|
|
159
|
+
|
|
160
|
+
IGK's 0.67 shows the method works when clonotype depth suffices (the deep IG compartment carries tissue signal);
|
|
161
|
+
the flat ΔC across every chain says the tumour-infiltrating repertoire adds **no prognostic value beyond clinical
|
|
162
|
+
covariates** at TCGA RNA-seq depth.
|
|
163
|
+
|
|
164
|
+
### TCGA survival — biology-grounded features — `benchmark_repertoire_tcga_survival.py`
|
|
165
|
+
|
|
166
|
+
The clonotype *embedding* adds nothing, but interpretable **biology axes** (isotype from `c_call`, infiltration /
|
|
167
|
+
hot-vs-cold, atypicality = gene-usage divergence from the tumour-type centroid, clonal expansion — modelled on an
|
|
168
|
+
internal AIRR-tissue EDA) do, cancer-specifically. Base Cox = age+sex+stage+log reads; **bold** = best gain per row.
|
|
169
|
+
|
|
170
|
+
C-index gain over clinical (5-fold CV):
|
|
171
|
+
|
|
172
|
+
| cancer | C base | isotype | infiltration | atypicality | clonality | all-AIRR |
|
|
173
|
+
|---|---|---|---|---|---|---|
|
|
174
|
+
| SKCM (melanoma) | 0.609 | +0.008 | +0.034 | −0.001 | +0.031 | **+0.036** |
|
|
175
|
+
| KIRP (renal pap.) | 0.717 | −0.008 | **+0.030** | −0.003 | +0.014 | +0.015 |
|
|
176
|
+
| LGG (glioma) | 0.764 | −0.006 | **+0.019** | +0.008 | +0.016 | +0.011 |
|
|
177
|
+
| KIRC (renal cc.) | 0.721 | +0.005 | **+0.009** | +0.002 | +0.002 | +0.006 |
|
|
178
|
+
|
|
179
|
+
(LUAD / STAD / OV / BLCA: AIRR gain ≈ 0 or negative.)
|
|
180
|
+
|
|
181
|
+
KM median-split log-rank p (the EDA's stratification test — surfaces threshold effects the linear C-index misses;
|
|
182
|
+
**bold** = p<0.05):
|
|
183
|
+
|
|
184
|
+
| cancer | infiltration | IgA fraction | atypicality |
|
|
185
|
+
|---|---|---|---|
|
|
186
|
+
| KIRC | **0.002** | 0.089 | 0.880 |
|
|
187
|
+
| SKCM | **0.000** | **0.026** | 0.716 |
|
|
188
|
+
| KIRP | **0.020** | 0.750 | 0.555 |
|
|
189
|
+
| OV | **0.044** | 0.581 | 0.691 |
|
|
190
|
+
| LGG | **0.022** | 0.076 | **0.001** |
|
|
191
|
+
| BLCA | 0.676 | **0.010** | 0.243 |
|
|
192
|
+
|
|
193
|
+
**Read:** infiltration (hot/cold) stratifies survival in **5/8** cancers (melanoma, both renal, ovarian, glioma)
|
|
194
|
+
and adds C-index in melanoma/KIRP; **IgA** (isotype / mucosal) stratifies **bladder** + melanoma; **atypicality**
|
|
195
|
+
stratifies **glioma** — each matching known immune biology. Prognosis lives in infiltration magnitude + isotype +
|
|
196
|
+
typicality, **not** clonotype identity, and is cancer-specific — the reason the identity embedding's ΔC was flat.
|
|
197
|
+
|
|
198
|
+
## Repertoire embedding for TME & survival — pan-cancer (33 TCGA types)
|
|
199
|
+
|
|
200
|
+
The reframing: the prognostic axes above are **channels of one TME-aware, multi-chain repertoire embedding**
|
|
201
|
+
Φ(S) (`_tcga_embedding.py`: per-chain identity ‖ diversity ‖ coverage/infiltration, + isotype + composition
|
|
202
|
+
+ atypicality). `benchmark_repertoire_tcga_pancancer.py ALL` fits Φ once over 9 425 OS-annotated samples
|
|
203
|
+
(78-dim, 5 embeddable chains + all 7 in the composition channel) and, per cancer, reports the CV **ΔC-index**
|
|
204
|
+
of clinical+Φ over clinical (age+sex+stage+log reads) and a **likelihood-ratio p** for the Φ block.
|
|
205
|
+
|
|
206
|
+
Cancers where Φ is robustly prognostic (**both** LR p<0.05 **and** CV ΔC>0; bold):
|
|
207
|
+
|
|
208
|
+
| cancer | n | events | C base | C+Φ | ΔC | LR p | top channel |
|
|
209
|
+
|---|---|---|---|---|---|---|---|
|
|
210
|
+
| **SKCM** (melanoma) | 460 | 219 | 0.609 | 0.647 | **+0.039** | **0.000** | coverage |
|
|
211
|
+
| **BLCA** (bladder) | 411 | 182 | 0.650 | 0.675 | **+0.025** | **0.002** | identity |
|
|
212
|
+
| **HNSC** (head & neck) | 500 | 218 | 0.617 | 0.639 | **+0.022** | **0.012** | coverage |
|
|
213
|
+
| **LGG** (glioma) | 423 | 112 | 0.764 | 0.779 | **+0.016** | **0.000** | coverage |
|
|
214
|
+
|
|
215
|
+
Strong effect-size positives (ΔC>0, LR n.s. — power-limited): SARC +0.038 (isotype), KIRP +0.034 (atypicality),
|
|
216
|
+
LUAD +0.009, KIRC +0.008, BRCA +0.007. Immune-cold / small cohorts are flat-to-negative (overfit): LIHC −0.029,
|
|
217
|
+
STAD −0.026, PAAD −0.029, UCEC −0.072 (n=181/35ev).
|
|
218
|
+
|
|
219
|
+
**Pan-cancer (20 evaluable):** mean ΔC ≈ 0 (median +0.003), Φ significant in 5/20 — most-informative channel
|
|
220
|
+
tally **coverage 5 · atypicality 5 · composition 4 · isotype 3 · identity 2 · diversity 1**. The signal is
|
|
221
|
+
cancer-specific and lives in the **TME channels (infiltration, atypicality, composition), not clonotype
|
|
222
|
+
identity**; the pan-cancer mean is ≈0 because immune-cold cohorts overfit and cancel the immune-hot wins —
|
|
223
|
+
so the honest claim is *Φ adds significant prognostic value in immunologically active cancers* (melanoma,
|
|
224
|
+
glioma, head&neck, bladder), not universally.
|
|
225
|
+
|
|
226
|
+
### TME states (unsupervised) — `benchmark_repertoire_tcga_tme.py ALL 6`
|
|
227
|
+
|
|
228
|
+
KMeans (k=6) on Φ's TME channels (9 425 samples) recovers coherent, interpretable microenvironment states
|
|
229
|
+
(z-scored channel means; **bold** = defining extreme). The UMAP (`experiments/figures/umap_tcga_tme`) shows a
|
|
230
|
+
clean **infiltration gradient** (hot → cold) organising the pan-cancer cohort.
|
|
231
|
+
|
|
232
|
+
| state | n | infiltration | T-vs-B | switch | diversity | death | HR (vs ref) | enriched cancers |
|
|
233
|
+
|---|---|---|---|---|---|---|---|---|
|
|
234
|
+
| cold-humoral | 1565 | −0.53 | −0.36 B | +0.39 | −0.40 | 0.33 | **1.14** (p=0.011) | BRCA, BLCA, PRAD |
|
|
235
|
+
| hot-diverse | 2090 | **+1.03** | −0.70 B | +0.35 | **+0.92** | 0.31 | 0.92 (p=0.072) | BRCA, LUAD, STAD |
|
|
236
|
+
| T-balanced | 1277 | −0.10 | **+0.86 T** | +0.15 | +0.06 | 0.29 | 1.09 | KIRC, PRAD, BRCA |
|
|
237
|
+
| cold-T unsw. | 883 | **−1.58** | **+1.72 T** | **−2.20** | −1.66 | 0.30 | 1.05 | LGG, LIHC, SARC |
|
|
238
|
+
| cold | 871 | −1.27 | +0.74 T | −0.70 | −1.13 | 0.28 | 1.08 | THCA, LIHC, LGG |
|
|
239
|
+
| warm-B (ref) | 2739 | +0.48 | −0.45 B | +0.37 | +0.40 | 0.31 | — | BRCA, LUAD, HNSC |
|
|
240
|
+
|
|
241
|
+
Stratified multivariate log-rank across states (blocking tumour type) **p=0.0038** — the states are prognostic
|
|
242
|
+
*beyond* cancer type. The **cold-humoral** low-infiltration state carries the **worst** outcome (HR 1.14, p=0.011);
|
|
243
|
+
the **hot-diverse** state trends protective (HR 0.92). Glioma (LGG) falls in the cold, strongly T-skewed states,
|
|
244
|
+
matching its known low-but-T-biased infiltrate. The repertoire embedding organises the TME unsupervised.
|
|
245
|
+
|
|
246
|
+
### In-silico evolution — perturb infiltration, decode the coupled response — `benchmark_repertoire_tcga_insilico.py ALL`
|
|
247
|
+
|
|
248
|
+
`mir.repertoire.sample_descriptor` makes every metric a **smooth derivable coordinate** (infiltration = log-mass,
|
|
249
|
+
diversity = log n_eff, clonality = Σw², identity = kernel mean), so the cohort's joint distribution is a
|
|
250
|
+
generative **manifold** and moving a sample along the infiltration axis (hot↔cold) while staying on it predicts
|
|
251
|
+
how the other metrics respond. Gaussian-manifold conditional slopes d(metric)/d(infiltration), pan-cancer mean:
|
|
252
|
+
|
|
253
|
+
| response as tumour gets hotter | slope | reading |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| diversity | **+0.84** | hot ⇒ more diverse (universal, all cancers) |
|
|
256
|
+
| class-switch | +0.52 | hot ⇒ more class-switched B cells |
|
|
257
|
+
| IgG | +0.49 | hot ⇒ more IgG (mature humoral) |
|
|
258
|
+
| T-vs-B | −0.63 | hot ⇒ more B-skewed (TLS-like) |
|
|
259
|
+
| atypicality | −0.23 | hot ⇒ less atypical / more convergent |
|
|
260
|
+
|
|
261
|
+
**In-silico "make this tumour hotter"** (move cold→hot on-manifold, read the CoxPH Δlog-HR): predicted
|
|
262
|
+
**protective in 12/20** cancers (SKCM −0.49, HNSC −0.63, LUAD −0.44, LAML −0.96 …) — TIL-is-good — but correctly
|
|
263
|
+
**adverse in glioma** (LGG **+1.19**, GBM +0.47) and renal (KIRC/KIRP +0.3), matching the known "immune-hot glioma
|
|
264
|
+
is worse" biology. The embedding *simulates* the hot↔cold survival axis; the couplings are learned, not imposed.
|
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to `mirpy-lib` (import `mir`). This project follows semantic versioning; the v3 line is a
|
|
4
4
|
greenfield ML/embedding rewrite (the classical v1.x/v2 toolkit is frozen on branch `legacy-v2`).
|
|
5
5
|
|
|
6
|
+
## 3.2.0 — 2026-07-17
|
|
7
|
+
|
|
8
|
+
Minor: one new public module, nothing removed or changed.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`mir.explain`** (T7) — explainable readouts over any repertoire feature matrix.
|
|
13
|
+
`ChannelSpec` / `ChannelBuilder` / `stack_embeddings` attach the name→column map that `Φ.vector`
|
|
14
|
+
does not carry (`stack_embeddings` is exact: `X[i] == embs[i].vector`, names only, no transform);
|
|
15
|
+
`channel_report` ablates each named channel under a caller-supplied scorer (leave-one-in by
|
|
16
|
+
default; `mode="both"` adds the conditional half that exposes *redundant* channels — high `delta`,
|
|
17
|
+
`delta_out≈0`; optional row-permutation p-values); `channel_drivers` hops from a winning
|
|
18
|
+
**kernel-mean** channel to the clonotypes driving it via `class_witness`, and refuses channels with
|
|
19
|
+
no clonotype pre-image (a Hill number's "drivers" are a category error, not an open question).
|
|
20
|
+
Scorer-agnostic by design — the library never sees the labels and ships no scorers, so a Cox
|
|
21
|
+
C-index and a CV AUC both plug in. **No existing module changed.**
|
|
22
|
+
|
|
6
23
|
## 3.1.1 — 2026-07-14
|
|
7
24
|
|
|
8
25
|
Maintenance re-release of 3.1.0 with no functional changes — 3.1.0 was withdrawn from PyPI, and
|
|
@@ -6,6 +6,28 @@ library** for immune receptors (TCR/BCR). A greenfield, slim rewrite: prototype
|
|
|
6
6
|
(TCREMP) now, neural codecs + density methods next (Part 2). The classical v1.x/v2 toolkit is
|
|
7
7
|
frozen on branch **`legacy-v2`** (`mirpy-lib` 2.x) — do not develop there.
|
|
8
8
|
|
|
9
|
+
## Repo split (2026-07-16) — three homes
|
|
10
|
+
This repo is now **the library + its CI/test data only**. Analysis and manuscript live elsewhere:
|
|
11
|
+
- **`~/vcs/code/mirpy`** (here): `mir/` library, unit + fast/slow CI tests (`tests/`, on slim bundled
|
|
12
|
+
data), plans for new features, docs, and **slim tabular benchmark results** for README/docs. Benchmark
|
|
13
|
+
*scripts* and the theory *appendix* have moved out (below).
|
|
14
|
+
- **`~/vcs/projects/2026-mirpy-analysis`**: all **benchmark scripts** (`benchmarks/` — local + aldan3),
|
|
15
|
+
full result docs (BENCHMARKS/THEORY/…), figures, run outputs, dataset catalog. Run the mirpy library
|
|
16
|
+
from here; refresh numbers-of-record here.
|
|
17
|
+
- **`~/vcs/manuscripts/2026-mirpy-ms`**: the **theory appendix** (`appendix/tcremp_theory.tex`) + the paper
|
|
18
|
+
(`manuscript-latex/`). (Earlier this appendix lived here in the code repo; it migrated on 2026-07-16.)
|
|
19
|
+
|
|
20
|
+
Compute: heavy benchmarks run on the **aldan3** cluster (env `mirpy`, `sbatch`); light jobs local. See the
|
|
21
|
+
analysis repo.
|
|
22
|
+
|
|
23
|
+
## Worktrees — REQUIRED for Claude sessions
|
|
24
|
+
**Work in a git worktree for this repo, never directly on the main checkout.** Multiple Claude sessions
|
|
25
|
+
edit mirpy concurrently (the 2026-07-16 split was done across parallel sessions and hit collisions), so at
|
|
26
|
+
the start of any session that will modify this repo, call **`EnterWorktree`** (isolated worktree under
|
|
27
|
+
`.claude/worktrees/`, fresh branch off `origin/master`); commit + push from there; merge via PR/fast-forward.
|
|
28
|
+
This keeps concurrent sessions from clobbering each other's working trees. The same applies to the sibling
|
|
29
|
+
repos (`2026-mirpy-analysis`, `2026-mirpy-ms`).
|
|
30
|
+
|
|
9
31
|
## Reuse, don't duplicate — the ecosystem
|
|
10
32
|
mirpy v3 has **no AIRR data-model layer of its own**; it works on `vdjtools` polars frames and
|
|
11
33
|
delegates:
|
|
@@ -46,13 +68,13 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
46
68
|
PCs; diverse chains (IGH/TR*) 2000 / ~65 PCs (95%), ~220–300 PCs (99%, for codec reconstruction).
|
|
47
69
|
- `bench/` — `vdjdb.py` (loader), `metrics.py` (`cluster(method=…)` DBSCAN default | HDBSCAN | OPTICS,
|
|
48
70
|
kneedle eps, F1/retention), `theory.py` (S1–S3 + T5 + T6 `tcrnet_convergence` + `codec_losslessness`).
|
|
49
|
-
Needs `[bench]`. Clustering is a precision/coverage trade-off (`
|
|
71
|
+
Needs `[bench]`. Clustering is a precision/coverage trade-off (`benchmarks/benchmark_clustering.py`):
|
|
50
72
|
DBSCAN tightest/purest (paper regime), HDBSCAN ~3× coverage at lower F1 (variable-density), OPTICS
|
|
51
73
|
dominated, KMeans no noise-rejection.
|
|
52
74
|
- `density.py` — continuous-density TCRNET/ALICE (T6): `fit_density_space` (one shared PCA basis),
|
|
53
75
|
`neighbor_enrichment` (balloon adaptive-radius Poisson/binomial + water-level calibration;
|
|
54
76
|
`backend=` **exact** BallTree default | **kdtree** exact scipy cKDTree 5–9× faster | **ann**
|
|
55
|
-
pynndescent ~30× at ≥1e5, recall<1 conservative — `
|
|
77
|
+
pynndescent ~30× at ≥1e5, recall<1 conservative — `benchmarks/benchmark_ann.py`),
|
|
56
78
|
`enriched_mask`, `denoise_and_cluster`, `generate_background` (vdjtools P_gen, lazy). Torch-free
|
|
57
79
|
(scipy/sklearn). Prefer a **biological control** as background (differential) over P_gen.
|
|
58
80
|
**Abundance-aware** (T6 sec:dens-abund): pass `abundance=` (clone sizes) + `weight="log1p"`/`anscombe`
|
|
@@ -63,6 +85,18 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
63
85
|
clonotype-cloud PCA + RFF basis, prototype-hash-verified `RepertoireSpace`), `sample_embedding`
|
|
64
86
|
(`Φ(S)` = RFF kernel mean ‖ coverage-standardized Hill ‖ second-moment Fisher; `n_eff=(Σw²)⁻¹`),
|
|
65
87
|
`mmd_distance`/`mmd_matrix`/`hla_stratified_mmd`, `class_witness` (supervised MMD motif finder). Torch-free.
|
|
88
|
+
Opt-in `fit_repertoire_space(n_eigs=r)` swaps the second-moment block's full `D₂(D₂+1)/2` upper-triangle
|
|
89
|
+
for its top-`r` eigenvalues (rotation-invariant spectrum; default `None` = upper-tri, unchanged — but
|
|
90
|
+
lossy for the *directional* HLA imprint, so the full triangle stays the recommended block; `benchmark_repertoire_spectral.py`).
|
|
91
|
+
- `explain.py` — **explainable readouts over any feature matrix** (T7). `ChannelSpec`/`ChannelBuilder`/
|
|
92
|
+
`stack_embeddings` attach the name→column map `Φ.vector` does not carry (per-chain blocks merge by
|
|
93
|
+
name); `channel_report(X, spec, scorer, base=, mode="in"|"out"|"both", n_permutations=)` ablates each
|
|
94
|
+
channel under a **caller-supplied** scorer — the library never sees `y` and ships no scorers, so a Cox
|
|
95
|
+
C-index and a CV AUC both plug in. `mode="in"` (default) is marginal, because the scorers reduce their
|
|
96
|
+
input and leave-one-*out* is near-blind to narrow channels; `"both"` adds the conditional half, and
|
|
97
|
+
high `delta` + `delta_out≈0` is the **redundancy** signature. `channel_drivers` hops channel→clonotypes
|
|
98
|
+
via `class_witness`, but **only** for a channel declared `attributable` (a kernel mean); a Hill number
|
|
99
|
+
has no clonotype pre-image and it raises. Depends one-way on `repertoire.py`; nothing there changed.
|
|
66
100
|
- `ml/` — Part 2 (torch), neural codecs + `set_encoder.py` (learned repertoire track: Set-Transformer/DeepRC
|
|
67
101
|
attention pooling + `SetEncoderBundle`).
|
|
68
102
|
- `resources/` — `prototypes/` (TSVs + manifest), `gene_library/` (region_annotations.txt),
|
|
@@ -73,8 +107,8 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
73
107
|
(pure-Python hatchling; no C build). Extras: `[bench] [annotate] [build] [ml] [docs] [dev]`.
|
|
74
108
|
- Tests: `python -m pytest tests/ -q` (78 pass; `-m "not integration"` for the ~5s fast tier —
|
|
75
109
|
the pynndescent ANN parity test carries a one-time JIT cost). All self-contained on bundled resources.
|
|
76
|
-
- Experiments: `python
|
|
77
|
-
`python
|
|
110
|
+
- Experiments: `python benchmarks/reproduce_supplementary.py` (theory S1–S3),
|
|
111
|
+
`python benchmarks/benchmark_vdjdb.py` (Table S1). Analyses: `analyze_prototype_counts.py`
|
|
78
112
|
(geometry saturates by K≈100 — T.1/S4), `analyze_pc_decomposition.py` (V/J η² ≈0.44/0.49,
|
|
79
113
|
CDR3-length η² 0.13 & R²=0.95; germline low-rank ~13 PCs — T.4). See `THEORY.md`.
|
|
80
114
|
|
|
@@ -100,7 +134,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
100
134
|
**PCA-compacted junction embedding** (T3: 1000-D junction → ~51 PCs @95% var on arda coords,
|
|
101
135
|
99% for reconstruction), fit train-only.
|
|
102
136
|
V/J stay exact germline lookups (nothing to learn); the codec's job is the junction part.
|
|
103
|
-
Results on M3 MPS (`
|
|
137
|
+
Results on M3 MPS (`benchmarks/train_{forward_encoder,inverse_decoder,pgen_regressor}.py`),
|
|
104
138
|
**re-pinned on the arda-native coords** (2026-07-13):
|
|
105
139
|
- **DONE forward codec** (`tokenize,encoder,train`): seq → compact code, reconstruction
|
|
106
140
|
cosine **0.9984** (n=8k; paper 0.887). DNN inference is K-independent. Geometry is fine at
|
|
@@ -111,7 +145,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
111
145
|
(over-compacted); 99% (284 PCs) restores 0.40.** The T5 chain-adaptive lesson now bites TRB
|
|
112
146
|
too, not just IGH/TRD — the decoder default is 99%.
|
|
113
147
|
- **DONE Pgen regressor** (`train.train_pgen_regressor`): seq → log10 Pgen(1mm), r **0.967**,
|
|
114
|
-
**~190× faster** than the native DP. Breakdown (`
|
|
148
|
+
**~190× faster** than the native DP. Breakdown (`benchmarks/benchmark_pgen_variants.py`):
|
|
115
149
|
r ranks marginalized > J > V > V&J (a CDR3-only regressor best predicts the pure-CDR3
|
|
116
150
|
marginalized Pgen; V&J-conditional depends on unseen genes) and 1mm > exact (smoother ball).
|
|
117
151
|
- **DONE unified codec** (`codec.py`): jointly train encoder+decoder with a geometry-anchor
|
|
@@ -121,9 +155,9 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
121
155
|
rotation match.** `CodecBundle` serializes the PCA transform + a prototype hash + weights;
|
|
122
156
|
`load` refuses a prototype-hash mismatch so incomparable embeddings can't be mixed. Any
|
|
123
157
|
trained codec MUST be shipped as a bundle, never bare weights.
|
|
124
|
-
- Per-chain/species breakdown: `
|
|
158
|
+
- Per-chain/species breakdown: `benchmarks/benchmark_codec_chains.py` (forward cos 0.997–0.999
|
|
125
159
|
universal; inverse chain-dependent — short κ/λ/γ/mouse easy, IGH/TRD hard).
|
|
126
|
-
- **DONE lossless-recon depth/K/data sweep** (`
|
|
160
|
+
- **DONE lossless-recon depth/K/data sweep** (`benchmarks/benchmark_lossless_{depth,kpc}.py`,
|
|
127
161
|
real held-out TRB from `isalgo/airr_control` vs arda landmarks): exact-match is **training-data-
|
|
128
162
|
limited, not architecture-limited** — the old "~0.40 ceiling" was n≈8k starvation. Same one-shot
|
|
129
163
|
decoder: n=20k→50k→100k drives exact **0.885→0.941→0.958** (token 0.996→0.998) at K=2000/PC=400,
|
|
@@ -140,7 +174,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
140
174
|
is V/J/CDR1/CDR2). Isotype is a low-cardinality categorical (~9 IGH classes ≈ 3 bits) *independent*
|
|
141
175
|
of V/J/CDR3 ⇒ not reconstructable ⇒ **carry `c_call` as an exact stored column, never embed it**
|
|
142
176
|
(same as v_call/j_call metadata). No codec change.
|
|
143
|
-
- **DONE T5 (SHM/IGH)** (`bench.theory.shm_embedding_drift`, `
|
|
177
|
+
- **DONE T5 (SHM/IGH)** (`bench.theory.shm_embedding_drift`, `benchmarks/benchmark_igh_shm.py`):
|
|
144
178
|
SHM embedding drift is ~linear/sublinear in mutation load (bounded; IGH 104/mut < TRB 128/mut
|
|
145
179
|
— IGH lowest slope, robust to SHM). IGH's hard reconstruction is **over-compaction, not the
|
|
146
180
|
frame**: on arda coords 95% code (95 PCs) → exact 0.115, 99% (422 PCs) → 0.356 (> old 0.152;
|
|
@@ -151,7 +185,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
151
185
|
enrichment `E(z)=f_obs/f_gen` in embedding space; Poisson (ALICE, P_gen bg) or binomial
|
|
152
186
|
(TCRNET, control bg) + BH q; water-level calibration for the naive regime. Theory
|
|
153
187
|
`tcrnet_convergence` confirms the r→0 graph limit (ρ 0.37→−0.05 as radius grows). Benchmarks
|
|
154
|
-
`
|
|
188
|
+
`benchmarks/benchmark_density_{yfv,ankspond,tcrnet,vdjdb}.py` on HF
|
|
155
189
|
`isalgo/airr_{yfv19,ankspond,benchmark,control}` + VDJdb slim.
|
|
156
190
|
**Key lesson**: real repertoires are pervasively convergent, so a P_gen background flags ~40% of
|
|
157
191
|
clones — use a *biological control* (differential: day15-vs-day0, B27±, CMV-vs-control) for
|
|
@@ -167,7 +201,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
167
201
|
HLA-stratified; `class_witness` = supervised MMD motif finder; learned co-equal Set-Transformer/DeepRC in
|
|
168
202
|
`mir.ml.set_encoder`. Reuse: `TCREmp.embed`, `density.{_WEIGHTS,fit_density_space,calibrate_radius}`,
|
|
169
203
|
`vdjtools.stats.inext`, `preprocess.downsample`, `ml.bundle` hashing. Tests
|
|
170
|
-
`tests/test_{repertoire,set_encoder}.py`; benchmarks `
|
|
204
|
+
`tests/test_{repertoire,set_encoder}.py`; benchmarks `benchmarks/benchmark_repertoire_{aging,depth,cmvhla,hla}.py`
|
|
171
205
|
(shared `_cohort.py`; `airr_benchmark` aging 79, `airr_hip` Emerson 2017 786). **Key empirical lesson**
|
|
172
206
|
(`THEORY.md` T7; **all adversarially verified — two over-claims caught & corrected**): depth-robustness holds
|
|
173
207
|
(`prop:kme`, slope −0.55) but is a **generic KME/MMD Monte-Carlo rate**, not embedding-specific. **Age & CMV
|
|
@@ -197,4 +231,64 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
|
|
|
197
231
|
the paper's clones (0.37β/0.45α, GT is 87% α). Long-past exposure has no batch-robust bulk signal at RNA-seq depth.
|
|
198
232
|
- **Parallelism/GPU documented** (README "Performance & parallelism"): `TCREmp(threads=0)` all-core; density
|
|
199
233
|
`backend="kdtree"` multicore exact; `pick_device` **CUDA→MPS→CPU** (was MPS-only) + `MIR_DEVICE`.
|
|
234
|
+
- **Sample-level refinements + TCGA — 2026-07-14 (pm)** (`BENCHMARKS.md` "2026-07-14 (pm)"; all numbers recorded):
|
|
235
|
+
- **WS1 spectral block** (`fit_repertoire_space(n_eigs=r)`, `benchmark_repertoire_spectral.py`): opt-in top-`r`
|
|
236
|
+
eigenvalues of the second-moment covariance. **Lossy for HLA** — HLA-A\*02 lives in *which* clones co-occur
|
|
237
|
+
(directional), eigenvalues are rotation-invariant → top-r ≤0.55 while full upper-tri reaches **0.593** (D₂=512).
|
|
238
|
+
Default `None` = upper-tri (unchanged); keep the full triangle. Test `test_spectral_second_block_top_r_eigvals`.
|
|
239
|
+
- **WS2 COVID witness** (`benchmark_repertoire_covidwitness.py`, vdjtools `biomarker.fisher_association`): answers
|
|
240
|
+
"Fisher passes but embedding vanishes — why?" → **breadth, not depth**. Genome-wide Fisher passes 0 clones at
|
|
241
|
+
150–300 donors (any depth 20k/120k); full ~1137-donor cohort passes 39β/4α (user tmp scan). Sample-level lever
|
|
242
|
+
is **batch control** (β witness whole 0.51 → mixed-batch **0.75**); per-allele HLA-stratification adds noise
|
|
243
|
+
(median≈whole). HLA+α+β is not the key — breadth is.
|
|
244
|
+
- **WS2b COVID motif recovery** (`benchmark_repertoire_covidmotif.py`, full 1137-donor incidence Fisher, both
|
|
245
|
+
chains): the *breadth-powered* way to find motifs. **α genome-wide recovers 4 GT-true clones = a coherent
|
|
246
|
+
public family CAG·NYGGSQGNLIF (paper cluster 31)**; β recovers 0 (rarer/HLA-restricted). **HLA restriction
|
|
247
|
+
adds 0** — the recoverable α clones are already public. **WS1 (2026-07-15) closed the α/β loop and REFUTED
|
|
248
|
+
the "β is depth/HLA-limited" guess**: β at full *native* depth genome-wide, and `native_beta` across 12
|
|
249
|
+
class-II/B carrier strata at native depth, both recover **0** GT-β. β's GT clones are short **public
|
|
250
|
+
bystanders** (`CASSx…YEQYF` TRBJ2-7, present in COVID *and* healthy; 253/256 present, median incidence 9),
|
|
251
|
+
so they don't discriminate status by *incidence* — the α/β split is **status-enriched vs public-bystander,
|
|
252
|
+
not depth/breadth/HLA**. (Untested caveat: an abundance/size-based test could still catch β clones enriched
|
|
253
|
+
in *magnitude* not *breadth*.)
|
|
254
|
+
- **WS3 UMAP** (`plot_sample_umap.py {covid,aging,hip}`): faceted UMAP of Φ₁ (=MMD geometry) by age/HLA/batch/
|
|
255
|
+
COVID/CMV → `benchmarks/figures/`. covid19 visibly clusters by **batch** in raw Φ₁, dissolves after
|
|
256
|
+
`residualize` (the `prop:batch` story made visual).
|
|
257
|
+
- **WS4 TCGA** (`_tcga.py` local-first loader, untars once; `benchmark_repertoire_tcga.py`; `lifelines` in
|
|
258
|
+
`[bench]`): 9591 samples, 7 chains, OS survival. **Tumor-type separation is depth-dependent** — deepest chain
|
|
259
|
+
**IGK 0.67** ≫ shallow TRB 0.52 / IGH 0.50 (IG light chains carry tissue signal). **Survival = honest negative**:
|
|
260
|
+
ΔC-index ≈0 for every chain over a clinical Cox (age+sex+stage+log reads, base C 0.66–0.73) — the tumour-
|
|
261
|
+
infiltrating repertoire adds no prognostic value at RNA-seq depth. No grade column (used stage); PFS empty.
|
|
262
|
+
- **WS4b TCGA survival via biology features** (`_tcga_features.py` isotype/infiltration/atypicality/clonality;
|
|
263
|
+
`benchmark_repertoire_tcga_survival.py`) — the prognosis the *identity embedding* misses lives in interpretable
|
|
264
|
+
axes (modelled on an internal AIRR-tissue EDA). **Infiltration (hot/cold Z-axis) stratifies survival in 5/8
|
|
265
|
+
cancers** (KIRC/SKCM/KIRP/OV/LGG log-rank p<0.05) and adds C-index in melanoma (+0.036) / KIRP (+0.030);
|
|
266
|
+
**IgA isotype** stratifies bladder (mucosal, p=0.010) + melanoma; **atypicality** stratifies glioma (p=0.001)
|
|
267
|
+
— all matching known immunobiology. Isotype/atypicality show via KM stratification, not linear C-index. Net:
|
|
268
|
+
tissue prognosis = infiltration magnitude + isotype + typicality, **not clonotype identity**.
|
|
269
|
+
- **REFRAME → TME-aware repertoire embedding, pan-cancer** (`_tcga_embedding.py` = per-chain identity ‖
|
|
270
|
+
diversity ‖ coverage/infiltration ‖ isotype ‖ composition ‖ atypicality — the biology axes recast as Φ(S)
|
|
271
|
+
channels; `benchmark_repertoire_tcga_{pancancer,tme}.py`; THEORY.md T7 "Repertoire embeddings for the TME",
|
|
272
|
+
BENCHMARKS.md "Repertoire embedding for TME & survival"). One Φ over 9425 OS samples (78-dim, 5 embeddable
|
|
273
|
+
chains + all 7 via composition). **Robustly prognostic** (LR p<0.05 & CV ΔC>0) in **SKCM +0.039 / BLCA
|
|
274
|
+
+0.025 / HNSC +0.022 / LGG +0.016**; effect-size positives SARC (isotype) / KIRP (atypicality). Top channel
|
|
275
|
+
= **coverage/atypicality/composition, ~never identity**; immune-cold cohorts overfit → pan-cancer mean ΔC≈0
|
|
276
|
+
masks the immune-hot wins. **Paradigm lesson (T7 #6): the same Φ(S) that reads infection/HLA in blood via
|
|
277
|
+
its identity channels stratifies the TME + survival in tissue via its non-identity channels.** Unsupervised
|
|
278
|
+
Φ clustering → TME states (`benchmark_repertoire_tcga_tme.py`, UMAP `benchmarks/figures/umap_tcga_tme`).
|
|
279
|
+
- **Derivable descriptor + in-silico evolution** (`mir.repertoire.sample_descriptor`/`RepertoireDescriptor`/
|
|
280
|
+
`decode_metrics`; `benchmark_repertoire_tcga_insilico.py`; THEORY.md T7 lesson 7): **mass-preserving** smooth
|
|
281
|
+
descriptor — infiltration=log-mass, diversity=log n_eff, clonality=Σw², identity=kernel mean are all smooth
|
|
282
|
+
coordinates (keeps the mass Φ normalises away). The cohort coordinate distribution = a generative manifold;
|
|
283
|
+
perturbing infiltration + conditioning (Gaussian) = **in-silico evolution**: hotter ⇒ diversity +0.84 /
|
|
284
|
+
switch +0.52 / T-vs-B −0.63; CoxPH "make hotter" protective 12/20 cancers, adverse in glioma (LGG +1.19) —
|
|
285
|
+
learned couplings match immunobiology. Test `test_descriptor_metrics_derivable_smooth_and_decodable`.
|
|
286
|
+
**TODO**: per-cancer n_pc; Thorsson immune-subtype validation; a learned (flow/VAE) generative manifold for
|
|
287
|
+
full sequence-level simulation. "Promote the multi-chain descriptor" is **partially done** (2026-07-17):
|
|
288
|
+
`build_embedding` was four things — (a) per-locus space fitting, (b) channel assembly + registry,
|
|
289
|
+
(c) TME feature engineering, (d) impute/z-score. `mir.explain` promotes **(b)+(d)**; **(c)** is correctly
|
|
290
|
+
analysis-local (isotype/composition/atypicality are tissue features, not library concerns — though
|
|
291
|
+
atypicality, being a Φ-geometry op, would belong in `repertoire.py` as `centroid_atypicality` if ever
|
|
292
|
+
wanted). **Remaining**: (a) — "fit one `RepertoireSpace` per locus over a cohort" is construction, so it
|
|
293
|
+
belongs in `mir.repertoire` as a separate, smaller follow-up.
|
|
200
294
|
- Full plan: `~/.claude/plans/i-want-to-completely-crystalline-lake.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mirpy-lib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: ML-oriented embeddings for immune receptor repertoires (TCR/BCR)
|
|
5
5
|
Project-URL: Homepage, https://github.com/antigenomics/mirpy
|
|
6
6
|
Project-URL: Repository, https://github.com/antigenomics/mirpy
|
|
@@ -31,6 +31,7 @@ Requires-Dist: vdjmatch; extra == 'annotate'
|
|
|
31
31
|
Provides-Extra: bench
|
|
32
32
|
Requires-Dist: huggingface-hub; extra == 'bench'
|
|
33
33
|
Requires-Dist: kneed; extra == 'bench'
|
|
34
|
+
Requires-Dist: lifelines; extra == 'bench'
|
|
34
35
|
Requires-Dist: matplotlib; extra == 'bench'
|
|
35
36
|
Requires-Dist: pynndescent; extra == 'bench'
|
|
36
37
|
Requires-Dist: seaborn; extra == 'bench'
|
|
@@ -33,7 +33,7 @@ Phenotype-labelled repertoire cohorts for the §T.7 sample-level embedding bench
|
|
|
33
33
|
| `airr_hip` | HF `isalgo/airr_hip` = **Emerson et al. 2017** (*Nat Genet*, PMID 28369038, doi:10.1038/ng.3822) | manifest `metadata.txt`: `file_name, sample_id, age, race, sex, cmv, hla`; **786 subjects**; TCRβ + **CMV serostatus** (`+`/`-`/`NA` = 340/421/25) + **HLA-A/B typing** (comma-sep `HLA-A*02,…`; A*02 most common, n=294). Sample files are the `file_name` path (`corr/HIP*.txt.gz`), full-depth (~10⁵ clonotypes) | `hf_hub_download(repo_id="isalgo/airr_hip", filename="metadata.txt", repo_type="dataset")` then per-sample `file_name`. Downsample per-sample; **age-match CMV±** (CMV rises with age, a diversity confound) |
|
|
34
34
|
| SRA shallow RNA-seq | HF `isalgo/airr_benchmark/sra/` (`meta.tsv`, `samples.tar.gz`) | `PMID Run BioProject Sample`; 2993 rows; all PMID 30830871, BioProject PRJNA511467 | low-coverage bulk-RNA-seq stress set (the `10²–10⁴` clonotype/chain regime); accession manifest only |
|
|
35
35
|
| `airr_covid19` | HF `isalgo/airr_covid19` = **Vlasova, Nekrasova, Komkov et al. 2026** (*Genome Med* 18, 20; DNA-multiplex FMBA cohort — cite per the dataset card, ⚠ verify before appendix use) | `metadata.tsv`: `file_name, reads, batch_id, sample_id, COVID_status` (COVID/healthy/precovid/unknown), `COVID_IgG/IgM/PCR`, full **4-digit HLA class I+II both alleles** (`HLA-{A,B,C}_{1,2}`, `HLA-{DPB1,DQB1,DRB1}_{1,2}`, `HLA-DRB{3,4,5}_1`), `donor_id`; **1258 donors**, **paired TRA+TRB**. **9 real sequencing batches** (`2020/09…2021/01_FMBA_NovaSeq*`, 103–185 donors each), partly status-confounded (NovaSeq4≈all-healthy, NovaSeq9=all-precovid; NovaSeq5/6/7 mixed). Ships `covid_associated_clonotypes.csv` (114 CDR3 clusters, `has_covid_association` T/F — COVID ground-truth motifs). **No `age`** (age+sex live in the sibling `isalgo/airr_covid19_vacc`). Files: **local git-LFS checkout** at `~/hf/airr_covid19/<file_name>` (vdjtools cols `count/freq/cdr3aa/v/j`), *not* the HF hub cache — load by local path, not `hf_hub_download` | local `~/hf/airr_covid19/`; `git clone git@hf.co:datasets/isalgo/airr_covid19` (LFS). Downsample per-sample. **Batch is a strong nuisance ⇒ prop:batch test bed**; HLA ⟂ batch (donor genetics) vs COVID-status ⟂̸ batch (confounded) |
|
|
36
|
-
| `airr_tcga` | HF `isalgo/airr_tcga` = **TCGA** tumour bulk RNA-seq → AIRR (RNA-seq AIRR method Bolotin et al. *Nat Biotechnol* 2017, PMID 29020005) | `metadata.tsv`: `sample_id` (`TCGA-XX-XXXX.N`), `subject_id`, `cancer_type`/`disease`/`study_id` (**33 cancer types**), `sex/race/age`, `tumor_stage
|
|
36
|
+
| `airr_tcga` | HF `isalgo/airr_tcga` = **TCGA** tumour bulk RNA-seq → AIRR (RNA-seq AIRR extraction method Bolotin et al. *Nat Biotechnol* 2017, PMID 29020005) | `metadata.tsv`: `sample_id` (`TCGA-XX-XXXX.N`), `subject_id`, `cancer_type`/`disease`/`study_id` (**33 cancer types**), `sex/race/age`, **stage** (`cancer_stage` S1–S4 + `tumor_stage` i–iv; no grade column), `therapy/response`, `OS`+`OS_event` (days + 0/1 death, ~9510 usable; **`PFS`/`PFS_event` are present-but-empty — unusable**), **`total_reads`** (raw FASTQ total, GDC realigned-BAM `Total_Reads`, verified ≠ aligned; 99.6% present) + `aligned_reads`; **9591 samples**, **TCR+BCR but IG-dominant** (~97% IG: IGK/IGL/IGH; ~3% TR), median **634** clonotypes/sample (RNA-seq regime). Caveats: 487 lack clinical, 42 lack `total_reads` | **local git-LFS checkout** at `~/hf/airr_tcga/` (`git clone git@hf.co:datasets/isalgo/airr_tcga`) or `hf_hub_download(repo_id="isalgo/airr_tcga", filename="samples.tar.gz"/"metadata.tsv", repo_type="dataset")` + `load.py`. Per-sample AIRR in `samples.tar.gz` (`samples/<sample_id>.tsv`, AIRR cols). Separate **`metadata.hla.tsv`** = donor-level **HLA class-I** keyed by `subject_id` (`HLA-{A,B,C}_{1,2}`, 4-digit, + `hla_source`), union of TCGA **PanImmune** (Thorsson et al. *Immunity* 2018, PMID 29628290; primary, 7649) + **OptiType** fill (Szolek et al. *Bioinformatics* 2014, PMID 25143287; 1474); **9123/9450 donors = 96.5%**, → 9263 samples. **Tissue depth↔infiltration test bed** (`sec:samp-norm`, `prop:infiltration`): `total_reads` is the technical denominator for the receptor read-fraction infiltration proxy; HLA enables the `prop:hla` stratification |
|
|
37
37
|
|
|
38
38
|
## Theory appendix (`appendix/`)
|
|
39
39
|
|
|
@@ -241,6 +241,60 @@ against prototype k; `D_ij = ‖φ(i) − φ(j)‖₂` the embedding-space Eucli
|
|
|
241
241
|
pairwise repertoire MMD must use the **unbiased** estimator (`mmd_matrix(unbiased=True)`) when samples differ
|
|
242
242
|
in depth/diversity — the biased V-statistic's `1/n_eff` self-term otherwise fakes a divergence signal.
|
|
243
243
|
|
|
244
|
+
- **2026-07-14 (pm) additions** (`BENCHMARKS.md` "2026-07-14 (pm)"; three more generalizable lessons):
|
|
245
|
+
**(3) the interaction block's information is directional, not spectral.** The second-moment block
|
|
246
|
+
`Σ w ψ₂ψ₂ᵀ` carries the HLA imprint in *which* public clones co-occur (its eigen-*vectors*), so an opt-in
|
|
247
|
+
top-`r` **eigenvalue** compaction (`fit_repertoire_space(n_eigs=r)`) — rotation-invariant — is lossy: HLA-A\*02
|
|
248
|
+
AUC ≤0.55 vs 0.593 for the full upper triangle. Keep the upper triangle; the spectral path is a compact
|
|
249
|
+
fingerprint only. **(4) clone-level significance is cohort-breadth-limited.** Per-clonotype Fisher (Emerson
|
|
250
|
+
incidence) needs the full cohort: 0 COVID clones clear BH FDR at 150–300 donors *at any read depth*, but ~1137
|
|
251
|
+
donors yield 39β/4α — so a bulk sample-level embedding cannot recover a signal that is not yet significant at
|
|
252
|
+
its own donor count; the tractable sample-level lever is **batch control** (mixed-batch witness β 0.51→0.75),
|
|
253
|
+
not per-allele HLA stratification. **(5) tissue signal is depth-gated.** On TCGA (bulk RNA-seq, IG-dominant),
|
|
254
|
+
the *deepest* chain IGK separates 33 tumour types at AUC 0.67 while shallow TR (~tens of clonotypes/sample)
|
|
255
|
+
sits at chance — but the embedding adds **no** survival value over clinical covariates (ΔC-index≈0, every
|
|
256
|
+
chain), a clean prognostic negative at RNA-seq depth (`benchmark_repertoire_tcga.py`).
|
|
257
|
+
|
|
258
|
+
### Repertoire embeddings for the tumour microenvironment & survival (pan-cancer)
|
|
259
|
+
|
|
260
|
+
The clonotype-*identity* embedding being flat for tumour survival is not the end of the paradigm — it is a
|
|
261
|
+
statement about *which channel* carries the signal. A repertoire embedding is a **multi-channel object**, and
|
|
262
|
+
different questions engage different channels:
|
|
263
|
+
|
|
264
|
+
| channel | what it measures | question it answers |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| kernel-mean + second-moment (identity) | *which* clones / public structure | antigen specificity, HLA imprint, infection/vaccine response |
|
|
267
|
+
| diversity (Hill) | clonality / richness | immune ageing, exhaustion |
|
|
268
|
+
| coverage / infiltration | receptor read load (magnitude Φ₁ normalises away) | **tumour infiltration — hot vs cold** |
|
|
269
|
+
| isotype / C-gene (IGH class-switch) | plasma / mucosal humoral state | **B-cell / TLS-driven TME** |
|
|
270
|
+
| composition (T-vs-B, 7-chain balance) | TME cell-type mix | **immune contexture** |
|
|
271
|
+
| atypicality (Φ-distance to tumour-type centroid) | selection / divergence from the typical repertoire | antigen-driven remodelling |
|
|
272
|
+
|
|
273
|
+
Making these channels explicit (`_tcga_embedding.py`) turns the "biology features" into one **TME-aware,
|
|
274
|
+
multi-chain repertoire embedding** Φ(S). Pan-cancer (33 TCGA types, 9 425 OS-annotated samples,
|
|
275
|
+
`benchmark_repertoire_tcga_pancancer.py`): over a clinical Cox (age+sex+stage+log reads), Φ is **robustly
|
|
276
|
+
prognostic** (LR p<0.05 *and* CV ΔC>0) in **SKCM +0.039, BLCA +0.025, HNSC +0.022, LGG +0.016** — the
|
|
277
|
+
immunologically active cancers — with strong power-limited positives in SARC (isotype +0.038) and KIRP
|
|
278
|
+
(atypicality +0.034). The most-informative channel is **coverage (infiltration) / atypicality / composition**,
|
|
279
|
+
essentially never clonotype identity; immune-cold small cohorts overfit (LIHC/PAAD/UCEC negative), so the
|
|
280
|
+
pan-cancer mean ΔC≈0 masks a clear immune-hot-cancer effect. **Lesson (6):** the same Φ(S) that fingerprints
|
|
281
|
+
infection and HLA in blood also stratifies the tumour microenvironment and survival in tissue — one just reads
|
|
282
|
+
the *non-identity* channels (infiltration magnitude + humoral class + contexture), which the depth-normalised,
|
|
283
|
+
single-chain kernel mean discards by construction. Unsupervised clustering of Φ recovers interpretable TME
|
|
284
|
+
states (hot-diverse / cold-humoral / T-skewed-unswitched), characterised by these channels, enriched in the
|
|
285
|
+
expected cancers, and prognostic **beyond** tumour type (stratified multivariate log-rank p=0.004; the
|
|
286
|
+
low-infiltration cold-humoral state carries the worst outcome, HR 1.14) — `benchmark_repertoire_tcga_tme.py`.
|
|
287
|
+
|
|
288
|
+
**Lesson (7) — derivable metrics ⇒ a simulatable embedding.** Because the mass-preserving descriptor
|
|
289
|
+
(`mir.repertoire.sample_descriptor`) makes infiltration (log-mass), diversity (log n_eff) and clonality (Σw²)
|
|
290
|
+
**smooth analytic coordinates** — the "smoother form" of the Hill block, keeping the total mass Φ normalises
|
|
291
|
+
away — the cohort's joint coordinate distribution is a generative **manifold**. Perturbing the infiltration
|
|
292
|
+
coordinate and conditioning the rest (Gaussian manifold) is *in-silico evolution*: pan-cancer, a hotter tumour
|
|
293
|
+
is predicted to become more diverse (+0.84), more class-switched (+0.52) and more B-skewed (−0.63 T-vs-B), and
|
|
294
|
+
the CoxPH survival response is protective in 12/20 cancers but adverse in glioma (LGG Δlog-HR +1.19) — learned
|
|
295
|
+
couplings that recover known immunobiology (`benchmark_repertoire_tcga_insilico.py`). The embedding is thus not
|
|
296
|
+
only a descriptor but a *model* one can sample and evolve.
|
|
297
|
+
|
|
244
298
|
## Reproduced numbers (v3 pipeline)
|
|
245
299
|
|
|
246
300
|
Run `python experiments/reproduce_supplementary.py` (S1–S3) and
|
|
@@ -102,6 +102,16 @@ One vector per repertoire: RFF kernel mean ‖ Hill diversity ‖ second moment;
|
|
|
102
102
|
:undoc-members:
|
|
103
103
|
:show-inheritance:
|
|
104
104
|
|
|
105
|
+
Explainable readouts (``mir.explain``)
|
|
106
|
+
--------------------------------------
|
|
107
|
+
|
|
108
|
+
Which named channel of ``Φ`` carries the signal, and which clonotypes drive it (Theory T7).
|
|
109
|
+
|
|
110
|
+
.. automodule:: mir.explain
|
|
111
|
+
:members:
|
|
112
|
+
:undoc-members:
|
|
113
|
+
:show-inheritance:
|
|
114
|
+
|
|
105
115
|
Benchmark harness (``mir.bench``)
|
|
106
116
|
---------------------------------
|
|
107
117
|
|
|
@@ -53,6 +53,9 @@ Capabilities (see the :doc:`API reference <api>`)
|
|
|
53
53
|
- **Repertoire embedding** — one fixed vector per sample: RFF kernel mean, coverage-standardised
|
|
54
54
|
Hill diversity, and a second-moment Fisher block; MMD / HLA-stratified distance and a motif
|
|
55
55
|
witness (``mir.repertoire``, T7).
|
|
56
|
+
- **Explainable readouts** — name ``Φ``'s channels and ablate them under *your* scorer to see which
|
|
57
|
+
one carries the signal (and whether it is merely redundant), then hop from the winning kernel-mean
|
|
58
|
+
channel to the clonotypes driving it (``mir.explain``, T7).
|
|
56
59
|
- **Neural codecs** — forward / inverse / Pgen / unified codecs and a learned repertoire set
|
|
57
60
|
encoder; CUDA → MPS → CPU device selection (``mir.ml``, ``[ml]`` extra).
|
|
58
61
|
- **Benchmark harness** — VDJdb clustering + F1/retention and reproduced theory (``mir.bench``).
|