mirpy-lib 4.2.0__tar.gz → 4.3.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-4.2.0 → mirpy_lib-4.3.0}/CHANGELOG.md +120 -0
- mirpy_lib-4.3.0/PKG-INFO +296 -0
- mirpy_lib-4.3.0/README.md +238 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/custom.css +10 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/api.rst +2 -1
- mirpy_lib-4.3.0/docs/channels.rst +188 -0
- mirpy_lib-4.3.0/docs/cli.rst +169 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/conf.py +6 -0
- mirpy_lib-4.3.0/docs/getting-started.rst +198 -0
- mirpy_lib-4.3.0/docs/glossary.rst +158 -0
- mirpy_lib-4.3.0/docs/index.rst +293 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/math.rst +19 -19
- mirpy_lib-4.3.0/docs/notebooks.rst +83 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/signature.rst +34 -12
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/usage.rst +59 -4
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/pyproject.toml +3 -1
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/skills/mirpy/SKILL.md +30 -1
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/__init__.py +1 -1
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/cli.py +29 -5
- mirpy_lib-4.3.0/src/mir/signature/features.py +496 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/signature/signature.py +80 -16
- mirpy_lib-4.2.0/PKG-INFO +0 -640
- mirpy_lib-4.2.0/README.md +0 -584
- mirpy_lib-4.2.0/docs/channels.rst +0 -80
- mirpy_lib-4.2.0/docs/examples.rst +0 -51
- mirpy_lib-4.2.0/docs/index.rst +0 -141
- mirpy_lib-4.2.0/docs/notebooks.rst +0 -65
- mirpy_lib-4.2.0/src/mir/signature/features.py +0 -222
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/.gitignore +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/LICENSE +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark_bg.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light_bg.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/data/signature_reference_studies.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/Makefile +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_ext/themed_graphviz.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/depth_robustness.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_dark.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_dark.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_light.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_light.svg +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/sample_embedding_diagram.png +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_templates/site-nav.html +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/preprocessing.rst +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/setup.sh +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/aliases.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/alleles.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/eval.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/metrics.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/theory.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/vdjdb.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/cohort.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/density.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/germline.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/junction.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/shm.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/pca.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/presets.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/prototypes.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/tcremp.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/explain.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/generate.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/bundle.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/codec.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/decoder.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/diffusion.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/encoder.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/set_encoder.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/tokenize.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/train.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/py.typed +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/repertoire.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/README.md +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/build_gene_library.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/build_region_annotations.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/imgt_gene_library.txt +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/olga_gene_library.txt +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/region_annotations.txt +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/build_germline_dist.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGH.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGK.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGL.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRA.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRB.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRD.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRG.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGH.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGK.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGL.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRA.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRB.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRD.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRG.npz +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/.gitkeep +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/generate_prototypes.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGH.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGK.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGL.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRA.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRB.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRD.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRG.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/manifest.json +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/mouse_TRA.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/mouse_TRB.tsv +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/signature/corpora.json +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/signature/__init__.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/track.py +0 -0
- {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/twin.py +0 -0
|
@@ -3,6 +3,126 @@
|
|
|
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
|
+
## 4.3.0 — 2026-09-28
|
|
7
|
+
|
|
8
|
+
The geometry half emits embedding **diversity**, not only Rao — and stops emitting a presence mask
|
|
9
|
+
under a diversity name. `ISSUES.md` items 11, 12 and 13. Requires vdjtools >= 4.3.0.
|
|
10
|
+
|
|
11
|
+
### Fixed: Rao of a sub-`min_clonotypes` locus was `0.0`, and that is not a measurement
|
|
12
|
+
|
|
13
|
+
`rsig:div:<L>:rao` came out as exactly `0.0` when a locus held a single clonotype. The arithmetic is
|
|
14
|
+
right — a one-point set has no dispersion — and the column is then not a diversity read-out. It is
|
|
15
|
+
`mask:<L>:present` in different units, sitting at the bottom of a distribution whose other values
|
|
16
|
+
are around 16.
|
|
17
|
+
|
|
18
|
+
On 885 bulk blood RNA-seq samples over seven loci it affected TRD in 118 samples (13.3%), TRG in 8,
|
|
19
|
+
IGH in 1 and IGK in 1. Against the non-zero distribution (1st percentile 16.43, median 16.63), a
|
|
20
|
+
single-clonotype locus lands about **29 robust deviations** below the 1st percentile after
|
|
21
|
+
standardising, and nothing in the output said the value was categorically different from its
|
|
22
|
+
neighbours.
|
|
23
|
+
|
|
24
|
+
Both consequences were found by a check rather than by a failure:
|
|
25
|
+
|
|
26
|
+
- A Cox screen over 261 channels returned a **hazard ratio of 739 per standard deviation at
|
|
27
|
+
p = 7e-155** for `rsig:div:IGH:rao` against overall survival on 838 patients and 305 events — off
|
|
28
|
+
**one patient**. The Spearman correlation between that channel and survival is **-0.023**: there
|
|
29
|
+
is no monotone association at all, and the fit was reading a single extreme point.
|
|
30
|
+
- A downstream baseline moved by **0.077 ROC-AUC** in one direction and 0.054 in the other, from
|
|
31
|
+
four values in one cohort.
|
|
32
|
+
|
|
33
|
+
The family is now a **hole** below the same floor `band_shares` has always used to drop a
|
|
34
|
+
compartment rather than zero it, with `mask` carrying the reason. `features.rao_of` still returns
|
|
35
|
+
the raw zero for a caller who wants it.
|
|
36
|
+
|
|
37
|
+
The **geometry is still computed** below the floor: `Phi` is perfectly measurable from three
|
|
38
|
+
clonotypes even when its dispersion is not, so holing the raw features would have thrown away a
|
|
39
|
+
measurable embedding to fix an unmeasurable diversity.
|
|
40
|
+
|
|
41
|
+
### Added: the embedding-diversity family
|
|
42
|
+
|
|
43
|
+
Rao is the metric-space analogue of Simpson diversity, and it was the only such read-out the
|
|
44
|
+
geometry half gave up — everything else it knew was inside a rotated component, which has no name a
|
|
45
|
+
domain reader can use. That is the wrong way round: the reason to embed a repertoire is that a
|
|
46
|
+
counting index cannot see that two clonotypes are one substitution apart.
|
|
47
|
+
|
|
48
|
+
Per locus, all from the chunked pass that already computes `Phi`:
|
|
49
|
+
|
|
50
|
+
| channel | what it is the analogue of |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `div:<L>:q_v`, `q_j`, `q_c` | Simpson per germline component. The strides are literal column offsets, so "how much of this is V-driven" needs no attribution model — and the three sum to `rao` |
|
|
53
|
+
| `div:<L>:q_frac_v/j/c` | a composition of *where* the diversity lives, in clr coordinates. No counting analogue exists |
|
|
54
|
+
| `div:<L>:evenness` | normalised Shannon / clonality, composition held fixed. Bounded, far less depth-fragile than richness |
|
|
55
|
+
| `div:<L>:eff_dim` | **richness** — directions of receptor space occupied. 1,000 clones in one convergent cluster occupy few; 1,000 unrelated clones occupy many |
|
|
56
|
+
| `div:<L>:eff_dim_pr` | the order-2 version, led by the dominant directions |
|
|
57
|
+
| `div:<L>:q_top`, `q_singleton`, `q_ratio_top` | the diversity *of* a clone-size compartment, rather than its share, which `band` already carries |
|
|
58
|
+
| `disp:<L>:top_singleton`, `cos_top_singleton`, `norm` | a displacement, which no index has |
|
|
59
|
+
| `disp:IGH:IgG_IgM`, `IgA_IgM` and cosines | class-switch geometry |
|
|
60
|
+
|
|
61
|
+
Only ever **within** one locus: each locus has its own prototype panel, so `Phi(TRA)` and `Phi(TRB)`
|
|
62
|
+
are vectors in different spaces and a distance between them is arithmetic without a meaning.
|
|
63
|
+
|
|
64
|
+
**Cost, measured on this box at the shipped `K = 256` (so `p = 768`).** Added to the `Phi` pass, per
|
|
65
|
+
locus: 3.3 ms at 50 clonotypes, 3.9 ms at 200, 25.3 ms at 700, 34.3 ms at 2,500, 61.9 ms at 10,000.
|
|
66
|
+
A realistic seven-locus blood sample (two deep loci, five shallow) pays roughly **125 ms**, which
|
|
67
|
+
about doubles `rsig`'s own per-sample cost and adds under a tenth to a full signature run.
|
|
68
|
+
|
|
69
|
+
Nearly all of that is one eigendecomposition. Two exact identities keep the rest free: `sum lambda`
|
|
70
|
+
is the trace of the covariance and `sum lambda^2` is its squared Frobenius norm, so `eff_dim_pr`
|
|
71
|
+
costs two reductions and no spectrum. `eff_dim` needs eigenvalues, and is taken on the **smaller**
|
|
72
|
+
of the `p x p` covariance and the `n x n` weighted Gram — they have identical non-zero eigenvalues,
|
|
73
|
+
and eigendecomposition is cubic (25.7 ms at 768, 0.29 ms at 100), which is what makes a shallow
|
|
74
|
+
locus cheap. The accumulator itself is the `p x p` second moment rather than the Gram, because that
|
|
75
|
+
is what keeps the pass **linear in sequencing depth**.
|
|
76
|
+
|
|
77
|
+
### Added: `mask` on the geometry half, and `named=` on `rsig`
|
|
78
|
+
|
|
79
|
+
`rsig:mask:<L>:present` / `:estimable`, under the same names and the same convention as the
|
|
80
|
+
statistics half — two products, one convention. `mask` is a **feature block**, not diagnostics:
|
|
81
|
+
which loci a donor resolved is biology, and adding the presence flags moved an external ROC-AUC
|
|
82
|
+
from 0.6243 to 0.6676 on the cohort where it was measured.
|
|
83
|
+
|
|
84
|
+
`rsig(..., named=True)` returns the reportable raw blocks — `depth`, `band`, `band_igh` — matching
|
|
85
|
+
`vsig`.
|
|
86
|
+
|
|
87
|
+
### Note: no corpus is invalidated
|
|
88
|
+
|
|
89
|
+
A channel is pass-through: the rotation is indexed by `raw_columns` and nothing else, and
|
|
90
|
+
`corpus.apply` fills every registered channel from the sample. Every artifact already published
|
|
91
|
+
gains these columns with **no refit and no new download**.
|
|
92
|
+
|
|
93
|
+
### Added: `mir signature --min-clonotypes N`
|
|
94
|
+
|
|
95
|
+
The estimability floor was reachable from Python (`rsig(..., min_clonotypes=5)`) and from the
|
|
96
|
+
command's own epilog, which told readers to use a `--min-clonotypes` flag the parser did not
|
|
97
|
+
define. It defines it now. `tests/test_cli_flags_are_documented.py` pins both directions: every
|
|
98
|
+
flag the help text names must exist, and every flag that changes an answer must appear in
|
|
99
|
+
`docs/cli.rst`.
|
|
100
|
+
|
|
101
|
+
### Documentation
|
|
102
|
+
|
|
103
|
+
- **`examples/signature_pipeline.py` ran a command that no longer exists.** It invoked
|
|
104
|
+
`mir signature --preset classify` — a flag deleted in 4.0.0 — swallowed the non-zero exit into a
|
|
105
|
+
status line, and then failed reading the file the command had not written. Rewritten against the
|
|
106
|
+
real interface: `--corpus blood --components 32`, both halves run and joined for real at the end,
|
|
107
|
+
and a failing subprocess now raises instead of rendering as status. It also described `mir
|
|
108
|
+
signature` as emitting both halves and showed `vsig:` columns, which it has not done since 4.0.0.
|
|
109
|
+
- **`examples/signature.py`** described the `contrast` block and a frozen naive vector, both
|
|
110
|
+
removed in 4.0.0, and closed by saying no corpus-fitted rotation ships — which is what 4.0.0
|
|
111
|
+
changed. Corrected, with what replaced each.
|
|
112
|
+
- **`docs/examples.rst` deleted.** It duplicated `docs/notebooks.rst`, linked a
|
|
113
|
+
`examples/feature_vectors.py` that no longer exists, and described presets and a `nuisance`
|
|
114
|
+
control removed in 4.0.0. `notebooks.rst` is now the single worked-examples page and lists all
|
|
115
|
+
six notebooks.
|
|
116
|
+
- **Corpus claims corrected.** The docs said four corpora ship with the wheel; nine are published,
|
|
117
|
+
and since 4.2.0 the artifacts are release assets fetched on first use.
|
|
118
|
+
- **`--named`, `--winsor-p`, `--on-duplicate` and `--min-clonotypes`** added to `docs/cli.rst`,
|
|
119
|
+
which had documented six of the ten options that change an answer.
|
|
120
|
+
- A plain-language introduction to what the geometry half measures, ahead of the mathematics; the
|
|
121
|
+
glossary now delegates the shared signature vocabulary to vdjtools' glossary explicitly rather
|
|
122
|
+
than leaving it undefined in both.
|
|
123
|
+
- `tests/test_examples_wiring.py` checks every notebook's cell graph statically — a cell may not
|
|
124
|
+
consume a name no cell provides, and no name may be defined twice.
|
|
125
|
+
|
|
6
126
|
## 4.2.0 — 2026-09-28
|
|
7
127
|
|
|
8
128
|
The `rsig` half of the three real corpora, and the mechanism that ships them. Requires vdjtools
|
mirpy_lib-4.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mirpy-lib
|
|
3
|
+
Version: 4.3.0
|
|
4
|
+
Summary: ML-oriented embeddings for immune receptor repertoires (TCR/BCR)
|
|
5
|
+
Project-URL: Homepage, https://github.com/antigenomics/mirpy
|
|
6
|
+
Project-URL: Repository, https://github.com/antigenomics/mirpy
|
|
7
|
+
Project-URL: Documentation, https://antigenomics.github.io/mirpy
|
|
8
|
+
Project-URL: Issues, https://github.com/antigenomics/mirpy/issues
|
|
9
|
+
Author-email: ISALGO lab <mikhail.shugay@gmail.com>
|
|
10
|
+
License: GPL-3.0-or-later
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: AIRR,BCR,TCR,VDJdb,embedding,immunosequencing,prototypes
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: polars
|
|
24
|
+
Requires-Dist: scikit-learn
|
|
25
|
+
Requires-Dist: scipy
|
|
26
|
+
Requires-Dist: seqtree>=1.0.0
|
|
27
|
+
Requires-Dist: vdjtools>=4.3.0
|
|
28
|
+
Provides-Extra: ann
|
|
29
|
+
Requires-Dist: numba>=0.60; extra == 'ann'
|
|
30
|
+
Requires-Dist: pynndescent; extra == 'ann'
|
|
31
|
+
Provides-Extra: annotate
|
|
32
|
+
Requires-Dist: vdjmatch; extra == 'annotate'
|
|
33
|
+
Provides-Extra: bench
|
|
34
|
+
Requires-Dist: huggingface-hub; extra == 'bench'
|
|
35
|
+
Requires-Dist: kneed; extra == 'bench'
|
|
36
|
+
Requires-Dist: lifelines; extra == 'bench'
|
|
37
|
+
Requires-Dist: matplotlib; extra == 'bench'
|
|
38
|
+
Requires-Dist: seaborn; extra == 'bench'
|
|
39
|
+
Provides-Extra: build
|
|
40
|
+
Requires-Dist: biopython; extra == 'build'
|
|
41
|
+
Requires-Dist: vdjtools[model]>=4.2.0; extra == 'build'
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: psutil>=5; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: pydata-sphinx-theme<1,>=0.15; extra == 'docs'
|
|
48
|
+
Requires-Dist: sphinx-copybutton; extra == 'docs'
|
|
49
|
+
Requires-Dist: sphinx-design; extra == 'docs'
|
|
50
|
+
Requires-Dist: sphinx<9,>=7.2; extra == 'docs'
|
|
51
|
+
Provides-Extra: examples
|
|
52
|
+
Requires-Dist: marimo; extra == 'examples'
|
|
53
|
+
Requires-Dist: matplotlib; extra == 'examples'
|
|
54
|
+
Requires-Dist: umap-learn; extra == 'examples'
|
|
55
|
+
Provides-Extra: ml
|
|
56
|
+
Requires-Dist: torch; extra == 'ml'
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
<p align="center">
|
|
60
|
+
<picture>
|
|
61
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/mirpy_dark.svg">
|
|
62
|
+
<source media="(prefers-color-scheme: light)" srcset="assets/mirpy_light.svg">
|
|
63
|
+
<!-- Absolute PNG fallback: PyPI strips <picture>/<source> and cannot render a relative or
|
|
64
|
+
raw-served SVG, so the logo must be an absolute-URL raster here. GitHub uses the SVG sources. -->
|
|
65
|
+
<img alt="mirpy" src="https://raw.githubusercontent.com/antigenomics/mirpy/master/assets/mirpy_light.png" width="360">
|
|
66
|
+
</picture>
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<h1 align="center">mirpy — ML embeddings for immune repertoires</h1>
|
|
70
|
+
|
|
71
|
+
<p align="center">
|
|
72
|
+
<a href="https://pypi.org/project/mirpy-lib/"><img alt="PyPI" src="https://img.shields.io/pypi/v/mirpy-lib.svg"></a>
|
|
73
|
+
<a href="https://pypi.org/project/mirpy-lib/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/mirpy-lib.svg"></a>
|
|
74
|
+
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-GPLv3-green"></a>
|
|
75
|
+
<a href="https://docs.isalgo.dev/mirpy/"><img alt="Docs" src="https://img.shields.io/badge/docs-docs.isalgo.dev-blue"></a>
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
<p align="center"><b>
|
|
79
|
+
<a href="https://docs.isalgo.dev/mirpy/getting-started.html">Get started</a> ·
|
|
80
|
+
<a href="https://docs.isalgo.dev/mirpy/usage.html">Guides</a> ·
|
|
81
|
+
<a href="https://docs.isalgo.dev/mirpy/cli.html">Commands</a> ·
|
|
82
|
+
<a href="https://docs.isalgo.dev/mirpy/api.html">API</a> ·
|
|
83
|
+
<a href="https://docs.isalgo.dev/mirpy/math.html">Theory</a> ·
|
|
84
|
+
<a href="https://docs.isalgo.dev/mirpy/notebooks.html">Examples</a> ·
|
|
85
|
+
<a href="https://docs.isalgo.dev/mirpy/glossary.html">Glossary</a>
|
|
86
|
+
</b></p>
|
|
87
|
+
|
|
88
|
+
Turn T- and B-cell receptor repertoires into fixed-length numeric vectors you can cluster,
|
|
89
|
+
visualise, and hand to a machine-learning model — at the scale of one receptor, one repertoire, or a
|
|
90
|
+
whole cohort.
|
|
91
|
+
|
|
92
|
+
mirpy implements **TCREMP**: each receptor is embedded by its alignment distances to a fixed set of
|
|
93
|
+
*prototype* sequences, so Euclidean distance in embedding space approximates pairwise alignment
|
|
94
|
+
distance. That turns a set of variable-length strings into a coordinate system, which is what every
|
|
95
|
+
downstream method actually needs.
|
|
96
|
+
|
|
97
|
+
PyPI package `mirpy-lib`, import name `mir`. Pure Python; the alignment, Pgen and sampling work is
|
|
98
|
+
reused from [seqtree](https://github.com/antigenomics/seqtree) and
|
|
99
|
+
[vdjtools](https://github.com/antigenomics/vdjtools).
|
|
100
|
+
|
|
101
|
+
## Install
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install mirpy-lib
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Optional extras, each for one job:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install "mirpy-lib[ann]" # pynndescent: approximate neighbours at whole-repertoire scale
|
|
111
|
+
pip install "mirpy-lib[ml]" # torch: the neural codecs and the learned set encoder
|
|
112
|
+
pip install "mirpy-lib[bench]" # the benchmark harness and theory experiments
|
|
113
|
+
pip install "mirpy-lib[examples]" # marimo, matplotlib, umap-learn: to run the notebooks
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Three scales
|
|
117
|
+
|
|
118
|
+
Choosing the right scale is most of the work:
|
|
119
|
+
|
|
120
|
+
| Scale | One unit is | You get | Module |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| Clonotype | one receptor | a point in prototype space | `mir.embedding` |
|
|
123
|
+
| Repertoire | one sample from one donor | one fingerprint, `Φ(S)` | `mir.repertoire` |
|
|
124
|
+
| Cohort | many donors, several chains each | one comparable matrix | `mir.cohort` |
|
|
125
|
+
|
|
126
|
+
## Quick start
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import polars as pl
|
|
130
|
+
from mir.embedding.tcremp import TCREmp
|
|
131
|
+
|
|
132
|
+
model = TCREmp.from_defaults("human", "TRB") # per-chain preset: 2000 prototypes
|
|
133
|
+
df = pl.DataFrame({
|
|
134
|
+
"v_call": ["TRBV10-3*01", "TRBV20-1*01"],
|
|
135
|
+
"j_call": ["TRBJ2-7*01", "TRBJ1-2*01"],
|
|
136
|
+
"junction_aa": ["CASSIRSSYEQYF", "CSARVSGYYGYTF"],
|
|
137
|
+
})
|
|
138
|
+
X = model.embed(df) # (2, 6000) float32 — three distances per prototype
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Denoise, then cluster:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from mir.embedding.pca import pca_denoise
|
|
145
|
+
from mir.bench.metrics import cluster
|
|
146
|
+
labels = cluster(pca_denoise(model.embed(vdjdb_df), n_components=65))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
From the shell:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
mir embed clonotypes sample.tsv -o clonotypes.parquet --pca 50
|
|
153
|
+
mir embed repertoires cohort/*.tsv.gz -o phi.tsv --mmd mmd.tsv
|
|
154
|
+
mir signature --corpus blood cohort/*.tsv.gz -o rsig.parquet
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**[Getting started](https://docs.isalgo.dev/mirpy/getting-started.html)** walks through all of this
|
|
158
|
+
in about fifteen minutes, offline, with nothing to download.
|
|
159
|
+
|
|
160
|
+
## Where to start
|
|
161
|
+
|
|
162
|
+
| You want to | Go to |
|
|
163
|
+
|---|---|
|
|
164
|
+
| Embed receptors and cluster antigen-specific ones | [Getting started](https://docs.isalgo.dev/mirpy/getting-started.html) |
|
|
165
|
+
| Pick a prototype count and a PCA dimension | [Which prototypes](https://docs.isalgo.dev/mirpy/usage.html#which-prototypes) |
|
|
166
|
+
| Find enriched or antigen-driven neighbourhoods | [`mir.density`](https://docs.isalgo.dev/mirpy/usage.html#density-and-background-subtraction) |
|
|
167
|
+
| Compare whole repertoires rather than clonotypes | [`mir.repertoire`](https://docs.isalgo.dev/mirpy/usage.html) |
|
|
168
|
+
| Get one fixed, named feature vector per sample | [Signatures](https://docs.isalgo.dev/mirpy/signature.html) |
|
|
169
|
+
| Name which part of a vector carries a signal | [Channels](https://docs.isalgo.dev/mirpy/channels.html) |
|
|
170
|
+
| Understand why any of this is valid | [Theory](https://docs.isalgo.dev/mirpy/math.html) |
|
|
171
|
+
| Look up a command or a function | [Commands](https://docs.isalgo.dev/mirpy/cli.html) · [API](https://docs.isalgo.dev/mirpy/api.html) |
|
|
172
|
+
| Check what a term means | [Glossary](https://docs.isalgo.dev/mirpy/glossary.html) |
|
|
173
|
+
|
|
174
|
+
## What is in the box
|
|
175
|
+
|
|
176
|
+
| Module | What it does |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `mir.embedding` | `TCREmp` and `PairedTCREmp`, PCA denoising, the bundled prototypes and per-chain presets |
|
|
179
|
+
| `mir.distances` | Junction distance through `seqtree.gapblock`, plus precomputed arda germline distances |
|
|
180
|
+
| `mir.density` | Graph-free TCRNET and ALICE neighbourhood enrichment in embedding space, with exact, kdtree and approximate backends |
|
|
181
|
+
| `mir.repertoire` | One vector per sample: kernel mean, coverage-standardised Hill diversity, second moment; MMD distance, motif witness, sub-probability measures |
|
|
182
|
+
| `mir.signature` | The portable signature — fixed, named, already-standardised columns that join to vdjtools' half on `sample_id` |
|
|
183
|
+
| `mir.explain` | Which named channel carries a signal under *your* scorer, and which clonotypes drive it |
|
|
184
|
+
| `mir.cohort` | The digital donor — multi-chain donor embeddings, batch residualisation, sample clustering |
|
|
185
|
+
| `mir.track` | Exposure trajectory — a latent progression axis disentangled from a known covariate |
|
|
186
|
+
| `mir.generate`, `mir.twin` | Sample new synthetic donor states, or perturb and resample one donor's state |
|
|
187
|
+
| `mir.ml` | Neural codecs, learned set encoders, diffusion generator — experimental, `[ml]` extra |
|
|
188
|
+
| `mir.bench` | VDJdb loader, clustering metrics, cohort scorers, reproduced theory experiments |
|
|
189
|
+
| `mir.cli` | The `mir` console script — `embed clonotypes`, `embed repertoires`, `signature`, `corpus` |
|
|
190
|
+
|
|
191
|
+
## Prototypes are the coordinate system
|
|
192
|
+
|
|
193
|
+
Every embedding is distances to prototypes, so the prototype set *is* the coordinate system. mirpy
|
|
194
|
+
ships **10,000 real receptors per chain** — a fixed-seed uniform sample of unique, productive,
|
|
195
|
+
germline-resolvable clonotypes from arda-annotated repertoires, for human TRA/TRB/TRG/TRD/IGH/IGK/IGL
|
|
196
|
+
and mouse TRA/TRB. Nothing is downloaded at import or build time.
|
|
197
|
+
|
|
198
|
+
Real rather than model-generated, because synthetic junctions have degenerate lengths and embed
|
|
199
|
+
measurably worse. To ask whether a result depends on *which* prototypes you drew, take a replicate:
|
|
200
|
+
the geometry agrees at R = 0.993 between two independent draws at the default `n=1000`, and 0.997 at
|
|
201
|
+
`n=2000`, so from about `n≈500` up the draw is not what your result rests on.
|
|
202
|
+
|
|
203
|
+
Each replicate is a **different coordinate system** — distances within one are comparable, across two
|
|
204
|
+
are not, and every container refuses a prototype-hash mismatch rather than mixing them silently.
|
|
205
|
+
[Details and the full presets table](https://docs.isalgo.dev/mirpy/usage.html#which-prototypes).
|
|
206
|
+
|
|
207
|
+
## Repertoire fingerprints
|
|
208
|
+
|
|
209
|
+
One fixed vector `Φ(S)` per repertoire, robust down into the low-coverage bulk-RNA-seq regime. It
|
|
210
|
+
sketches the repertoire as a weighted cloud of receptor points in three blocks: a random-feature
|
|
211
|
+
kernel mean, a coverage-standardised Hill diversity profile, and a second-moment block carrying
|
|
212
|
+
co-occurrence structure. Distance between two repertoires is the MMD.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from mir.repertoire import fit_repertoire_space, sample_embedding, mmd_matrix
|
|
216
|
+
|
|
217
|
+
space = fit_repertoire_space(model, pooled_clonotypes) # ONE basis for the whole cohort
|
|
218
|
+
embs = [sample_embedding(space, s) for s in samples]
|
|
219
|
+
D = mmd_matrix(embs, unbiased=True)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Every sample in a cohort must go through one prototype set and one basis, or the distances are not
|
|
223
|
+
comparable. Use `unbiased=True` whenever depths differ: the biased estimator's `1/n_eff` self-term
|
|
224
|
+
inflates low-diversity samples and manufactures signal with the wrong sign.
|
|
225
|
+
|
|
226
|
+
## Signatures
|
|
227
|
+
|
|
228
|
+
`Φ(S)` is fitted on *your* cohort, which is what makes it powerful and also what makes it
|
|
229
|
+
incomparable with anyone else's. When you need a vector a collaborator can reproduce independently,
|
|
230
|
+
use a **signature** instead: fixed, named columns standardised against a published reference corpus.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
mir signature --corpus blood cohort/*.tsv.gz -o rsig.tsv # the geometry half
|
|
234
|
+
vdjtools signature --corpus blood cohort/*.tsv.gz -o vsig.tsv # the statistics half
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The two join on `sample_id`. Nine corpora are published, all at 256 components per locus; artifacts
|
|
238
|
+
are fetched and digest-verified on first use. `--corpus` is required — a signature is comparable to
|
|
239
|
+
another one only if both were rotated through the same corpus.
|
|
240
|
+
[Signatures](https://docs.isalgo.dev/mirpy/signature.html) ·
|
|
241
|
+
[Channels](https://docs.isalgo.dev/mirpy/channels.html)
|
|
242
|
+
|
|
243
|
+
## Performance
|
|
244
|
+
|
|
245
|
+
CPU-parallel by default; the GPU is used only by `mir.ml`. Embedding runs the C++
|
|
246
|
+
`seqtree.gapblock` scorer at about 530 million pairs per second on 16 cores and releases the GIL.
|
|
247
|
+
Density defaults to an exact multicore kd-tree; the approximate backend is about 30× faster past 1e5
|
|
248
|
+
clones and is deliberately asymmetric — only the observed side is approximate, which biases
|
|
249
|
+
enrichment down, while the background is always exact.
|
|
250
|
+
[Full table of parallelism settings](https://docs.isalgo.dev/mirpy/usage.html#performance-and-parallelism).
|
|
251
|
+
|
|
252
|
+
## Examples
|
|
253
|
+
|
|
254
|
+
Six runnable [marimo](https://marimo.io) notebooks — plain Python files, so they diff like
|
|
255
|
+
source:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
pip install "mirpy-lib[examples]"
|
|
259
|
+
marimo edit examples/signature_pipeline.py # start here
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`signature_pipeline` (a folder of AIRR files to a joinable table — **read this one first**) ·
|
|
263
|
+
`quickstart` (embed, denoise, cluster, UMAP) · `signature` (how the halves differ, and how many
|
|
264
|
+
components survive a refit) · `density` (enrichment against a background model) ·
|
|
265
|
+
`trajectory_and_twin` (time courses and counterfactual donors) · `theory` (the results the
|
|
266
|
+
library rests on). See the
|
|
267
|
+
[gallery](https://docs.isalgo.dev/mirpy/notebooks.html).
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
Repo-local `.venv` via [uv](https://docs.astral.sh/uv/):
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
bash setup.sh --dev-parents --tests # editable-installs sibling seqtree / vdjtools checkouts
|
|
275
|
+
python -m pytest tests/ -q
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
See [`CLAUDE.md`](CLAUDE.md) for the architecture and the reuse map.
|
|
279
|
+
|
|
280
|
+
## Citing
|
|
281
|
+
|
|
282
|
+
Method: Kremlyakova *et al.*, *TCREMP: a bioinformatic pipeline for efficient embedding of T-cell
|
|
283
|
+
receptor sequences*, **J Mol Biol** 437 (2025) 169205.
|
|
284
|
+
|
|
285
|
+
The theory this implements is collected in the
|
|
286
|
+
[mathematical foundations](https://docs.isalgo.dev/mirpy/math.html); derivations and recorded
|
|
287
|
+
benchmark numbers live in the companion analysis repository `2026-mirpy-analysis`, with the LaTeX
|
|
288
|
+
appendix in `2026-mirpy-ms`.
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
GPL-3.0-or-later. The classical v1.x/v2 repertoire toolkit — parsing, overlap, diversity, TCRnet,
|
|
293
|
+
GLIPH — is frozen on the [`legacy-v2`](https://github.com/antigenomics/mirpy/tree/legacy-v2) branch
|
|
294
|
+
(`mirpy-lib` 2.x); its functionality lives on in
|
|
295
|
+
[vdjtools](https://github.com/antigenomics/vdjtools) and
|
|
296
|
+
[vdjmatch](https://github.com/antigenomics/vdjmatch).
|
|
@@ -0,0 +1,238 @@
|
|
|
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
|
+
|
|
11
|
+
<h1 align="center">mirpy — ML embeddings for immune repertoires</h1>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://pypi.org/project/mirpy-lib/"><img alt="PyPI" src="https://img.shields.io/pypi/v/mirpy-lib.svg"></a>
|
|
15
|
+
<a href="https://pypi.org/project/mirpy-lib/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/mirpy-lib.svg"></a>
|
|
16
|
+
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-GPLv3-green"></a>
|
|
17
|
+
<a href="https://docs.isalgo.dev/mirpy/"><img alt="Docs" src="https://img.shields.io/badge/docs-docs.isalgo.dev-blue"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center"><b>
|
|
21
|
+
<a href="https://docs.isalgo.dev/mirpy/getting-started.html">Get started</a> ·
|
|
22
|
+
<a href="https://docs.isalgo.dev/mirpy/usage.html">Guides</a> ·
|
|
23
|
+
<a href="https://docs.isalgo.dev/mirpy/cli.html">Commands</a> ·
|
|
24
|
+
<a href="https://docs.isalgo.dev/mirpy/api.html">API</a> ·
|
|
25
|
+
<a href="https://docs.isalgo.dev/mirpy/math.html">Theory</a> ·
|
|
26
|
+
<a href="https://docs.isalgo.dev/mirpy/notebooks.html">Examples</a> ·
|
|
27
|
+
<a href="https://docs.isalgo.dev/mirpy/glossary.html">Glossary</a>
|
|
28
|
+
</b></p>
|
|
29
|
+
|
|
30
|
+
Turn T- and B-cell receptor repertoires into fixed-length numeric vectors you can cluster,
|
|
31
|
+
visualise, and hand to a machine-learning model — at the scale of one receptor, one repertoire, or a
|
|
32
|
+
whole cohort.
|
|
33
|
+
|
|
34
|
+
mirpy implements **TCREMP**: each receptor is embedded by its alignment distances to a fixed set of
|
|
35
|
+
*prototype* sequences, so Euclidean distance in embedding space approximates pairwise alignment
|
|
36
|
+
distance. That turns a set of variable-length strings into a coordinate system, which is what every
|
|
37
|
+
downstream method actually needs.
|
|
38
|
+
|
|
39
|
+
PyPI package `mirpy-lib`, import name `mir`. Pure Python; the alignment, Pgen and sampling work is
|
|
40
|
+
reused from [seqtree](https://github.com/antigenomics/seqtree) and
|
|
41
|
+
[vdjtools](https://github.com/antigenomics/vdjtools).
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install mirpy-lib
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Optional extras, each for one job:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install "mirpy-lib[ann]" # pynndescent: approximate neighbours at whole-repertoire scale
|
|
53
|
+
pip install "mirpy-lib[ml]" # torch: the neural codecs and the learned set encoder
|
|
54
|
+
pip install "mirpy-lib[bench]" # the benchmark harness and theory experiments
|
|
55
|
+
pip install "mirpy-lib[examples]" # marimo, matplotlib, umap-learn: to run the notebooks
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Three scales
|
|
59
|
+
|
|
60
|
+
Choosing the right scale is most of the work:
|
|
61
|
+
|
|
62
|
+
| Scale | One unit is | You get | Module |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| Clonotype | one receptor | a point in prototype space | `mir.embedding` |
|
|
65
|
+
| Repertoire | one sample from one donor | one fingerprint, `Φ(S)` | `mir.repertoire` |
|
|
66
|
+
| Cohort | many donors, several chains each | one comparable matrix | `mir.cohort` |
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import polars as pl
|
|
72
|
+
from mir.embedding.tcremp import TCREmp
|
|
73
|
+
|
|
74
|
+
model = TCREmp.from_defaults("human", "TRB") # per-chain preset: 2000 prototypes
|
|
75
|
+
df = pl.DataFrame({
|
|
76
|
+
"v_call": ["TRBV10-3*01", "TRBV20-1*01"],
|
|
77
|
+
"j_call": ["TRBJ2-7*01", "TRBJ1-2*01"],
|
|
78
|
+
"junction_aa": ["CASSIRSSYEQYF", "CSARVSGYYGYTF"],
|
|
79
|
+
})
|
|
80
|
+
X = model.embed(df) # (2, 6000) float32 — three distances per prototype
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Denoise, then cluster:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from mir.embedding.pca import pca_denoise
|
|
87
|
+
from mir.bench.metrics import cluster
|
|
88
|
+
labels = cluster(pca_denoise(model.embed(vdjdb_df), n_components=65))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
From the shell:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
mir embed clonotypes sample.tsv -o clonotypes.parquet --pca 50
|
|
95
|
+
mir embed repertoires cohort/*.tsv.gz -o phi.tsv --mmd mmd.tsv
|
|
96
|
+
mir signature --corpus blood cohort/*.tsv.gz -o rsig.parquet
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**[Getting started](https://docs.isalgo.dev/mirpy/getting-started.html)** walks through all of this
|
|
100
|
+
in about fifteen minutes, offline, with nothing to download.
|
|
101
|
+
|
|
102
|
+
## Where to start
|
|
103
|
+
|
|
104
|
+
| You want to | Go to |
|
|
105
|
+
|---|---|
|
|
106
|
+
| Embed receptors and cluster antigen-specific ones | [Getting started](https://docs.isalgo.dev/mirpy/getting-started.html) |
|
|
107
|
+
| Pick a prototype count and a PCA dimension | [Which prototypes](https://docs.isalgo.dev/mirpy/usage.html#which-prototypes) |
|
|
108
|
+
| Find enriched or antigen-driven neighbourhoods | [`mir.density`](https://docs.isalgo.dev/mirpy/usage.html#density-and-background-subtraction) |
|
|
109
|
+
| Compare whole repertoires rather than clonotypes | [`mir.repertoire`](https://docs.isalgo.dev/mirpy/usage.html) |
|
|
110
|
+
| Get one fixed, named feature vector per sample | [Signatures](https://docs.isalgo.dev/mirpy/signature.html) |
|
|
111
|
+
| Name which part of a vector carries a signal | [Channels](https://docs.isalgo.dev/mirpy/channels.html) |
|
|
112
|
+
| Understand why any of this is valid | [Theory](https://docs.isalgo.dev/mirpy/math.html) |
|
|
113
|
+
| Look up a command or a function | [Commands](https://docs.isalgo.dev/mirpy/cli.html) · [API](https://docs.isalgo.dev/mirpy/api.html) |
|
|
114
|
+
| Check what a term means | [Glossary](https://docs.isalgo.dev/mirpy/glossary.html) |
|
|
115
|
+
|
|
116
|
+
## What is in the box
|
|
117
|
+
|
|
118
|
+
| Module | What it does |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `mir.embedding` | `TCREmp` and `PairedTCREmp`, PCA denoising, the bundled prototypes and per-chain presets |
|
|
121
|
+
| `mir.distances` | Junction distance through `seqtree.gapblock`, plus precomputed arda germline distances |
|
|
122
|
+
| `mir.density` | Graph-free TCRNET and ALICE neighbourhood enrichment in embedding space, with exact, kdtree and approximate backends |
|
|
123
|
+
| `mir.repertoire` | One vector per sample: kernel mean, coverage-standardised Hill diversity, second moment; MMD distance, motif witness, sub-probability measures |
|
|
124
|
+
| `mir.signature` | The portable signature — fixed, named, already-standardised columns that join to vdjtools' half on `sample_id` |
|
|
125
|
+
| `mir.explain` | Which named channel carries a signal under *your* scorer, and which clonotypes drive it |
|
|
126
|
+
| `mir.cohort` | The digital donor — multi-chain donor embeddings, batch residualisation, sample clustering |
|
|
127
|
+
| `mir.track` | Exposure trajectory — a latent progression axis disentangled from a known covariate |
|
|
128
|
+
| `mir.generate`, `mir.twin` | Sample new synthetic donor states, or perturb and resample one donor's state |
|
|
129
|
+
| `mir.ml` | Neural codecs, learned set encoders, diffusion generator — experimental, `[ml]` extra |
|
|
130
|
+
| `mir.bench` | VDJdb loader, clustering metrics, cohort scorers, reproduced theory experiments |
|
|
131
|
+
| `mir.cli` | The `mir` console script — `embed clonotypes`, `embed repertoires`, `signature`, `corpus` |
|
|
132
|
+
|
|
133
|
+
## Prototypes are the coordinate system
|
|
134
|
+
|
|
135
|
+
Every embedding is distances to prototypes, so the prototype set *is* the coordinate system. mirpy
|
|
136
|
+
ships **10,000 real receptors per chain** — a fixed-seed uniform sample of unique, productive,
|
|
137
|
+
germline-resolvable clonotypes from arda-annotated repertoires, for human TRA/TRB/TRG/TRD/IGH/IGK/IGL
|
|
138
|
+
and mouse TRA/TRB. Nothing is downloaded at import or build time.
|
|
139
|
+
|
|
140
|
+
Real rather than model-generated, because synthetic junctions have degenerate lengths and embed
|
|
141
|
+
measurably worse. To ask whether a result depends on *which* prototypes you drew, take a replicate:
|
|
142
|
+
the geometry agrees at R = 0.993 between two independent draws at the default `n=1000`, and 0.997 at
|
|
143
|
+
`n=2000`, so from about `n≈500` up the draw is not what your result rests on.
|
|
144
|
+
|
|
145
|
+
Each replicate is a **different coordinate system** — distances within one are comparable, across two
|
|
146
|
+
are not, and every container refuses a prototype-hash mismatch rather than mixing them silently.
|
|
147
|
+
[Details and the full presets table](https://docs.isalgo.dev/mirpy/usage.html#which-prototypes).
|
|
148
|
+
|
|
149
|
+
## Repertoire fingerprints
|
|
150
|
+
|
|
151
|
+
One fixed vector `Φ(S)` per repertoire, robust down into the low-coverage bulk-RNA-seq regime. It
|
|
152
|
+
sketches the repertoire as a weighted cloud of receptor points in three blocks: a random-feature
|
|
153
|
+
kernel mean, a coverage-standardised Hill diversity profile, and a second-moment block carrying
|
|
154
|
+
co-occurrence structure. Distance between two repertoires is the MMD.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from mir.repertoire import fit_repertoire_space, sample_embedding, mmd_matrix
|
|
158
|
+
|
|
159
|
+
space = fit_repertoire_space(model, pooled_clonotypes) # ONE basis for the whole cohort
|
|
160
|
+
embs = [sample_embedding(space, s) for s in samples]
|
|
161
|
+
D = mmd_matrix(embs, unbiased=True)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Every sample in a cohort must go through one prototype set and one basis, or the distances are not
|
|
165
|
+
comparable. Use `unbiased=True` whenever depths differ: the biased estimator's `1/n_eff` self-term
|
|
166
|
+
inflates low-diversity samples and manufactures signal with the wrong sign.
|
|
167
|
+
|
|
168
|
+
## Signatures
|
|
169
|
+
|
|
170
|
+
`Φ(S)` is fitted on *your* cohort, which is what makes it powerful and also what makes it
|
|
171
|
+
incomparable with anyone else's. When you need a vector a collaborator can reproduce independently,
|
|
172
|
+
use a **signature** instead: fixed, named columns standardised against a published reference corpus.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
mir signature --corpus blood cohort/*.tsv.gz -o rsig.tsv # the geometry half
|
|
176
|
+
vdjtools signature --corpus blood cohort/*.tsv.gz -o vsig.tsv # the statistics half
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The two join on `sample_id`. Nine corpora are published, all at 256 components per locus; artifacts
|
|
180
|
+
are fetched and digest-verified on first use. `--corpus` is required — a signature is comparable to
|
|
181
|
+
another one only if both were rotated through the same corpus.
|
|
182
|
+
[Signatures](https://docs.isalgo.dev/mirpy/signature.html) ·
|
|
183
|
+
[Channels](https://docs.isalgo.dev/mirpy/channels.html)
|
|
184
|
+
|
|
185
|
+
## Performance
|
|
186
|
+
|
|
187
|
+
CPU-parallel by default; the GPU is used only by `mir.ml`. Embedding runs the C++
|
|
188
|
+
`seqtree.gapblock` scorer at about 530 million pairs per second on 16 cores and releases the GIL.
|
|
189
|
+
Density defaults to an exact multicore kd-tree; the approximate backend is about 30× faster past 1e5
|
|
190
|
+
clones and is deliberately asymmetric — only the observed side is approximate, which biases
|
|
191
|
+
enrichment down, while the background is always exact.
|
|
192
|
+
[Full table of parallelism settings](https://docs.isalgo.dev/mirpy/usage.html#performance-and-parallelism).
|
|
193
|
+
|
|
194
|
+
## Examples
|
|
195
|
+
|
|
196
|
+
Six runnable [marimo](https://marimo.io) notebooks — plain Python files, so they diff like
|
|
197
|
+
source:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install "mirpy-lib[examples]"
|
|
201
|
+
marimo edit examples/signature_pipeline.py # start here
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`signature_pipeline` (a folder of AIRR files to a joinable table — **read this one first**) ·
|
|
205
|
+
`quickstart` (embed, denoise, cluster, UMAP) · `signature` (how the halves differ, and how many
|
|
206
|
+
components survive a refit) · `density` (enrichment against a background model) ·
|
|
207
|
+
`trajectory_and_twin` (time courses and counterfactual donors) · `theory` (the results the
|
|
208
|
+
library rests on). See the
|
|
209
|
+
[gallery](https://docs.isalgo.dev/mirpy/notebooks.html).
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
Repo-local `.venv` via [uv](https://docs.astral.sh/uv/):
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
bash setup.sh --dev-parents --tests # editable-installs sibling seqtree / vdjtools checkouts
|
|
217
|
+
python -m pytest tests/ -q
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
See [`CLAUDE.md`](CLAUDE.md) for the architecture and the reuse map.
|
|
221
|
+
|
|
222
|
+
## Citing
|
|
223
|
+
|
|
224
|
+
Method: Kremlyakova *et al.*, *TCREMP: a bioinformatic pipeline for efficient embedding of T-cell
|
|
225
|
+
receptor sequences*, **J Mol Biol** 437 (2025) 169205.
|
|
226
|
+
|
|
227
|
+
The theory this implements is collected in the
|
|
228
|
+
[mathematical foundations](https://docs.isalgo.dev/mirpy/math.html); derivations and recorded
|
|
229
|
+
benchmark numbers live in the companion analysis repository `2026-mirpy-analysis`, with the LaTeX
|
|
230
|
+
appendix in `2026-mirpy-ms`.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
GPL-3.0-or-later. The classical v1.x/v2 repertoire toolkit — parsing, overlap, diversity, TCRnet,
|
|
235
|
+
GLIPH — is frozen on the [`legacy-v2`](https://github.com/antigenomics/mirpy/tree/legacy-v2) branch
|
|
236
|
+
(`mirpy-lib` 2.x); its functionality lives on in
|
|
237
|
+
[vdjtools](https://github.com/antigenomics/vdjtools) and
|
|
238
|
+
[vdjmatch](https://github.com/antigenomics/vdjmatch).
|