mirpy-lib 3.3.0__tar.gz → 3.4.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.
Files changed (102) hide show
  1. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/.gitignore +3 -3
  2. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/CHANGELOG.md +47 -0
  3. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/CLAUDE.md +43 -8
  4. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/PKG-INFO +58 -16
  5. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/README.md +53 -12
  6. mirpy_lib-3.4.0/docs/_static/custom.css +9 -0
  7. mirpy_lib-3.4.0/docs/_static/depth_robustness.png +0 -0
  8. mirpy_lib-3.4.0/docs/_static/mirpy_dark.png +0 -0
  9. mirpy_lib-3.4.0/docs/_static/mirpy_dark.svg +1 -0
  10. mirpy_lib-3.4.0/docs/_static/mirpy_light.png +0 -0
  11. mirpy_lib-3.4.0/docs/_static/mirpy_light.svg +1 -0
  12. mirpy_lib-3.4.0/docs/_static/sample_embedding_diagram.png +0 -0
  13. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/docs/api.rst +44 -0
  14. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/docs/conf.py +5 -3
  15. mirpy_lib-3.4.0/docs/examples.rst +31 -0
  16. mirpy_lib-3.4.0/docs/index.rst +110 -0
  17. mirpy_lib-3.4.0/docs/usage.rst +269 -0
  18. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/pyproject.toml +19 -11
  19. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/requirements.txt +1 -1
  20. mirpy_lib-3.4.0/setup.sh +88 -0
  21. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/__init__.py +1 -1
  22. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/bench/__init__.py +3 -1
  23. mirpy_lib-3.4.0/src/mir/bench/eval.py +219 -0
  24. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/bench/metrics.py +11 -0
  25. mirpy_lib-3.4.0/src/mir/cli.py +207 -0
  26. mirpy_lib-3.4.0/src/mir/cohort.py +431 -0
  27. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/density.py +41 -12
  28. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/embedding/prototypes.py +5 -4
  29. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/embedding/tcremp.py +1 -2
  30. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/bundle.py +2 -1
  31. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/repertoire.py +205 -24
  32. mirpy_lib-3.3.0/BENCHMARKS.md +0 -264
  33. mirpy_lib-3.3.0/REPERTOIRE_EMBEDDING.md +0 -186
  34. mirpy_lib-3.3.0/REPERTOIRE_LESSONS.md +0 -78
  35. mirpy_lib-3.3.0/SQRT_D_MIGRATION.md +0 -157
  36. mirpy_lib-3.3.0/THEORY.md +0 -322
  37. mirpy_lib-3.3.0/docs/index.rst +0 -68
  38. mirpy_lib-3.3.0/docs/usage.rst +0 -154
  39. mirpy_lib-3.3.0/environment.yml +0 -15
  40. mirpy_lib-3.3.0/setup.sh +0 -90
  41. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/LICENSE +0 -0
  42. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/SOURCES.md +0 -0
  43. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/assets/mirpy_dark.png +0 -0
  44. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/assets/mirpy_dark.svg +0 -0
  45. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/assets/mirpy_light.png +0 -0
  46. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/assets/mirpy_light.svg +0 -0
  47. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/docs/Makefile +0 -0
  48. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0}/docs/requirements.txt +0 -0
  49. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/aliases.py +0 -0
  50. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/alleles.py +0 -0
  51. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/bench/theory.py +0 -0
  52. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/bench/vdjdb.py +0 -0
  53. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/distances/__init__.py +0 -0
  54. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/distances/germline.py +0 -0
  55. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/distances/junction.py +0 -0
  56. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/embedding/__init__.py +0 -0
  57. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/embedding/pca.py +0 -0
  58. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/embedding/presets.py +0 -0
  59. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/explain.py +0 -0
  60. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/__init__.py +0 -0
  61. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/codec.py +0 -0
  62. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/decoder.py +0 -0
  63. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/encoder.py +0 -0
  64. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/set_encoder.py +0 -0
  65. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/tokenize.py +0 -0
  66. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/ml/train.py +0 -0
  67. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/py.typed +0 -0
  68. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/__init__.py +0 -0
  69. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/README.md +0 -0
  70. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/__init__.py +0 -0
  71. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/build_gene_library.py +0 -0
  72. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/build_region_annotations.py +0 -0
  73. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/imgt_gene_library.txt +0 -0
  74. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/olga_gene_library.txt +0 -0
  75. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/gene_library/region_annotations.txt +0 -0
  76. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/build_germline_dist.py +0 -0
  77. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_IGH.npz +0 -0
  78. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_IGK.npz +0 -0
  79. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_IGL.npz +0 -0
  80. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_TRA.npz +0 -0
  81. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_TRB.npz +0 -0
  82. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_TRD.npz +0 -0
  83. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/human_TRG.npz +0 -0
  84. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_IGH.npz +0 -0
  85. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_IGK.npz +0 -0
  86. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_IGL.npz +0 -0
  87. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_TRA.npz +0 -0
  88. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_TRB.npz +0 -0
  89. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_TRD.npz +0 -0
  90. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/germline_dist/mouse_TRG.npz +0 -0
  91. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/.gitkeep +0 -0
  92. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/generate_prototypes.py +0 -0
  93. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_IGH.tsv +0 -0
  94. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_IGK.tsv +0 -0
  95. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_IGL.tsv +0 -0
  96. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_TRA.tsv +0 -0
  97. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_TRB.tsv +0 -0
  98. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_TRD.tsv +0 -0
  99. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/human_TRG.tsv +0 -0
  100. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/manifest.json +0 -0
  101. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/mouse_TRA.tsv +0 -0
  102. {mirpy_lib-3.3.0 → mirpy_lib-3.4.0/src}/mir/resources/prototypes/mouse_TRB.tsv +0 -0
@@ -10,7 +10,7 @@ tests/assets/vdjdb.slim.txt.gz
10
10
  tests/assets/real_repertoires/
11
11
  tests/assets/srx_repertoires/
12
12
  *.airr.tsv
13
- notebooks/assets/
13
+ examples/assets/
14
14
 
15
15
  ## VSCode
16
16
 
@@ -184,10 +184,10 @@ cython_debug/
184
184
  # and can be added to the global gitignore or merged into this file. For a more nuclear
185
185
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
186
186
  #.idea/
187
- mir/docs
187
+ src/mir/docs
188
188
  .idea
189
189
  public
190
- notebooks/assets/derived/
190
+ examples/assets/derived/
191
191
 
192
192
  # ML run artifacts (checkpoints/metrics) — never committed
193
193
  experiments/runs/
@@ -3,6 +3,53 @@
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.4.0 — 2026-07-18
7
+
8
+ Minor: a command-line interface, a uv-based dev setup, and a documentation overhaul. No public
9
+ Python API removed; one optional-dependency group split out.
10
+
11
+ ### Added
12
+
13
+ - **`mir` command-line interface** (`[project.scripts]`, also `python -m mir.cli`) — the two
14
+ embedding scales without writing Python:
15
+ - `mir embed clonotypes SAMPLE` → a per-clonotype TCREMP embedding table (`e0…`).
16
+ - `mir embed repertoires SAMPLE…` → one repertoire vector `Φ(S)` per sample **per chain** on one
17
+ shared basis (`phi0…`), with optional `--mmd` pairwise-distance output.
18
+
19
+ Inputs are any format `vdjtools.io` reads (AIRR/vdjtools/MiXCR/immunoSEQ/parquet); output is TSV
20
+ or Parquet. See `mir embed <cmd> -h`. Tests in `tests/test_cli.py`.
21
+ - **`mir.repertoire.correct_batch`** — Harmony-like cluster-aware batch correction on a stacked
22
+ sample×feature Φ matrix. Removes the batch offset *per soft cluster* (batch-diversity-penalised),
23
+ so a batch confounded with a biological cluster is corrected without erasing that biology; reduces
24
+ exactly to `mir.cohort.residualize` at `n_clusters=1` / `theta=0` (`prop:batch`).
25
+ - **`[ann]` optional-dependency group** for the approximate-NN density backend (`pynndescent`).
26
+
27
+ ### Changed
28
+
29
+ - **Development now uses a repo-local `.venv` via [uv](https://docs.astral.sh/uv/)** instead of
30
+ conda. `setup.sh` is rewritten (bash/zsh portable; `--dev-parents`, `--docs`, `--tests`); the
31
+ conda `environment.yml` is removed. Runtime is unchanged — still a pure-Python `py3-none-any` wheel.
32
+ - **`pynndescent` moved from `[bench]` to the new `[ann]` extra.** `[bench]` is now all pure-wheel
33
+ (no numba/llvmlite), so `pip install "mirpy-lib[bench]"` resolves cleanly on any Python. Users of
34
+ `density.neighbor_enrichment(backend="ann")` should install `"mirpy-lib[ann]"`.
35
+ - **`vdjtools>=3.0.0`** (was `>=2.3.0`).
36
+ - Documentation overhaul: a use-case-driven user guide, the two CLI commands documented, an
37
+ examples/notebooks page, `mir.cohort` and `mir.bench.eval` added to the API reference, a logo, and
38
+ the sample-embedding schematic + real depth-robustness figure. Zero-warning Sphinx build.
39
+ - **Repo layout** (no effect on the installed package): adopted the **src-layout** (`mir/` →
40
+ `src/mir/`); renamed `notebooks/` → `examples/`; and moved the working result/plan markdown out of
41
+ the repo root — `THEORY.md` to the manuscript repo, `BENCHMARKS.md` / `REPERTOIRE_{EMBEDDING,LESSONS}.md`
42
+ / `SQRT_D_MIGRATION.md` / `ROADMAP.md` to `2026-mirpy-analysis/benchmarks/`. Root keeps
43
+ README / CHANGELOG / CLAUDE / SOURCES.
44
+
45
+ ### Fixed
46
+
47
+ - `DEFAULT_GAP_POSITIONS = (3, 4, -4, -3)` had three independent definitions
48
+ (`distances/junction`, `embedding/tcremp`, `ml/bundle`); now defined once in `distances.junction`
49
+ and imported, so the coordinate constant cannot drift.
50
+ - `cohort.cluster_samples` docstring described itself; `AntigenMetric` and `mir.bench.eval` gained
51
+ the docstrings/module-map entries they were missing.
52
+
6
53
  ## 3.3.0 — 2026-07-17
7
54
 
8
55
  Minor: one new public parameter, nothing removed or changed.
@@ -8,9 +8,12 @@ frozen on branch **`legacy-v2`** (`mirpy-lib` 2.x) — do not develop there.
8
8
 
9
9
  ## Repo split (2026-07-16) — three homes
10
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).
11
+ - **`~/vcs/code/mirpy`** (here): **src-layout** — the library is `src/mir/` (import `mir`); unit +
12
+ fast/slow CI tests (`tests/`, on slim bundled data), runnable `examples/` (marimo), docs. As of the
13
+ 3.4.x cleanup the result/plan markdown moved out: **`THEORY.md` → the manuscript repo**;
14
+ **`BENCHMARKS.md`, `REPERTOIRE_{EMBEDDING,LESSONS}.md`, `SQRT_D_MIGRATION.md`, `ROADMAP.md` →
15
+ `2026-mirpy-analysis/benchmarks/`**. Only `README`/`CHANGELOG`/`CLAUDE`/`SOURCES` stay at the root.
16
+ Benchmark *scripts* and the theory *appendix* had already moved out (below).
14
17
  - **`~/vcs/projects/2026-mirpy-analysis`**: all **benchmark scripts** (`benchmarks/` — local + aldan3),
15
18
  full result docs (BENCHMARKS/THEORY/…), figures, run outputs, dataset catalog. Run the mirpy library
16
19
  from here; refresh numbers-of-record here.
@@ -53,7 +56,7 @@ negative S2) — so prototypes use real reads, not model generation.
53
56
  mirpy is normally read-only to the sibling repos; the `from_arda` builder + a tandem-D generation
54
57
  fix were added to `vdjtools` under the owner's direction (this is that owner's ecosystem).
55
58
 
56
- ## Layout (`mir/`)
59
+ ## Layout (`src/mir/`)
57
60
  - `aliases.py`, `alleles.py` — species/locus + allele normalization.
58
61
  - `distances/junction.py` — `junction_distance_matrix` (seqtree.gapblock). Coordinate knobs (all
59
62
  default to the published space, threaded through `TCREmp`): `metric="squared"` (=`d`, default) |
@@ -88,6 +91,20 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
88
91
  Opt-in `fit_repertoire_space(n_eigs=r)` swaps the second-moment block's full `D₂(D₂+1)/2` upper-triangle
89
92
  for its top-`r` eigenvalues (rotation-invariant spectrum; default `None` = upper-tri, unchanged — but
90
93
  lossy for the *directional* HLA imprint, so the full triangle stays the recommended block; `benchmark_repertoire_spectral.py`).
94
+ Also `fit_repertoire_spaces` (one basis **per locus** — the multi-chain fit), `centroid_atypicality`
95
+ (per-sample cosine distance to a group centroid — a Φ-geometry op feeding the digital-donor atypicality channel),
96
+ and `correct_batch` (Harmony-like cluster-aware batch correction on a stacked Φ matrix; reduces to
97
+ `cohort.residualize` at `n_clusters=1`/`theta=0` — `prop:batch`; test in `test_repertoire.py`).
98
+ - `cohort.py` — **the digital donor** (T.7): `fit_donor_embeddings`/`DonorCohort` fuse per-chain identity
99
+ (kernel-mean, cross-sample PCA-reduced) ‖ diversity ‖ coverage across loci through **one `ChannelBuilder`**,
100
+ with an `extra_channels` hook for the analysis's own tissue/clinical blocks; comparability bites twice
101
+ (per-locus `prototype_hash` **and** the stored identity PCA) so `save`/`load` verify every hash and
102
+ `transform` is the only held-out path. Plus `residualize` (batch cookbook), `cluster_samples`
103
+ (MMD→precomputed-metric cluster), `incidence_biomarkers` (subject-incidence Fisher, delegating to
104
+ `vdjtools.biomarker.fisher`). Generalizes the analysis-repo `_tcga_embedding.build_embedding` glue.
105
+ - `bench/eval.py` — the scorers `channel_report` consumes (kept out of `explain.py` so it stays scorer-free):
106
+ `cv_auc` / `held_out_auc` (classification), `cv_cindex` / `km_logrank` (Cox survival, `[bench]`→lifelines),
107
+ `kmer_matrix` (baseline).
91
108
  - `explain.py` — **explainable readouts over any feature matrix** (T7). `ChannelSpec`/`ChannelBuilder`/
92
109
  `stack_embeddings` attach the name→column map `Φ.vector` does not carry (per-chain blocks merge by
93
110
  name); `channel_report(X, spec, scorer, base=, mode="in"|"out"|"both", n_permutations=)` ablates each
@@ -99,14 +116,22 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
99
116
  has no clonotype pre-image and it raises. Depends one-way on `repertoire.py`; nothing there changed.
100
117
  - `ml/` — Part 2 (torch), neural codecs + `set_encoder.py` (learned repertoire track: Set-Transformer/DeepRC
101
118
  attention pooling + `SetEncoderBundle`).
119
+ - `cli.py` — the `mir` console script (argparse, stdlib): `embed clonotypes` (TCREmp table) /
120
+ `embed repertoires` (per-locus `fit_repertoire_space`→`sample_embedding` Φ(S), optional `--mmd`).
121
+ Reads via `vdjtools.io.read`; writes TSV/parquet.
102
122
  - `resources/` — `prototypes/` (TSVs + manifest), `gene_library/` (region_annotations.txt),
103
123
  `germline_dist/` (baked `.npz`, from `build_germline_dist.py`).
104
124
 
105
125
  ## Build / test / run
106
- - Conda env **`mirpy`** (Python 3.12; do NOT use `.venv` here). `pip install -e .`
107
- (pure-Python hatchling; no C build). Extras: `[bench] [annotate] [build] [ml] [docs] [dev]`.
108
- - Tests: `python -m pytest tests/ -q` (78 pass; `-m "not integration"` for the ~5s fast tier —
109
- the pynndescent ANN parity test carries a one-time JIT cost). All self-contained on bundled resources.
126
+ - **Repo-local `.venv` via uv** (Python 3.12; conda retired 2026-07-18). `bash setup.sh`
127
+ (bash/zsh; `--dev-parents` editable-installs `../seqtree ../vdjtools ../vdjmatch`, `--docs`,
128
+ `--tests`), or `uv pip install -e ".[dev,bench]"`. Pure-Python hatchling; no C build for `mir`
129
+ itself. Extras: `[bench] [ann] [annotate] [build] [ml] [docs] [dev] [examples]` (`[ann]` =
130
+ pynndescent, split out of `[bench]` so `[bench]` stays numba-free / clean to resolve).
131
+ - CLI: `mir embed clonotypes SAMPLE` (→ clonotype embedding table) / `mir embed repertoires
132
+ SAMPLE… ` (→ per-sample-per-chain Φ(S)); `mir/cli.py`, `tests/test_cli.py`.
133
+ - Tests: `python -m pytest tests/ -q` (`-m "not integration and not benchmark"` for the fast tier;
134
+ torch/pynndescent tests skip unless `[ml]`/`[ann]` installed). All self-contained on bundled resources.
110
135
  - Experiments: `python benchmarks/reproduce_supplementary.py` (theory S1–S3),
111
136
  `python benchmarks/benchmark_vdjdb.py` (Table S1). Analyses: `analyze_prototype_counts.py`
112
137
  (geometry saturates by K≈100 — T.1/S4), `analyze_pc_decomposition.py` (V/J η² ≈0.44/0.49,
@@ -126,6 +151,16 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
126
151
  GPU only in `mir.ml`: `pick_device()` = **CUDA → MPS → CPU** auto, override `device=`/`MIR_DEVICE`.
127
152
 
128
153
  ## Open loops / next steps
154
+ - **Embedding-tier roadmap** (`2026-mirpy-analysis/benchmarks/ROADMAP.md`, moved out in the 3.4.x
155
+ cleanup) — the "vdjtools at the embedding level" audit +
156
+ plan (three verbs: make / measure / generate-decode). **Phase 0** (robustness + optimization quick wins)
157
+ and **Phase 1** (cohort tier: `bench/eval.py`, `repertoire.{fit_repertoire_spaces,centroid_atypicality}`,
158
+ `cohort.py` digital donor) are **DONE**. Next: **Phase 2** generative loop (`generate.py` `DescriptorDensity`
159
+ + `evolve`/`sample`, `CodecBundle.from_unified/from_decoder`), then multimodal encoders + embedding
160
+ trajectory. **Analysis-repo follow-up:** refactor `_tcga_embedding.build_embedding` onto
161
+ `cohort.fit_donor_embeddings` (+ `extra_channels` for isotype/composition/atypicality) and re-verify the
162
+ pan-cancer ΔC numbers. NB Phase 0 flipped the density default to `backend="kdtree"` — re-verify any
163
+ recorded balloon-mode baselines (±1 boundary counts).
129
164
  - **v3.0 remaining**: 10X paired benchmark; docs (Sphinx theory section + notebooks); CI; publish
130
165
  `py3-none-any` wheel; regenerate `generate_prototypes.py` via `vdjtools.model.generate`.
131
166
  - **Bench tuning**: raw kneedle eps over-merges; `cluster(eps_factor=0.4)` recovers the paper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mirpy-lib
3
- Version: 3.3.0
3
+ Version: 3.4.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
@@ -25,7 +25,9 @@ Requires-Dist: scikit-learn
25
25
  Requires-Dist: scipy
26
26
  Requires-Dist: seqtree>=0.3.0
27
27
  Requires-Dist: tqdm
28
- Requires-Dist: vdjtools>=2.3.0
28
+ Requires-Dist: vdjtools>=3.0.0
29
+ Provides-Extra: ann
30
+ Requires-Dist: pynndescent; extra == 'ann'
29
31
  Provides-Extra: annotate
30
32
  Requires-Dist: vdjmatch; extra == 'annotate'
31
33
  Provides-Extra: bench
@@ -33,11 +35,10 @@ Requires-Dist: huggingface-hub; extra == 'bench'
33
35
  Requires-Dist: kneed; extra == 'bench'
34
36
  Requires-Dist: lifelines; extra == 'bench'
35
37
  Requires-Dist: matplotlib; extra == 'bench'
36
- Requires-Dist: pynndescent; extra == 'bench'
37
38
  Requires-Dist: seaborn; extra == 'bench'
38
39
  Provides-Extra: build
39
40
  Requires-Dist: biopython; extra == 'build'
40
- Requires-Dist: vdjtools[model]>=2.3.0; extra == 'build'
41
+ Requires-Dist: vdjtools[model]>=3.0.0; extra == 'build'
41
42
  Provides-Extra: dev
42
43
  Requires-Dist: psutil>=5; extra == 'dev'
43
44
  Requires-Dist: pytest-cov; extra == 'dev'
@@ -56,6 +57,16 @@ Provides-Extra: ml
56
57
  Requires-Dist: torch; extra == 'ml'
57
58
  Description-Content-Type: text/markdown
58
59
 
60
+ <p align="center">
61
+ <picture>
62
+ <source media="(prefers-color-scheme: dark)" srcset="assets/mirpy_dark.svg">
63
+ <source media="(prefers-color-scheme: light)" srcset="assets/mirpy_light.svg">
64
+ <!-- Absolute PNG fallback: PyPI strips <picture>/<source> and cannot render a relative or
65
+ raw-served SVG, so the logo must be an absolute-URL raster here. GitHub uses the SVG sources. -->
66
+ <img alt="mirpy" src="https://raw.githubusercontent.com/antigenomics/mirpy/master/assets/mirpy_light.png" width="360">
67
+ </picture>
68
+ </p>
69
+
59
70
  <h1 align="center">mirpy — ML embeddings for immune repertoires</h1>
60
71
 
61
72
  [![PyPI](https://img.shields.io/pypi/v/mirpy-lib.svg)](https://pypi.org/project/mirpy-lib/)
@@ -65,7 +76,7 @@ Description-Content-Type: text/markdown
65
76
  **mirpy v3** turns T-/B-cell receptor sequences into fixed-length numeric vectors you can
66
77
  cluster, visualize, and feed to ML models. It implements **TCREMP** — embedding each receptor
67
78
  by its alignment distances to a fixed set of *prototype* sequences — so that Euclidean distance
68
- in embedding space approximates pairwise alignment distance (see [`THEORY.md`](THEORY.md)).
79
+ in embedding space approximates pairwise alignment distance (Theory T1).
69
80
 
70
81
  > v3 is a slim, embedding-focused rewrite. The classical repertoire toolkit (parsing, overlap,
71
82
  > diversity, TCRnet, GLIPH, …) lives on the **`legacy-v2`** branch (`mirpy-lib` 2.x) and in the
@@ -108,6 +119,24 @@ labels = cluster(pca_denoise(X, n_components=50))
108
119
  Paired chains concatenate per-chain embeddings via `PairedTCREmp`. Input/output are AIRR polars
109
120
  frames keyed by `vdjtools.io.schema` column names.
110
121
 
122
+ ## Command line
123
+
124
+ `pip install mirpy-lib` also installs a `mir` command for the two embedding scales — no Python
125
+ needed. Inputs are any format `vdjtools.io` reads (AIRR TSV, vdjtools, MiXCR, immunoSEQ, parquet).
126
+
127
+ ```bash
128
+ # one repertoire -> per-clonotype embedding table (e0…), the input to clustering / ML
129
+ mir embed clonotypes sample.tsv --pca 50 -o clonotypes.parquet
130
+
131
+ # a dataset of repertoires -> one fingerprint Φ(S) per sample, per chain (phi0…), on one
132
+ # shared basis so the rows are mutually comparable; --mmd also writes the pairwise MMD matrix
133
+ mir embed repertoires cohort/*.tsv.gz -o phi.tsv --mmd mmd.tsv
134
+ ```
135
+
136
+ `mir embed clonotypes -h` / `mir embed repertoires -h` list every flag (species, locus,
137
+ prototype count, weight, Φ blocks, …). Sample id defaults to the filename stem; the locus is
138
+ inferred per file (or restrict with `--locus`).
139
+
111
140
  ## Recommended presets
112
141
 
113
142
  `TCREmp.from_defaults(species, locus)` uses the per-chain preset when `n_prototypes` is
@@ -145,9 +174,11 @@ Xr = pca_denoise(X, n_components=p.n_components_recon) # codec reconstruction
145
174
  | `mir.embedding.tcremp` | `TCREmp` / `PairedTCREmp` — the prototype embedding |
146
175
  | `mir.embedding.pca` | PCA denoising of embeddings |
147
176
  | `mir.distances` | junction distance (`seqtree.gapblock`; `metric`/`matrix`/`alignment` options) + baked germline distances |
148
- | `mir.bench` | VDJdb loader, clustering (`cluster(method=…)`: DBSCAN/HDBSCAN/OPTICS) + F1/retention, theory experiments (incl. `codec_losslessness`) |
177
+ | `mir.bench` | VDJdb loader, clustering (`cluster(method=…)`: DBSCAN/HDBSCAN/OPTICS) + F1/retention, theory experiments (incl. `codec_losslessness`), cohort scorers (`bench.eval`: `cv_auc`/`cv_cindex`/`km_logrank`) |
149
178
  | `mir.density` | continuous-density TCRNET/ALICE — enrichment (+ clonal-abundance channel, `backend=` exact/kdtree/ann) + noise-filtering (Theory T6) |
150
- | `mir.repertoire` | sample-level (repertoire) embedding — RFF kernel mean ‖ Hill diversity ‖ second moment; MMD / HLA-stratified distance; motif witness (Theory §T.7) |
179
+ | `mir.repertoire` | sample-level (repertoire) embedding — RFF kernel mean ‖ Hill diversity ‖ second moment; MMD / HLA-stratified distance; motif witness; `centroid_atypicality`, multi-locus `fit_repertoire_spaces` (Theory §T.7) |
180
+ | `mir.explain` | named-channel fusion (`ChannelBuilder`) + scorer-agnostic ablation (`channel_report`/`channel_drivers`) — which part of Φ carries the signal (§T.7) |
181
+ | `mir.cohort` | the **digital donor** — multi-chain `fit_donor_embeddings`/`DonorCohort` (+ `transform`/`save`/`load`) + `residualize` / `cluster_samples` / `incidence_biomarkers` (§T.7) |
151
182
  | `mir.ml` | neural codecs (forward/inverse/Pgen/unified) + learned repertoire `set_encoder` (Set-Transformer/DeepRC) — Part 2, experimental; `[ml]` extra |
152
183
 
153
184
  ## Background subtraction & clustering (`mir.density`)
@@ -174,11 +205,12 @@ Use a **biological control** as the background when you have one (e.g. pre- vs p
174
205
  patient vs healthy) — differential enrichment cancels generic public convergence and isolates the
175
206
  antigen-specific response. With no control, `generate_background(locus, n)` samples the vdjtools
176
207
  P_gen model (the ALICE regime); the "water level" of a naive repertoire is handled by the
177
- empirical-null calibration. See `experiments/benchmark_density_{yfv,ankspond,tcrnet}.py`.
208
+ empirical-null calibration. The density benchmarks (YFV, ankylosing-spondylitis B27, TCRNET)
209
+ live in the companion [`2026-mirpy-analysis`](https://github.com/antigenomics) repo.
178
210
 
179
211
  At whole-repertoire scale, pass `neighbor_enrichment(..., backend="kdtree")` (exact scipy cKDTree,
180
212
  5–9× faster than the default BallTree) or `backend="ann"` (approximate pynndescent, ~30× faster
181
- past ~10⁵ clones, trading a small conservative undercount); see `experiments/benchmark_ann.py`.
213
+ past ~10⁵ clones, trading a small conservative undercount; `pip install "mirpy-lib[ann]"`).
182
214
 
183
215
  ## Sample-level (repertoire) embedding (`mir.repertoire`)
184
216
 
@@ -213,16 +245,24 @@ a batch offset is first-order and cancels, while a batch-orthogonal signal (e.g.
213
245
  empirical rule of thumb — **diversity for how-even, the embedding for which-clones**: clone-size
214
246
  phenotypes (age, CMV) are a diversity summary's turf, while clonotype identity (HLA — strongest in
215
247
  TRA and class II) lives in the second moment / witness. A learned co-equal set encoder
216
- (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). See
217
- `experiments/benchmark_repertoire_*.py`, `BENCHMARKS.md`, and `THEORY.md` T7.
248
+ (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). Recorded results and theory
249
+ (T7) live in the companion [`2026-mirpy-analysis`](https://github.com/antigenomics) repo
250
+ (`benchmarks/{BENCHMARKS,THEORY}.md`) alongside the benchmark scripts.
218
251
 
219
252
  ## Reproduce the paper
220
253
 
254
+ The self-contained theory notebooks run on bundled data:
255
+
221
256
  ```bash
222
- python experiments/reproduce_supplementary.py # supplementary S1–S3
223
- python experiments/benchmark_vdjdb.py # Table S1 (needs a VDJdb dump)
257
+ pip install "mirpy-lib[examples]"
258
+ marimo edit examples/theory.py # supplementary S1–S3 (distance laws, D↔d, prototype robustness)
259
+ marimo edit examples/quickstart.py # embed + cluster VDJdb antigens
224
260
  ```
225
261
 
262
+ The full benchmark suite (VDJdb Table S1, density, repertoire/TCGA) and result docs live in the
263
+ companion analysis repo [`2026-mirpy-analysis`](https://github.com/antigenomics) — this repo is the
264
+ library + CI tests only.
265
+
226
266
  Method: Kremlyakova *et al.*, *TCREMP: a bioinformatic pipeline for efficient embedding of
227
267
  T-cell receptor sequences*, **J Mol Biol** 437 (2025) 169205.
228
268
 
@@ -236,12 +276,14 @@ mirpy is CPU-parallel by default and uses the GPU for the neural codecs. Knobs,
236
276
  | Density kNN / balloon | `neighbor_enrichment(..., backend=…)` | `"exact"` (BallTree, **1 core**) | `backend="kdtree"` = exact scipy cKDTree, **all cores** (`workers=-1`), 5–9× faster; `backend="ann"` = pynndescent, auto all-core, ~30× at ≥1e5. Prefer `kdtree` for multicore exact. |
237
277
  | Clustering | `cluster(..., n_jobs=-1)` | sklearn default (1) | forwarded to DBSCAN/OPTICS/HDBSCAN via `**kwargs`; parallelizes the neighbour search. |
238
278
  | BLAS (PCA, RFF, matmul) | `OMP_NUM_THREADS` / `OPENBLAS_NUM_THREADS` env | all cores | numpy/sklearn use the platform BLAS; cap via env if oversubscribed. |
239
- | Neural codecs (`mir.ml`) | `pick_device()` / `device=` / `MIR_DEVICE` env | **CUDA → MPS → CPU**, auto | every `train_*` / codec / bundle takes `device=`; e.g. `MIR_DEVICE=cuda:1 python experiments/train_forward_encoder.py`. Torch-free paths (`density`, `repertoire`) never touch the GPU. |
279
+ | Neural codecs (`mir.ml`) | `pick_device()` / `device=` / `MIR_DEVICE` env | **CUDA → MPS → CPU**, auto | every `train_*` / codec / bundle takes `device=`; e.g. `MIR_DEVICE=cuda:1` pins the second GPU. Torch-free paths (`density`, `repertoire`) never touch the GPU. |
240
280
 
241
281
  Rule of thumb: leave `threads=0` (all cores) for embedding; switch density to `backend="kdtree"`
242
282
  for exact multicore or `"ann"` at whole-repertoire scale; the GPU is used only by `mir.ml`.
243
283
 
244
284
  ## Development
245
285
 
246
- Conda env `mirpy` (Python 3.12): `bash setup.sh`. Tests: `python -m pytest tests/ -q`.
247
- See [`CLAUDE.md`](CLAUDE.md) for the architecture and reuse map.
286
+ Repo-local `.venv` via [uv](https://docs.astral.sh/uv/) (bash/zsh): `bash setup.sh` add
287
+ `--dev-parents` to editable-install the sibling `seqtree` / `vdjtools` / `vdjmatch` checkouts and
288
+ `--tests` to run the fast suite. Tests: `python -m pytest tests/ -q`. See [`CLAUDE.md`](CLAUDE.md)
289
+ for the architecture and reuse map.
@@ -1,3 +1,13 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/mirpy_dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="assets/mirpy_light.svg">
5
+ <!-- Absolute PNG fallback: PyPI strips <picture>/<source> and cannot render a relative or
6
+ raw-served SVG, so the logo must be an absolute-URL raster here. GitHub uses the SVG sources. -->
7
+ <img alt="mirpy" src="https://raw.githubusercontent.com/antigenomics/mirpy/master/assets/mirpy_light.png" width="360">
8
+ </picture>
9
+ </p>
10
+
1
11
  <h1 align="center">mirpy — ML embeddings for immune repertoires</h1>
2
12
 
3
13
  [![PyPI](https://img.shields.io/pypi/v/mirpy-lib.svg)](https://pypi.org/project/mirpy-lib/)
@@ -7,7 +17,7 @@
7
17
  **mirpy v3** turns T-/B-cell receptor sequences into fixed-length numeric vectors you can
8
18
  cluster, visualize, and feed to ML models. It implements **TCREMP** — embedding each receptor
9
19
  by its alignment distances to a fixed set of *prototype* sequences — so that Euclidean distance
10
- in embedding space approximates pairwise alignment distance (see [`THEORY.md`](THEORY.md)).
20
+ in embedding space approximates pairwise alignment distance (Theory T1).
11
21
 
12
22
  > v3 is a slim, embedding-focused rewrite. The classical repertoire toolkit (parsing, overlap,
13
23
  > diversity, TCRnet, GLIPH, …) lives on the **`legacy-v2`** branch (`mirpy-lib` 2.x) and in the
@@ -50,6 +60,24 @@ labels = cluster(pca_denoise(X, n_components=50))
50
60
  Paired chains concatenate per-chain embeddings via `PairedTCREmp`. Input/output are AIRR polars
51
61
  frames keyed by `vdjtools.io.schema` column names.
52
62
 
63
+ ## Command line
64
+
65
+ `pip install mirpy-lib` also installs a `mir` command for the two embedding scales — no Python
66
+ needed. Inputs are any format `vdjtools.io` reads (AIRR TSV, vdjtools, MiXCR, immunoSEQ, parquet).
67
+
68
+ ```bash
69
+ # one repertoire -> per-clonotype embedding table (e0…), the input to clustering / ML
70
+ mir embed clonotypes sample.tsv --pca 50 -o clonotypes.parquet
71
+
72
+ # a dataset of repertoires -> one fingerprint Φ(S) per sample, per chain (phi0…), on one
73
+ # shared basis so the rows are mutually comparable; --mmd also writes the pairwise MMD matrix
74
+ mir embed repertoires cohort/*.tsv.gz -o phi.tsv --mmd mmd.tsv
75
+ ```
76
+
77
+ `mir embed clonotypes -h` / `mir embed repertoires -h` list every flag (species, locus,
78
+ prototype count, weight, Φ blocks, …). Sample id defaults to the filename stem; the locus is
79
+ inferred per file (or restrict with `--locus`).
80
+
53
81
  ## Recommended presets
54
82
 
55
83
  `TCREmp.from_defaults(species, locus)` uses the per-chain preset when `n_prototypes` is
@@ -87,9 +115,11 @@ Xr = pca_denoise(X, n_components=p.n_components_recon) # codec reconstruction
87
115
  | `mir.embedding.tcremp` | `TCREmp` / `PairedTCREmp` — the prototype embedding |
88
116
  | `mir.embedding.pca` | PCA denoising of embeddings |
89
117
  | `mir.distances` | junction distance (`seqtree.gapblock`; `metric`/`matrix`/`alignment` options) + baked germline distances |
90
- | `mir.bench` | VDJdb loader, clustering (`cluster(method=…)`: DBSCAN/HDBSCAN/OPTICS) + F1/retention, theory experiments (incl. `codec_losslessness`) |
118
+ | `mir.bench` | VDJdb loader, clustering (`cluster(method=…)`: DBSCAN/HDBSCAN/OPTICS) + F1/retention, theory experiments (incl. `codec_losslessness`), cohort scorers (`bench.eval`: `cv_auc`/`cv_cindex`/`km_logrank`) |
91
119
  | `mir.density` | continuous-density TCRNET/ALICE — enrichment (+ clonal-abundance channel, `backend=` exact/kdtree/ann) + noise-filtering (Theory T6) |
92
- | `mir.repertoire` | sample-level (repertoire) embedding — RFF kernel mean ‖ Hill diversity ‖ second moment; MMD / HLA-stratified distance; motif witness (Theory §T.7) |
120
+ | `mir.repertoire` | sample-level (repertoire) embedding — RFF kernel mean ‖ Hill diversity ‖ second moment; MMD / HLA-stratified distance; motif witness; `centroid_atypicality`, multi-locus `fit_repertoire_spaces` (Theory §T.7) |
121
+ | `mir.explain` | named-channel fusion (`ChannelBuilder`) + scorer-agnostic ablation (`channel_report`/`channel_drivers`) — which part of Φ carries the signal (§T.7) |
122
+ | `mir.cohort` | the **digital donor** — multi-chain `fit_donor_embeddings`/`DonorCohort` (+ `transform`/`save`/`load`) + `residualize` / `cluster_samples` / `incidence_biomarkers` (§T.7) |
93
123
  | `mir.ml` | neural codecs (forward/inverse/Pgen/unified) + learned repertoire `set_encoder` (Set-Transformer/DeepRC) — Part 2, experimental; `[ml]` extra |
94
124
 
95
125
  ## Background subtraction & clustering (`mir.density`)
@@ -116,11 +146,12 @@ Use a **biological control** as the background when you have one (e.g. pre- vs p
116
146
  patient vs healthy) — differential enrichment cancels generic public convergence and isolates the
117
147
  antigen-specific response. With no control, `generate_background(locus, n)` samples the vdjtools
118
148
  P_gen model (the ALICE regime); the "water level" of a naive repertoire is handled by the
119
- empirical-null calibration. See `experiments/benchmark_density_{yfv,ankspond,tcrnet}.py`.
149
+ empirical-null calibration. The density benchmarks (YFV, ankylosing-spondylitis B27, TCRNET)
150
+ live in the companion [`2026-mirpy-analysis`](https://github.com/antigenomics) repo.
120
151
 
121
152
  At whole-repertoire scale, pass `neighbor_enrichment(..., backend="kdtree")` (exact scipy cKDTree,
122
153
  5–9× faster than the default BallTree) or `backend="ann"` (approximate pynndescent, ~30× faster
123
- past ~10⁵ clones, trading a small conservative undercount); see `experiments/benchmark_ann.py`.
154
+ past ~10⁵ clones, trading a small conservative undercount; `pip install "mirpy-lib[ann]"`).
124
155
 
125
156
  ## Sample-level (repertoire) embedding (`mir.repertoire`)
126
157
 
@@ -155,16 +186,24 @@ a batch offset is first-order and cancels, while a batch-orthogonal signal (e.g.
155
186
  empirical rule of thumb — **diversity for how-even, the embedding for which-clones**: clone-size
156
187
  phenotypes (age, CMV) are a diversity summary's turf, while clonotype identity (HLA — strongest in
157
188
  TRA and class II) lives in the second moment / witness. A learned co-equal set encoder
158
- (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). See
159
- `experiments/benchmark_repertoire_*.py`, `BENCHMARKS.md`, and `THEORY.md` T7.
189
+ (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). Recorded results and theory
190
+ (T7) live in the companion [`2026-mirpy-analysis`](https://github.com/antigenomics) repo
191
+ (`benchmarks/{BENCHMARKS,THEORY}.md`) alongside the benchmark scripts.
160
192
 
161
193
  ## Reproduce the paper
162
194
 
195
+ The self-contained theory notebooks run on bundled data:
196
+
163
197
  ```bash
164
- python experiments/reproduce_supplementary.py # supplementary S1–S3
165
- python experiments/benchmark_vdjdb.py # Table S1 (needs a VDJdb dump)
198
+ pip install "mirpy-lib[examples]"
199
+ marimo edit examples/theory.py # supplementary S1–S3 (distance laws, D↔d, prototype robustness)
200
+ marimo edit examples/quickstart.py # embed + cluster VDJdb antigens
166
201
  ```
167
202
 
203
+ The full benchmark suite (VDJdb Table S1, density, repertoire/TCGA) and result docs live in the
204
+ companion analysis repo [`2026-mirpy-analysis`](https://github.com/antigenomics) — this repo is the
205
+ library + CI tests only.
206
+
168
207
  Method: Kremlyakova *et al.*, *TCREMP: a bioinformatic pipeline for efficient embedding of
169
208
  T-cell receptor sequences*, **J Mol Biol** 437 (2025) 169205.
170
209
 
@@ -178,12 +217,14 @@ mirpy is CPU-parallel by default and uses the GPU for the neural codecs. Knobs,
178
217
  | Density kNN / balloon | `neighbor_enrichment(..., backend=…)` | `"exact"` (BallTree, **1 core**) | `backend="kdtree"` = exact scipy cKDTree, **all cores** (`workers=-1`), 5–9× faster; `backend="ann"` = pynndescent, auto all-core, ~30× at ≥1e5. Prefer `kdtree` for multicore exact. |
179
218
  | Clustering | `cluster(..., n_jobs=-1)` | sklearn default (1) | forwarded to DBSCAN/OPTICS/HDBSCAN via `**kwargs`; parallelizes the neighbour search. |
180
219
  | BLAS (PCA, RFF, matmul) | `OMP_NUM_THREADS` / `OPENBLAS_NUM_THREADS` env | all cores | numpy/sklearn use the platform BLAS; cap via env if oversubscribed. |
181
- | Neural codecs (`mir.ml`) | `pick_device()` / `device=` / `MIR_DEVICE` env | **CUDA → MPS → CPU**, auto | every `train_*` / codec / bundle takes `device=`; e.g. `MIR_DEVICE=cuda:1 python experiments/train_forward_encoder.py`. Torch-free paths (`density`, `repertoire`) never touch the GPU. |
220
+ | Neural codecs (`mir.ml`) | `pick_device()` / `device=` / `MIR_DEVICE` env | **CUDA → MPS → CPU**, auto | every `train_*` / codec / bundle takes `device=`; e.g. `MIR_DEVICE=cuda:1` pins the second GPU. Torch-free paths (`density`, `repertoire`) never touch the GPU. |
182
221
 
183
222
  Rule of thumb: leave `threads=0` (all cores) for embedding; switch density to `backend="kdtree"`
184
223
  for exact multicore or `"ann"` at whole-repertoire scale; the GPU is used only by `mir.ml`.
185
224
 
186
225
  ## Development
187
226
 
188
- Conda env `mirpy` (Python 3.12): `bash setup.sh`. Tests: `python -m pytest tests/ -q`.
189
- See [`CLAUDE.md`](CLAUDE.md) for the architecture and reuse map.
227
+ Repo-local `.venv` via [uv](https://docs.astral.sh/uv/) (bash/zsh): `bash setup.sh` add
228
+ `--dev-parents` to editable-install the sibling `seqtree` / `vdjtools` / `vdjmatch` checkouts and
229
+ `--tests` to run the fast suite. Tests: `python -m pytest tests/ -q`. See [`CLAUDE.md`](CLAUDE.md)
230
+ for the architecture and reuse map.
@@ -0,0 +1,9 @@
1
+ /* Figures with dark ink on a transparent background (schematics, plots exported from LaTeX/gnuplot)
2
+ stay legible in dark mode by rendering on a white card. Scoped to `.mir-fig` so the theme-aware
3
+ logo (only-light / only-dark) is untouched. */
4
+ img.mir-fig {
5
+ background: #ffffff;
6
+ padding: 10px 14px;
7
+ border-radius: 8px;
8
+ max-width: 100%;
9
+ }