Custom oxonium ions via the monosaccharide diagnostic-ion column#2665
Open
trishorts wants to merge 8 commits into
Open
Custom oxonium ions via the monosaccharide diagnostic-ion column#2665trishorts wants to merge 8 commits into
trishorts wants to merge 8 commits into
Conversation
Problem ------- GlycoSearchEngine.cs and GlycoPeptides.DiagonsticFilter index Glycan.AllOxoniumIons by bare integer literals (4, 5, 9, 10, 12, 14). Each slot's meaning lives only in scattered comments, and reordering AllOxoniumIons would silently break the diagnostic logic with no compile-time or test-time signal. Fix --- - Add OxoniumIonReservedIndices with named int constants R138, R144, HexNAc204, NeuAc274, NeuAc292, HexHexNAc366 for the frozen slots. - GlycoSearchEngine.cs: OxoniumIon204Index now forwards to OxoniumIonReservedIndices.HexNAc204; R138/R144 literals replaced in the 138/144 ratio computations (two sites). - GlycoPeptides.DiagonsticFilter: [4]/[10]/[12]/[14] replaced with the named constants. Pure rename; filter logic unchanged. Tests ----- - Reserved_Indices_Match_AllOxoniumIons_Positions: asserts the exact scaled-int mass at each reserved index, failing loudly if anyone reorders or edits Glycan.AllOxoniumIons. Files changed ------------- EngineLayer\GlycoSearch\OxoniumIonReservedIndices.cs EngineLayer\GlycoSearch\GlycoSearchEngine.cs EngineLayer\GlycoSearch\GlycoPeptides.cs Test\OxoniumIonReservedIndicesTests.cs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem ------- PR smith-chem-wisc#2660 lets a custom monosaccharide carry diagnostic-ion m/z values (MonosaccharidesCustom.tsv column 4), but those ions only contribute to DiagnosticIonScore -- they never gate a candidate. Users had no way to require that a given oxonium ion co-occur with its linked monosaccharide, which is the diagnostic-filter behavior wanted for the custom-oxonium feature. Fix --- Reuse the per-monosaccharide diagnostic ions already registered by PR smith-chem-wisc#2660 (single source of truth; no second TSV or registry). - Glycan.cs: add HasCustomOxoniumIons (cheap gate), CustomOxoniumIons (m/z-scaled + Kind-index pairs in deterministic order) and AllOxoniumIonsIncludingCustoms (built-ins + appended customs; returns the built-in array unchanged when no customs -> byte-identical default). - GlycoPeptides.ScanOxoniumIonFilter: probe intensities over the combined list so custom ions get a slot after the built-ins. - GlycoPeptides.DiagonsticFilter: after the unchanged built-in rules, when customs exist, iterate CustomOxoniumIons and reject on strict mismatch (ion observed XOR linked monosaccharide present). Gated on HasCustomOxoniumIons, so the default no-customs path is untouched. - Helpers CheckOxoniumPresence (intensity > 0, mirrors the built-in 204 check) and ApplyStrictMonoFilter (hasSignal == hasMono). Tests ----- - CustomOxoniumIons_DefaultNoCustoms_EmptyAndArrayIsByteIdentical: combined array is the same reference as AllOxoniumIons when no customs. - RegisterCustomMonosaccharide_WithDiagnosticIons_AppearsInCustomOxoniumIonsAndCombinedArray. - ApplyStrictMonoFilter_AcceptsOnlyWhenSignalMatchesMonoPresence (4 cases). - CheckOxoniumPresence_PositiveIntensityIsPresentZeroIsAbsent. - DiagonsticFilter_NoCustoms_BuiltInRejectStillApplies (legacy path intact). - DiagonsticFilter_CustomIon Observed/Absent x Mono present/absent: the four strict accept/reject combinations. - DiagonsticFilter_TwoCustoms_OneMismatch_Rejects. - LoadCustomMonosaccharides_DiagnosticIonsColumn_RoundTripsIntoCustomOxoniumIons. Files changed ------------- EngineLayer\GlycoSearch\Glycan.cs EngineLayer\GlycoSearch\GlycoPeptides.cs Test\CustomOxoniumFilterTests.cs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem
-------
The strict custom-oxonium branch added to DiagonsticFilter should add no
meaningful cost when no customs are registered (the default) and should
scale acceptably with the number of custom ions. Nothing measured this.
Fix
---
Add two NUnit tests, both [Explicit] + [Category("Performance")] so they
are skipped by the normal suite (timing assertions are noisy) and run only
via the new Test\perf\run-perf.ps1 (filters on Category=Performance).
- Filter_NoCustoms_OverheadOverLegacyIsNegligible: times DiagonsticFilter
at zero customs against a captured pre-Phase-3 baseline. Asserts the
absolute per-call addition stays small; reports ns/call.
- Filter_FiftyCustomIons_PerCallUnderTenMicroseconds: registers 50 custom
ions (5 monosaccharides x 10 ions), forces the worst case (all match so
the loop runs fully), asserts mean < 10 us/call.
Measured on the dev box: ~9 ns/call added at zero customs (a single
Dictionary.Count check; ScanOxoniumIonFilter returns the built-in array
unchanged with no allocation), and ~1.36 us/call at 50 custom ions.
Tests
-----
- Filter_NoCustoms_OverheadOverLegacyIsNegligible
- Filter_FiftyCustomIons_PerCallUnderTenMicroseconds
Files changed
-------------
Test\CustomOxoniumPerformanceTests.cs
Test\perf\run-perf.ps1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem ------- The strict custom-oxonium loop in DiagonsticFilter has two defensive guards -- the custom ion index may fall past the end of the intensity array, and the custom monosaccharide index may fall past the end of glycanBox.Kind. The happy-path tests always pass correctly sized inputs, so the false branches of both guards were never exercised. Fix --- Add a test that registers a custom monosaccharide, then calls DiagonsticFilter with an intensity array that has no slot for the custom ion and a Kind array shorter than the custom monosaccharide's index. Both guards must treat the entry as absent and the call must not throw. Coverage of the patch after this test (local ReportGenerator, merged from the integration run and the fast unit run): every added or changed line in Glycan.cs, GlycoPeptides.cs, GlycoSearchEngine.cs and OxoniumIonReservedIndices.cs is covered. Class line coverage: Glycan 98.8%, GlycoSearchEngine 97.3%, GlycoPeptides 90.2% (remaining gaps are pre-existing methods outside this patch). Tests ----- - DiagonsticFilter_UndersizedInputs_GuardsTreatAsAbsentAndDoNotThrow Files changed ------------- Test\CustomOxoniumFilterTests.cs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem ------- The MonosaccharidesCustom.tsv banner described column 4 (DiagnosticIonMasses) only as ions added to the diagnostic-ion SET (scoring). After the strict custom-oxonium filter, those same ions also gate candidates when the oxonium filter is on, but the banner did not say so. Fix --- Extend the column 4 documentation to state that the listed ions also act as strict custom oxonium ions when the Oxonium Ion Filter is enabled (observed without the monosaccharide, or monosaccharide present without the ion, rejects the candidate), with the standard caveat that ion prevalence is acquisition dependent and should be validated before use as a filter. Tests ----- - None (documentation/banner text only). Files changed ------------- EngineLayer\Glycan_Mods\MonosaccharidesCustom.tsv Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…c ions Problem ------- The custom-monosaccharide feature and the strict custom-oxonium filter are configured through MonosaccharidesCustom.tsv, but there was no end-user documentation explaining how to define a monosaccharide, reference it in a glycan database, and attach diagnostic/oxonium ions (including how the strict OxoniumIonFilt gate behaves). Fix --- Add docs/Custom-Monosaccharides-and-Diagnostic-Ions.md, a wiki-style guide: file location and format, the built-in monosaccharide reference table, how to define and then use a custom sugar in O-glycan (structure) and N-glycan (composition) databases, the dual role of column-4 diagnostic ions (scoring vs strict filter), an end-to-end worked example, the loader error messages, tips and an FAQ. Tests ----- - None (documentation only). Files changed ------------- docs\Custom-Monosaccharides-and-Diagnostic-Ions.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2665 +/- ##
=======================================
Coverage 93.29% 93.30%
=======================================
Files 196 196
Lines 21320 21350 +30
Branches 3994 4003 +9
=======================================
+ Hits 19891 19921 +30
Misses 893 893
Partials 536 536
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
RayMSMS
approved these changes
Jul 14, 2026
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.
Custom oxonium ions via the monosaccharide diagnostic-ion column
Summary
Lets users turn the diagnostic ions they attach to a custom monosaccharide into a strict diagnostic filter during glyco search. The data source is the existing
DiagnosticIonMassescolumn (column 4) ofGlycan_Mods/MonosaccharidesCustom.tsvadded in #2660 — there is no new file and no new registry. When the glyco Oxonium Ion Filter is enabled, each such ion must co-occur with its linked monosaccharide: an ion observed without its monosaccharide (or a monosaccharide present without its ion) rejects the candidate.Default behavior is unchanged. The shipped TSV registers no custom monosaccharides, so the new code path is gated off and the combined oxonium-ion array is the built-in array itself.
How to add a custom oxonium ion
Glycan_Mods/MonosaccharidesCustom.tsvin the MetaMorpheus data directory.DiagnosticIonMasses), comma-separated.TSV format (column 4)
Worked example
With the oxonium filter on, a candidate glycopeptide is rejected if 175.02482 or 157.01425 is observed but the candidate has no HexA, or if it has HexA but neither ion is observed.
What the strict filter does
For each registered custom diagnostic ion, with the oxonium filter enabled:
"Observed" uses the same presence rule as the built-in 204 check (a peak matched within the product mass tolerance). The filter is gated by the existing Oxonium Ion Filter parameter; with it off, column-4 ions keep their prior scoring-only role.
What changed under the hood
GlycoSearch/OxoniumIonReservedIndices.cs(new): named constants for the six built-in oxonium-array indices the engine had hardcoded (4, 5, 9, 10, 12, 14).GlycoSearch/GlycoSearchEngine.cs: the hardcoded indices now use those constants (pure rename).GlycoSearch/Glycan.cs:HasCustomOxoniumIons,CustomOxoniumIons((m/z, Kind-index) pairs), andAllOxoniumIonsIncludingCustoms(built-ins + appended customs; returns the built-in array unchanged when there are no customs).GlycoSearch/GlycoPeptides.cs:ScanOxoniumIonFilterprobes the combined ion list;DiagonsticFilterapplies the strict rule after the unchanged built-in rules, gated onHasCustomOxoniumIons; helpersCheckOxoniumPresenceandApplyStrictMonoFilter.Glycan_Mods/MonosaccharidesCustom.tsv: banner documents the column-4 ions' new strict-filter role.Tests
CustomOxoniumIonsand the combined array.ApplyStrictMonoFilter(four cases),CheckOxoniumPresence.LoadCustomMonosaccharidescolumn-4 ions appear inCustomOxoniumIons.[Explicit],Category=Performance): negligible per-call overhead at zero customs; under 10 µs/call at 50 custom ions.Compatibility / rollout
AllOxoniumIonsordering is unchanged and guarded by a test.🤖 Generated with Claude Code