Skip to content

Comet v2026.02.2

Choose a tag to compare

@jke000 jke000 released this 11 Aug 21:57
· 34 commits to master since this release
6edec91

NOTE: 1fff1db previously tagged as v2026.02.2 on 2026/08/10. That tag was deleted on 2026/08/11 and re-tagged.

What's Changed

This release fixes a bug in fragment-ion-index (FI) variable-modification handling that could silently mis-score modified peptides, unifies and further simplifies the on-disk index format across both index search modes (substantially reducing peptide-index (PI) memory usage and index build time/size for both modes), and restores full self-description to .idx files — an index once again carries everything needed to search it correctly, including per-modification and per-peptide modification count limits that were never persisted even before this development cycle began.

Bug Fixes

Fragment-ion-index modification scoring:

  • Fixed FI (AddFragments(), AddFragmentsThreadProc()) using a compacted internal variable-mod-slot index directly as a raw variable_modNN slot index. The two only coincide when a peptide's active modifications happen to be contiguous starting at variable_mod01 — any other combination (e.g. a lower-numbered variable_modNN left unused, or simply a peptide carrying more than one modification type at once) silently computed the precursor mass and modified fragment-ion masses against the wrong modification. A fourth, independent copy of the same bug existed in CometSearch.cpp's SearchFragmentIndex(), the per-query FI scoring function — affecting every FI search with more than a trivial single-slot modification config, not just gap configs.
    • Both xcorr and SP were corrupted for affected hits, confirmed by tracing the actual data flow: SearchFragmentIndex() builds piVarModSites[] (using the buggy translation) and passes it directly into XcorrScoreI() for xcorr in the same call; when a hit becomes the new top score, that same array is copied verbatim into the stored result, which CometPostAnalysis::CalculateSP() later reads as-is for SP — one bad array, propagated into both scores, not two independent bugs.
    • Scope: FI only. PI (CometPeptideIndex::MaterializeOneEntry()/EnumerateIndexPeptideMods()) already performed the correct compacted-to-real-slot translation before this fix — confirmed by diff, this release only refactors that already-correct PI logic into shared helpers, with no behavior change. Plain, non-indexed search never uses the compacted-slot representation at all — it assigns real variable_modNN indices directly during on-the-fly digestion, so this bug class was structurally impossible there. PI and non-indexed scoring were unaffected throughout.
    • Real-world impact, measured against the v2026.02.1 release binary on a human phosphoproteomics FI search (Met-oxidation + STY-phosphorylation, 54,445 spectra, MM2_R1.raw): +6.8% more PSMs at 1% FDR by E-value (16,238 → 17,347).
  • Fixed an accompanying out-of-bounds read: the fragment-ion b/y-ion mass loop indexed the mod-slot translation table with -1 (the ordinary "not modified in this particular combination" sentinel) for peptides with more modifiable sites than max_variable_mods_in_peptide allows — confirmed as a real heap-buffer-overflow under AddressSanitizer.
  • New regression test (t25_fi_mod_slot_gap) added to tests/unit/run_tests.py, deliberately configured with a non-contiguous modification slot so this class of bug can't hide behind an all-slot-0 test config again.

FI analysis: IMAC enriched sample, human canonical target-decoy, 16M, 80STY, 1% FDR cutoff by E-value

# variable mods v2026.02.1 (prev) v2026.02.2 (current) Δ
0 (unmodified) 304 268 −36
1 13,123 13,784 +661
2 2,656 3,066 +410
3 153 222 +69
4 2 7 +5
Total 16,238 17,347 +1,109
Phospho Ox-Met v2026.02.1 (prev) v2026.02.2 (current) Δ
0 0 304 268 −36
1 0 13,112 13,766 +654
2 0 1,889 2,101 +212
0 1 11 18 +7
1 1 765 963 +198
2 1 141 193 +52
0 2 2 2 0
1 2 12 29 +17
2 2 2 7 +5

Corrupt/truncated index-file hardening:

  • ReadPeptideIndex() now validates footer offsets and per-entry length fields against the file's actual size before trusting them for allocation or memcpy sizing, instead of risking a multi-GB allocation attempt or an out-of-bounds read on a truncated or corrupted .idx file.
  • Fixed 4 call sites (CreateFragmentIndex(), RunSearch()'s legacy batch overload, FiStrategy::initialize(), InitializeSingleSpectrumSearch()'s FI branch) that never checked ReadPeptideIndex()'s return value — a corrupt-file error previously printed a clean message and then the search proceeded anyway with an uninitialized index, segfaulting.
  • Hardened MaterializeOneEntry() and SearchPeptideIndex() against out-of-range indices from a corrupt .idx, failing the one affected candidate cleanly instead of risking an uncaught exception mid-search.
  • Extended to the header itself: malformed or truncated VariableMod:/RequireVariableMod:/MaxVariableModsInPeptide: lines are now rejected with a specific error identifying the bad entry, instead of silently leaving a modification at its default (inactive) state. A related unbounded write into the fixed-size modification-character buffer while parsing VariableMod: was also fixed.

Protein modification-list filtering on indexed search:

  • Fixed protein_modslist_file silently never taking effect against an FI or PI search. ReadProteinVarModFilterFile() was only ever called when building an index or running a plain (non-indexed) FASTA search; a search against an existing .idx never called it, so bVarModProteinFilter stayed off regardless of what protein_modslist_file was set to at search time, even though each peptide's protein-filter bitmask is faithfully carried in the index. Now runs unconditionally, matching plain-FASTA search behavior.

Index self-description restored:

  • An .idx file built earlier in this same development cycle required matching index_search_type, variable_modNN, require_variable_mod, and protein_modslist_file values to be supplied again at search time — get any of them wrong and a search would silently run with the wrong modifications or the wrong index mode. Fixed: an .idx now records its own search mode (PI or FI) and complete variable-modification configuration — including, as a new capability that predates this whole development cycle, per-modification and per-peptide modification count limits, which were never persisted in any prior Comet version — directly in its own header, and that recorded configuration is authoritative at search time. RealtimeSearch.exe and its Linux test driver no longer need to independently guess or re-supply modification settings for a .idx they didn't just build.
  • Fixed an unrelated, intermittently-reproducing memory bug surfaced while validating the above: several Results struct fields (tracking, among other things, N-terminal-methionine-clipping state and per-candidate protein bookkeeping) were never explicitly initialized for unused result slots. Reading one of these before it was ever set is undefined behavior, confirmed via AddressSanitizer/UndefinedBehaviorSanitizer to cause sporadic, heap-layout-sensitive corruption (garbled file paths, spectra silently dropped from a search) with no consistent reproduction pattern.

Performance Improvements

  • PI memory usage reduced ~1.6× by splitting the in-memory index into a shared raw-peptide table plus a compact 24-byte-per-variant array, materializing full peptide records on demand during search instead of pre-expanding every modified variant up front — mirroring the approach FI already used. Measured on a 125M-variant real-world index: RTS memory 10.48GB → 6.58GB, index build time 3m30s → 46s, build peak memory 22.8GB → 7.6GB.
  • MaterializeOneEntry()'s per-candidate modification-slot table is now built once per search (thread-safe one-time init) instead of being recomputed on every mass-window candidate in the search hot path.
  • The mod-permutation tables and compact variant array are no longer computed or persisted at index-build time at all — for either mode, they're generated once in memory per search session directly from the raw peptide table + the modification configuration recorded in the index, mirroring the regeneration step FI already paid at search-time startup. Measured on the same human canonical target-decoy fasta as the bug-fix analysis above (4.4M raw peptides, 79M peptide variants): PI index build time 31s → 3s (~10×), build peak memory 4.8GB → 1.1GB (~4.4×), and the .idx file itself 2.09GB → 277MB (~7.6×). Search-time cost is essentially unchanged (regeneration happens once per session either way) — RTS memory measured slightly lower in this same test (15.5GB → 13.7GB), since the format no longer holds a separately-persisted variant-array section in addition to the regenerated one.

Functional Changes

  • An .idx file is fully self-describing as before: it records which mode (PI or FI) it was built for, and its complete enzyme, static-modification, and variable-modification configuration — including per-modification and per-peptide modification count limits — directly in its own header. Searching an existing .idx no longer requires (or honors) index_search_type, variable_modNN, require_variable_mod, or protein_modslist_file at search time; values recorded in the file always take precedence. index_search_type is still accepted as a parameter, but now only matters when Comet must auto-build a missing .idx from a FASTA file, to choose which mode, FI or PI, to build.

Tools and Build

  • comet.exe -D<database>.idx -i/-j-built index now correctly enforces the digest_mass_range/peptide_length_range set at search time (previously written to the .idx header but never read back, so a narrower search-time range had no effect on PI and only a coincidental effect on FI).
  • Migrated the ~21 hand-run functional test cases into tests/unit/run_tests.py (T21), and added automated RTS FI/PI single-spectrum regression coverage (T22) plus full-scale internal-decoy/target-decoy and FI/PI-vs-plain-FASTA parity checks against real data (T23/T24, opt-in via --bigdata).
  • T23/T24 now also compare every config against the v2025.03.0 release binary (auto-downloaded on first use) to catch cross-version regressions in both result counts and search/build timing.

Full Changelog: v2026.02.1...v2026.02.2