-
Notifications
You must be signed in to change notification settings - Fork 42
Expose the most-abundant observed mass on IsotopicEnvelope (resolved) #1078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
trishorts
merged 14 commits into
smith-chem-wisc:master
from
trishorts:mostAbundantMass
Jun 30, 2026
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
c2d11ad
feat(decon): expose most-abundant and average observed masses on enveβ¦
trishorts c144c38
refactor(decon): address PR #1078 review β drop GetMostAbundantOffsetβ¦
trishorts 56d9e0a
refactor(decon): compute observed envelope masses on access
trishorts 8ba1175
test(decon): harden most-abundant/average observed-mass coverage
trishorts 9e8ad92
Merge remote-tracking branch 'upstream/master' into mostAbundantMass
trishorts ee004e4
Merge branch 'master' into mostAbundantMass
nbollis cd519e1
test(deconvolution): tighten AverageObservedMass bounds and cover zerβ¦
trishorts c9c6182
Merge branch 'master' into mostAbundantMass
trishorts 2b776be
refactor(envelope): scope PR to resolved most-abundant mass; public fβ¦
trishorts 8f292b5
Merge remote-tracking branch 'origin/mostAbundantMass' into mostAbundβ¦
trishorts 5e12a96
Merge branch 'master' into mostAbundantMass
trishorts b14e6f5
refactor(envelope): rename MostAbundantObservedMass -> MostAbundantObβ¦
trishorts 673b4fe
Merge remote-tracking branch 'origin/mostAbundantMass' into mostAbundβ¦
trishorts dc94f52
Merge branch 'master' into mostAbundantMass
trishorts File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Chemistry; | ||
| using MassSpectrometry; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Test | ||
| { | ||
| /// <summary> | ||
| /// Unit tests for the resolved "most abundant mass" precursor-selection support (Strategy B). | ||
| /// | ||
| /// Terminology pinned by these tests: | ||
| /// β’ most-abundant mass = the neutral mass of the single most intense (tallest) isotopic peak, | ||
| /// proton-corrected (<see cref="IsotopicEnvelope.MostAbundantObservedNeutralMass"/>); | ||
| /// β’ most-abundant offset = GetDiffToMonoisotopic(GetMostIntenseMassIndex(mono)) on the averagine | ||
| /// model β the gap from the monoisotopic mass to that tallest isotopologue. | ||
| /// The intensity-weighted average (centroid) mass and the isotopically-unresolved path are a | ||
| /// separate change and are tested with that work, not here. All tests build synthetic envelopes | ||
| /// from the Averagine model β no deconvolution is run. | ||
| /// </summary> | ||
| [TestFixture] | ||
| public sealed class TestMostAbundantMass | ||
| { | ||
| private static readonly AverageResidue Model = new Averagine(); | ||
|
|
||
| // Most-abundant offset for a monoisotopic mass = the averagine diff-to-monoisotopic of the | ||
| // nearest mass bin (the mass-keyed composition consumers use in place of a dedicated method). | ||
| private static double MostAbundantOffset(double monoMass) => Model.GetDiffToMonoisotopic(Model.GetMostIntenseMassIndex(monoMass)); | ||
|
|
||
| /// <summary> | ||
| /// Builds a perfect synthetic envelope: peaks at exact theoretical m/z with | ||
| /// Averagine-proportional intensities. (Same construction as TestDeconvolutionScorerUnit.) | ||
| /// </summary> | ||
| private static List<(double mz, double intensity)> BuildPerfectPeaks(double monoMass, int charge, double baseIntens = 1e6) | ||
| { | ||
| int avgIdx = Model.GetMostIntenseMassIndex(monoMass); | ||
| double[] rawMasses = Model.GetAllTheoreticalMasses(avgIdx); | ||
| double[] rawIntens = Model.GetAllTheoreticalIntensities(avgIdx); | ||
|
|
||
| var sorted = rawMasses.Zip(rawIntens).OrderBy(p => p.First).ToArray(); | ||
|
|
||
| double isotopeStep = Constants.C13MinusC12 / charge; | ||
| double monoMz = monoMass.ToMz(charge); | ||
| var peaks = new List<(double mz, double intensity)>(); | ||
| for (int n = 0; n < sorted.Length; n++) | ||
| { | ||
| double intensity = baseIntens * sorted[n].Second; | ||
| if (intensity < baseIntens * 0.001) continue; | ||
| peaks.Add((monoMz + n * isotopeStep, intensity)); | ||
| } | ||
| return peaks; | ||
| } | ||
|
|
||
| private static IsotopicEnvelope BuildPerfectEnvelope(double monoMass, int charge, double baseIntens = 1e6) | ||
| { | ||
| var peaks = BuildPerfectPeaks(monoMass, charge, baseIntens); | ||
| return new IsotopicEnvelope(0, peaks, monoMass, charge, peaks.Sum(p => p.intensity), 0.999); | ||
| } | ||
|
|
||
| // ββ AverageResidue most-abundant offset βββββββββββββββββββββββββββββββββ | ||
|
|
||
| [Test] | ||
| public void MostAbundantOffset_IsNonNegativeAndNonDecreasingWithMass() | ||
| { | ||
| double[] masses = { 500, 2000, 5000, 10000, 20000, 40000 }; | ||
| double prev = double.NegativeInfinity; | ||
| foreach (double m in masses) | ||
| { | ||
| double offset = MostAbundantOffset(m); | ||
| Assert.That(offset, Is.GreaterThanOrEqualTo(-1e-6)); // ~0 at tiny mass (mono IS most abundant) | ||
| Assert.That(offset, Is.GreaterThanOrEqualTo(prev - 1e-6), $"offset decreased at mass {m}"); | ||
| prev = offset; | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| public void MostAbundantOffset_IsNearZeroForSmallMass() | ||
| { | ||
| // A small peptide's monoisotopic peak is (nearly) the most abundant. | ||
| Assert.That(MostAbundantOffset(500), Is.LessThan(0.5)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void MostAbundantOffset_GrowsRoughlyOneNeutronPer1600Da() | ||
| { | ||
| // ~1 13C neutron (~1.00235 Da) per ~1.6 kDa. Assert the offset at 16 kDa is in a | ||
| // physically plausible band (β 9β11 Da) rather than an exact value. | ||
| double offset = MostAbundantOffset(16000); | ||
| Assert.That(offset, Is.GreaterThan(8.0).And.LessThan(12.0)); | ||
| } | ||
|
|
||
| // ββ IsotopicEnvelope most-abundant observed mass ββββββββββββββββββββββββ | ||
|
|
||
| [Test] | ||
| public void MostAbundantObservedNeutralMass_IsProtonCorrectedNeutralMass() | ||
| { | ||
| const int charge = 10; | ||
| var env = BuildPerfectEnvelope(15000, charge); | ||
|
|
||
| // The proton-corrected neutral mass equals the most intense peak's m/z .ToMass(charge)... | ||
| double mostIntenseMz = env.Peaks.MaxBy(p => p.intensity).mz; | ||
| Assert.That(env.MostAbundantObservedNeutralMass, Is.EqualTo(mostIntenseMz.ToMass(charge)).Within(1e-6)); | ||
|
|
||
| // ...and it differs from the un-proton-corrected mz*|z| field by exactly z proton masses. | ||
| Assert.That(env.MostAbundantObservedIsotopicMass - env.MostAbundantObservedNeutralMass, | ||
| Is.EqualTo(charge * Constants.ProtonMass).Within(1e-6)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void MostAbundantObservedNeutralMass_MatchesMonoPlusAveragineOffset() | ||
| { | ||
| // Ties the two features together: the observed most-abundant neutral mass of a perfect | ||
| // envelope β candidate monoisotopic + averagine most-abundant offset. | ||
| const double mono = 12000; | ||
| const int charge = 12; | ||
| var env = BuildPerfectEnvelope(mono, charge); | ||
|
|
||
| double predicted = mono + MostAbundantOffset(mono); | ||
| Assert.That(env.MostAbundantObservedNeutralMass, Is.EqualTo(predicted).Within(0.15)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void DeconvolutionConstructor_ComputesMostAbundantObservedNeutralMass() | ||
| { | ||
| // The 5-arg mzLib-deconvolution constructor must compute the most-abundant observed mass. | ||
| const double mono = 12000; | ||
| const int charge = 12; | ||
| var peaks = BuildPerfectPeaks(mono, charge); | ||
| var env = new IsotopicEnvelope(peaks, mono, charge, peaks.Sum(p => p.intensity), 0.5); | ||
|
|
||
| double mostIntenseMz = peaks.MaxBy(p => p.intensity).mz; | ||
| Assert.That(env.MostAbundantObservedNeutralMass, Is.EqualTo(mostIntenseMz.ToMass(charge)).Within(1e-6)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void FileReadEnvelope_HasNoMostAbundantPeak_ReturnsSentinel() | ||
| { | ||
| // The file-read constructor carries a neutral mass but no observed isotopic envelope, so the | ||
| // most-abundant observed mass is undefined: both the m/zΓ|charge| form and the proton-corrected | ||
| // form report the -1 sentinel rather than a synthetic value. | ||
| const double mono = 8000; | ||
| const int charge = 8; | ||
| var env = new IsotopicEnvelope(mono, 1e6, charge); | ||
|
|
||
| Assert.That(env.MostAbundantObservedIsotopicMass, Is.EqualTo(-1)); | ||
| Assert.That(env.MostAbundantObservedNeutralMass, Is.EqualTo(-1)); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.