Repository navigation
Comet v2026.02.2
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 rawvariable_modNNslot index. The two only coincide when a peptide's active modifications happen to be contiguous starting atvariable_mod01— any other combination (e.g. a lower-numberedvariable_modNNleft 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 inCometSearch.cpp'sSearchFragmentIndex(), 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()buildspiVarModSites[](using the buggy translation) and passes it directly intoXcorrScoreI()for xcorr in the same call; when a hit becomes the new top score, that same array is copied verbatim into the stored result, whichCometPostAnalysis::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 realvariable_modNNindices 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).
- Both xcorr and SP were corrupted for affected hits, confirmed by tracing the actual data flow:
- 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 thanmax_variable_mods_in_peptideallows — confirmed as a real heap-buffer-overflow under AddressSanitizer. - New regression test (
t25_fi_mod_slot_gap) added totests/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 ormemcpysizing, instead of risking a multi-GB allocation attempt or an out-of-bounds read on a truncated or corrupted.idxfile.- Fixed 4 call sites (
CreateFragmentIndex(),RunSearch()'s legacy batch overload,FiStrategy::initialize(),InitializeSingleSpectrumSearch()'s FI branch) that never checkedReadPeptideIndex()'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()andSearchPeptideIndex()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 parsingVariableMod:was also fixed.
Protein modification-list filtering on indexed search:
- Fixed
protein_modslist_filesilently 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.idxnever called it, sobVarModProteinFilterstayed off regardless of whatprotein_modslist_filewas 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
.idxfile built earlier in this same development cycle required matchingindex_search_type,variable_modNN,require_variable_mod, andprotein_modslist_filevalues 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.idxnow 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.exeand its Linux test driver no longer need to independently guess or re-supply modification settings for a.idxthey didn't just build. - Fixed an unrelated, intermittently-reproducing memory bug surfaced while validating the above: several
Resultsstruct 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
.idxfile 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
.idxfile 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.idxno longer requires (or honors)index_search_type,variable_modNN,require_variable_mod, orprotein_modslist_fileat search time; values recorded in the file always take precedence.index_search_typeis still accepted as a parameter, but now only matters when Comet must auto-build a missing.idxfrom 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 thedigest_mass_range/peptide_length_rangeset at search time (previously written to the.idxheader 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