private static int OrderIntensityDesc(RankedMI mi1, RankedMI mi2) { float i1 = mi1.Intensity, i2 = mi2.Intensity; if (i1 > i2) { return(-1); } if (i1 < i2) { return(1); } return(-OrderMz(mi1, mi2)); }
private bool ApplyRanking(RankingState rankingState, double ionMz, MatchedFragmentIon match, bool filter, int start, int end, double startMz, ref RankedMI rankedMI) { // Avoid ranking precursor ions without losses, if the precursor isotopes will // not be taken from product ions if (!ExcludePrecursorIsotopes || match.IonType != IonType.precursor || match.Losses != null) { int offset = OrdinalToOffset(match.IonType, match.Ordinal); var type = match.IonType; if (filter) { if (TargetInfoObj.LookupMods == null || !TargetInfoObj.LookupMods.HasCrosslinks) { if (!TransitionSettings.Accept(Sequence, MoleculeMassesObj.precursorMz, type, offset, ionMz, start, end, startMz)) { return(false); } } } if (rankingState.matchAll) { if (MinMz > ionMz || ionMz > MaxMz) { return(false); } if (!RankTypes.Contains(type)) { return(false); } if (RankLimit.HasValue && rankingState.Ranked >= RankLimit) { return(false); } if (type != IonType.precursor) { // CONSIDER(bspratt) we may eventually want adduct-level control for small molecules, not just abs charge if (!RankCharges.Contains(Math.Abs(match.Charge.AdductCharge))) { return(false); } } } rankedMI = rankedMI.ChangeRank(rankingState.RankNext()); return(true); } return(false); }
private bool MatchNext(RankingState rankingState, double ionMz, MatchedFragmentIon match, bool filter, int end, int start, double startMz, ref RankedMI rankedMI) { // Unless trying to match everything, stop looking outside the instrument range if (!rankingState.matchAll && !HasLosses && ionMz > MaxMz) { return(false); } // Check filter properties, if appropriate if ((rankingState.matchAll || ionMz >= MinMz) && Math.Abs(ionMz - rankedMI.ObservedMz) < Libraries.IonMatchTolerance) { // Make sure each m/z value is only used for the most intense peak // that is within the tolerance range. if (rankingState.IsSeen(ionMz)) { return(true); // Keep looking } rankingState.Seen(ionMz); // If this m/z already matched a different ion, just remember the second ion. if (rankedMI.MatchedIons != null) { // If first type was excluded from causing a ranking, but second does, then make it the first // Otherwise, this can cause very mysterious failures to rank transitions that appear in the // document. if (rankedMI.Rank == 0 && ApplyRanking(rankingState, ionMz, match, filter, start, end, startMz, ref rankedMI)) { rankedMI = rankedMI.ChangeMatchedIons(rankedMI.MatchedIons.Prepend(match)); } else { rankedMI = rankedMI.ChangeMatchedIons(rankedMI.MatchedIons.Append(match)); } if (rankedMI.MatchedIons.Count < MAX_MATCH) { return(true); } rankingState.matched = true; return(false); } double predictedMz = match.PredictedMz; // Avoid using the same predicted m/z on two different peaks if (predictedMz == ionMz || !rankingState.IsSeen(predictedMz)) { rankingState.Seen(predictedMz); ApplyRanking(rankingState, ionMz, match, filter, start, end, startMz, ref rankedMI); rankedMI = rankedMI.ChangeMatchedIons(ImmutableList.Singleton(match)); rankingState.matched = !rankingState.matchAll; return(rankingState.matchAll); } } // Stop looking once the mass has been passed, unless there are losses to consider if (HasLosses) { return(true); } return(ionMz <= rankedMI.ObservedMz); }
private RankedMI CalculateRank(RankingState rankingState, RankedMI rankedMI) { // Rank based on filtered range, if the settings use it in picking bool filter = (Pick == TransitionLibraryPick.filter); var knownFragments = MoleculeMassesObj.MatchIonMasses.KnownFragments; if (knownFragments != null) { // Small molecule work - we only know about the fragments we're given, we can't predict others foreach (IonType type in Types) { if (Transition.IsPrecursor(type)) { var matchedFragmentIon = MakeMatchedFragmentIon(type, 0, PrecursorAdduct, null, out double matchMz); if (!MatchNext(rankingState, matchMz, matchedFragmentIon, filter, 0, 0, 0, ref rankedMI)) { // If matched return. Otherwise look for other ion types. if (rankingState.matched) { rankingState.Clean(); return(rankedMI); } } } else { for (var i = 0; i < knownFragments.Count; i++) { var fragment = knownFragments[i]; double matchMz = MoleculeMassesObj.PredictIonMasses.KnownFragments[i].PredictedMz; if (!MatchNext(rankingState, matchMz, fragment, filter, 0, 0, fragment.PredictedMz, ref rankedMI)) { // If matched return. Otherwise look for other ion types. if (rankingState.matched) { rankingState.Clean(); return(rankedMI); } } } } } return(rankedMI); } // Look for a predicted match within the acceptable tolerance int len = MoleculeMassesObj.MatchIonMasses.FragmentMasses.GetLength(1); foreach (IonType type in Types) { if (Transition.IsPrecursor(type)) { foreach (var losses in TransitionGroup.CalcTransitionLosses(type, 0, MassType, PotentialLosses)) { var matchedFragmentIon = MakeMatchedFragmentIon(type, 0, PrecursorAdduct, losses, out double matchMz); if (!MatchNext(rankingState, matchMz, matchedFragmentIon, filter, len, len, 0, ref rankedMI)) { // If matched return. Otherwise look for other ion types. if (rankingState.matched) { rankingState.Clean(); return(rankedMI); } } } continue; } foreach (var adduct in Adducts) { // Precursor charge can never be lower than product ion charge. if (Math.Abs(PrecursorAdduct.AdductCharge) < Math.Abs(adduct.AdductCharge)) { continue; } int start = 0, end = 0; double startMz = 0; if (filter) { start = TransitionSettings.Filter.FragmentRangeFirst.FindStartFragment( MoleculeMassesObj.MatchIonMasses.FragmentMasses, type, adduct, MoleculeMassesObj.precursorMz, TransitionSettings.Filter.PrecursorMzWindow, out startMz); end = TransitionSettings.Filter.FragmentRangeLast.FindEndFragment(type, start, len); if (Transition.IsCTerminal(type)) { Helpers.Swap(ref start, ref end); } } // These inner loops are performance bottlenecks, and the following // code duplication proved the fastest implementation under a // profiler. Apparently .NET failed to inline an attempt to put // the loop contents in a function. if (Transition.IsCTerminal(type)) { for (int i = len - 1; i >= 0; i--) { foreach (var losses in TransitionGroup.CalcTransitionLosses(type, i, MassType, PotentialLosses)) { var matchedFragmentIon = MakeMatchedFragmentIon(type, i, adduct, losses, out double matchMz); if (!MatchNext(rankingState, matchMz, matchedFragmentIon, filter, end, start, startMz, ref rankedMI)) { if (rankingState.matched) { rankingState.Clean(); return(rankedMI); } i = -1; // Terminate loop on i break; } } } } else { for (int i = 0; i < len; i++) { foreach (var losses in TransitionGroup.CalcTransitionLosses(type, i, MassType, PotentialLosses)) { var matchedFragmentIon = MakeMatchedFragmentIon(type, i, adduct, losses, out double matchMz); if (!MatchNext(rankingState, matchMz, matchedFragmentIon, filter, end, start, startMz, ref rankedMI)) { if (rankingState.matched) { rankingState.Clean(); return(rankedMI); } i = len; // Terminate loop on i break; } } } } } } return(rankedMI); }
private static int OrderMz(RankedMI mi1, RankedMI mi2) { return(mi1.ObservedMz.CompareTo(mi2.ObservedMz)); }
public LibraryRankedSpectrumInfo RankSpectrum(SpectrumPeaksInfo info, int minPeaks, double?score) { var ionsToReturn = FragmentFilterObj.FragmentMatchCount; RankingState rankingState = new RankingState() { matchAll = MatchAll, }; // Get the library spectrum mass-intensity pairs IList <SpectrumPeaksInfo.MI> listMI = info.Peaks; // Because sorting and matching observed ions with predicted // ions appear as bottlenecks in a profiler, a minimum number // of peaks may be supplied to allow the use of a 2-phase linear // filter that can significantly reduce the number of peaks // needing the O(n*log(n)) sorting and the O(n*m) matching. int len = listMI.Count; float intensityCutoff = 0; if (minPeaks != -1) { // Start searching for good cut-off at mean intensity. double totalIntensity = info.Intensities.Sum(); FindIntensityCutoff(listMI, 0, (float)(totalIntensity / len) * 2, minPeaks, 1, ref intensityCutoff, ref len); } // Create filtered peak array storing original index for m/z ordering // to avoid needing to sort to return to this order. RankedMI[] arrayRMI = new RankedMI[len]; // Detect when m/z values are out of order, and use the expensive sort // by m/z to correct this. double lastMz = double.MinValue; bool sortMz = false; for (int i = 0, j = 0, lenOrig = listMI.Count; i < lenOrig; i++) { SpectrumPeaksInfo.MI mi = listMI[i]; if (mi.Intensity >= intensityCutoff || intensityCutoff == 0) { arrayRMI[j] = new RankedMI(mi, j); j++; } if (!ionsToReturn.HasValue) { if (mi.Mz < lastMz) { sortMz = true; } lastMz = mi.Mz; } } // The one expensive sort is used to determine rank order // by intensity, or m/z in case of a tie. Array.Sort(arrayRMI, OrderIntensityDesc); RankedMI[] arrayResult = new RankedMI[ionsToReturn.HasValue ? ionsToReturn.Value : arrayRMI.Length]; foreach (RankedMI rmi in arrayRMI) { var rankedRmi = CalculateRank(rankingState, rmi); // If not filtering for only the highest ionMatchCount ranks if (!ionsToReturn.HasValue) { // Put the ranked record back where it started in the // m/z ordering to avoid a second sort. arrayResult[rmi.IndexMz] = rankedRmi; } // Otherwise, if this ion was ranked, add it to the result array else if (rankedRmi.Rank > 0) { int countRanks = rankedRmi.Rank; arrayResult[countRanks - 1] = rankedRmi; // And stop when the array is full if (countRanks == ionsToReturn.Value) { break; } } } // Is this a theoretical library with no intensity variation? If so it can't be ranked. // If it has any interesting peak annotations, pass those through if (rankingState.Ranked == 0 && arrayRMI.All(rmi => rmi.Intensity == arrayRMI[0].Intensity)) { // Only do this if we have been asked to limit the ions matched, and there are any annotations if (ionsToReturn.HasValue && arrayRMI.Any(rmi => rmi.HasAnnotations)) { // Pass through anything with an annotation as being of probable interest arrayResult = arrayRMI.Where(rmi => rmi.HasAnnotations).ToArray(); ionsToReturn = null; } } // If not enough ranked ions were found, fill the rest of the results array if (ionsToReturn.HasValue) { for (int i = rankingState.Ranked; i < ionsToReturn.Value; i++) { arrayResult[i] = RankedMI.EMPTY; } } // If all ions are to be included, and some were found out of order, then // the expensive full sort by m/z is necessary. else if (sortMz) { Array.Sort(arrayResult, OrderMz); } double?spectrumScore; if (score == null && GroupDocNode.HasLibInfo && GroupDocNode.LibInfo is BiblioSpecSpectrumHeaderInfo libInfo) { spectrumScore = libInfo.Score; } else { spectrumScore = score; } return(new LibraryRankedSpectrumInfo(PredictLabelType, Libraries.IonMatchTolerance, arrayResult, spectrumScore)); }