Skip to content

Keep both modifications that share a residue on generated decoy fragments - #71

Merged
maccoss merged 1 commit into
mainfrom
fix/decoy-stacked-modifications
Sep 30, 2026
Merged

maccoss merged 1 commit into
mainfrom
fix/decoy-stacked-modifications

Conversation

@maccoss

@maccoss maccoss commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • The library loaders put an N-terminal modification on residue 0, where a modification of the first residue also sits ((UniMod:1)M(UniMod:35)), and the target's fragments carry both masses. recalculate_fragments built its per-position mass map with insert, so the second modification replaced the first. Every decoy fragment spanning that residue came out one modification light: 42.011 Da for an N-terminal acetyl.
  • The masses at one position now add (*mod_masses.entry(new_pos).or_insert(0.0) += m.mass_delta). With at most one modification per position the map is unchanged, so only libraries that hold such an entry get different decoys. None of the regression libraries does: Stellar, Astral and stellar-libdecoy were scanned.
  • Mirrors osprey: Added blib fragment ion typing from RefSpectraPeakAnnotations and fixed stacked-mod decoys ProteoWizard/pwiz#4730, where DecoyGenerator.RecalculateFragments now sums through PeptideFragmentMass.ModMassesByPosition, so the two implementations keep generating the same decoys.

Test plan

  • cargo fmt --check, cargo clippy --workspace -- -D warnings, cargo test --workspace
  • test_decoy_fragment_carries_stacked_modifications (target MPEPTIDEK with acetyl and oxidation on residue 0; decoy EDITPEPMK): b3 carries neither mass, while b8, y2 and y4 carry both. It failed before the fix on b8.
  • The C# counterpart DecoyConstructionTest.DecoyFragmentCarriesStackedModifications fails the same way before its fix, and the C# Stellar regression passes unchanged.

Co-Authored-By: Claude noreply@anthropic.com

…ents

The library loaders put an N-terminal modification on residue 0, where a
modification of the first residue also sits ((UniMod:1)M(UniMod:35)), and
the target's fragments carry both. recalculate_fragments built its
per-position mass map with insert, so the second modification replaced the
first and every decoy fragment spanning that residue was one modification
light - 42 Da for an N-terminal acetyl. The masses at one position now add.

Only libraries holding such an entry change; none of the regression
libraries does (Stellar, Astral and stellar-libdecoy scanned).

Mirrors ProteoWizard/pwiz (DecoyGenerator.RecalculateFragments, which now
sums through PeptideFragmentMass.ModMassesByPosition);
test_decoy_fragment_carries_stacked_modifications is the Rust twin of
DecoyConstructionTest.DecoyFragmentCarriesStackedModifications, and fails
before the fix on b8.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 28, 2026 22:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The focused fix is correct and covered by a representative regression test.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes generated decoy fragment masses when multiple modifications share one residue.

Changes:

  • Accumulates modification mass deltas per decoy position.
  • Adds regression coverage for stacked N-terminal/residue modifications.
  • Documents the fix in release notes.
File Description
crates/​osprey-scoring/​src/​lib.rs Sums co-located modifications and tests affected fragments.
release-notes/​RELEASE_NOTES_next.md Records the decoy mass correction.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@maccoss
maccoss requested a balanced review from Copilot September 30, 2026 02:51
@maccoss
maccoss merged commit d19be4c into main Sep 30, 2026
5 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Existing score caches can bypass the corrected calculation and preserve incorrect decoy scores.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

// Find new position for this modification
if let Some(new_pos) = position_mapping.iter().position(|&old| old == m.position) {
mod_masses.insert(new_pos, m.mass_delta);
*mod_masses.entry(new_pos).or_insert(0.0) += m.mass_delta;
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