Add TULIP-TCR pMHC:TCR binding predictor - #222
Closed
iskandr wants to merge 1 commit into
Closed
Conversation
Adds a `Tulip` predictor wrapping TULIP-TCR (https://github.com/barthelemymp/TULIP-TCR), producing pMHC_TCR_binding predictions like NetTCR but also taking the presenting MHC allele as input. License / isolation: TULIP-TCR is GPLv3 and pinned to transformers==4.32.1; mhctools is Apache-2.0 and depends on neither torch nor transformers. So the wrapper vendors NONE of TULIP — no source, weights, or tokenizers. It runs a user-provided checkout out-of-process, in a separate interpreter, via TULIP's own predict.py (the same "shell out to a user-provided install" pattern used for the DTU netMHC tools and NetTCR). Nothing here imports TULIP's GPL code. Two things are supplied via constructor args or env vars: * TULIP_HOME — a TULIP-TCR checkout (predict.py, src/, tokenizers, weights) * TULIP_PYTHON — an isolated Python 3.11 interpreter with torch and transformers==4.32.1. 3.11 matters: transformers 4.32.1 resolves tokenizers 0.13.x, which has no cp312 wheel and would otherwise build from source (Rust); 3.11 has a prebuilt wheel, so the install needs no compiler. scripts/setup_tulip_env.sh builds that env (uv or venv+pip) and clones TULIP. The wrapper writes an input CSV, invokes predict.py in the checkout, and maps its per-peptide output scores back to (peptide, MHC, CDR3a, CDR3b) by position (predict.py doesn't echo the MHC column). Scores are TULIP's log-likelihood (higher = more likely to bind). Tests (tests/test_tulip.py): binary-free unit tests mock the subprocess to cover input-CSV construction, per-peptide output parsing, position/score mapping, MHC pass-through, dedup, and error propagation (added to the public CI subset); end-to-end tests run only when TULIP_HOME + TULIP_PYTHON are set. A new integration-tulip CI job builds the isolated sidecar (Python 3.11), clones the public TULIP repo, and runs the wrapper end-to-end. Full suite: 526 passed, 38 skipped, 2 xfailed (netMHCpan 4.2 + TULIP sidecar). Bump version to 3.20.0. Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
Contributor
Author
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.
Adds a
Tulippredictor wrapping TULIP-TCR — pMHC:TCR binding, likeNetTCR, but it also takes the presenting MHC allele as an input.The licensing/isolation problem (why this looks different from other predictors)
TULIP-TCR is GPLv3 and is coupled to
transformers==4.32.1(its model code is a fork of that release's internals). mhctools is Apache-2.0 and depends on neither torch nor transformers. So this wrapper vendors none of TULIP — no source, no weights, no tokenizers. It runs a user-provided checkout out-of-process, in a separate interpreter, by invoking TULIP's ownpredict.pyvia subprocess — the same "shell out to a user-provided install" pattern already used for the DTUnetMHC*tools andNetTCR. Nothing in mhctools imports TULIP's GPL code, so the Apache package stays clean.This is a deliberate deviation from "bundle the weights": bundling GPL weights/source into an Apache package isn't license-clean, and the external-checkout pattern is both correct and consistent with the existing predictors.
Setup (two env vars)
TULIP_HOME— a clone of TULIP-TCR (predict.py,src/, tokenizers, releasedmodel_weights/).TULIP_PYTHON— an isolated Python 3.11 interpreter withtorch+transformers==4.32.1.scripts/setup_tulip_env.shdoes both (clones TULIP, builds the env viauvorvenv+pip, smoke-tests the import).Why 3.11 specifically:
transformers==4.32.1resolvestokenizers 0.13.x, which has no cp312 wheel — on 3.12 it builds from source and needs a Rust toolchain. On 3.11 a prebuilt wheel exists, so the install is wheels-only. (Confirmed empirically: moderntransformersdoes not work — TULIP breaks at model construction onBertConfig.cross_attention_hidden_size, removed in 5.x — so the old pin is required, not optional.)Usage
predict(peptides, tcrs, mhc=...),predict_pairs([(peptide, TCR[, mhc]), ...]), andpredict_dataframe(...)mirrorNetTCR. Kind ispMHC_TCR_bindingwithmhc_dependence="single_allele".How the bridge works
The wrapper writes an input CSV, runs
predict.pyin the checkout (cwd=TULIP_HOMEsoaatok/,mhctok/,src/resolve), and maps the per-peptide output scores back to each(peptide, MHC, CDR3a, CDR3b)by position —predict.pydoesn't echo the MHC column, so content-joining wouldn't disambiguate same-peptide/different-MHC rows; position within each peptide group does.Tests & CI
tests/test_tulip.py: binary-free unit tests mock the subprocess to cover input-CSV construction, per-peptide output parsing, position/score mapping, MHC pass-through, dedup, and error propagation (added to the public CI subset, runs on all Python versions). End-to-end tests run only whenTULIP_HOME+TULIP_PYTHONare set.integration-tulipCI job: builds the isolated Python-3.11 sidecar, clones the public TULIP repo, and runs the wrapper end-to-end.Local full suite: 526 passed, 38 skipped, 2 xfailed (real netMHCpan 4.2 + a real TULIP sidecar; end-to-end scores match
predict.py's own output exactly).https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG