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.
Files changed (118) hide show
  1. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/CHANGELOG.md +120 -0
  2. mirpy_lib-4.3.0/PKG-INFO +296 -0
  3. mirpy_lib-4.3.0/README.md +238 -0
  4. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/custom.css +10 -0
  5. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/api.rst +2 -1
  6. mirpy_lib-4.3.0/docs/channels.rst +188 -0
  7. mirpy_lib-4.3.0/docs/cli.rst +169 -0
  8. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/conf.py +6 -0
  9. mirpy_lib-4.3.0/docs/getting-started.rst +198 -0
  10. mirpy_lib-4.3.0/docs/glossary.rst +158 -0
  11. mirpy_lib-4.3.0/docs/index.rst +293 -0
  12. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/math.rst +19 -19
  13. mirpy_lib-4.3.0/docs/notebooks.rst +83 -0
  14. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/signature.rst +34 -12
  15. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/usage.rst +59 -4
  16. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/pyproject.toml +3 -1
  17. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/skills/mirpy/SKILL.md +30 -1
  18. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/__init__.py +1 -1
  19. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/cli.py +29 -5
  20. mirpy_lib-4.3.0/src/mir/signature/features.py +496 -0
  21. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/signature/signature.py +80 -16
  22. mirpy_lib-4.2.0/PKG-INFO +0 -640
  23. mirpy_lib-4.2.0/README.md +0 -584
  24. mirpy_lib-4.2.0/docs/channels.rst +0 -80
  25. mirpy_lib-4.2.0/docs/examples.rst +0 -51
  26. mirpy_lib-4.2.0/docs/index.rst +0 -141
  27. mirpy_lib-4.2.0/docs/notebooks.rst +0 -65
  28. mirpy_lib-4.2.0/src/mir/signature/features.py +0 -222
  29. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/.gitignore +0 -0
  30. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/LICENSE +0 -0
  31. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark.png +0 -0
  32. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark.svg +0 -0
  33. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_dark_bg.svg +0 -0
  34. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light.png +0 -0
  35. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light.svg +0 -0
  36. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/assets/mirpy_light_bg.svg +0 -0
  37. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/data/signature_reference_studies.tsv +0 -0
  38. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/Makefile +0 -0
  39. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_ext/themed_graphviz.py +0 -0
  40. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/depth_robustness.png +0 -0
  41. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_dark.png +0 -0
  42. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_dark.svg +0 -0
  43. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_light.png +0 -0
  44. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/mirpy_light.svg +0 -0
  45. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_static/sample_embedding_diagram.png +0 -0
  46. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/_templates/site-nav.html +0 -0
  47. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/docs/preprocessing.rst +0 -0
  48. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/setup.sh +0 -0
  49. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/aliases.py +0 -0
  50. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/alleles.py +0 -0
  51. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/__init__.py +0 -0
  52. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/eval.py +0 -0
  53. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/metrics.py +0 -0
  54. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/theory.py +0 -0
  55. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/bench/vdjdb.py +0 -0
  56. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/cohort.py +0 -0
  57. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/density.py +0 -0
  58. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/__init__.py +0 -0
  59. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/germline.py +0 -0
  60. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/junction.py +0 -0
  61. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/distances/shm.py +0 -0
  62. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/__init__.py +0 -0
  63. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/pca.py +0 -0
  64. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/presets.py +0 -0
  65. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/prototypes.py +0 -0
  66. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/embedding/tcremp.py +0 -0
  67. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/explain.py +0 -0
  68. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/generate.py +0 -0
  69. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/__init__.py +0 -0
  70. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/bundle.py +0 -0
  71. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/codec.py +0 -0
  72. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/decoder.py +0 -0
  73. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/diffusion.py +0 -0
  74. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/encoder.py +0 -0
  75. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/set_encoder.py +0 -0
  76. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/tokenize.py +0 -0
  77. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/ml/train.py +0 -0
  78. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/py.typed +0 -0
  79. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/repertoire.py +0 -0
  80. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/__init__.py +0 -0
  81. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/README.md +0 -0
  82. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/__init__.py +0 -0
  83. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/build_gene_library.py +0 -0
  84. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/build_region_annotations.py +0 -0
  85. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/imgt_gene_library.txt +0 -0
  86. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/olga_gene_library.txt +0 -0
  87. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/gene_library/region_annotations.txt +0 -0
  88. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/build_germline_dist.py +0 -0
  89. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGH.npz +0 -0
  90. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGK.npz +0 -0
  91. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_IGL.npz +0 -0
  92. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRA.npz +0 -0
  93. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRB.npz +0 -0
  94. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRD.npz +0 -0
  95. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/human_TRG.npz +0 -0
  96. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGH.npz +0 -0
  97. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGK.npz +0 -0
  98. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_IGL.npz +0 -0
  99. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRA.npz +0 -0
  100. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRB.npz +0 -0
  101. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRD.npz +0 -0
  102. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/germline_dist/mouse_TRG.npz +0 -0
  103. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/.gitkeep +0 -0
  104. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/generate_prototypes.py +0 -0
  105. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGH.tsv +0 -0
  106. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGK.tsv +0 -0
  107. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_IGL.tsv +0 -0
  108. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRA.tsv +0 -0
  109. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRB.tsv +0 -0
  110. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRD.tsv +0 -0
  111. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/human_TRG.tsv +0 -0
  112. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/manifest.json +0 -0
  113. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/mouse_TRA.tsv +0 -0
  114. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/prototypes/mouse_TRB.tsv +0 -0
  115. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/resources/signature/corpora.json +0 -0
  116. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/signature/__init__.py +0 -0
  117. {mirpy_lib-4.2.0 → mirpy_lib-4.3.0}/src/mir/track.py +0 -0
  118. {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
@@ -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).