Lazily validate netMHCpan alleles + round-trip the tool's own spelling - #219
Merged
Conversation
Determining a predictor's supported alleles runs `netMHCpan -listMHC` and normalizes every line (~11k alleles for netMHCpan 4.2) through mhcgnomes. This ran on *every* predictor construction, so building several predictors re-ran the subprocess and re-parsed the whole list each time (~2.5s each), and re-logged one INFO line per allele it couldn't parse. - Memoize the supported-allele set per (command, supported_allele_flag). The first predictor pays the cost; subsequent ones are ~60x faster (measured 2.45s -> 0.04s on netMHCpan 4.2). - Downgrade the per-allele "Skipping allele ..." messages from INFO to DEBUG and replace them with a single DEBUG summary, so constructing a predictor no longer spams the user (the detail is still available at DEBUG). Combined with caching, it also can't repeat. Note: the skipped names (some BoLA/Mamu/H-2 and annotated HLA) are ones mhcgnomes *can* parse but that the legacy AlleleName shim rejects. Simply retaining them via mhcgnomes' canonical form is not enough to make them usable — netMHCpan accepts only its own -listMHC spelling and rejects mhcgnomes' renamed forms (e.g. H-2-Qa1 works but its canonical H2-T23 does not), and it even rejects some of these names outright (Mamu-B12). That round-trip fix is left as separate follow-up work. Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
…lling
Follow-up to the caching change: instead of normalizing the predictor's
entire ~13k-name -listMHC output up front (slow, and it spammed the log
about names it couldn't parse), keep the raw names verbatim and validate the
user's handful of alleles against them.
- _determine_supported_alleles now returns the RAW -listMHC names (just a
line split, no mhcgnomes). Cheap and cached.
- _resolve_supported_alleles validates each requested allele with a fast
verbatim path: if prepare_allele_name(allele) is printed by -listMHC, use
it directly. mhcgnomes only ever runs on the user's alleles, so the common
human-HLA case never parses the 13k list. First-predictor construction
drops from ~2.45s to ~0.09s.
- Only when the fast path misses is the full {normalized: raw} map built
(once, cached) — for alleles supported under a spelling our normalizer
rewrites. In that case we pass the predictor its OWN -listMHC spelling on
the command line, which fixes a real bug: e.g. requesting BoLA-1:00901
previously sent netMHCpan "BoLA-109:01", which it rejects; ~2,456 mostly
non-human alleles were unrunnable and now work end-to-end.
- On collision (netMHCpan lists both BoLA-1:00901 and BoLA-100901 for the
same allele) prefer a colon-containing spelling, the form it accepts.
Validated on real netMHCpan 4.2: human HLA validates without the full parse,
BoLA-1:00901 resolves to its own spelling and predicts, unsupported alleles
still raise UnsupportedAllele. All netMHC-family integration tests pass.
Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
Addresses issues found reviewing this PR (one was a CI failure): 1. (blocking) Restore the "incorrect executable version?" SystemError. Dropping up-front normalization meant a mismatched binary's usage/error text passed through as a non-empty raw set, so test_executable_mismatch_3_4 got UnsupportedAllele instead of SystemError. _determine_supported_alleles now probes the raw names and raises if none parse as an allele -- it stops at the first success, so it stays O(1) for a real list. 2. Deterministic CLI spelling on collision. 15 Mamu alleles have two colon-containing spellings for one normalized name; the old colon-only preference left the winner up to set iteration order. _prefer_allele_spelling now picks deterministically: colon form, then earliest colon (right after the gene, e.g. Mamu-A1:00101), then lexicographic. 3. prepare_allele_name is now called during __init__ (in _resolve_supported_alleles); guard it so a prepare that raises (netMHCIIpan on unexpected genes) falls through to the slow path instead of crashing construction. 4. Tests: garbage-output -> SystemError, probe stops early, deterministic tie-break, resolved spelling reaches the -a argument, prepare-failure fallback. Verified on real netMHCpan 4.2: version-mismatch tests pass, Mamu-A1:00101 resolves to its own spelling and predicts, human HLA still fast-path. Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
This was referenced Jul 9, 2026
iskandr
added a commit
that referenced
this pull request
Jul 9, 2026
* Support un-normalizable exotic alleles (#220) netMHCpan's -listMHC includes alleles mhcgnomes can't parse: exotic non-human alleles (H-2-Qa1, BoLA-amani.1) and HLA low/null-expression variants (HLA-A30:14L). Requesting one raised AlleleParseError during predictor construction even though netMHCpan supports it. This is the tail left over after #219 (which handled ~2,456 alleles that normalize but to a spelling netMHCpan rejects); these names don't normalize at all. Carry the predictor's own -listMHC spelling as identity for these: * add normalize_allele_name_or_raw(): normalize when possible, else fall back to a canonical raw form (strip whitespace and the '*' gene/allele separator). netMHCpan echoes a requested "HLA-A30:14L" back as "HLA-A*30:14L", so stripping '*' makes both spellings one identity. * BasePredictor.__init__ gains keep_unparseable_alleles (default False); command-line predictors pass True since they validate against the raw supported list. IEDB / in-process predictors still raise as before. * output parser (parsing.py) uses the raw fallback instead of raising on an un-normalizable echoed allele, so requested and echoed forms match in _check_results. 14 of the 21 affected names now round-trip end-to-end against netMHCpan 4.2 (11 BoLA, H-2-Qa1/Qa2, HLA-A30:14L in both spellings). The other 7 (Mamu-B12/B17/B20/B22, BoLA-T2C, H2-Qa1, H2-Qa2) are listed by netMHCpan but rejected on -a by the binary itself, so they fail at predict time with "Missing predictions" — a documented netMHCpan inconsistency mhctools can't work around. Tests: binary-free unit tests for the fallback and keep_unparseable gate (tests/test_unparseable_alleles.py, added to the public CI subset) plus a netMHCpan integration test asserting the round-trip identity. Bump version to 3.19.0. Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG * Address review feedback on exotic-allele support Simplify and harden the changes from the previous commit (no behavior change): * _check_hla_alleles: replace the try/except in the dedup loop with a branch on keep_unparseable. This drops a redundant second parse of unparseable names (normalize_allele_name_or_raw re-parsed after the try already failed) and removes the now-unused AlleleParseError import. keep_unparseable=True -> normalize_allele_name_or_raw (parse or raw fallback); otherwise normalize_allele_name, which still raises as before. * normalize_allele_name_or_raw: document that un-normalizable exotic alleles are effectively case-sensitive (parseable names are uppercased before normalization, but the raw fallback preserves case to match the predictor's -listMHC / output spelling), so they must be requested with the tool's own casing. * Add a binary-free regression test that runs an exotic allele (H-2-Qa1) through the netMHC output parser, which previously raised on names mhcgnomes can't parse. Full suite: 510 passed, 38 skipped, 2 xfailed (netMHCpan 4.2). Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Determining a command-line predictor's supported alleles ran e.g.
netMHCpan -listMHCand normalized every one of its ~13,000 names through mhcgnomes on every predictor construction. That was slow (~2.45s each), spammed one INFO line per unparseable name, and — more subtly — mis-spelled alleles on the way back out: mhctools re-derived the CLI name viaprepare(normalize(user)), which produces a form netMHCpan rejects for ~2,456 (mostly non-human) alleles. E.g. requestingBoLA-1:00901sent netMHCpanBoLA-109:01→ "cannot be found in hla_pseudo list", so those alleles were unrunnable.Change: lazy validation + round-trip the predictor's own spelling
_determine_supported_allelesreturns the RAW-listMHCnames verbatim (a line split — no mhcgnomes). Cheap and cached per(command, flag).prepare_allele_name(allele) ∈ raw_set. mhcgnomes only ever runs on the user's handful of alleles, so the common human-HLA case never parses the 13k list. First-predictor construction drops ~2.45s → ~0.09s (~27×); the earlier in-memory cache only helped the 2nd predictor onward — this helps the first too.{normalized: raw}map once (cached) and look the allele up by normalized name, then pass netMHCpan its own-listMHCspelling on the command line. This fixes the round-trip bug —BoLA-1:00901now resolves toBoLA-1:00901and runs end-to-end. ~2,456 previously-unrunnable non-human alleles now work.BoLA-1:00901andBoLA-100901for one allele) prefer a colon-containing spelling — the form it actually accepts on-a.Human-HLA identity/output matching is unchanged (
self.allelesstays normalized); only the CLI spelling and the timing of normalization change.Why not fully skip validation
~19% of netMHCpan's own names don't round-trip through
normalize→prepare, so a naiveprepare(user) ∈ raw_setcheck with no fallback would falsely reject them. The fast-path-then-normalized-map design keeps validation robust while staying lazy for the common case.Verified on real netMHCpan 4.2 (Darwin_arm64)
BoLA-1:00901resolves to its own spelling and predicts (was impossible before).UnsupportedAllele.Tests
tests/test_supported_allele_cache.py(binary-free): raw+cached-listMHC;_determine_supported_allelesruns no mhcgnomes; fast path resolves without building the normalized map; slow path returns the predictor's own spelling; collision prefers the colon form; unsupported raises; no INFO spam.Version bumped 3.17.0 → 3.18.0.
https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG