Skip to main content

mhcmatch

mhcmatch — Peptide–MHC presentation & cross-reactivity

PyPI CI docs python license

Peptide–MHC presentation, cross-reactivity, and motif tools — the applied peptide–MHC layer on top of the seqtree fuzzy-search substrate. mhcmatch productionizes the reference seqtree.pmhc methodology (anchor-masked TCR-facing homology, presentation-aware E-values, allele guessing) and adds a pseudosequence-based cross-allele diffusion model that rescues rare alleles by borrowing from groove-similar frequent ones.

The mathematical/statistical theory is in appendix/mhcmatch.tex; the development plan is in ROADMAP.md.

What it does (v0)

  1. MHC restriction & presentation — rank presenting alleles for a peptide (single / set / all, human & mouse), flag non-binders, and scan a whole protein for presented peptides.
  2. Large-scale similarity search — find similar peptides across big sets / proteomes, either by same-MHC binding (presentation signature) or similar TCR recognition (anchor-masked, TCR-facing); neoantigen molecular mimicry with per-allele E-values.
  3. Anchor / TCR-facing split — decompose a peptide into anchor and TCR-facing parts (X masks).
  4. Near-exact source lookup — find the self peptide a neoantigen derives from + its parent protein / mutated position, against a reference proteome.
  5. Motif logos — per-allele information-content logos with length distributions.
  6. Pseudosequence diffusion — allele similarity, clustering, and kernel-shrinkage pooling over 34-mer groove pseudosequences (rare-allele rescue).
  7. Quantitative affinity (IC50 nM) — a pan-allele Potts / direct-coupling model (peptide×pocket couplings, fit on measured IEDB IC50) predicts nM affinity and the neoantigen-fitness differentials — Łuksza amplitude A = Kd_WT/Kd_MT and the differential agretopicity index — for MHC-I and MHC-II, human and mouse. Optional structure-based MJ ΔΔG via the [structure] extra (tcren).

Install

bash setup.sh            # repo-local .venv + editable install (uses sibling ../seqtree if present)
bash setup.sh --tests    # + pytest
bash setup.sh --logo     # + logomaker/matplotlib for rendering logos

Quickstart

import mhcmatch

# build from the isalgo/pmhc_data table (full or shortlist tier)
store = mhcmatch.Store.from_pmhc("pmhc_full.tsv.gz", species="human")

store.restriction("NLVPMVATV")                  # ranked presenting alleles + binder flags
store.is_binder("NLVPMVATV", "HLA-A*02:01")
store.scan_protein(my_protein, cls="mhc1")       # presented peptides in a protein
store.decompose("NLVPMVATV", cls="mhc1")         # (tcr_facing, presentation) with X masks

# similarity at scale
mhcmatch.search.search("NLVPMVATV", big_peptide_set, mode="tcr")   # TCR-facing homologs
mhcmatch.search.find_mimics("EAAGIGILTV", self_set, bacterial_sets={...})

# near-exact source of a neoantigen
pm = mhcmatch.Proteome.from_hf("human")          # auto-fetched from HF (or .from_fasta(<your FASTA>))
pm.find_source("NLVPMVATV", max_subs=1)

# pseudosequence allele similarity + rare-allele diffusion
ps = mhcmatch.Pseudoseq("mhc1")
ps.neighbors("HLA-A*02:01", candidates=store.alleles("mhc1"))

# diffusion-powered forward scorer (rescues rare alleles by borrowing from groove-neighbours)
am = store.anchor_model("mhc1")          # learned anchor weights + bounded-prior shrinkage
am.score("NLVPMVATV", "HLA-A*02:01")     # anchor log-odds; am.score(..., raw=True) disables borrowing

# footprint (which core positions) and background (the log-odds null) tune the model to the question:
store.anchor_model("mhc1", footprint="adaptive")             # anchors for rare alleles, full core otherwise
store.anchor_model("mhc1", background="proteome")            # presentation null (is it presented at all?)
store.anchor_model("mhc1", background="ligand")              # specificity null (which allele? — default)

# calibrated, cross-allele-comparable output (NetMHCpan %Rank_EL analogue + P(present) + band)
for r in store.restriction("NLVPMVATV", cls="mhc1", calibrated=True):
    print(r.allele, r.rank, r.p_present, r.band)             # e.g. HLA-A*02:01  1.6  0.98  weak

mhcmatch.logo.motif(store, "HLA-A*02:01", "mhc1")

# quantitative affinity + neoantigen-fitness differentials (Potts model, vendored weights)
aff = store.affinity_model("mhc1")
aff.predict_ic50("NLVPMVATV", "HLA-A*02:01")            # -> ~64 nM
aff.amplitude("NLVPMVATL", "NLVPMVATV", "HLA-A*02:01")  # Kd_WT/Kd_MT (Łuksza eq. 9) -> ~2.05
aff.dai("NLVPMVATL", "NLVPMVATV", "HLA-A*02:01")        # differential agretopicity (log10 ratio)
store.affinity_model("mhc2").predict_ic50("PKYVKQNTLKLAT", "HLA-DRB1*15:01")   # MHC-II, core auto-located

Command line

mhcmatch decompose NLVPMVATV                                  # anchor / TCR-facing split (no data)
set -x MHCMATCH_PMHC /path/to/pmhc_data                       # or pass --pmhc to each command
mhcmatch restriction NLVPMVATV --allele 'A*02:01' --diffuse   # allele name auto-resolved; rare-aware
mhcmatch restriction NLVPMVATV --calibrated                   # + %rank, P(present), binding band
mhcmatch scan my_protein.fasta --correction bh                # presented windows, BH-FDR controlled
mhcmatch source MKTAYIAKW --proteome human                    # HF name auto-fetched (or a FASTA path)
mhcmatch logo 'HLA-A*02:01'
mhcmatch affinity NLVPMVATV --allele 'A*02:01' --wt NLVPMVATL   # IC50 nM + amplitude A=Kd_WT/Kd_MT + DAI

Data

  • Reference ligands: the public HF dataset isalgo/pmhc_data (full / shortlist tiers). Store.from_pmhc() auto-fetches pmhc/pmhc_<tier>.tsv.gz on first use (cached by huggingface_hub) — no manual download, which is what lets the container/nextflow deploy bootstrap with no pre-staged data. Override with a local copy via Store.from_pmhc(path=...) or $MHCMATCH_PMHC.
  • Pseudosequences: 34-mer groove pseudosequences vendored in src/mhcmatch/data/ (see its PROVENANCE.md).
  • Reference proteomes: the human (UP000005640) and mouse (UP000000589) UniProt proteomes — plus pathogen proteomes for mimicry — live in the same HF dataset. Proteome.from_hf("human") / mhcmatch source --proteome human auto-fetch them (cached), or mhcmatch bootstrap --proteome human,mouse to pre-fetch. Pass your own FASTA to Proteome.from_fasta to override.

Benchmark vs NetMHCpan

Benchmarks live in a separate repo. bench/ moved to 2026-mhcmatch-benchmark — the head-to-head harness, the bench/results/*.md tables referenced throughout, and their provenance notes. Paths like bench/results/... below resolve there, not here.

A reproducible head-to-head against NetMHCpan-4.2b and NetMHCIIpan-4.3i lives in bench/compare/ (results in bench/results/compare_*.md, provenance and caveats in bench/compare/SOURCES.md). It compares the two tools on the same per-(peptide, allele) task, stratified by allele rarity, with AUROC / AUPRC / PPV@k, bootstrap CIs and paired significance. Headline results (shortlist tier, human, seed 0):

  • Allele-specificity (which allele presents a peptide — the restriction problem): mhcmatch beats NetMHCpan on MHC-I medium and frequent alleles on AUROC, AUPRC and PPV@k (all p < 0.001; e.g. frequent AUPRC 0.850 vs 0.769). Rare MHC-I is a wash (+0.008 AUROC, p = 0.41).
  • Presented-vs-random screening (background="proteome"): mhcmatch beats NetMHCpan on MHC-I frequent alleles (AUROC p < 0.001, AUPRC 0.881 vs 0.846, p = 0.001). Medium and rare are a wash — the deltas sit inside the CI. This task is much easier for both tools (every AUROC ≥ 0.97).
  • MHC-II: NetMHCIIpan leads on medium and frequent alleles (p < 0.001); rare is a wash (Δ AUROC +0.013 specificity / −0.015 screening, both inside the CI). Read these with compare/SOURCES.md in hand: NetMHCIIpan trained on essentially all public IEDB eluted-ligand data, so the in-corpus medium/frequent strata are contaminated in its favour and the rare/zero-shot axis is the fair one.
  • Speed: mhcmatch scores ~68× faster (pure Python, ~195k peptide-allele scores/s).
python bench/compare/run_compare.py --cls mhc1 --decoy-mode hard   --background ligand    # specificity
python bench/compare/run_compare.py --cls mhc1 --decoy-mode random --background proteome  # screening

Quantitative affinity (Potts head)

The affinity head is benchmarked head-to-head against NetMHCpan-4.2 −BA / NetMHCIIpan-4.3 −BA on held-out measured IEDB IC50 (bench/affinity/; provenance in bench/affinity/SOURCES.md). Metric: median per-allele Spearman ρ against measured log-IC50, and AUROC at the 500 nM binder threshold, on the same held-out (peptide, allele) pairs. Honest numbers (per-allele held-out split, seed 0):

class stratum alleles mhcmatch ρ netMHCpan ρ mhcmatch AUROC netMHCpan AUROC
MHC-I human common 31 0.702 0.792 0.851 0.913
MHC-I human rare 37 0.485 0.765 0.754 0.930
MHC-II human common 28 0.531 0.774 0.798 0.923
MHC-II human rare 12 0.457 0.755 0.749 0.914
MHC-II mouse (rare) 5 0.507 0.716 0.787 0.893

NetMHCpan/IIpan lead on this eval, but the gap is inflated by train/test overlap we cannot undo: both tools trained on much of IEDB, so the held-out pairs are in-sample for them and out-of-sample for mhcmatch. On truly unseen alleles (leave-20-alleles-out, zero training rows for the allele) the Potts model still generalizes — MHC-I common orphan ρ ≈ 0.57 — because its peptide×pocket couplings interpolate across groove-similar alleles. The affinity head is a compact, dependency-light linear model (numpy-only dot product at predict time, ~µs/peptide) and gives the WT-vs-mutant ratio (amplitude / DAI) that percentile ranks cannot express.

python bench/affinity/train_potts.py --cls mhc1 --alpha 40                 # MHC-I head-to-head
python bench/affinity/train_potts.py --cls mhc2 --species all --alpha 40   # MHC-II, human + mouse

Status

Beta (v0.4). Affinity (IC50 nM) + neoantigen amplitude/DAI shipped for MHC-I/II, human & mouse; optional structure-based MJ ΔΔG via the [structure] extra. See ROADMAP.md for what's next (order-k Markov / covariance null, a learned reranker for rare-allele screening, full-tier + temporal cluster sweeps, and the stability/cleavage/immunogenicity predictors).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mhcmatch-0.6.0.tar.gz (5.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mhcmatch-0.6.0-py3-none-any.whl (463.5 kB view details)

Uploaded Python 3

File details

Details for the file mhcmatch-0.6.0.tar.gz.

File metadata

  • Download URL: mhcmatch-0.6.0.tar.gz
  • Upload date:
  • Size: 5.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mhcmatch-0.6.0.tar.gz
Algorithm Hash digest
SHA256 f7ce1e30caf12154166d537392b6c5064c1d21b97fc62b1d2e632c36bb4b1e0c
MD5 c3c9156b77a1d72087684dd518522ec9
BLAKE2b-256 a801df0192267b66eb426d90d307e47581b455c6484d473881fafbfa9ed7feed

See more details on using hashes here.

Provenance

The following attestation bundles were made for mhcmatch-0.6.0.tar.gz:

Publisher: publish.yml on antigenomics/mhcmatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mhcmatch-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: mhcmatch-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 463.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mhcmatch-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ca89627e594968ded4b3d2f8d627608582081b02602b5be77c9dc17529b0f3a4
MD5 2a8a30b99ec97134e8419d6308bccb70
BLAKE2b-256 bb083d755a47d511282dec9222842def01684bfa0acc0731b94319546358a42a

See more details on using hashes here.

Provenance

The following attestation bundles were made for mhcmatch-0.6.0-py3-none-any.whl:

Publisher: publish.yml on antigenomics/mhcmatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page