Skip to content

Commit 215897a

Browse files
mikesshclaude
andcommitted
release: v3.4.0
Splits the CHANGELOG so 3.3.0 describes what was actually tagged at 827a8d9 (the model workshop and the arda CLI fixes) and 3.4.0 carries what came after: the collapse-representative bug, the OLGA quirks writeup, live EM progress and CLI verbosity, checkpointing with exact resume, and all seven bundled `learned` models retrained on the full non-functional corpus. A minor rather than a patch because the shipped default models changed -- that is user-visible regardless of the API additions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 24fe01a commit 215897a

4 files changed

Lines changed: 79 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,82 @@
33
Notable changes to vdjtools v2. Releases before 3.0.0 are recorded in the git tags
44
(`v2.5.0``v2.9.0`) and their commit history.
55

6+
## 3.4.0 — 2026-08-12
7+
8+
Follows 3.3.0's model workshop with the defects that workshop then found, and retrains every
9+
bundled model.
10+
11+
### Added
12+
13+
- **Live EM progress, checkpointing and exact resume.** `infer`/`infer_native` take
14+
`progress=callable(iter, loglik, rel_change, n_scoreable)``infer.print_progress()` is a
15+
ready-made one — so a long fit is visibly converging rather than merely running; the relative
16+
change it reports is exactly what is compared against `tol`. They also take
17+
`checkpoint=DIR`/`checkpoint_every=N`, saving the model after each iteration (written to a
18+
sibling directory and swapped in, so a kill mid-write leaves the previous checkpoint loadable),
19+
and `infer.resume(DIR, seqs)` continues from one. **Resuming is exact**: 3 iterations plus a
20+
resumed 4 give the same log-likelihood *and* the same tables as an uninterrupted 7, and the
21+
training log spans every attempt. Exposed as
22+
`vdjtools model learn -v --checkpoint DIR --resume DIR`; `vdjtools model build -v` additionally
23+
stops swallowing arda's mapping output. This matters because IGH's EM enumerates ~1,225 D pairs
24+
per read against TRB's 9 — 12 minutes per iteration on a 112-core node, and more than 110 minutes
25+
without finishing one on a laptop.
26+
27+
### Changed
28+
29+
- **All seven bundled `learned` models retrained** on the full non-functional read corpus (every
30+
available read; out-of-frame *and* stop-codon, since both escaped selection). Every one converged
31+
on tolerance with a step-by-step monotone log-likelihood, and `check_model` reports zero errors:
32+
33+
| locus | clonotypes | iters | log-likelihood |
34+
|---|---|---|---|
35+
| TRA | 34,238 | 10 | −21.52 → −20.01 |
36+
| TRB | 122,703 | 11 | −37.01 → −33.76 |
37+
| TRG | 14,305 | 8 | −21.90 → −20.33 |
38+
| TRD | 10,915 | 7 | −40.94 → −35.79 |
39+
| IGH | 141,607 | 9 | −57.16 → −49.88 |
40+
| IGK | 256,347 | 6 | −16.18 → −14.28 |
41+
| IGL | 23,469 | 12 | −6.63 → −6.00 |
42+
43+
Each now ships its training log, so a model states what it was fitted on. IGH was trained on a
44+
112-core cluster node; the rest fit comfortably on a laptop.
45+
- **Ambiguous junction bases are substituted, not dropped.** They previously crashed EM with a bare
46+
`KeyError` from inside the native encoder. Both training entry points now substitute `A` by
47+
default and warn with the count (`ambiguous=None` drops instead) — it affects ~0.01% of these
48+
reads, so dropping cost sample size for nothing. It is a substitution, not a marginalization.
49+
50+
### Fixed
51+
52+
- **`collapse_alleles` could give a gene a germline it could not use — in the default path.**
53+
`load_bundled(..., collapse=True)` picks one representative allele per gene and averages the
54+
other alleles' conditionals onto it. The representative was chosen by usage alone, but IMGT ships
55+
some alleles with a **truncated** CDR3-region germline: human `IGKV3-20*02` is 11 nt against
56+
`*01`'s 30 and carried the higher learned usage, so it became the gene's germline — relabelled
57+
`*01`, which was doubly misleading — and 25% of the gene's own averaged deletion distribution
58+
landed on trims the 11-nt germline cannot reach. The Pgen DP never visits those, so that quarter
59+
of the probability vanished from every Pgen through IGKV3-20 instead of being redistributed.
60+
The representative is now chosen by **germline length first, usage second** (alleles of a gene are
61+
near-identical through the CDR3 region, so a large length gap means an incomplete database entry),
62+
and the collapsed deletion conditionals are **projected onto the representative's reachable
63+
support and renormalized**. Every bundled model is now clean at `collapse=True`.
64+
- **A failed `arda` run reported nothing but an exit code.** `annotate_reads` passed
65+
`capture_output=True` and let `CalledProcessError` propagate, so arda's own message was swallowed
66+
— precisely how the CLI rename in 3.3.0 stayed invisible. It now raises with arda's stderr and the
67+
installed `arda-mapper` version.
68+
- `reference.read_fasta` wraps arda's FASTA parser with gzip support; arda's opens with plain
69+
`open()`, so a `.gz` reached it as mojibake and died on the first byte.
70+
71+
### Documented
72+
73+
- **Known quirks of the OLGA models** (`docs/model.rst`). The bundled `olga` set is a bit-faithful
74+
import, and that includes its defects. Verified against OLGA's raw `model_marginals.txt` with
75+
OLGA's own parser: deletion mass on unreachable trims is **OLGA's**, with identical fractions to
76+
4 dp (`IGHV4-30-4*01` 100% — its Pgen is identically zero in OLGA too — `IGKJ4*02` 80.9%,
77+
`TRAV20*03` 54.7%), and our Pgen matches olga-pip exactly (ratio 1.000000) on every sequence OLGA
78+
will score. Correcting it would break the exact-OLGA-Pgen invariant, so `check_model` reports it
79+
at `warn` rather than `error` for an OLGA-sourced model. Also covers the empty-germline ORF genes,
80+
protocol-specific V/J usage, and OLGA's refusal of out-of-frame input.
81+
682
## 3.3.0 — 2026-08-12
783

884
The recombination model becomes a workshop: buildable on your own reference, checkable, comparable,
@@ -61,19 +137,6 @@ scoreable and extendable. See the new [user guide](https://docs.isalgo.dev/vdjto
61137
- **Table export/import**: `marginals_frame` / `set_marginals`, and `save_model(..., fmt="tsv")`
62138
with format auto-detection on load, so a hand-edited TSV directory is a first-class model input.
63139

64-
- **Live EM progress, checkpointing and resume.** `infer`/`infer_native` take
65-
`progress=callable(iter, loglik, rel_change, n_scoreable)``infer.print_progress()` is a
66-
ready-made one — so a long fit is visibly converging rather than merely running; the relative
67-
change it reports is the exact quantity compared against `tol`. They also take
68-
`checkpoint=DIR`/`checkpoint_every=N`, saving the model after each iteration (swapped into place,
69-
so a kill mid-write leaves the previous checkpoint loadable), and `infer.resume(DIR, seqs)`
70-
continues from one. **Resuming is exact** — 3 iterations plus a resumed 4 give the same
71-
log-likelihood *and* the same tables as an uninterrupted 7 — and the training log spans every
72-
attempt. Exposed as `vdjtools model learn -v --checkpoint DIR --resume DIR`, and
73-
`vdjtools model build -v` additionally stops swallowing arda's mapping output.
74-
This matters because IGH's EM enumerates ~1,225 D pairs per read against TRB's 9: 14 minutes per
75-
iteration on a 112-core node, and more than 110 minutes for a single iteration on a laptop.
76-
77140
### Changed
78141

79142
- **`pgen_nt` now releases the GIL**, the one Pgen binding that still held it after the Phase-13
@@ -87,22 +150,6 @@ scoreable and extendable. See the new [user guide](https://docs.isalgo.dev/vdjto
87150

88151
### Fixed
89152

90-
- **`collapse_alleles` could give a gene a germline it could not use — in the default path.**
91-
`load_bundled(..., collapse=True)` picks one representative allele per gene and averages the
92-
other alleles' conditionals onto it. The representative was chosen by usage alone, but IMGT ships
93-
some alleles with a **truncated** CDR3-region germline: human `IGKV3-20*02` is 11 nt against
94-
`*01`'s 30 and carried the higher learned usage, so it became the gene's germline — relabelled
95-
`*01`, which was doubly misleading — and 25% of the gene's own averaged deletion distribution
96-
landed on trims the 11-nt germline cannot reach. The Pgen DP never visits those, so that quarter
97-
of the probability vanished from every Pgen through IGKV3-20 instead of being redistributed.
98-
The representative is now chosen by **germline length first, usage second** (alleles of a gene are
99-
near-identical through the CDR3 region, so a large length gap means an incomplete database entry),
100-
and the collapsed deletion conditionals are **projected onto the representative's reachable
101-
support and renormalized**. Every bundled model is now clean at `collapse=True`.
102-
- **A failed `arda` run reported nothing but an exit code.** `annotate_reads` passed
103-
`capture_output=True` and let `CalledProcessError` propagate, so arda's own message was swallowed
104-
— which is precisely how the CLI rename above stayed invisible. It now raises with arda's stderr
105-
and the installed `arda-mapper` version.
106153
- **`annotate_reads` was calling an arda CLI that no longer exists.** It shelled out to
107154
`arda rnaseq map -o …`, but arda 2.19 turned `rnaseq` into the full map→assemble→correct preset
108155
with no stage positional and `-p/--out-prefix` in place of `-o`, so every real invocation exited

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
project = "vdjtools"
44
author = "ISALGO laboratory"
55
copyright = "2026, ISALGO laboratory"
6-
version = release = "3.3.0"
6+
version = release = "3.4.0"
77

88
extensions = [
99
"sphinx.ext.autodoc",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
44

55
[project]
66
name = "vdjtools" # if the PyPI name is taken, fall back to a distinct dist name (cf. arda -> arda-mapper)
7-
version = "3.3.0"
7+
version = "3.4.0"
88
description = "TCR/BCR repertoire analysis — Pgen/generation/inference, diversity, overlap, biomarkers (Python + C++)"
99
readme = "README.md"
1010
requires-python = ">=3.10"

python/vdjtools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
feature that needs them is used.
1616
"""
1717

18-
__version__ = "3.3.0"
18+
__version__ = "3.4.0"
1919
__all__ = ["__version__"]

0 commit comments

Comments
 (0)