From 65b3c3e967ddf9cef57d10b1a6b9fd005d9dda1b Mon Sep 17 00:00:00 2001 From: Brian Pratt Date: Mon, 3 Mar 2025 12:25:45 -0800 Subject: [PATCH 1/2] Fix for a "polarity mismatch" exception when searching for nearest precursor m/z match in a mixed polarity document when the m/z is negative and has greater absolute value than any negative precursor m/z. (Test for the fix was added opportunistically to an existing test involving mixed polarity documents) Reported by user Paul --- .../Skyline/Model/Results/PeptideFinder.cs | 35 ++++++++++++++++--- .../TestFunctional/ExportIsolationListTest.cs | 12 +++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs b/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs index 8bc27d4507..b0369e8892 100644 --- a/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs +++ b/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs @@ -18,7 +18,6 @@ */ using System; using System.Collections.Generic; -using System.Collections; using pwiz.Common.Chemistry; namespace pwiz.Skyline.Model.Results @@ -59,6 +58,7 @@ public PeptideDocNode FindPeptide(SignedMz precursorMz) return null; // Find closest precursor Mz match. + // Watch out for negative values, which sort before positive values. var lookup = new PeptidePrecursorMz(null, precursorMz); int i = _precursorMzPeptideList.BinarySearch(lookup, PeptidePrecursorMz.COMPARER); if (i < 0) @@ -67,11 +67,33 @@ public PeptideDocNode FindPeptide(SignedMz precursorMz) if (i >= _precursorMzPeptideList.Count) i = _precursorMzPeptideList.Count - 1; else if (i > 0 && - precursorMz - _precursorMzPeptideList[i - 1].PrecursorMz < - _precursorMzPeptideList[i].PrecursorMz - precursorMz) + _precursorMzPeptideList[i - 1].PrecursorMz.IsNegative == precursorMz.IsNegative && + _precursorMzPeptideList[i].PrecursorMz.IsNegative == precursorMz.IsNegative && + precursorMz - _precursorMzPeptideList[i - 1].PrecursorMz < + _precursorMzPeptideList[i].PrecursorMz - precursorMz) i--; } var closestMatch = _precursorMzPeptideList[i]; + if (closestMatch.PrecursorMz.IsNegative != precursorMz.IsNegative) + { + // Just past the negative range, or just below the positive range + if (precursorMz.IsNegative) + { + if (!_precursorMzPeptideList[0].PrecursorMz.IsNegative) + { + return null; // There aren't any negative values in the list + } + closestMatch = _precursorMzPeptideList[i - 1]; // End of negative range + } + else + { + if (i+1 >= _precursorMzPeptideList.Count) + { + return null; // There aren't any positive values in the list + } + closestMatch = _precursorMzPeptideList[i + 1]; // Start of positive range + } + } // Return color seed only if the match is within allowed tolerance. return Math.Abs(closestMatch.PrecursorMz - precursorMz) > _mzMatchTolerance @@ -97,10 +119,15 @@ public class MzComparer : IComparer public int Compare(PeptidePrecursorMz p1, PeptidePrecursorMz p2) { // ReSharper disable PossibleNullReferenceException - return Comparer.Default.Compare(p1.PrecursorMz, p2.PrecursorMz); + return p1.PrecursorMz.CompareTo(p2.PrecursorMz); // ReSharper restore PossibleNullReferenceException } } + + public override string ToString() + { + return $@"{PrecursorMz.RawValue} {NodePeptide}"; // For debug convenience, not user-facing + } } } } diff --git a/pwiz_tools/Skyline/TestFunctional/ExportIsolationListTest.cs b/pwiz_tools/Skyline/TestFunctional/ExportIsolationListTest.cs index bfd0ffaf50..385c6d83c0 100644 --- a/pwiz_tools/Skyline/TestFunctional/ExportIsolationListTest.cs +++ b/pwiz_tools/Skyline/TestFunctional/ExportIsolationListTest.cs @@ -23,11 +23,13 @@ using System.Linq; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.Common.Chemistry; using pwiz.Skyline.Alerts; using pwiz.Skyline.FileUI; using pwiz.Skyline.Model; using pwiz.Skyline.Model.DocSettings; using pwiz.Skyline.Model.DocSettings.Extensions; +using pwiz.Skyline.Model.Results; using pwiz.Skyline.Properties; using pwiz.Skyline.Util.Extensions; using pwiz.SkylineTestUtil; @@ -151,6 +153,16 @@ protected override void DoTest() var ceFirst = AsSmallMoleculesNegative ? 20.3 : 20.4; var ceLast = AsSmallMoleculesNegative ? 19.1 : 19.2; + // Test an issue found in the PeptideFinder class with mixed polarity docs + if (SkylineWindow.Document.IsMixedPolarity()) + { + var beyondMaxNegMz = (from precursor in SkylineWindow.Document.MoleculeTransitionGroups + where precursor.PrecursorMz.IsNegative + select precursor.PrecursorMz.RawValue).Min()-100.0; + var finder = new PeptideFinder(SkylineWindow.Document); + AssertEx.IsNull(finder.FindPeptide(new SignedMz(beyondMaxNegMz))); // This will throw a "polarity mismatch" exception if the issue is not fixed + } + // Export Agilent unscheduled DDA list. ExportIsolationList( "AgilentUnscheduledDda.csv", From 744e9565ca69c603167fa614cf33e86d99fc7463 Mon Sep 17 00:00:00 2001 From: Brian Pratt Date: Tue, 4 Mar 2025 09:07:38 -0800 Subject: [PATCH 2/2] Improvements based on Nick review --- .../Skyline/Model/Results/PeptideFinder.cs | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs b/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs index b0369e8892..29fd7347b8 100644 --- a/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs +++ b/pwiz_tools/Skyline/Model/Results/PeptideFinder.cs @@ -58,47 +58,42 @@ public PeptideDocNode FindPeptide(SignedMz precursorMz) return null; // Find closest precursor Mz match. - // Watch out for negative values, which sort before positive values. var lookup = new PeptidePrecursorMz(null, precursorMz); int i = _precursorMzPeptideList.BinarySearch(lookup, PeptidePrecursorMz.COMPARER); - if (i < 0) + if (i >= 0) { - i = ~i; - if (i >= _precursorMzPeptideList.Count) - i = _precursorMzPeptideList.Count - 1; - else if (i > 0 && - _precursorMzPeptideList[i - 1].PrecursorMz.IsNegative == precursorMz.IsNegative && - _precursorMzPeptideList[i].PrecursorMz.IsNegative == precursorMz.IsNegative && - precursorMz - _precursorMzPeptideList[i - 1].PrecursorMz < - _precursorMzPeptideList[i].PrecursorMz - precursorMz) - i--; + return _precursorMzPeptideList[i].NodePeptide; // Exact match } - var closestMatch = _precursorMzPeptideList[i]; - if (closestMatch.PrecursorMz.IsNegative != precursorMz.IsNegative) + + // BinarySearch returns bitwise complement of the index of the next larger element, + // but the closest match might be the element we just passed. Also, if precursor Mz is negative, + // we have to make sure search hasn't crossed over into positive territory. (SignedMz sorts + // negative values before positive ones e.g. -100, -200, 100, 200) + i = ~i; + PeptidePrecursorMz closestMatch = null; + var closestDistance = double.MaxValue; + for (var candidateIndex = i - 1; candidateIndex <= i; candidateIndex++) { - // Just past the negative range, or just below the positive range - if (precursorMz.IsNegative) + if (candidateIndex < 0 || candidateIndex >= _precursorMzPeptideList.Count) + { + continue; + } + var candidate = _precursorMzPeptideList[candidateIndex]; + if (candidate.PrecursorMz.IsNegative != precursorMz.IsNegative) { - if (!_precursorMzPeptideList[0].PrecursorMz.IsNegative) - { - return null; // There aren't any negative values in the list - } - closestMatch = _precursorMzPeptideList[i - 1]; // End of negative range + continue; } - else + + var distance = Math.Abs(candidate.PrecursorMz - precursorMz); + if (distance < closestDistance) { - if (i+1 >= _precursorMzPeptideList.Count) - { - return null; // There aren't any positive values in the list - } - closestMatch = _precursorMzPeptideList[i + 1]; // Start of positive range + closestMatch = candidate; + closestDistance = distance; } } - // Return color seed only if the match is within allowed tolerance. - return Math.Abs(closestMatch.PrecursorMz - precursorMz) > _mzMatchTolerance - ? null - : closestMatch.NodePeptide; + // Return only if the match is within allowed tolerance. + return closestDistance > _mzMatchTolerance ? null : closestMatch?.NodePeptide; } private sealed class PeptidePrecursorMz