Skip to content

metadata: read only the proteins a search hits - #35

Merged
dmx2 merged 1 commit into
IEDB:masterfrom
Roman-Young:perf/metadata-for-hits
Oct 15, 2026
Merged

dmx2 merged 1 commit into
IEDB:masterfrom
Roman-Young:perf/metadata-for-hits

Conversation

@Roman-Young

Copy link
Copy Markdown
Contributor

Follow-up to #25. After a search finds its hits, each hit only has a protein number, so we look up that protein's details (ID, name, gene, species...) and join them on. Right now that lookup reads the details for every protein in the proteome (82k for human) on every search, and the join then throws away every protein that wasn't hit. On small searches that's most of the runtime (eg. an exact search of 1,000 MHC ligands spends 0.26 of its 0.29 s on it).

This PR only looks up the proteins the search hit, then does the same join as before. For a search with 3 hits on 2 proteins:

              protein lookups
before #25    3        one per hit row (protein #2 read twice)
#25           82,000   every protein in the proteome, once
this PR       2        each hit protein, once

So it keeps what #25 fixed (no repeated copies) without reading the whole proteome.

In the code, rs_metadata takes an optional list of protein numbers (without one it still returns the whole table), and _to_dataframe passes the distinct proteins from the hits. I also set maintain_order='left' on the join since polars doesn't guarantee join order by default. Same order as now, it's just guaranteed.

That exact search goes from 0.29 s to 0.04 s, and mismatch, discontinuous and indel searches are 16 to 32% faster. Searches that hit most of the proteome take the same time as before.

Output is unchanged. The proteins it skips are the ones the join was already throwing away. The new test_metadata_rows.py checks this for every mode: it builds each result from the same raw hits twice, once reading only the hit proteins and once reading every protein, and makes sure they're identical. Added its CI matrix row. I also ran 28 searches against the human proteome on master and on this branch (every mode, every output format, the CLI) and got the same results, apart from row order in exact search and ties in best match, which already change from run to run on master. Full suite passes on Python 3.9 to 3.13 and polars 1.31 (our minimum) through 2.0.

One behavior change: indel batches that mix short and long queries, and best match without k, read hits from one .pepidx and metadata from another. If those were built from different versions of the FASTA, a hit can point to a protein the metadata index doesn't have. Master returns that hit with a blank Protein ID (and best match can drop the query). Now it raises an error that says to rebuild the .pepidx files.

Found a few unrelated bugs while testing this, I'll put those in a separate issue.

Since IEDB#25, hits get their protein metadata from one join against a
per-protein table instead of a copy in every hit row, but that table holds
the whole proteome: 82k proteins with nine text fields each for human, read
from the index and converted however few proteins the search hit. On small
searches that cost more than the search itself (an exact search of 1,000 MHC
ligands took 0.29 s, 0.26 s of it building the table).

rs_metadata now takes an optional list of protein numbers and returns just
those rows, ascending and deduplicated, raising ValueError for a number the
index doesn't have; without the list it returns the whole table as before.
_to_dataframe passes the distinct proteins in the hits, and the join asks for
the hits' row order (maintain_order='left') instead of relying on polars'
unspecified default. A hit on a protein the index doesn't have means the
search read stale .pepidx files, so that error now says to rebuild them.

Output is unchanged. test_metadata_rows.py builds each mode's frame twice from
the same raw hits, once reading only the hit proteins and once reading every
protein, and requires the two to be identical; it also covers duplicate,
unsorted, empty and out-of-range requests, the column types of a search that
hits nothing, and a stale index. The new file has its CI matrix row.

@dmx2 dmx2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

@dmx2
dmx2 merged commit 5d2138b into IEDB:master Oct 15, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants